我的Windows Socket API 使用經(jīng)驗(yàn) 文章作者:唐綱
本文是我在進(jìn)行MS-Windows,、HP-Unix網(wǎng)絡(luò)編程的實(shí)踐過(guò)程中總結(jié)出來(lái)的一些經(jīng)驗(yàn),,僅供大家參考。本文所談到的Socket函數(shù)如果沒(méi)有特別說(shuō)明,,都是指的Windows Socket API,。
一、WSAStartup函數(shù) int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData ); 使
用Socket的程序在使用Socket之前必須調(diào)用WSAStartup函數(shù),。該函數(shù)的第一個(gè)參數(shù)指明程序請(qǐng)求使用的Socket版本,,其中高位字節(jié)指
明副版本、低位字節(jié)指明主版本,;操作系統(tǒng)利用第二個(gè)參數(shù)返回請(qǐng)求的Socket的版本信息,。當(dāng)一個(gè)應(yīng)用程序調(diào)用WSAStartup函數(shù)時(shí),操作系統(tǒng)根據(jù)
請(qǐng)求的Socket版本來(lái)搜索相應(yīng)的Socket庫(kù),,然后綁定找到的Socket庫(kù)到該應(yīng)用程序中,。以后應(yīng)用程序就可以調(diào)用所請(qǐng)求的Socket庫(kù)中的其
它Socket函數(shù)了。該函數(shù)執(zhí)行成功后返回0,。 例:假如一個(gè)程序要使用2.1版本的Socket,那么程序代碼如下 wVersionRequested = MAKEWORD( 2, 1 ); err = WSAStartup( wVersionRequested, &wsaData );
二,、WSACleanup函數(shù) int WSACleanup (void); 應(yīng)用程序在完成對(duì)請(qǐng)求的Socket庫(kù)的使用后,要調(diào)用WSACleanup函數(shù)來(lái)解除與Socket庫(kù)的綁定并且釋放Socket庫(kù)所占用的系統(tǒng)資源,。
三,、socket函數(shù) SOCKET socket( int af, int type, int protocol );
應(yīng)用程序調(diào)用socket函數(shù)來(lái)創(chuàng)建一個(gè)能夠進(jìn)行網(wǎng)絡(luò)通信的套接字。第一個(gè)參數(shù)指定應(yīng)用程序使用的通信協(xié)議的協(xié)議族,,對(duì)于TCP/IP協(xié)議族,,該參數(shù)置
PF_INET;第二個(gè)參數(shù)指定要?jiǎng)?chuàng)建的套接字類(lèi)型,流套接字類(lèi)型為SOCK_STREAM,、數(shù)據(jù)報(bào)套接字類(lèi)型為SOCK_DGRAM,;第三個(gè)參數(shù)指定應(yīng)
用程序所使用的通信協(xié)議。該函數(shù)如果調(diào)用成功就返回新創(chuàng)建的套接字的描述符,,如果失敗就返回INVALID_SOCKET,。套接字描述符是一個(gè)整數(shù)類(lèi)型的
值。每個(gè)進(jìn)程的進(jìn)程空間里都有一個(gè)套接字描述符表,,該表中存放著套接字描述符和套接字?jǐn)?shù)據(jù)結(jié)構(gòu)的對(duì)應(yīng)關(guān)系,。該表中有一個(gè)字段存放新創(chuàng)建的套接字的描述符,
另一個(gè)字段存放套接字?jǐn)?shù)據(jù)結(jié)構(gòu)的地址,,因此根據(jù)套接字描述符就可以找到其對(duì)應(yīng)的套接字?jǐn)?shù)據(jù)結(jié)構(gòu),。每個(gè)進(jìn)程在自己的進(jìn)程空間里都有一個(gè)套接字描述符表但是套
接字?jǐn)?shù)據(jù)結(jié)構(gòu)都是在操作系統(tǒng)的內(nèi)核緩沖里,。下面是一個(gè)創(chuàng)建流套接字的例子: struct protoent *ppe; ppe=getprotobyname("tcp"); SOCKET ListenSocket=socket(PF_INET,SOCK_STREAM,ppe->p_proto);
四、closesocket函數(shù) int closesocket( SOCKET s );
closesocket函數(shù)用來(lái)關(guān)閉一個(gè)描述符為s套接字,。由于每個(gè)進(jìn)程中都有一個(gè)套接字描述符表,,表中的每個(gè)套接字描述符都對(duì)應(yīng)了一個(gè)位于操作系統(tǒng)緩沖
區(qū)中的套接字?jǐn)?shù)據(jù)結(jié)構(gòu),因此有可能有幾個(gè)套接字描述符指向同一個(gè)套接字?jǐn)?shù)據(jù)結(jié)構(gòu),。套接字?jǐn)?shù)據(jù)結(jié)構(gòu)中專(zhuān)門(mén)有一個(gè)字段存放該結(jié)構(gòu)的被引用次數(shù),,即有多少個(gè)套接
字描述符指向該結(jié)構(gòu)。當(dāng)調(diào)用closesocket函數(shù)時(shí),,操作系統(tǒng)先檢查套接字?jǐn)?shù)據(jù)結(jié)構(gòu)中的該字段的值,,如果為1,就表明只有一個(gè)套接字描述符指向它,因此操作系統(tǒng)就先把s在套接字描述符表中對(duì)應(yīng)的那條表項(xiàng)清除,并且釋放s對(duì)應(yīng)的套接字?jǐn)?shù)據(jù)結(jié)構(gòu),;如果該字段大于1,那么操作系統(tǒng)僅僅清除s在套接字描述符表中的對(duì)應(yīng)表項(xiàng),,并且把s對(duì)應(yīng)的套接字?jǐn)?shù)據(jù)結(jié)構(gòu)的引用次數(shù)減1。 closesocket函數(shù)如果執(zhí)行成功就返回0,,否則返回SOCKET_ERROR,。
五、send函數(shù) int send( SOCKET s, const char FAR *buf, int len, int flags );
不論是客戶(hù)還是服務(wù)器應(yīng)用程序都用send函數(shù)來(lái)向TCP連接的另一端發(fā)送數(shù)據(jù),??蛻?hù)程序一般用send函數(shù)向服務(wù)器發(fā)送請(qǐng)求,而服務(wù)器則通常用send
函數(shù)來(lái)向客戶(hù)程序發(fā)送應(yīng)答,。該函數(shù)的第一個(gè)參數(shù)指定發(fā)送端套接字描述符,;第二個(gè)參數(shù)指明一個(gè)存放應(yīng)用程序要發(fā)送數(shù)據(jù)的緩沖區(qū);第三個(gè)參數(shù)指明實(shí)際要發(fā)送的
數(shù)據(jù)的字節(jié)數(shù),;第四個(gè)參數(shù)一般置0,。這里只描述同步Socket的send函數(shù)的執(zhí)行流程。當(dāng)調(diào)用該函數(shù)時(shí),,send先比較待發(fā)送數(shù)據(jù)的長(zhǎng)度len和套接
字s的發(fā)送緩沖區(qū)的長(zhǎng)度,,如果len大于s的發(fā)送緩沖區(qū)的長(zhǎng)度,該函數(shù)返回SOCKET_ERROR,;如果len小于或者等于s的發(fā)送緩沖區(qū)的長(zhǎng)度,,那么
send先檢查協(xié)議是否正在發(fā)送s的發(fā)送緩沖中的數(shù)據(jù),如果是就等待協(xié)議把數(shù)據(jù)發(fā)送完,,如果協(xié)議還沒(méi)有開(kāi)始發(fā)送s的發(fā)送緩沖中的數(shù)據(jù)或者s的發(fā)送緩沖中沒(méi)
有數(shù)據(jù),,那么send就比較s的發(fā)送緩沖區(qū)的剩余空間和len,如果len大于剩余空間大小send就一直等待協(xié)議把s的發(fā)送緩沖中的數(shù)據(jù)發(fā)送完,,如果
len小于剩余空間大小send就僅僅把buf中的數(shù)據(jù)copy到剩余空間里(注意并不是send把s的發(fā)送緩沖中的數(shù)據(jù)傳到連接的另一端的,,而是協(xié)議傳
的,,send僅僅是把buf中的數(shù)據(jù)copy到s的發(fā)送緩沖區(qū)的剩余空間里)。如果send函數(shù)copy數(shù)據(jù)成功,,就返回實(shí)際copy的字節(jié)數(shù),如果
send在copy數(shù)據(jù)時(shí)出現(xiàn)錯(cuò)誤,,那么send就返回SOCKET_ERROR,;如果send在等待協(xié)議傳送數(shù)據(jù)時(shí)網(wǎng)絡(luò)斷開(kāi)的話(huà),那么send函數(shù)也返
回SOCKET_ERROR,。要注意send函數(shù)把buf中的數(shù)據(jù)成功copy到s的發(fā)送緩沖的剩余空間里后它就返回了,,但是此時(shí)這些數(shù)據(jù)并不一定馬上被
傳到連接的另一端。如果協(xié)議在后續(xù)的傳送過(guò)程中出現(xiàn)網(wǎng)絡(luò)錯(cuò)誤的話(huà),,那么下一個(gè)Socket函數(shù)就會(huì)返回SOCKET_ERROR,。(每一個(gè)除send外的
Socket函數(shù)在執(zhí)行的最開(kāi)始總要先等待套接字的發(fā)送緩沖中的數(shù)據(jù)被協(xié)議傳送完畢才能繼續(xù),如果在等待時(shí)出現(xiàn)網(wǎng)絡(luò)錯(cuò)誤,,那么該Socket函數(shù)就返回
SOCKET_ERROR) 注意:在Unix系統(tǒng)下,,如果send在等待協(xié)議傳送數(shù)據(jù)時(shí)網(wǎng)絡(luò)斷開(kāi)的話(huà),調(diào)用send的進(jìn)程會(huì)接收到一個(gè)SIGPIPE信號(hào),,進(jìn)程對(duì)該信號(hào)的默認(rèn)處理是進(jìn)程終止,。
六、recv函數(shù) int recv( SOCKET s, char FAR *buf, int len, int flags );
不論是客戶(hù)還是服務(wù)器應(yīng)用程序都用recv函數(shù)從TCP連接的另一端接收數(shù)據(jù),。該函數(shù)的第一個(gè)參數(shù)指定接收端套接字描述符,;第二個(gè)參數(shù)指明一個(gè)緩沖區(qū),該
緩沖區(qū)用來(lái)存放recv函數(shù)接收到的數(shù)據(jù),;第三個(gè)參數(shù)指明buf的長(zhǎng)度,;第四個(gè)參數(shù)一般置0。這里只描述同步Socket的recv函數(shù)的執(zhí)行流程,。當(dāng)應(yīng)
用程序調(diào)用recv函數(shù)時(shí),,recv先等待s的發(fā)送緩沖中的數(shù)據(jù)被協(xié)議傳送完畢,如果協(xié)議在傳送s的發(fā)送緩沖中的數(shù)據(jù)時(shí)出現(xiàn)網(wǎng)絡(luò)錯(cuò)誤,,那么recv函數(shù)返
回SOCKET_ERROR,,如果s的發(fā)送緩沖中沒(méi)有數(shù)據(jù)或者數(shù)據(jù)被協(xié)議成功發(fā)送完畢后,recv先檢查套接字s的接收緩沖區(qū),,如果s接收緩沖區(qū)中沒(méi)有數(shù)
據(jù)或者協(xié)議正在接收數(shù)據(jù),,那么recv就一直等待,只到協(xié)議把數(shù)據(jù)接收完畢,。當(dāng)協(xié)議把數(shù)據(jù)接收完畢,,recv函數(shù)就把s的接收緩沖中的數(shù)據(jù)copy到
buf中(注意協(xié)議接收到的數(shù)據(jù)可能大于buf的長(zhǎng)度,所以在這種情況下要調(diào)用幾次recv函數(shù)才能把s的接收緩沖中的數(shù)據(jù)copy完,。recv函數(shù)僅僅
是copy數(shù)據(jù),,真正的接收數(shù)據(jù)是協(xié)議來(lái)完成的),,recv函數(shù)返回其實(shí)際copy的字節(jié)數(shù)。如果recv在copy時(shí)出錯(cuò),,那么它返回
SOCKET_ERROR,;如果recv函數(shù)在等待協(xié)議接收數(shù)據(jù)時(shí)網(wǎng)絡(luò)中斷了,那么它返回0,。 注意:在Unix系統(tǒng)下,,如果recv函數(shù)在等待協(xié)議接收數(shù)據(jù)時(shí)網(wǎng)絡(luò)斷開(kāi)了,那么調(diào)用recv的進(jìn)程會(huì)接收到一個(gè)SIGPIPE信號(hào),,進(jìn)程對(duì)該信號(hào)的默認(rèn)處理是進(jìn)程終止,。
七、bind函數(shù) int bind( SOCKET s, const struct sockaddr FAR *name, int namelen );
當(dāng)創(chuàng)建了一個(gè)Socket以后,,套接字?jǐn)?shù)據(jù)結(jié)構(gòu)中有一個(gè)默認(rèn)的IP地址和默認(rèn)的端口號(hào),。一個(gè)服務(wù)程序必須調(diào)用bind函數(shù)來(lái)給其綁定一個(gè)IP地址和一個(gè)特
定的端口號(hào)??蛻?hù)程序一般不必調(diào)用bind函數(shù)來(lái)為其Socket綁定IP地址和斷口號(hào),。該函數(shù)的第一個(gè)參數(shù)指定待綁定的Socket描述符;第二個(gè)參數(shù)
指定一個(gè)sockaddr結(jié)構(gòu),,該結(jié)構(gòu)是這樣定義的: struct sockaddr { u_short sa_family; char sa_data[14]; }; sa_family指定地址族,,對(duì)于TCP/IP協(xié)議族的套接字,給其置AF_INET,。當(dāng)對(duì)TCP/IP協(xié)議族的套接字進(jìn)行綁定時(shí),,我們通常使用另一個(gè)地址結(jié)構(gòu): 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指明端口號(hào),;sin_addr結(jié)構(gòu)體中只有一個(gè)唯一的字段s_addr,,表示IP地址,該字
段是一個(gè)整數(shù),,一般用函數(shù)inet_addr()把字符串形式的IP地址轉(zhuǎn)換成unsigned
long型的整數(shù)值后再置給s_addr,。有的服務(wù)器是多宿主機(jī),至少有兩個(gè)網(wǎng)卡,,那么運(yùn)行在這樣的服務(wù)器上的服務(wù)程序在為其Socket綁定IP地址時(shí)
可以把htonl(INADDR_ANY)置給s_addr,,這樣做的好處是不論哪個(gè)網(wǎng)段上的客戶(hù)程序都能與該服務(wù)程序通信;如果只給運(yùn)行在多宿主機(jī)上的
服務(wù)程序的Socket綁定一個(gè)固定的IP地址,,那么就只有與該IP地址處于同一個(gè)網(wǎng)段上的客戶(hù)程序才能與該服務(wù)程序通信,。我們用0來(lái)填充
sin_zero數(shù)組,目的是讓sockaddr_in結(jié)構(gòu)的大小與sockaddr結(jié)構(gòu)的大小一致,。下面是一個(gè)bind函數(shù)調(diào)用的例子: 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函數(shù) int listen( SOCKET s, int backlog ); 服務(wù)程序可以調(diào)用listen函數(shù)使其流套接字s處于監(jiān)聽(tīng)狀態(tài),。處于監(jiān)聽(tīng)狀態(tài)的流套接字s將維護(hù)一個(gè)客戶(hù)連接請(qǐng)求隊(duì)列,,該隊(duì)列最多容納backlog個(gè)客戶(hù)連接請(qǐng)求。假如該函數(shù)執(zhí)行成功,,則返回0,;如果執(zhí)行失敗,則返回SOCKET_ERROR,。
九,、accept函數(shù) SOCKET accept( SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen );
服務(wù)程序調(diào)用accept函數(shù)從處于監(jiān)聽(tīng)狀態(tài)的流套接字s的客戶(hù)連接請(qǐng)求隊(duì)列中取出排在最前的一個(gè)客戶(hù)請(qǐng)求,并且創(chuàng)建一個(gè)新的套接字來(lái)與客戶(hù)套接字創(chuàng)建連
接通道,,如果連接成功,就返回新創(chuàng)建的套接字的描述符,,以后與客戶(hù)套接字交換數(shù)據(jù)的是新創(chuàng)建的套接字,;如果失敗就返回INVALID_SOCKET。該函
數(shù)的第一個(gè)參數(shù)指定處于監(jiān)聽(tīng)狀態(tài)的流套接字,;操作系統(tǒng)利用第二個(gè)參數(shù)來(lái)返回新創(chuàng)建的套接字的地址結(jié)構(gòu),;操作系統(tǒng)利用第三個(gè)參數(shù)來(lái)返回新創(chuàng)建的套接字的地址
結(jié)構(gòu)的長(zhǎng)度。下面是一個(gè)調(diào)用accept的例子: struct sockaddr_in ServerSocketAddr; int addrlen; addrlen=sizeof(ServerSocketAddr); ServerSocket=accept(ListenSocket,(struct sockaddr *)&ServerSocketAddr,&addrlen);
十,、connect函數(shù) int connect( SOCKET s, const struct sockaddr FAR *name, int namelen ); 客戶(hù)程序調(diào)用connect函數(shù)來(lái)使客戶(hù)Socket s與監(jiān)聽(tīng)于name所指定的計(jì)算機(jī)的特定端口上的服務(wù)Socket進(jìn)行連接,。如果連接成功,connect返回0,;如果失敗則返回SOCKET_ERROR,。下面是一個(gè)例子: 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));
|