Windows网络编程学习笔记(6) Socket关闭、流式协议、分组-重组I/O

来源:互联网 发布:网络武侠小说排行榜 编辑:程序博客网 时间:2024/06/09 21:55

Socket的关闭与流式协议(Stream Protocols)、分组-重组I/O(Scatter-Gather I/O)

Socket关闭连接shutdown()/closesocket()函数

一旦你使用完一个Socket连接后,需要及时关闭它来释放相关资源。
释放一个Socket句柄关联的资源需要调用 closesocket() 函数。
然而closesocket() 函数在某些特定环境下会造成数据丢失的负面影响。
因此在调用closesocket()之前,需要先使用 shutdown() 函数关闭连接以确保数据被对方收到。 

接下来介绍这两个函数。


shutdown()函数

一个好的程序退出前必须通知对方数据已经发送完毕,对方也要做样的事。
这被称之为完整关闭 graceful close,依赖shutdown()来实现。

int shutdown(
    SOCKET s, 
    int how
);

int how 参数可以是:SD_RECEIVE, SD_SEND, 或 SD_BOTH
SD_RECEIVE: 指定socket 接下来不能再调用接收数据的函数,这对更底层的协议没有影响。
            另外,对于TCP sockets,如果数据以队列形式接收或者数据后来到达了,连接都会拒绝。
            然而,对于UDP sockets,接下来的数据仍然会被接收并被放入接收队列,(因为shutdown对于无连接协议没有意义)
SD_SEND: 指定socket 接下来不能再调用发送数据的函数。
         对于TCP sockets,所有数据发送完毕和被确认接收后,将发送一个FIN。
SD_BOTH: 既有SD_RECEIVE又有SD_SEND的效果。

注意: 不是所有面向连接协议支持完整关闭(graceful close)、提供shutdown()的功能。

有些协议(如 ATM)只需要调用closesocket()来终结会话。


closesocket()函数

int closesocket (SOCKET s);

调用closesocket(SOCKET s)时将释放该socket句柄和调用该socket发生WSAENOTSOCK失败时产生的更深层次的调用。
与该socket句柄相关的资源都会被释放,包括缓冲队列中的数据将会被丢弃。

在此过程中任何线程发起的挂起同步调用(pending syschronous calls)将会被取消并且不发送任何通知消息。
挂起重叠操作(pending overlapped)也会被取消。任何事件、例程将会以WSA_OPERATION_ABORTED错误失败。

有关重叠操作等会在后面详解,此处可以略过。


流式协议(Stream Protocols)

大多数面向连接通讯都是流式协议。
流式协议:发送方和接收方将数据进行拆分成多个分组并重组。

注意:流式协议中你不能确保你使用 发送/接收(recv()/send()) 函数传输的数据量大小正好是你所请求的。

char sendbuff[2048];    //发送缓冲区
int  nBytes = 2048;    //发送数据大小
ret = send(s, sendbuff, nBytes, 0);    //(假设s时一个有效的socket连接)

很可能send()返回值ret小于2048字节。
该返回值时实际发送出去的字节数,因为系统为每一个socket分配了一个特定的缓冲区。
在TCP/IP中,我们知道收/发双方需要维护一个收/发窗口尺寸(window size),当接收方无法及时处理数据时,
会将发送窗口尺寸调小,即使发送方受到限制。假设发送方窗口尺寸被限制为1024字节,那么为了发送2048
字节的数据,必须分两次发送才能发送完。

下面的代码以流式协议确保你的数据被完整发送:

char sendbuff[2048];    //发送缓冲区int  nBytes = 2048,    //缓冲区大小     nLeft,    //剩余未发送数据大小     idx;    //发送偏移,即从idx标号的数据开始发送nLeft = nBytes;idx = 0;while (nLeft > 0)    //每次发送ret字节后,继续发送剩余的数据{    ret = send(s, &sendbuff[idx], nLeft, 0);    if (ret == SOCKET_ERROR)    {        // Error    }    nLeft -= ret;    idx += ret;}

流式协议在接收数据上使用同样原则,但相对次要。
因为流式socket是一个持续的数据流,当一个应用读取数据时,并不关心它应该读多少数据。
如果你的应用想使用流式协议,接收一系列大小不等的数据,你将做些额外的操作。
若果所有的数据大小相等,将会方便很多,比如512字节大小的数据接收代码如下所示:


char    recvbuff[1024];int     ret,        nLeft,        idx;nLeft = 512;idx = 0;while (nLeft > 0){    ret = recv(s, &recvbuff[idx], nLeft, 0);    if (ret == SOCKET_ERROR)    {        // Error    }    idx += ret;    nLeft -= ret;}

当你的接收消息大小不一致时,将会变得略微复杂,你需要写一个你自己的协议来让接受者知道它将要接收多少数据量。

比如在数据包的最开始四个字节保存数据包大小,接受者收到后先解析这四个字节来得知还要接收多少数据。


分组-重组I/O(Scatter-Gather I/O)

分组-重组特性最开始在 Berkeley Sockets 中提出,它使用recv()和write()方法。
这个特性对发送具有特定格式的数据很有用。
比如,客户端向服务端发送的消息经常是由32字节头、64字节数据块、16字节尾组成。
此时可以调用WSASend()并使用三个WSABUF结构体为元素组成的数组。
每一个元素包含三种数据类型,在接收端,WSARecv()被调用,并使用三个WSABUF结构体,
分别包含32字节、64字节、16字节的数据。

流式sockets(stream-based sockets),重组-分组操作将多个WSABUF当成一个连续缓冲来处理,即可能发送部分,同样地,接收方可能在未
完全接收完该数据就返回。

而在基于消息的sockets(message-based)中,将按指定大小接收整个消息。若接收缓冲区不够大,则返回WSAEMSGSIZE错误。
并且删除超出大小的数据。当然,支持MSG_PARTIAL可以防止数据丢失。
2 0
原创粉丝点击