Winsocket入門教程二:非阻塞式服務器和客戶端程序(TCP)_第1頁
Winsocket入門教程二:非阻塞式服務器和客戶端程序(TCP)_第2頁
Winsocket入門教程二:非阻塞式服務器和客戶端程序(TCP)_第3頁
Winsocket入門教程二:非阻塞式服務器和客戶端程序(TCP)_第4頁
Winsocket入門教程二:非阻塞式服務器和客戶端程序(TCP)_第5頁
已閱讀5頁,還剩18頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、Winsocket入門教程二:非阻塞式服務器和客戶端程序(TCP) HYPERLINK JavaScript:d=document;t=d.selection?(d.selection.type!=None?d.selection.createRange().text:):(d.getSelection?d.getSelection():);void(saveit=window.open(/storeit.aspx?t=+escape(d.title)+&u=+escape(d.location.href)+&c=+escape(t),saveit,scrollbars=no,width=59

2、0,height=300,left=75,top=20,status=no,resizable=yes);saveit.focus(); o 收藏到我的網(wǎng)摘中,并分享給我的朋友 收藏 上次為大家介紹了阻塞式多線程服務端程序和阻塞式客戶端程序的設計方法,但是在上文的最后也提到過,服務器程序會因為建立連接和關閉連接而頻繁的創(chuàng)建和關閉線程會產生大量的內存碎片,從而導致服務端程序不能保證長時間的穩(wěn)定運行。因此我在這里為大家介紹另外一種建立服務器和客戶端程序的方法,即建立非阻塞式的服務器和客戶端程序。 那什么是非阻塞呢?非阻塞是相對于阻塞而言,阻塞指的是在進行一個操作的時候,如服務器接收客戶端的連接(a

3、ccept),服務器或者客戶端讀寫數(shù)據(jù)(read、write),如果該操作沒有執(zhí)行完成(成功或者失敗都算是執(zhí)行完成),則程序會一直阻塞在操作執(zhí)行的地方,直到該操作返回一個明確的結果。而非阻塞式程序則不一樣,非阻塞式程序會在產生阻塞操作的地方阻塞一定的時間(該時間可以由程序員自己設置)。如果操作沒有完成,在到達所設置的時間之后,無論該操作成功與否,都結束該操作而執(zhí)行程序下面的操作。 為了執(zhí)行非阻塞操作,我們在創(chuàng)建了一個套接口后,需要將套接口設置為非阻塞的套接口。為了將套接口設置成為非阻塞套接口,我們需要調用ioctlsocket函數(shù)將套接口設置為非阻塞的套接口。ioctlsocket函數(shù)的定義如

4、下: int ioctlsocket( SOCKET HYPERLINK http:/mk:MSITStoreE/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/MSDN/2001OCT/1033/WinSock.chm: s, long HYPERLINK http:/mk:MSITStoreE/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/MSDN/2001OCT/1033/WinSock.chm: cmd, u_long FAR * HYPERLINK http:/mk:MSITStoreE/%E5%BC%80%E5%8F%91%E5

5、%B7%A5%E5%85%B7/MSDN/2001OCT/1033/WinSock.chm: argp) 該函數(shù)的作用是控制套接口的I/O模式。 參數(shù)s表示要設置的套接口;參數(shù)cmd表示要對該套接口設置的命令,為了要將套接口設置成為非阻塞的,我們應該填寫FIONBIO;argp表示填寫命令的值,如我們要將套接口設置成非阻塞的,我們需要將值設置成為1,如果我們要將套接口設置成為非阻塞狀態(tài)的話,我們將值設置成為0就是了。 為了進行非阻塞的操作,我們需要在進行操作之前調用select函數(shù),select函數(shù)的定義如下: int select(int HYPERLINK http:/mk:MSITSto

6、reE/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/MSDN/2001OCT/1033/WinSock.chm: nfds, fd_set FAR * HYPERLINK http:/mk:MSITStoreE/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/MSDN/2001OCT/1033/WinSock.chm: readfds, fd_set FAR * HYPERLINK http:/mk:MSITStoreE/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/MSDN/2001OCT/1033/WinS

7、ock.chm: writefds, fd_set FAR * HYPERLINK http:/mk:MSITStoreE/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/MSDN/2001OCT/1033/WinSock.chm: exceptfds, const struct timeval FAR * HYPERLINK http:/mk:MSITStoreE/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/MSDN/2001OCT/1033/WinSock.chm: timeout); 該函數(shù)設定一個或多個套接口的狀態(tài),并進行必要的等

8、待,以便執(zhí)行異步I/0(非阻塞)操作。 參數(shù)nfds被忽略,該參數(shù)的作用僅僅是為了與伯克利套接口相兼容;參數(shù)readfds表示要檢測的可讀套接口的集合(該參數(shù)可選,可為設置為NULL);參數(shù)readfds表示要檢測的可寫套接口的集合(該參數(shù)可選,可為設置為NULL);參數(shù)exceptfds表示要檢測的套接口的錯誤(該參數(shù)可選,可為設置為NULL);參數(shù)timeout表示執(zhí)行該函數(shù)時需要等待的時間,如果為NULL則表示阻塞操作,為0則表示立即返回。 下面讓我們來看看參數(shù)類型fd_set,fd_set表示套接字的集合。在使用select函數(shù)時,我們需要將相應的套接字加入到相應的集合中。如果集合中的

9、套接字有信號,select函數(shù)的返回值即為集合中有信號的套接字數(shù)量。 我們用下面的幾個宏來操作fd_set集合。我們可以使用FD_SET(s, *set)將套接字s加入到集合set中;我們可以使用FD_CLR(s, *set)將套接字s移除出集合set;我們可以使用FD_ZERO(*set)將集合set清空;最后,我們可以使用FD_ISSET(s, *set)來判斷套接字s是否在集合中有信號。 接下來再讓我們來看看select函數(shù)的三個集合參數(shù)readfds、writefds以及exceptfds。 readfds表示可讀套接字的集合,可讀套接字在三種情況下有信號出現(xiàn):一、如果集合中有套接字處

10、于監(jiān)聽狀態(tài),并且該套接字上有來自客戶端的連接請求;二、如果集合中的套接字收到了send操作發(fā)送過來的數(shù)據(jù);三、如果集合中的套接字被關閉、重置或者中斷。 writefds表示可寫套接字的集合,可寫套接字在兩種情況下有信號出現(xiàn):一、集合中的套接字經(jīng)過connect操作后,連接成功;二、可以用send操作向集合中的套接字寫數(shù)據(jù)。 exceptfds表示錯誤套接字的集合,錯誤套接字在兩種情況下有信號出現(xiàn):一、集合中的套接字經(jīng)過connect操作后,連接失敗;二、有帶外數(shù)據(jù)到來。 在我們了解了創(chuàng)建服務器和客戶端程序的基礎知識后,我們再來看看示例程序,以加深我們對知識的理解。 程序的運行結果如下所示: 下

11、面是服務器程序的代碼: HYPERLINK /shining100/archive/2010/06/06/5651878.aspx view plain HYPERLINK /shining100/archive/2010/06/06/5651878.aspx copy to clipboard HYPERLINK /shining100/archive/2010/06/06/5651878.aspx print HYPERLINK /shining100/archive/2010/06/06/5651878.aspx ?#include #include #include #include

12、#pragmacomment(lib,ws2_32.lib) #defineASSERTassert usingstd:cin; usingstd:cout; usingstd:endl; usingstd:list; typedeflistSocketList; typedeflist:iteratorSocketListIterator; staticconstintc_iPort=10001; boolGraceClose(SOCKET*ps); intmain() intiRet=SOCKET_ERROR; /初始化Winsocket,所有Winsocket程序必須先使用WSAStar

13、tup進行初始化 WSADATAdata; ZeroMemory(&data,sizeof(WSADATA); iRet=WSAStartup(MAKEWORD(2,0),&data); ASSERT(SOCKET_ERROR!=iRet); /建立服務端程序的監(jiān)聽套接字 SOCKETskListen=INVALID_SOCKET; skListen=socket(AF_INET,SOCK_STREAM,0); ASSERT(INVALID_SOCKET!=skListen); /初始化監(jiān)聽套接字地址信息 sockaddr_inadrServ;/表示網(wǎng)絡地址 ZeroMemory(&adrSe

14、rv,sizeof(sockaddr_in); adrServ.sin_family=AF_INET;/初始化地址格式,只能為AF_INET adrServ.sin_port=htons(c_iPort);/初始化端口,由于網(wǎng)絡字節(jié)順序和主機字節(jié)順序相反,所以必須使用htons將主機字節(jié)順序轉換成網(wǎng)絡字節(jié)順序 adrServ.sin_addr.s_addr=INADDR_ANY;/初始化IP,由于是服務器程序,所以可以將INADDR_ANY賦給該字段,表示任意的IP /綁定監(jiān)聽套接字到本地 iRet=bind(skListen,(sockaddr*)&adrServ,sizeof(sockad

15、dr_in); ASSERT(SOCKET_ERROR!=iRet); /使用監(jiān)聽套接字進行監(jiān)聽 iRet=listen(skListen,FD_SETSIZE);/SOMAXCONN表示可以連接到該程序的最大連接數(shù) ASSERT(SOCKET_ERROR!=iRet); coutServerbeganlistening.0) sockaddr_inadrClt; intiLen=sizeof(sockaddr_in); ZeroMemory(&adrClt,iLen); SOCKETs=accept(skListen,(sockaddr*)&adrClt,&iLen); ASSERT(INV

16、ALID_SOCKET!=s); sl.push_back(s); coutServeracceptedaconnection.Thesocketiss0) for(SocketListIteratoriter=sl.begin();iter!=sl.end();+iter) /如果有數(shù)據(jù)可讀,則遍歷套接字列表中的所有套接字 /檢測出有數(shù)據(jù)可讀的套接字 iRet=FD_ISSET(*iter,&fsRead); if(iRet0) /讀取套接字上的數(shù)據(jù) constintc_iBufLen=512; charszBufc_iBufLen+1=0; intiRead=SOCKET_ERROR; i

17、Read=recv(*iter,szBuf,c_iBufLen,0); if(0=iRead)/讀取出現(xiàn)錯誤或者對方關閉連接 iRead=0?coutConnectionshutdownatsocket*iterendl: coutConnectionrecverroratsocket*iterendl; iRet=GraceClose(&(*iter);/如果出錯則關閉套接字 ASSERT(iRet); else szBufiRead=0; coutServerrecvedmessagefromsocket*iter:szBufendl; /創(chuàng)建可寫集合 FD_SETfsWrite; FD_

18、ZERO(&fsWrite); FD_SET(*iter,&fsWrite); /如果有數(shù)據(jù)可寫,則向客戶端發(fā)送數(shù)據(jù) iRet=select(1,NULL,&fsWrite,NULL,&tv); if(0iRet) intiWrite=SOCKET_ERROR; iWrite=send(*iter,szBuf,iRead,0); if(SOCKET_ERROR=iWrite) coutSendmessageerroratsocket*iter0); if(SOCKET_ERROR=iRet) returnfalse; /清理該套接字的資源 iRet=closesocket(*ps); if(S

19、OCKET_ERROR=iRet) returnfalse; *ps=INVALID_SOCKET; returntrue; HTMLCONTROL Forms.HTML:TextArea.1 服務器程序的重點是我們需要將接受自客戶端程序的套接字加入到一個鏈表中,以方便我們的管理。 HYPERLINK /shining100/archive/2010/06/06/5651878.aspx view plain HYPERLINK /shining100/archive/2010/06/06/5651878.aspx copy to clipboard HYPERLINK /shining100

20、/archive/2010/06/06/5651878.aspx print HYPERLINK /shining100/archive/2010/06/06/5651878.aspx ?FD_SET(skListen,&fsListen); iRet=select(1,&fsListen,NULL,NULL,&tv); if(iRet0) sockaddr_inadrClt; intiLen=sizeof(sockaddr_in); ZeroMemory(&adrClt,iLen); SOCKETs=accept(skListen,(sockaddr*)&adrClt,&iLen); ASS

21、ERT(INVALID_SOCKET!=s); sl.push_back(s); coutServeracceptedaconnection.Thesocketiss=iRead)/讀取出現(xiàn)錯誤或者對方關閉連接 iRead=0?coutConnectionshutdownatsocket*iterendl: coutConnectionrecverroratsocket*iterendl; iRet=GraceClose(&(*iter);/如果出錯則關閉套接字 ASSERT(iRet); HTMLCONTROL Forms.HTML:TextArea.1 HYPERLINK /shining

22、100/archive/2010/06/06/5651878.aspx view plain HYPERLINK /shining100/archive/2010/06/06/5651878.aspx copy to clipboard HYPERLINK /shining100/archive/2010/06/06/5651878.aspx print HYPERLINK /shining100/archive/2010/06/06/5651878.aspx ?sl.remove(INVALID_SOCKET);/刪除無效的套接字,套接字在關閉后被設置為無效 HTMLCONTROL Form

23、s.HTML:TextArea.1 接下來再讓我們來看看客戶端程序的代碼。 HYPERLINK /shining100/archive/2010/06/06/5651878.aspx view plain HYPERLINK /shining100/archive/2010/06/06/5651878.aspx copy to clipboard HYPERLINK /shining100/archive/2010/06/06/5651878.aspx print HYPERLINK /shining100/archive/2010/06/06/5651878.aspx ?#include #

24、include #include #pragmacomment(lib,ws2_32.lib) #defineASSERTassert usingstd:cin; usingstd:cout; usingstd:endl; staticconstcharc_szIP=; staticconstintc_iPort=10001; boolGraceClose(SOCKET*ps); intmain() intiRet=SOCKET_ERROR; /初始化Winsocket,所有Winsocket程序必須先使用WSAStartup進行初始化 WSADATAdata; ZeroMemory(&dat

25、a,sizeof(WSADATA); iRet=WSAStartup(MAKEWORD(2,0),&data); ASSERT(SOCKET_ERROR!=iRet); /建立連接套接字 SOCKETskClient=INVALID_SOCKET; skClient=socket(AF_INET,SOCK_STREAM,0); ASSERT(INVALID_SOCKET!=skClient); /初始化連接套接字地址信息 sockaddr_inadrServ;/表示網(wǎng)絡地址 ZeroMemory(&adrServ,sizeof(sockaddr_in); adrServ.sin_family=

26、AF_INET;/初始化地址格式,只能為AF_INET adrServ.sin_port=htons(c_iPort);/初始化端口,由于網(wǎng)絡字節(jié)順序和主機字節(jié)順序相反,所以必須使用htons將主機字節(jié)順序轉換成網(wǎng)絡字節(jié)順序 adrServ.sin_addr.s_addr=inet_addr(c_szIP);/初始化IP,由于網(wǎng)絡字節(jié)順序和主機字節(jié)順序相反,所以必須使用inet_addr將主機字節(jié)順序轉換成網(wǎng)絡字節(jié)順序 /將套接口從阻塞狀態(tài)設置到非阻塞狀態(tài) unsignedlongulEnable=1; iRet=ioctlsocket(skClient,FIONBIO,&ulEnable);

27、 ASSERT(SOCKET_ERROR!=iRet); fd_setfsWrite; TIMEVALtv; tv.tv_sec=1; tv.tv_usec=0; coutClientbegantoconnecttotheserver.endl; for(;) /使用非阻塞方式連接服務器,請注意connect操作的返回值總是為SOCKET_ERROR iRet=connect(skClient,(sockaddr*)&adrServ,sizeof(sockaddr_in); intiErrorNo=SOCKET_ERROR; intiLen=sizeof(int); /如果getsockopt

28、返回值不為0,則說明有錯誤出現(xiàn) if(SOCKET_ERROR=iRet&0!=getsockopt(skClient,SOL_SOCKET,SO_ERROR,(char*)&iErrorNo,&iLen) coutAnerrorhappenedonconnectingtoserver.TheerrornoisiErrorNo .Theprogramwillexitnow.endl; exit(-1); FD_ZERO(&fsWrite); FD_SET(skClient,&fsWrite); /如果集合fsWrite中的套接字有信號,則說明連接成功,此時iRet的返回值大于0 iRet=select(1,NULL,&fsWrite,NULL,&tv); if(0iRet) cout

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論