① Socket編程中到底什麼是套接字
簡單的說就是通信的兩方的一種約定,用套接字中的相關函數來完成通信過程
應用層通過傳輸層進行數據通信時,TCP和UDP會遇到同時為多個應用程序進程提供並發服務的問題。多個TCP連接或多個應用程序進程可能需要通過同一個 TCP協議埠傳輸數據。為了區別不同的應用程序進程和連接,許多計算機操作系統為應用程序與TCP/IP協議交互提供了稱為套接字(Socket)的介面。
區分不同應用程序進程間的網路通信和連接,主要有3個參數:通信的目的IP地址、使用的傳輸層協議(TCP或UDP)和使用的埠號。Socket原意是 「插座」。通過將這3個參數結合起來,與一個「插座」Socket綁定,應用層就可以和傳輸層通過套接字介面,區分來自不同應用程序進程或網路連接的通信,實現數據傳輸的並發服務。
-- win API socket
本文所談到的Socket函數如果沒有特別說明,都是指的Windows Socket API。
一、WSAStartup函數
int WSAStartup(
WORD wVersionRequested,
LPWSADATA lpWSAData
);
使用Socket的程序在使用Socket之前必須調用WSAStartup函數。該函數的第一個參數指明程序請求使用的Socket版本,其中高位位元組指明副版本、低位位元組指明主版本;操作系統利用第二個參數返回請求的Socket的版本信息。當一個應用程序調用WSAStartup函數時,操作系統根據請求的Socket版本來搜索相應的Socket庫,然後綁定找到的Socket庫到該應用程序中。以後應用程序就可以調用所請求的 Socket庫中的其它Socket函數了。該函數執行成功後返回0。
例:假如一個程序要使用2.1版本的Socket,那麼程序代碼如下
wVersionRequested = MAKEWORD( 2, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
二、WSACleanup函數
int WSACleanup (void);
應用程序在完成對請求的Socket庫的使用後,要調用WSACleanup函數來解除與Socket庫的綁定並且釋放Socket庫所佔用的系統資源。
三、socket函數
SOCKET socket(
int af,
int type,
int protocol
);
應用程序調用socket函數來創建一個能夠進行網路通信的套接字。第一個參數指定應用程序使用的通信協議的協議族,對於TCP/IP協議族,該參數置PF_INET;第二個參數指定要創建的套接字類型,流套接字類型為SOCK_STREAM、數據報套接字類型為SOCK_DGRAM;第三個參數指定應用程序所使用的通信協議。該函數如果調用成功就返回新創建的套接字的描述符,如果失敗就返回INVALID_SOCKET。套接字描述符是一個整數類型的值。每個進程的進程空間里都有一個套接字描述符表,該表中存放著套接字描述符和套接字數據結構的對應關系。該表中有一個欄位存放新創建的套接字的描述符,另一個欄位存放套接字數據結構的地址,因此根據套接字描述符就可以找到其對應的套接字數據結構。每個進程在自己的進程空間里都有一個套接字描述符表但是套接字數據結構都是在操作系統的內核緩沖里。下面是一個創建流套接字的例子:
struct protoent *ppe;
ppe=getprotobyname("tcp");
SOCKET ListenSocket=socket(PF_INET,SOCK_STREAM,ppe->p_proto);
四、closesocket函數
int closesocket(
SOCKET s
);
closesocket函數用來關閉一個描述符為s套接字。由於每個進程中都有一個套接字描述符表,表中的每個套接字描述符都對應了一個位於操作系統緩沖區中的套接字數據結構,因此有可能有幾個套接字描述符指向同一個套接字數據結構。套接字數據結構中專門有一個欄位存放該結構的被引用次數,即有多少個套接字描述符指向該結構。當調用closesocket函數時,操作系統先檢查套接字數據結構中的該欄位的值,如果為1,就表明只有一個套接字描述符指向它,因此操作系統就先把s在套接字描述符表中對應的那條表項清除,並且釋放s對應的套接字數據結構;如果該欄位大於1,那麼操作系統僅僅清除s在套接字描述符表中的對應表項,並且把s對應的套接字數據結構的引用次數減1。
closesocket函數如果執行成功就返回0,否則返回SOCKET_ERROR。
五、send函數
int send(
SOCKET s,
const char FAR *buf,
int len,
int flags
);
不論是客戶還是伺服器應用程序都用send函數來向TCP連接的另一端發送數據。客戶程序一般用send函數向伺服器發送請求,而伺服器則通常用 send函數來向客戶程序發送應答。該函數的第一個參數指定發送端套接字描述符;第二個參數指明一個存放應用程序要發送數據的緩沖區;第三個參數指明實際要發送的數據的位元組數;第四個參數一般置0。這里只描述同步Socket的send函數的執行流程。當調用該函數時,send先比較待發送數據的長度 len和套接字s的發送緩沖區的長度,如果len大於s的發送緩沖區的長度,該函數返回SOCKET_ERROR;如果len小於或者等於s的發送緩沖區的長度,那麼send先檢查協議是否正在發送s的發送緩沖中的數據,如果是就等待協議把數據發送完,如果協議還沒有開始發送s的發送緩沖中的數據或者s的發送緩沖中沒有數據,那麼send就比較s的發送緩沖區的剩餘空間和len,如果len大於剩餘空間大小send就一直等待協議把s的發送緩沖中的數據發送完,如果len小於剩餘空間大小send就僅僅把buf中的數據到剩餘空間里(注意並不是send把s的發送緩沖中的數據傳到連接的另一端的,而是協議傳的,send僅僅是把buf中的數據到s的發送緩沖區的剩餘空間里)。如果send函數數據成功,就返回實際的位元組數,如果send在數據時出現錯誤,那麼send就返回SOCKET_ERROR;如果send在等待協議傳送數據時網路斷開的話,那麼send 函數也返回SOCKET_ERROR。要注意send函數把buf中的數據成功到s的發送緩沖的剩餘空間里後它就返回了,但是此時這些數據並不一定馬上被傳到連接的另一端。如果協議在後續的傳送過程中出現網路錯誤的話,那麼下一個Socket函數就會返回SOCKET_ERROR。(每一個除 send外的Socket函數在執行的最開始總要先等待套接字的發送緩沖中的數據被協議傳送完畢才能繼續,如果在等待時出現網路錯誤,那麼該Socket 函數就返回SOCKET_ERROR)
注意:在Unix系統下,如果send在等待協議傳送數據時網路斷開的話,調用send的進程會接收到一個SIGPIPE信號,進程對該信號的默認處理是進程終止。
六、recv函數
int recv(
SOCKET s,
char FAR *buf,
int len,
int flags
);
不論是客戶還是伺服器應用程序都用recv函數從TCP連接的另一端接收數據。該函數的第一個參數指定接收端套接字描述符;第二個參數指明一個緩沖區,該緩沖區用來存放recv函數接收到的數據;第三個參數指明buf的長度;第四個參數一般置0。這里只描述同步Socket的recv函數的執行流程。當應用程序調用recv函數時,recv先等待s的發送緩沖中的數據被協議傳送完畢,如果協議在傳送s的發送緩沖中的數據時出現網路錯誤,那麼 recv函數返回SOCKET_ERROR,如果s的發送緩沖中沒有數據或者數據被協議成功發送完畢後,recv先檢查套接字s的接收緩沖區,如果s接收緩沖區中沒有數據或者協議正在接收數據,那麼recv就一直等待,只到協議把數據接收完畢。當協議把數據接收完畢,recv函數就把s的接收緩沖中的數據 到buf中(注意協議接收到的數據可能大於buf的長度,所以在這種情況下要調用幾次recv函數才能把s的接收緩沖中的數據完。 recv函數僅僅是數據,真正的接收數據是協議來完成的),recv函數返回其實際的位元組數。如果recv在時出錯,那麼它返回 SOCKET_ERROR;如果recv函數在等待協議接收數據時網路中斷了,那麼它返回0。
注意:在Unix系統下,如果recv函數在等待協議接收數據時網路斷開了,那麼調用recv的進程會接收到一個SIGPIPE信號,進程對該信號的默認處理是進程終止。
七、bind函數
int bind(
SOCKET s,
const struct sockaddr FAR *name,
int namelen
);
當創建了一個Socket以後,套接字數據結構中有一個默認的IP地址和默認的埠號。一個服務程序必須調用bind函數來給其綁定一個IP地址和一個特定的埠號。客戶程序一般不必調用bind函數來為其Socket綁定IP地址和斷口號。該函數的第一個參數指定待綁定的Socket描述符;第二個參數指定一個sockaddr結構,該結構是這樣定義的:
struct sockaddr {
u_short sa_family;
char sa_data[14];
};
sa_family指定地址族,對於TCP/IP協議族的套接字,給其置AF_INET。當對TCP/IP協議族的套接字進行綁定時,我們通常使用另一個地址結構:
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
其中sin_family置AF_INET;sin_port指明埠號;sin_addr結構體中只有一個唯一的欄位s_addr,表示IP地址,該欄位是一個整數,一般用函數inet_addr()把字元串形式的IP地址轉換成unsigned long型的整數值後再置給s_addr。有的伺服器是多宿主機,至少有兩個網卡,那麼運行在這樣的伺服器上的服務程序在為其Socket綁定IP地址時可以把htonl(INADDR_ANY)置給s_addr,這樣做的好處是不論哪個網段上的客戶程序都能與該服務程序通信;如果只給運行在多宿主機上的服務程序的Socket綁定一個固定的IP地址,那麼就只有與該IP地址處於同一個網段上的客戶程序才能與該服務程序通信。我們用0來填充 sin_zero數組,目的是讓sockaddr_in結構的大小與sockaddr結構的大小一致。下面是一個bind函數調用的例子:
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8888);
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(ListenSocket,(struct sockaddr *)&saddr,sizeof(saddr));
八、listen函數
int listen( SOCKET s, int backlog );
服務程序可以調用listen函數使其流套接字s處於監聽狀態。處於監聽狀態的流套接字s將維護一個客戶連接請求隊列,該隊列最多容納backlog個客戶連接請求。假如該函數執行成功,則返回0;如果執行失敗,則返回SOCKET_ERROR。
九、accept函數
SOCKET accept(
SOCKET s,
struct sockaddr FAR *addr,
int FAR *addrlen
);
服務程序調用accept函數從處於監聽狀態的流套接字s的客戶連接請求隊列中取出排在最前的一個客戶請求,並且創建一個新的套接字來與客戶套接字創建連接通道,如果連接成功,就返回新創建的套接字的描述符,以後與客戶套接字交換數據的是新創建的套接字;如果失敗就返回 INVALID_SOCKET。該函數的第一個參數指定處於監聽狀態的流套接字;操作系統利用第二個參數來返回新創建的套接字的地址結構;操作系統利用第三個參數來返回新創建的套接字的地址結構的長度。下面是一個調用accept的例子:
struct sockaddr_in ServerSocketAddr;
int addrlen;
addrlen=sizeof(ServerSocketAddr);
ServerSocket=accept(ListenSocket,(struct sockaddr *)&ServerSocketAddr,&addrlen);
十、connect函數
int connect(
SOCKET s,
const struct sockaddr FAR *name,
int namelen
);
客戶程序調用connect函數來使客戶Socket s與監聽於name所指定的計算機的特定埠上的服務Socket進行連接。如果連接成功,connect返回0;如果失敗則返回SOCKET_ERROR。下面是一個例子:
struct sockaddr_in daddr;
memset((void *)&daddr,0,sizeof(daddr));
daddr.sin_family=AF_INET;
daddr.sin_port=htons(8888);
daddr.sin_addr.s_addr=inet_addr("133.197.22.4");
connect(ClientSocket,(struct sockaddr *)&daddr,sizeof(daddr));
② Linux 進程間套接字通信(Socket)基礎知識
姓名:羅學元 學號:21181214375 學院:廣州研究院
【嵌牛導讀】Linux進程間套接字通信基礎
【嵌牛鼻子】Linux 進程間套接字及通信介紹
【嵌牛提問】Linux進程間套接字包含哪些內容,如何實現通信
一、套接字(Socket)通信原理
套接字通信允許互聯的位於不同計算機上的進程之間實現通信功能。
二、套接字的屬性
套接字的特性由3個屬性確定,它們分別是:域、類型和協議。
1. 套接字的域
它指定套接字通信中使用的網路介質,最常見的套接字域是AF_INET,它指的是Internet網路。當客戶使用套接字進行跨網路的連接時,它就需要用到伺服器計算機的IP地址和埠來指定一台聯網機器上的某個特定服務,所以在使用socket作為通信的終點,伺服器應用程序必須在開始通信之前綁定一個埠,伺服器在指定的埠等待客戶的連接。
另一個域AF_UNIX表示UNIX文件系統,就是文件輸入/輸出,它的地址就是文件名。
2. 套接字類型
網際網路提供了兩種通信機制:流(stream)和數據報(datagram),因而套接字的類型也就分為流套接字和數據報套接字。我們主要看流套接字。
流套接字由類型SOCK_STREAM指定,它們是在AF_INET域中通過TCP/IP連接實現,同時也是AF_UNIX中常用的套接字類型。
流套接字提供的是一個有序、可靠、雙向位元組流的連接,因此發送的數據可以確保不會丟失、重復或亂序到達,而且它還有一定的出錯後重新發送的機制。
與流套接字相對的是由類型SOCK_DGRAM指定的數據報套接字,它不需要建立連接和維持一個連接,它們在AF_INET中通常是通過UDP/IP實現的。它對可以發送的數據的長度有限制,數據報作為一個單獨的網路消息被傳輸,它可能丟失、復制或錯亂到達,UDP不是一個可靠的協議,但是它的速度比較高,因為它並不需要總是要建立和維持一個連接。
3.套接字協議
只要底層的傳輸機制允許不止一個協議來提供要求的套接字類型,我們就可以為套接字選擇一個特定的協議。通常只需要使用默認值。
三、套接字地址
每個套接字都有其自己的地址格式,對於AF_UNIX域套接字來說,它的地址由結構sockaddr_un來描述,該結構定義在頭文件
struct sockaddr_un{
sa_family_t sun_family; //AF_UNIX,它是一個短整型
char sum_path[]; //路徑名
};
對於AF_INET域套接字來說,它的地址結構由sockaddr_in來描述,它至少包括以下幾個成員:
struct sockaddr_in{
short int sin_family; //AN_INET
unsigned short int sin_port; //埠號
struct in_addr sin_addr; //IP地址
}
而in_addr被定義為:
struct in_addr{
unsigned long int s_addr;
}
四、基於流套接字的客戶/伺服器的工作流程
使用socket進行進程通信的進程採用的客戶/伺服器系統是如何工作的呢?
1.伺服器端
首先,伺服器應用程序用系統調用socket來創建一個套接字,它是系統分配給該伺服器進程的類似文件描述符的資源,它不能與其他的進程共享。
接下來,伺服器進程會給套接字起個名字,我們使用系統調用bind來給套接字命名。然後伺服器進程就開始等待客戶連接到這個套接字。
然後,系統調用listen來創建一個隊列,並將其用於存放來自客戶的進入連接。
最後,伺服器通過系統調用accept來接受客戶的連接。它會創建一個與原有的命名套接不同的新套接字,這個套接字只用於與這個特定客戶端進行通信,而命名套接字(即原先的套接字)則被保留下來繼續處理來自其他客戶的連接。
2.客戶端
基於socket的客戶端比伺服器端簡單。同樣,客戶應用程序首先調用socket來創建一個未命名的套接字,然後講伺服器的命名套接字作為一個地址來調用connect與伺服器建立連接。
一旦連接建立,我們就可以像使用底層的文件描述符那樣用套接字來實現雙向數據的通信。