读者必须注意:如果我们收到 FD_READ 事件通知的讯息,但是我们故意不呼叫 recv 或 recvfrom 来读取资料的话,尔後系统又收到资料时,并不会再次通知我们,一定要等我们呼叫了 recv 或 recvfrom 後,才有可能再收到FD_READ 的事件通知。
【FD_CLOSE 事件】
当系统知道对方已经将 Socket 关闭了的情况下(收到 FIN 通知,并和对方做关闭动作的 hand-shaking),我们会收到 FD_CLOSE 的事件通知,以便我们也能将这个相对的 Socket 关闭。FD_CLOSE 事件只会发生於 TCP Socket,因为它是 connection-oriented;对於connectionless 的 UDP Socket,即使设了FD_CLOSE,也不会有作用的。
程式中,当 Client 端送一个要求(request)来时,系统会以 ASYNC_EVENT 讯息通知我们的 hwnd 视窗;我们在利用 WSAGETSELECTEVENT(lParam) 及 WSAGETSELECTERROR(lParam) 知道是FD_READ 事件及检查无误後,便呼叫 recv() 函式来收取 Client 端送来的资料 。
recv(wParam, &data, sizeof(data), 0)
笔者在前一期文章中也曾提到说,FD_XXXX 事件发生,收到讯息时,视窗 handle 被呼叫时的参数 wParam 代表的就是事件发生的 Socket 号码,所以此处 wParam 的值也就是前面提到的 my_sd 这个 Socket 号码。recv() 的第四个参数设为 0,表示我们要将资料从系统的 input buffer 中读取并移走。收到要求後,我们要答覆 Client 端,也就是要送资料给 Client;这时我们就要利用 send() 这个函式了。
我们先将资料放到 data 这个资料暂存区,然後呼叫 send() 将它送出,我们利用的也是 wParam (my_sd) 这个同样的 Socket 来做传送的动作,因为它是双向的。
send(wParam, &data, strlen(data), 0)
Server 与 Client 收送资料一段时间後(资料全部收送完毕),如果 Client 端先呼叫 closesocket() 将它那端的 Socket 关闭,那麽系统在知道後,会通知我们一个FD_CLOSE 事件的讯息,此时我们也可以呼叫 closesocket() 将我们这端的 Socket 关闭了;当然我们也可以呼叫 closesocket() 先主动关闭我们这端的 Socket。
【Client 端的资料收送及关闭 Socket】
我们例子的 Client 是采 Blocking 模式,所以在呼叫 connect() 函式与 Server连接时,可能会等一下子才成功;connect() 函式返回後,且无错误发生的话, Client 与 Server 端的 TCP socket 连接就算成功了。这时,我们便可利用这个连接成功的 Socket 来送收资料了。由於我们并没有要设定为 Asynchronous 模式,所以也不用呼叫 WSAAsyncSelect() 来设定事件。Client 端通常是会先主动发出要求到 Server 端,因此我们呼叫 send() 来传送此一资料。我们的资料量很小,所以并不会被 send() 函式Block 住;不过如果您要送的资料量很大,那麽可能会等一段时间才会自 send() 函式返回;也就是说必须等资料都放到系统的 output buffer 後才会返回;这是因为我们Client的 Socket 是阻拦模式。如果我们用的是非阻拦模式的 Socket,那麽 send() 函式会视系统的 output buffer 的空间有多少,只拷贝那麽多的资料到 output buffer,然後就返回,并告知使用者送出了多少资料,并不须等所有资料都放到 output buffer 才返回。我们将要求放在 data 资料暂存区,然後呼叫 send() 将要求送出。资料送出
後,我们呼叫 recv() 来等待 Server 端的答覆。send(mysd, data, strlen(data), 0)
recv(mysd, &data, sizeof(data), 0)
由於我们 Client 端是 Blocking 模式,所以 recv() 会一直 Block 住,直到下列的情况之一发生,才会返回。
(1)Server 端送来资料。(此时 return 值是读取的资料长度)
(2)Server 端将相对的 Socket 关闭了。(此时的 return 值会是 0)
(3