Winsock(6) Socket关闭、流式协议、分组-重组I/O

本文详细介绍了Socket连接的关闭机制,包括shutdown()和closesocket()函数的使用,以及流式协议(StreamProtocols)和分组-重组I/O(Scatter-GatherI/O)在数据传输中的作用。探讨了如何确保数据的完整发送与接收,特别是在不同协议下的数据量控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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可以防止数据丢失。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值