winsocket基礎(chǔ)教程.doc_第1頁(yè)
winsocket基礎(chǔ)教程.doc_第2頁(yè)
winsocket基礎(chǔ)教程.doc_第3頁(yè)
winsocket基礎(chǔ)教程.doc_第4頁(yè)
winsocket基礎(chǔ)教程.doc_第5頁(yè)
已閱讀5頁(yè),還剩41頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

相信各位讀者現(xiàn)在對(duì)於 Winsock 的定義、系統(tǒng)環(huán)境,以及一些 Winsock Stack 及 Winsock 應(yīng)用程式,都有基本的認(rèn)識(shí)了。接下來(lái)筆者希望能分幾期為各位讀者介紹一下簡(jiǎn)單的 Winsock 網(wǎng)路應(yīng)用程式設(shè)計(jì)。我們將以 Winsock 1.1 規(guī)格所定義的 46 個(gè)應(yīng)用程式介面(API)為基礎(chǔ),逐步來(lái)建立一對(duì) TCP socket 主從架構(gòu)(Client / Server)的程式。在這兩個(gè)程式中,Server 將使用 Winsock 提供的非同步(asynchronous)函式來(lái)建立 socket 連結(jié)、關(guān)閉、及資料收送等等;而 Client 則采類似傳統(tǒng) UNIX 的阻攔式(blocking)。由於我們的重點(diǎn)并不在於 MS Windows SDK 的程式設(shè)計(jì),所以我們將使用最簡(jiǎn)便的方式來(lái)顯示訊息;有關(guān) MS Windows 程式的技巧,請(qǐng)各位讀者自行研究相關(guān)的書籍及文章。今天我們先要看一下主從架構(gòu) TCP socket 的建立連結(jié)(connect)及關(guān)閉(close)。以前筆者曾簡(jiǎn)單地介紹過(guò)主從架構(gòu)的概念,現(xiàn)在我們?cè)僖陨钌细鼫\顯的例子來(lái)說(shuō)明一下,讀者稍後也較容易能明白筆者的敘述。我們可以假設(shè) Server 就像是電信局所提供的一些服務(wù),比如104 查號(hào)臺(tái)或112 障礙臺(tái)。(1)電信局先建立好了一個(gè)電話總機(jī),這就像是呼叫 socket() 函式開啟了一個(gè) socket。(2)接著電信局將這個(gè)總機(jī)的號(hào)碼定為 104,就如同我們呼叫 bind() 函式,將 Server 的這個(gè) socket 指定(bind)在某一個(gè) port。當(dāng)然電信局必須讓用戶知道這個(gè)號(hào)碼;而我們的 Client 程式同樣也要知道 Server 所用的 port,待會(huì)才有辦法與之連接。(3)電信局的 104 查號(hào)臺(tái)底下會(huì)有一些自動(dòng)服務(wù)的分機(jī),但是它的數(shù)量是有限的,所以有時(shí)你會(huì)撥不通這個(gè)號(hào)碼(忙線)。同樣地,我們?cè)诮⒁粋€(gè) TCP Server socket 時(shí),也會(huì)呼叫 listen() 函式來(lái)監(jiān)聽等待;listen() 的第二個(gè)參數(shù)即是 waiting queue 的數(shù)目,通常數(shù)值是由 1 到 5。(事實(shí)上這兩者還是有點(diǎn)不一樣。)(4)用戶知道了電信局的這個(gè) 104 查號(hào)服務(wù),他就可以利用某個(gè)電話來(lái)?yè)芴?hào)連接這個(gè)服務(wù)了。這就是我們 Client 程式開啟一個(gè)相同的 TCP socket,然後呼叫 connect() 函式去連接 Server 指定的那個(gè) port。當(dāng)然了,和電話一樣,如果 waiting queue 滿了、與 Server 間線路不通、或是 Server 沒提供此項(xiàng)服務(wù)時(shí),你的連接就會(huì)失敗。(5)電信局查號(hào)臺(tái)的總機(jī)接受了這通查詢的電話後,它會(huì)轉(zhuǎn)到另一個(gè)分機(jī)做服務(wù),而總機(jī)本身則再回到等待的狀態(tài)。Server 的 listening socket 亦是一樣,當(dāng)你呼叫了 accept() 函式之後,Server 端的系統(tǒng)會(huì)建立一個(gè)新 socket 來(lái)對(duì)此連接做服務(wù),而原先的 socket 則再回到監(jiān)聽等待的狀態(tài)。(6)當(dāng)你查詢完畢了,你就可以掛上電話,彼此間也就離線了。Client和Server間的 socket 關(guān)閉亦是如此;不過(guò)這個(gè)關(guān)閉離線的動(dòng)作,可由 Client 端或Server 端任一方先關(guān)閉。有些電話查詢系統(tǒng)不也是如此嗎?接下來(lái),我們就來(lái)看主從架構(gòu)的 TCP socket 是如何利用這些 Winsock 函式來(lái)達(dá)成的;并利用資策會(huì)資訊技術(shù)處的WinKing這個(gè) Winsock Stack 中某項(xiàng)功能來(lái)顯示 sockets 狀態(tài)的變化。文章中僅列出程式的片段,完整的程式請(qǐng)看附錄的程式?!維erver 端建立 socket 并進(jìn)入監(jiān)聽等待狀態(tài)】首先我們先看 Server 端如何建立一個(gè) TCP socket,并使其進(jìn)入監(jiān)聽等待的狀態(tài)。在圖 1. 上,我們可以看到最先被呼叫到的是 WSAStartup() 函式。說(shuō)明下:WSAStartup():連結(jié)應(yīng)用程式與 Winsock.DLL 的第一個(gè)函式。格 式: int PASCAL FAR WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData );參 數(shù):wVersionRequested欲使用的 Windows Sockets API 版本lpWSAData 指向 WSADATA 資料的指標(biāo)傳回值:成功 - 0 失敗 - WSASYSNOTREADY / WSAVERNOTSUPPORTED / WSAEINVAL說(shuō)明: 此函式必須是應(yīng)用程式呼叫到 Windows Sockets DLL 函式中的第一個(gè),也唯有此函式呼叫成功後,才可以再呼叫其他 Windows Sockets DLL 的函式。此函式亦讓使用者可以指定要使用的 Windows Sockets API 版本,及獲取設(shè)計(jì)者的一些資訊。 程式中我們要用 Winsock 1.1,所以我們?cè)诔淌街杏幸欢螢椋篧SAStartup(WORD)(18)|1),(LPWSADATA) &WSAData)其中 (WORD)(18)|1) 表示我們要用的是 Winsock 1.1版本,而 WSAData 則是用來(lái)儲(chǔ)存由系統(tǒng)傳回的一些有關(guān)此一 Winsock Stack 的資料。再來(lái)我們呼叫 socket() 函式來(lái)開啟 Server 端的 TCP socket。socket():建立Socket。格 式: SOCKET PASCAL FAR socket( int af, int type, int protocol );參 數(shù): af 目前只提供 PF_INET(AF_INET) type Socket 的型態(tài) (SOCK_STREAM、SOCK_DGRAM) protocol通訊協(xié)定(如果使用者不指定則設(shè)為0)傳回值: 成功 - Socket 的識(shí)別碼 失敗 - INVALID_SOCKET(呼叫 WSAGetLastError() 可得知原因)說(shuō)明: 此函式用來(lái)建立一 Socket,并為此 Socket 建立其所使用的資源。 Socket 的型態(tài)可為 Stream Socket 或 Datagram Socket。我們要建立的是 TCP socket,所以程式中我們的第二個(gè)參數(shù)為 SOCK_STREAM,我們并將開啟的這個(gè) socket 號(hào)碼記在 listen_sd 這個(gè)變數(shù)。listen_sd = socket(PF_INET, SOCK_STREAM, 0)接下來(lái)我們要指定一個(gè)位址及 port 給 Server 的這個(gè) socket,這樣 Client 才知道待會(huì)要連接哪一個(gè)位址的哪個(gè) port;所以我們呼叫 bind() 函式。bind():指定 Socket 的 Local 位址 (Address)。格 式: int PASCAL FAR bind( SOCKET s, const struct sockaddr FAR *name, int namelen );參 數(shù): s Socket的識(shí)別碼nameSocket的位址值namelenname的長(zhǎng)度傳回值: 成功 - 0 失敗 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)說(shuō)明: 此一函式是指定 Local 位址及 Port 給某一未定名之 Socket。使用者若不在意位址或 Port 的值,那麼他可以設(shè)定位址為 INADDR_ANY,及 Port 為 0;那麼Windows Sockets 會(huì)自動(dòng)將其設(shè)定適當(dāng)之位址及 Port (1024 到 5000之間的值),使用者可以在此 Socket 真正連接完成後,呼叫 getsockname()來(lái)獲知其被設(shè)定的值。bind() 函式要指定位址及 port,這個(gè)位址必須是執(zhí)行這個(gè)程式所在機(jī)器的 IP 位址,所以如果讀者在設(shè)計(jì)程式時(shí)可以將位址設(shè)定為 INADDR_ANY,這樣 Winsock 系統(tǒng)會(huì)自動(dòng)將機(jī)器正確的位址填入。如果您要讓程式只能在某臺(tái)機(jī)器上執(zhí)行的話,那麼就將位址設(shè)定為該臺(tái)機(jī)器的 IP 位址。由於此端是 Server 端,所以我們一定要指定一個(gè) port 號(hào)碼給這個(gè) socket。讀者必須注意一點(diǎn),TCP socket 一旦選定了一個(gè)位址及 port 後,就無(wú)法再呼叫另一次 bind 來(lái)任意更改它的位址或 port。在程式中我們將 Server 端的 port 指定為 7016,位址則由系統(tǒng)來(lái)設(shè)定。struct sockaddr_in sa;sa.sin_family = PF_INET;sa.sin_port = htons(7016); /* port number */sa.sin_addr.s_addr = INADDR_ANY; /* address */bind(listen_sd, (struct sockaddr far *)&sa, sizeof(sa) 我們?cè)谥付?port 號(hào)碼時(shí)會(huì)用到 htons() 這個(gè)函式,主要是因?yàn)楦鳈C(jī)器的數(shù)值讀取方式不同(PC 與 UNIX 系統(tǒng)即不相同),所以我們利用這個(gè)函式來(lái)將 host order 的排列方式轉(zhuǎn)換成 network order 的排列方式;相同地,我們也可以呼叫ntohs() 這個(gè)相對(duì)的函式將其還原。(host order 各機(jī)器不同,但 network order 都相同)(htons 是針對(duì) short 數(shù)值,對(duì)於 long 數(shù)值則用 hotnl 及 ntohl)指定完位址及 port 之後,我們呼叫 listen() 函式,讓這個(gè) socket 進(jìn)入監(jiān)聽狀態(tài)。一個(gè) Server 端的 TCP socket 必須在做完了 listen 的呼叫後,才能接受 Client 端的連接。listen():設(shè)定 Socket 為監(jiān)聽狀態(tài),準(zhǔn)備被連接。格 式: int PASCAL FAR listen( SOCKET s, int backlog );參 數(shù): sSocket 的識(shí)別碼 backlog 未真正完成連接前(尚未呼叫 accept 前)彼端的連接要求的最大個(gè)數(shù)傳回值: 成功 - 0失敗 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)說(shuō)明: 使用者可利用此函式來(lái)設(shè)定 Socket 進(jìn)入監(jiān)聽狀態(tài),并設(shè)定最多可有多少個(gè)在未真正完成連接前的彼端的連接要求。(目前最大值限制為 5, 最小值為1)程式中我們將 backlog 設(shè)為 1 。listen(listen_sd, 1)呼叫完 listen 後,此時(shí) Client 端如果來(lái)連接的話,Client 端的連接動(dòng)作(connect)會(huì)成功,不過(guò)此時(shí) Server 端必須再呼叫 accept() 函式,才算正式完成 Server 端的連接動(dòng)作。但是我們什麼時(shí)候可以知道 Client 端來(lái)連接,而適時(shí)地呼叫 accept 呢?在這里我們就要利用一個(gè)很好用的 WSAAsyncSelect函式,將Server 端的這個(gè) socket 轉(zhuǎn)變成 Asynchronous 模式,讓系統(tǒng)主動(dòng)來(lái)通知我們有 Client 要連接了。(圖1. 中并未將此函式繪出)WSAAsyncSelect():要求某一 Socket 有事件 (event) 發(fā)生時(shí)通知使用者。格 式: int PASCAL FAR WSAAsyncSelect( SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent );參 數(shù): sSocket 的編號(hào) hWnd動(dòng)作完成後,接受訊息的視窗 handle wMsg 傳回視窗的訊息 lEvent 應(yīng)用程式有興趣的網(wǎng)路事件傳回值: 成功 - 0 失敗 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)說(shuō)明: 此函式是讓使用者用來(lái)要求 Windows Sockets DLL 在偵測(cè)到某Socket 有網(wǎng)路事件時(shí)送訊息到使用者指定的視窗;網(wǎng)路事件是由參數(shù) lEvent 設(shè)定。呼叫此函式會(huì)主動(dòng)將該 Socket 設(shè)定為 Non-blocking 模式。lEvent 的值可為以下之OR組合:(參見 WINSOCK第1.1版88、89頁(yè)) FD_READ、FD_WRITE、FD_OOB、FD_ACCEPT、FD_CONNECT、FD_CLOSE 使用者若是針對(duì)某一Socket再次呼叫此函式時(shí),會(huì)取消對(duì)該 Socket 原先之設(shè)定。若要取消對(duì)該Socket 的所有設(shè)定,則 lEvent 的值必須設(shè)為 0。 (圖2) WSAAsyncSelect 函式參數(shù)與應(yīng)用程式關(guān)系我們?cè)诔淌街幸?Winsock 系統(tǒng)知道 Client 要來(lái)連接時(shí),送一個(gè) ASYNC_EVENT 的訊息到程式中 hwnd 這個(gè)視窗;由於我們想知道的只有 accept 事件,所以我們只設(shè)定 FD_ACCEPT。WSAAsyncSelect(listen_sd, hwnd, ASYNC_EVENT, FD_ACCEPT) (圖 3)demoserv 在 WinKing 系統(tǒng)上建立 socket 并進(jìn)入監(jiān)聽狀態(tài)讀者必須注意一點(diǎn),WSAAsyncSelect 的設(shè)定是針對(duì)某一個(gè) socket;也就是說(shuō),只有當(dāng)您設(shè)定的這個(gè) socket (listen_sd)的那些事件(FD_ACCEPT)發(fā)生時(shí),您才會(huì)收到這個(gè)訊息(ASYNC_EVENT)。如果您開啟了很多 sockets,而要讓每個(gè) socket 都變成 asynchronous 模式的話,那麼就必須對(duì)每一個(gè) socket都呼叫 WSAAsyncSelect 來(lái)一一設(shè)定。而如果您想將某一個(gè) socket 的 async 事件通知設(shè)定取消的話,那麼同樣也是用 WSAAsyncSelect 這個(gè)函式;且第四個(gè)參數(shù)lEvent 一定要設(shè)為 0。WSAAsyncSelect( s, hWnd, 0, 0 ) - 取消所有 async 事件設(shè)定在這里筆者還要告訴各位一點(diǎn),呼叫 WSAAsyncSelect 的同時(shí)也將此一 socket 改變成非阻攔(non-blocking)模式。但是此時(shí)這個(gè) socket 不能很簡(jiǎn)單地用 ioctlsocket() 這個(gè)函式就將它再變回阻攔(blocking)模式。也就是說(shuō)WSAAsyncSelect 和 ioctlsocket 所改變的非阻攔模式仍是有些不同的。如果您想將一個(gè)非同步(asynchronous)模式的 socket 再變回阻攔模式的話,必須先呼叫 WSAAsyncSelect() 將所有的 async 事件取消,再用 ioctlsocket() 將它變回阻攔模式。ioctlsocket():控制 Socket 的模式。格 式: int PASCAL FAR ioctlsocket( SOCKET s, long cmd, u_long FAR *argP );參 數(shù):sSocket 的識(shí)別碼 cmd 指令名稱 argP指向 cmd 參數(shù)的指標(biāo)傳回值: 成功 - 0失敗 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)說(shuō)明: 此函式用來(lái)獲取或設(shè)定 Socket 的運(yùn)作參數(shù)。其所提供的指令有:(參見WINSOCK 第 1.1 版 35、36 頁(yè))cmd 的值可為:FIONBIO - 開關(guān) non-blocking 模式FIONREAD - 自 Socket 一次可讀取的資料量(目前 in buffer 的資料量)SIOCATMARK - OOB 資料是否已被讀取完 由於我們 Server 端的 socket 是用非同步模式,且設(shè)定了 FD_ACCEPT 事件,所以當(dāng) Client 端和我們連接時(shí),Winsock Stack 會(huì)主動(dòng)通知我們;我們?cè)傧葋?lái)看看 Client 端要如何和 Server 端建立連接?【Client 端向 Server 端主動(dòng)建立連接】Client 首先也是呼叫 WSAStartup() 函式來(lái)與 Winsock Stack 建立關(guān)系;然後同樣呼叫 socket() 來(lái)建立一個(gè) TCP socket。(讀者此時(shí)一定要用 TCP socket 來(lái)連接 Server 端的 TCP socket,而不能用 UDP socket 來(lái)連接;因?yàn)橄嗤瑓f(xié)定的 sockets 才能相通,TCP 對(duì) TCP,UDP 對(duì) UDP)和 Server 端的 socket 不同的地方是:Client 端的 socket 可以呼叫 bind()函式,由自己來(lái)指定 IP 位址及 port 號(hào)碼;但是也可以不呼叫 bind(),而由 Winsock Stack 來(lái)自動(dòng)設(shè)定 IP 位址及 port 號(hào)碼(此一動(dòng)作在呼叫 connect()函式時(shí)會(huì)由 Winsock 系統(tǒng)來(lái)完成)。通常我們是不呼叫 bind(),而由系統(tǒng)設(shè)定的,稍後可呼叫 getsockname() 函式來(lái)檢查系統(tǒng)幫我們?cè)O(shè)定了什麼 IP及port。一般言,系統(tǒng)會(huì)自動(dòng)幫我們?cè)O(shè)定的 port 號(hào)碼是在 1024 到 5000 之間;而如果讀者要自己用 bind 設(shè)定 port 的話,最好是 5000 以上的號(hào)碼。connect():要求連接某一 TCP Socket 到指定的對(duì)方。格 式: int PASCAL FAR connect( SOCKET s, const struct sockaddrFAR *name, int namelen );參 數(shù): s Socket 的識(shí)別碼name 此 Socket 想要連接的對(duì)方位址namelenname的長(zhǎng)度傳回值:成功 - 0 失敗 - SOCKET_ERROR (呼叫WSAGetLastError()可得知原因)說(shuō)明: 此函式用來(lái)向?qū)Ψ揭蠼⑦B接。若是指定的對(duì)方位址為 0 的話,會(huì)傳回錯(cuò)誤值。當(dāng)連接建立完成後,使用者即可利用此一 Socket 來(lái)做傳送或接收資料之用了。我們的例子中, Client 是要連接的是自己機(jī)器上 Server 所監(jiān)聽的 7016 這個(gè)port,所以我們有以下的程式片段。(假設(shè)我們機(jī)器的 IP 存在my_host_ip)struct sockaddr_in sa; /* 變數(shù)宣告 */sa.sin_family = PF_INET; /* 設(shè)定所要連接的 Server 端資料 */sa.sin_port = htons(7016);sa.sin_addr.s_addr = htonl(my_host_ip);connect(mysd, (struct sockaddr far *)&sa, sizeof(sa) /* 建立連接 */【Server 端接受 Client 端的連接】由於我們 Server 端的 socket 是設(shè)定為非同步模式,且是針對(duì) FD_ACCEPT這個(gè)事件,所以當(dāng) Client 來(lái)連接時(shí),我們 Server 端的 hwnd 這個(gè)視窗會(huì)收到Winsock Stack 送來(lái)的一個(gè) ASYNC_EVENT 的訊息。(參見前面 WSAAsyncSelect 的設(shè)定)這時(shí),我們應(yīng)該先利用 WSAGETSELECTERROR(lParam) 來(lái)檢查是否有錯(cuò)誤;并由 WSAGETSELECTEVENT(lParam) 得知是什麼事件發(fā)生(因?yàn)?WSAAsyncSelect 函式可針對(duì)同一個(gè) socket 同時(shí)設(shè)定很多事件,但是只用一個(gè)訊息來(lái)代表)(此處當(dāng)然是 FD_ACCEPT 事件);然後再呼叫相關(guān)的函式來(lái)處理此一事件。所以我們呼叫 accept() 函式來(lái)建立 Server 端的連接。accept():接受某一 Socket 的連接要求,以完成 Stream Socket 的連接。格 式: SOCKET PASCAL FAR accept( SCOKET s, struct sockaddr FAR *addr, int FAR *addrlen );參 數(shù):s Socket的識(shí)別碼 addr 存放來(lái)連接的彼端的位址 addrlenaddr的長(zhǎng)度傳回值:成功 - 新的Socket識(shí)別碼失敗 - INVALID_SOCKET (呼叫 WSAGetLastError() 可得知原因)說(shuō)明: Server 端之應(yīng)用程式呼叫此一函式來(lái)接受 Client 端要求之 Socket 連接動(dòng)作;如果Server 端之 Socket 是為 Blocking 模式,且沒有人要求連接動(dòng)作,那麼此一函式會(huì)被 Block ?。蝗绻麨?Non-Blocking 模式,此函式會(huì)馬上回覆錯(cuò)誤。accept() 函式的答覆值為一新的 Socket,此新建之 Socket 不可再用來(lái)接受其它的連接要求;但是原先監(jiān)聽之 Socket 仍可接受其他人的連接要求。TCP socket 的 Server 端在呼叫 accept() 後,會(huì)傳回一個(gè)新的 socket 號(hào)碼;而這個(gè)新的 socket 號(hào)碼才是真正與 Client 端相通的 socket。比如說(shuō),我們用socket() 建立了一個(gè) TCP socket,而此 socket 的號(hào)碼(系統(tǒng)給的)為 1,然後我們呼叫的bind()、listen()、accept() 都是針對(duì)此一 socket;當(dāng)我們?cè)诤艚?accept()後,傳回值是另一個(gè) socket 號(hào)碼(也是系統(tǒng)給的),比如說(shuō)3;那麼真正與 Client 端連接的是號(hào)碼 3 這個(gè) socket,我們收送資料也都是要利用 socket 3,而不是 socket 1;讀者不可搞錯(cuò)。我們?cè)诔淌街袑?duì) accept() 的呼叫如下;我們并可由第二個(gè)參數(shù)的傳回值,得知究竟是哪一個(gè) IP 位址及 port 號(hào)碼的 Client 與我們 Server 連接。struct sockaddr_in sa;int sa_len = sizeof(sa);my_sd = accept(listen_sd, (struct sockaddr far *)&sa, &sa_len)當(dāng) Server 端呼叫完 accept() 後,主從架構(gòu)的 TCP socket 連接才算真正建立完畢; Server 及 Client 端也就可以分別利用此一 socket 來(lái)送資料到對(duì)方或收對(duì)方送來(lái)的資料了。(有關(guān)資料的收送,我們等下一期再談) (圖 4) demoserv 與 democlnt 在 WinKing 上連接成功後狀態(tài)【Server 及 Client 端結(jié)束 socket 連接】最後我們來(lái)看一下如何結(jié)束 socket 的連接。socket 的關(guān)閉很簡(jiǎn)單,而且可由 Server 或 Client 的任一端先啟動(dòng),只要呼叫 closesocket() 就可以了。而要關(guān)閉監(jiān)聽狀態(tài)的 socket,同樣也是利用此一函式。closesocket():關(guān)閉某一Socket。格 式: int PASCAL FAR closesocket( SOCKET s );參 數(shù): s Socket 的識(shí)別碼傳回值: 成功 - 0失敗 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)說(shuō)明: 此一函式是用來(lái)關(guān)閉某一 Socket。若是使用者原先對(duì)要關(guān)閉之 Socket 設(shè)定 SO_DONTLINGER,則在呼叫此一函式後,會(huì)馬上回覆,但是此一 Sokcet 尚未傳送完畢的資料會(huì)繼續(xù)送完後才關(guān)閉。若是使用者原先設(shè)定此 Socket 為 SO_LINGER,則有兩種情況:(a) Timeout 設(shè)為 0 的話,此一 Socket 馬上重新設(shè)定 (reset),未傳完或未收到的資料全部遺失。(b) Timeout 不為 0 的話,則會(huì)將資料送完,或是等到 Timeout 發(fā)生後才真正關(guān)閉。程式結(jié)束前,讀者們可千萬(wàn)別忘了要呼叫 WSACleanup() 來(lái)通知 Winsock Stack;如果您不呼叫此一函式,Winsock Stack 中有些資源可能仍會(huì)被您占用而無(wú)法清除釋放喲。WSACleanup():結(jié)束 Windows Sockets DLL 的使用。格 式: int PASCAL FAR WSACleanup( void );參 數(shù): 無(wú)傳回值:成功 - 0失敗 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)說(shuō)明: 應(yīng)用程式在使用 Windows Sockets DLL 時(shí)必須先呼叫 WSAStartup() 來(lái)向 Windows Sockets DLL 注冊(cè);當(dāng)應(yīng)用程式不再需要使用 Windows Sockets DLL 時(shí),須呼叫此一函式來(lái)注銷使用,以便釋放其占用的資源?!窘Y(jié)語(yǔ)】這期筆者先介紹主從架構(gòu) TCP sockets 的連接及關(guān)閉,以後會(huì)再陸續(xù)介紹如何收送資料,以及其他 API 的使用。想要進(jìn)一步了解如何撰寫 Winsock 程式的讀者,可以好好研究一下筆者 demoserv 及 democlnt 這兩個(gè)程式;也許不是寫的很好,但是希望可以帶給不懂 Winsock 程式設(shè)計(jì)的人一個(gè)起步。讀者們亦可自行用 anonymous ftp 方式到 SEEDNET 臺(tái)北主機(jī) .tw (0)的 UPLOAD / WINKING 目錄下,取得筆者與陳建伶小姐所設(shè)計(jì)的 WinKing 這個(gè) Winsock Stack 的試用版,來(lái)跑 demoserv 與 democlnt 這兩個(gè)程式及其他許許多多的 Winsock 應(yīng)用程式。(正式版本請(qǐng)洽 SEEDNET 服務(wù)中心,新版的WinKing 已含 Windows 撥接及 PPP 程式,適合電話撥接用戶在 Windows 環(huán)境下使用 SEEDNET;WinKing 同樣也提供 Ethernet 環(huán)境的使用。)簡(jiǎn)單的 Winsock 應(yīng)用程式設(shè)計(jì)(2)林 軍 鼐 在前一期的文章中,筆者為大家介紹了如何在 Winsock 環(huán)境下,建立主從架構(gòu)(Client/Server)的 TCP socket 的連接建立與關(guān)閉;今天筆者將繼續(xù)為大家介紹如何利用 TCP socket 來(lái)收送資料,并詳細(xì)解說(shuō) WSAAsyncSelect 函式中的FD_READ 及 FD_WRITE 事件(筆者曾發(fā)現(xiàn)有相當(dāng)多人對(duì)這兩個(gè)事件甚不了解)。相信讀者們已經(jīng)知道 TCP socket 的連接是在 Client 端呼叫 connect 函式成功,且 Server 端呼叫 accept 函式後,才算完全建立成功;當(dāng)連接建立成功後,Client 及 Server 也就可以利用這個(gè)連接成功的 socket 來(lái)傳送資料到對(duì)方,或是收取對(duì)方送過(guò)來(lái)的資料了。 (圖 1. TCP socket 的資料收送)在介紹資料的收送前,筆者先介紹一下 TCP socket 與 UDP socket 在傳送資料時(shí)的特性:Stream (TCP) Socket 提供雙向、可靠、有次序、不重覆之資料傳送。Datagram (UDP) Socket 則提供雙向之溝通,但沒有可靠、有次序、不重覆等之保證; 所以使用者可能會(huì)收到無(wú)次序、重覆之資料,甚至資料在傳輸過(guò)程中也可能會(huì)遺漏。由於 UDP Socket 在傳送資料時(shí),并不保證資料能完整地送達(dá)對(duì)方,所以我們常用的一些應(yīng)用程式(如 telnet、mail、ftp、news.等)都是采用 TCP Socket,以保證資料的正確性。(TCP 及 UDP 封包的傳送協(xié)定不在我們討論圍,想要了解的讀者們,請(qǐng)自行參考相關(guān)書籍)TCP 及 UDP Socket 都是雙向的,所以我們是利用同一個(gè) Socket 來(lái)做傳送及收取資料的動(dòng)作;一般言 TCP Socket 的資料送、收是呼叫 send() 及 recv() 這兩個(gè)函式來(lái)達(dá)成,而 UDP Socket 則是用 sendto() 及 recvfrom() 這兩個(gè)函式。不過(guò) TCP Socket 也可用 sendto() 及 recvfrom() 函式,UDP Socket 同樣可用 send() 及 recv() 函式;這一點(diǎn)我們稍後再加以解釋?,F(xiàn)在我們先看一下 send() 及 recv() 的函式說(shuō)明,并回到我們的前一期程式。 send():使用連接式(connected)的 Socket 傳送資料。格 式: int PASCAL FAR send( SOCKET s, const char FAR *buf, int len, int flags );參 數(shù):s Socket 的識(shí)別碼buf存放要傳送的資料的暫存區(qū)len buf 的長(zhǎng)度f(wàn)lags此函式被呼叫的方式傳回值:成功 - 送出的資料長(zhǎng)度 失敗 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)說(shuō)明: 此函式適用於連接式的 Datagram 或 Stream Socket 來(lái)傳送資料。 對(duì) Datagram Socket 言,若是 datagram 的大小超過(guò)限制,則將不會(huì)送出任何資料,并會(huì)傳回錯(cuò)誤值。對(duì) Stream Socket 言,Blocking 模式下,若是傳送 (transport) 系統(tǒng)內(nèi)之儲(chǔ)存空間(output buffer)不夠存放這些要傳送的資料,send() 將會(huì)被 block 住,直到資料送完為止;如果該 Socket 被設(shè)定為 Non-Blocking 模式,那麼將視目前的 output buffer 空間有多少,就送出多少資料,并不會(huì)被 block 住。使用者亦須注意 send()函式執(zhí)行完成,并不表示資料已經(jīng)成功地送抵對(duì)方了,而是已經(jīng)放到系統(tǒng)的 output buffer 中,等待被送出。 flags 的值可設(shè)為 0 或 MSG_DONTROUTE 及 MSG_OOB 的組合。(參見 WINSOCK第1.1版48頁(yè)) recv():自 Socket 接收資料。格 式: int PASCAL FAR recv( SOCKET s, char FAR *buf, int len, int flags );參 數(shù):s Socket 的識(shí)別碼buf 存放接收到的資料的暫存區(qū)lenbuf 的長(zhǎng)度f(wàn)lags此函式被呼叫的方式傳回值:成功 - 接收到的資料長(zhǎng)度 (若對(duì)方 Socket 已關(guān)閉,則為 0)失敗 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)說(shuō)明: 此函式用來(lái)自連接式的 Datagram Socket 或 Stream Socket 接收資料。對(duì) Stream Socket 言,我們可以接收到目前 input buffer 內(nèi)有效的資料,但其數(shù)量不超過(guò) len 的大小。若是此 Socket 設(shè)定 SO_OOBINLINE,且有 out-of-band 的資料未被讀取,那麼只有 out-of-band 的資料被取出。對(duì) Datagram Socket 言,只取出第一個(gè) datagram;若是該 datagram 大 於使用者提供的儲(chǔ)存空間,那麼只有該空間大小的資料被取出,多馀的資料將遺失,且回覆錯(cuò)誤的訊息。另外如果 Socket 為 Blocking 模式,且目前 input buffer 內(nèi)沒有任何資料,則 recv() 將 block 到有任何資料到達(dá)為止;如果為 Non-Blocking 模式,且 input buffer 無(wú)任何資料,則會(huì)馬上回覆錯(cuò)誤。參數(shù) flags 的值可為 0 或 MSG_PEEK、MSG_OOB 的組合;MSG_PEEK 代表將資料拷貝到使用者提供的 buffer,但是資料并不從系統(tǒng)的 input buffer 中移走;0 則表示拷貝并移走。(參考 WINSOCK 第1.1版41 頁(yè))【Server 端的資料收送及關(guān)閉 Socket】在前一期中,我們說(shuō)建立的是一個(gè) Asynchronous 模式的 Server;程式中,我們?cè)鴮?duì) listen_sd 這個(gè) Socket 呼叫 WSAAsyncSelect() 函式,并設(shè)定 FD_ACCEPT 事件,所以當(dāng) Client 與我們連接時(shí),系統(tǒng)會(huì)傳給我們一個(gè) ASYNC_EVENT 訊息(請(qǐng)參見前一期文章內(nèi)容);我們?cè)谑盏接嵪⒉⑴袛嗍?FD_ACCEPT 事件,於是呼叫 accept() 來(lái)建立連接。my_sd = accept(listen_sd, (struct sockaddr far *)&sa, &sa_len)我們?cè)诤艚型?accept() 函式,成功地建立了 Server 端與 Client 端的連接後,此時(shí)便可利用新建的 Socket(my_sd)來(lái)收送資料了。由於我們同樣希望用 Asynchronous 的方式,因此要再利用 WSAAsyncSelect() 函式來(lái)幫新建的 Socket 設(shè)定一些事件,以便事件發(fā)生時(shí) Winsock Stack 能主動(dòng)通知我們。由於我們的 Server 是被動(dòng)的接受 Client 的要求,然後再做答覆,所以我們?cè)O(shè)定 FD_READ 事件;我們也希望 Winsock Stack 在知道 Client 關(guān)閉 Socket 時(shí),能主動(dòng)通知我們,所以同時(shí)也設(shè)定 FD_CLOSE 事件。(讀者須注意,我們?cè)O(shè)定事件的 Socket 號(hào)碼是呼叫 accept 後傳回的新 Socket 號(hào)碼,而不是原先監(jiān)聽狀態(tài)的 Socket 號(hào)碼)WSAAsyncSelect(my_sd, hwnd, ASYNC_EVENT, FD_READ|FD_CLOSE)在這里,我們同樣是利用 hwnd 這個(gè)視窗及 ASYNC_EVENT 這個(gè)訊息;在前文中,筆者曾告訴各位,在收到 ASYNC_EVENT 訊息時(shí),我們可以利用 WSAGETSELECTEVENT(lParam) 來(lái)判斷究竟是哪一事件(FD_READ 或 FD_CLOSE)發(fā)生了;所以并不會(huì)混淆。那我們到底在什麼時(shí)候會(huì)收到 FD_READ 或 FD_CLOSE 事件的訊息呢?【FD_READ 事件】我們會(huì)收到 FD_READ 事件通知我們?nèi)プx取資料的情況有 :(1)呼叫 WSAAsyncSelect 函式來(lái)對(duì)此 Socket 設(shè)定 FD_READ 事件時(shí), input buffer 中已有資料。(2)原先系統(tǒng)的 input buffer 是空的,當(dāng)系統(tǒng)再收到資料時(shí),會(huì)通知我們。(3)使用者呼叫 recv 或 recvfrom 函式,從 input buffer 讀取資料,但是并沒有一次將資料讀光,此時(shí)會(huì)再驅(qū)動(dòng)一個(gè) FD_READ 事件,表示仍有資料在 input buffer 中。讀者必須注意:如果我們收到 FD_READ 事件通知的訊息,但是我們故意不呼叫 recv 或 recvfrom 來(lái)讀取資料的話,爾後系統(tǒng)又收到資料時(shí),并不會(huì)再次通知我們,一定要等我們呼叫了 recv 或 recvfrom 後,才有可能再收到 FD_READ 的事件通知?!綟D_CLOSE 事件】當(dāng)系統(tǒng)知道對(duì)方已經(jīng)將 Socket 關(guān)閉了的情況下(收到 FIN 通知,并和對(duì)方做關(guān)閉動(dòng)作的 hand-shaking),我們會(huì)收到 FD_CLOSE 的事件通知,以便我們也能將這個(gè)相對(duì)的 Socket 關(guān)閉。FD_CLOSE 事件只會(huì)發(fā)生於 TCP Socket,因?yàn)樗?connection-oriented;對(duì)於 connectionless 的 UDP Socket,即使設(shè)了 FD_CLOSE,也不會(huì)有作用的。程式中,當(dāng) Client 端送一個(gè)要求(request)來(lái)時(shí),系統(tǒng)會(huì)以 ASYNC_EVENT 訊息通知我們的 hwnd 視窗;我們?cè)诶?WSAGETSELECTEVENT(lParam) 及 WSAGETSELECTERROR(lParam) 知道是 FD_READ 事件及檢查無(wú)誤後,便呼叫 recv() 函式來(lái)收取 Client 端送來(lái)的資料。recv(wParam, &data, sizeof(data), 0)筆者在前一期文章中也曾提到說(shuō),F(xiàn)D_XXXX 事件發(fā)生,收到訊息時(shí),視窗 handle 被呼叫時(shí)的參數(shù) wParam 代表的就是事件發(fā)生的 Socket 號(hào)碼,所以此處 wParam 的值也就是前面提到的 my_sd 這個(gè) Socket 號(hào)碼。recv() 的第四個(gè)參數(shù)設(shè)為 0,表示我們要將資料從系統(tǒng)的 input buffer 中讀取并移走。收到要求後,我們要答覆 Client 端,也就是要送資料給 Client;這時(shí)我們就要利用 send() 這個(gè)函式了。我們先將資料放到 data 這個(gè)資料暫存區(qū),然後呼叫 send() 將它送出,我們利用的也是 wParam (my_sd) 這個(gè)同樣的 Socket 來(lái)做傳送的動(dòng)作,因?yàn)樗请p向的。send(wParam, &data, strlen(data), 0)Server 與 Client 收送資料一段時(shí)間後(資料全部收送完畢),如果 Client 端先呼叫 closesocket() 將它那端的 Socket 關(guān)閉,那麼系統(tǒng)在知道後,會(huì)通知我們一個(gè) FD_CLOSE 事件的訊息,此時(shí)我們也可以呼叫 closesocket() 將我們這端的 Socket 關(guān)閉了;當(dāng)然我們也可以呼叫 closesocket() 先主動(dòng)關(guān)閉我們這端的 Socket?!綜lient 端的資料收送及關(guān)閉 Socket】我們例子的 Client 是采 Blocking 模式,所以在呼叫 connect() 函式與 Server 連接時(shí),可能會(huì)等一下子才成功;connect() 函式返回後,且無(wú)錯(cuò)誤發(fā)生的話,Client 與 Server 端的 TCP socket 連接就算成功了。這時(shí),我們便可利用這個(gè)連接成功的 Socket 來(lái)送收資料了。由於我們并沒有要設(shè)定為 Asynchronous 模式,所以也不用呼叫 WSAAsyncSelect() 來(lái)設(shè)定事件。Client 端通常是會(huì)先主動(dòng)發(fā)出要求到 Server 端,因此我們呼叫 send() 來(lái)傳送此一資料。我們的資料量很小,所以并不會(huì)被 send() 函式 Block ?。徊贿^(guò)如果您要送的資料量很大,那麼可能會(huì)等一段時(shí)間才會(huì)自 send() 函式返回;也就是說(shuō)必須等資料都放到系統(tǒng)的 output buffer 後才會(huì)返回;這是因?yàn)槲覀?Client 的 Socket 是阻攔模式。如果我們用的是非阻攔模式的 Socket,那麼 send() 函式會(huì)視系統(tǒng)的 output buffer 的空間有多少,只拷貝那麼多的資料到 output buffer,然後就返回,并告知使用者送出了多少資料,并不須等所有資料都放到 output buffer 才返回。我們將要求放在 data 資料暫存區(qū),然後呼叫 send() 將要求送出。資料送出後,我們呼叫 recv() 來(lái)等待 Server 端的答覆。send(mysd, data, strlen(data), 0)recv(mysd, &data, sizeof(data), 0)由於我們 Client 端是 Blocking 模式,所以 recv() 會(huì)一直 Block 住,直到下列的情況之一發(fā)生,才會(huì)返回。(1)Server 端送來(lái)資料。(此時(shí) return 值是讀取的資料長(zhǎng)度)(2)Server 端將相對(duì)的 Socket 關(guān)閉了。(此時(shí)的 return 值會(huì)是 0)(3)Client 端自己呼叫 WSACancelBlockingCall() 來(lái)取消 recv() 的呼叫。(此時(shí) return 值是 SOCKET_ERROR 錯(cuò)誤,錯(cuò)誤碼 10004 WSAEINTR)同樣地,資料全部送收完畢後,我們也呼叫 closesocket() 來(lái)將 Socket 關(guān)閉。 WSACancelBlockingCall():取消目前正在進(jìn)行中的 blocking 動(dòng)作。格 式: int PASCAL FAR WSACancelBlockingCall( void );參 數(shù): 無(wú)傳回值:成功 - 0失敗 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)說(shuō)明:此

溫馨提示

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

最新文檔

評(píng)論

0/150

提交評(píng)論