1. 程式人生 > >C語言程式設計---socket基礎

C語言程式設計---socket基礎

      最初socket是為支援TCP/IP協議而開發的,現在它已被認為是開發非RPCWindows網路應用程式的最好途徑。

socket作為BDS UNIX的程序通訊機制,是進行程式間通訊(IPC)的 BSD 方法,這意味著 socket 用來讓一個程序和其他的程序互通訊息。所謂socket通常也稱作"套接字",用於描述IP地址和埠,是一個通訊鏈的控制代碼。應用程式通常通過"套接字"向網路發出請求或者應答網路請求。

開發原理:

伺服器,使用ServerSocket監聽指定的埠,埠可以隨意指定(由於1024以下的埠通常屬於保留埠,在一些作業系統中不可以隨意使用,所以建議使用大於1024的埠),等待客戶連線請求,客戶連線後,會話產生;在完成會話後,關閉連線。

客戶端,使用Socket對網路上某一個伺服器的某一個埠發出連線請求,一旦連線成功,開啟會話;會話完成後,關閉Socket。客戶端不需要指定開啟的埠,通常臨時的、動態的分配一個1024以上的埠。

--win API socket

本文所談到的Socket函式如果沒有特別說明,都是指的Windows Socket API。

一、WSAStartup函式

<span style="font-size:14px;">intWSAStartup(
WORDwVersionRequested,
LPWSADATAlpWSAData
);</span>

      使用Socket的程式在使用Socket之前必須呼叫WSAStartup函式。該函式的第一個引數指明程式請求使用的Socket版本,其中高位位元組指明副版本、低位位元組指明主版本;作業系統利用第二個引數返回請求的Socket的版本資訊。當一個應用程式呼叫WSAStartup函式時,作業系統根據請求的Socket版本來搜尋相應的Socket庫,然後繫結找到的Socket庫到該應用程式中。以後應用程式就可以呼叫所請求的Socket庫中的其它Socket函數了。該函式執行成功後返回0。

例:假如一個程式要使用2.1版本的Socket,那麼程式程式碼如下

<span style="font-size:14px;">wVersionRequested= MAKEWORD( 2, 1 );
err= WSAStartup( wVersionRequested, &wsaData );</span>

二、WSACleanup函式

<span style="font-size:14px;">intWSACleanup (void);</span>

    應用程式在完成對請求的Socket庫的使用後,要呼叫WSACleanup函式來解除與Socket庫的繫結並且釋放Socket庫所佔用的系統資源。

三、socket函式

<span style="font-size:14px;">SOCKETsocket(
intaf,
inttype,
intprotocol
);</span>

       應用程式呼叫socket函式來建立一個能夠進行網路通訊的套接字。第一個引數指定應用程式使用的通訊協議的協議族,對於TCP/IP協議族,該引數置PF_INET;第二個引數指定要建立的套接字型別,流套接字型別為SOCK_STREAM、資料報套接字型別為SOCK_DGRAM;第三個引數指定應用程式所使用的通訊協議。該函式如果呼叫成功就返回新建立的套接字的描述符,如果失敗就返回INVALID_SOCKET。套接字描述符是一個整數型別的值。每個程序的程序空間裡都有一個套接字描述符表,該表中存放著套接字描述符和套接字資料結構的對應關係。該表中有一個欄位存放新建立的套接字的描述符,另一個欄位存放套接字資料結構的地址,因此根據套接字描述符就可以找到其對應的套接字資料結構。每個程序在自己的程序空間裡都有一個套接字描述符表但是套接字資料結構都是在作業系統的核心緩衝裡。下面是一個建立流套接字的例子:

<span style="font-size:14px;">structprotoent *ppe;
ppe=getprotobyname("tcp");
SOCKETListenSocket=socket(PF_INET,SOCK_STREAM,ppe->p_proto);</span>

四、closesocket函式

<span style="font-size:14px;">intclosesocket(
SOCKETs
);</span>

      closesocket函式用來關閉一個描述符為s套接字。由於每個程序中都有一個套接字描述符表,表中的每個套接字描述符都對應了一個位於作業系統緩衝區中的套接字資料結構,因此有可能有幾個套接字描述符指向同一個套接字資料結構。套接字資料結構中專門有一個欄位存放該結構的被引用次數,即有多少個套接字描述符指向該結構。當呼叫closesocket函式時,作業系統先檢查套接字資料結構中的該欄位的值,如果為1,就表明只有一個套接字描述符指向它,因此作業系統就先把s在套接字描述符表中對應的那條表項清除,並且釋放s對應的套接字資料結構;如果該欄位大於1,那麼作業系統僅僅清除s在套接字描述符表中的對應表項,並且把s對應的套接字資料結構的引用次數減1。

closesocket函式如果執行成功就返回0,否則返回SOCKET_ERROR。

五、send函式

<span style="font-size:14px;">intsend(
SOCKETs,
constchar FAR *buf,
intlen,
intflags
);</span>

        不論是客戶還是伺服器應用程式都用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中的資料copy到剩餘空間裡(注意並不是send把s的傳送緩衝中的資料傳到連線的另一端的,而是協議傳的,send僅僅是把buf中的資料copy到s的傳送緩衝區的剩餘空間裡)。如果send函式copy資料成功,就返回實際copy的位元組數,如果send在copy資料時出現錯誤,那麼send就返回SOCKET_ERROR;如果send在等待協議傳送資料時網路斷開的話,那麼send函式也返回SOCKET_ERROR。要注意send函式把buf中的資料成功copy到s的傳送緩衝的剩餘空間裡後它就返回了,但是此時這些資料並不一定馬上被傳到連線的另一端。如果協議在後續的傳送過程中出現網路錯誤的話,那麼下一個Socket函式就會返回SOCKET_ERROR。(每一個除send外的Socket函式在執行的最開始總要先等待套接字的傳送緩衝中的資料被協議傳送完畢才能繼續,如果在等待時出現網路錯誤,那麼該Socket函式就返回SOCKET_ERROR)

       注意:在Unix系統下,如果send在等待協議傳送資料時網路斷開的話,呼叫send的程序會接收到一個SIGPIPE訊號,程序對該訊號的預設處理是程序終止。

六、recv函式

<span style="font-size:14px;">intrecv(
SOCKETs,
charFAR *buf,
intlen,
intflags
);</span>

       不論是客戶還是伺服器應用程式都用recv函式從TCP連線的另一端接收資料。該函式的第一個引數指定接收端套接字描述符;第二個引數指明一個緩衝區,該緩衝區用來存放recv函式接收到的資料;第三個引數指明buf的長度;第四個引數一般置0。這裡只描述同步Socket的recv函式的執行流程。當應用程式呼叫recv函式時,recv先等待s的傳送緩衝中的資料被協議傳送完畢,如果協議在傳送s的傳送緩衝中的資料時出現網路錯誤,那麼recv函式返回SOCKET_ERROR,如果s的傳送緩衝中沒有資料或者資料被協議成功傳送完畢後,recv先檢查套接字s的接收緩衝區,如果s接收緩衝區中沒有資料或者協議正在接收資料,那麼recv就一直等待,只到協議把資料接收完畢。當協議把資料接收完畢,recv函式就把s的接收緩衝中的資料copy到buf中(注意協議接收到的資料可能大於buf的長度,所以在這種情況下要呼叫幾次recv函式才能把s的接收緩衝中的資料copy完。recv函式僅僅是copy資料,真正的接收資料是協議來完成的),recv函式返回其實際copy的位元組數。如果recv在copy時出錯,那麼它返回SOCKET_ERROR;如果recv函式在等待協議接收資料時網路中斷了,那麼它返回0。

       注意:在Unix系統下,如果recv函式在等待協議接收資料時網路斷開了,那麼呼叫recv的程序會接收到一個SIGPIPE訊號,程序對該訊號的預設處理是程序終止。

七、bind函式

<span style="font-size:14px;">intbind(
SOCKETs,
conststruct sockaddr FAR *name,
intnamelen
);</span>

       當建立了一個Socket以後,套接字資料結構中有一個預設的IP地址和預設的埠號。一個服務程式必須呼叫bind函式來給其繫結一個IP地址和一個特定的埠號。客戶程式一般不必呼叫bind函式來為其Socket繫結IP地址和斷口號。該函式的第一個引數指定待繫結的Socket描述符;第二個引數指定一個sockaddr結構,該結構是這樣定義的:

<span style="font-size:14px;">structsockaddr {
u_shortsa_family;
charsa_data[14];
};</span>

       sa_family指定地址族,對於TCP/IP協議族的套接字,給其置AF_INET。當對TCP/IP協議族的套接字進行繫結時,我們通常使用另一個地址結構:

<span style="font-size:14px;">structsockaddr_in {
shortsin_family;
u_shortsin_port;
structin_addr sin_addr;
charsin_zero[8];
};</span>

        其中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函式呼叫的例子:

<span style="font-size:14px;">structsockaddr_in saddr;
saddr.sin_family= AF_INET;
saddr.sin_port= htons(8888);
saddr.sin_addr.s_addr= htonl(INADDR_ANY);
bind(ListenSocket,(structsockaddr *)&saddr,sizeof(saddr));</span>

八、listen函式

<span style="font-size:14px;">intlisten( SOCKET s, int backlog );</span>

       服務程式可以呼叫listen函式使其流套接字s處於監聽狀態。處於監聽狀態的流套接字s將維護一個客戶連線請求佇列,該佇列最多容納backlog個客戶連線請求。假如該函式執行成功,則返回0;如果執行失敗,則返回SOCKET_ERROR。

九、accept函式

<span style="font-size:14px;">SOCKETaccept(
SOCKETs,
structsockaddr FAR *addr,
intFAR *addrlen
);</span>

       服務程式呼叫accept函式從處於監聽狀態的流套接字s的客戶連線請求佇列中取出排在最前的一個客戶請求,並且建立一個新的套接字來與客戶套接字建立連線通道,如果連線成功,就返回新建立的套接字的描述符,以後與客戶套接字交換資料的是新建立的套接字;如果失敗就返回INVALID_SOCKET。該函式的第一個引數指定處於監聽狀態的流套接字;作業系統利用第二個引數來返回新建立的套接字的地址結構;作業系統利用第三個引數來返回新建立的套接字的地址結構的長度。下面是一個呼叫accept的例子:

<span style="font-size:14px;">structsockaddr_in ServerSocketAddr;
intaddrlen;
addrlen=sizeof(ServerSocketAddr);
ServerSocket=accept(ListenSocket,(structsockaddr *)&ServerSocketAddr,&addrlen);</span>

十、connect函式

<span style="font-size:14px;">intconnect(
SOCKETs,
conststruct sockaddr FAR *name,
intnamelen
);</span>

       客戶程式呼叫connect函式來使客戶Socket s與監聽於name所指定的計算機的特定埠上的服務Socket進行連線。如果連線成功,connect返回0;如果失敗則返回SOCKET_ERROR。下面是一個例子:

<span style="font-size:14px;">structsockaddr_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,(structsockaddr *)&daddr,sizeof(daddr));</span>