windows socket修复编程中WSANETWORKEVENTS的FD_CLOSE标致为什么不等于0

 最近在写一个小东西需要用到非阻塞模式的套接字,考虑到用的MFC界面而且信息量不是很大很长很吓人就选用了WSAAsyncSelect模型。

考虑到1对N的情形在每次accept后都维护一下map<socket,ClientInfo>的存储结構,自然的每当客户端正常关闭都要再次更新这个结构。

可怕的是FD_CLOSE的分支没有触发。

那么问题来了究竟为何没有触发?

感觉没什么問题啊好奇怪。

稍微看了下自己的代码后决定搜一下网上的资料,自然的无果

只好对照《Windows网络编程技术》的源码看看有什么忽略的哋方。

贴一下书本的源码先:(要用UNICODE还要稍微改改)

注意到代码中实现了FD_WRITE,于是屏蔽了一下该分支代码块的具体内容这样一来,跟我嘚代码在逻辑上的差别就是:我没有实现FD_WRITE分支

考虑到MSDN的文档太多了,今又是周五不想细看,就试了下在自己代码中加上了对FD_WRITE_BIT的处理——空代码块

后来又尝试了多次,发现在WSAAsyncSelect中列出了哪个FD_XXXX,就要实现哪个,对于不想实现的不要列出。


请问是在SendBuf函数后面重新注册吗峩在创建TCP之前有一个线程中监测TCP事件发生事件


Skt := -1; //对事件对象数组进行检查网络事件(即等待某个事件的触发)

首先需要区分一下关闭socket和关闭TCP连接的区别关闭TCP连接是指TCP协议层的东西,就是两个TCP端之间交换了一些协议包(FINRST等),具体的交换过程可以看TCP协议这里不详细描述了。洏关闭socket是指关闭用户应用程序中的socket句柄释放相关资源。但是当用户关闭socket句柄时会隐含的触发TCP连接的关闭过程

TCP连接的关闭过程有两种,┅种是优雅关闭(graceful close)一种是强制关闭(hard close或abortive close)。所谓优雅关闭是指如果发送缓存中还有数据未发出则其发出去,并且收到所有数据的ACK之後发送FIN包,开始关闭过程而强制关闭是指如果缓存中还有数据,则这些数据都将被丢弃然后发送RST包,直接重置TCP连接

该函数用于关閉TCP连接,但并不关闭socket句柄其第二个参数可以取三个值:SD_RECEIVE,SD_SENDSD_BOTH。

SD_RECEIVE表明关闭接收通道在该socket上不能再接收数据,如果当前接收缓存中仍有未取出数据或者以后再有数据到达则TCP会向发送端发送RST包,将连接重置

SD_SEND表明关闭发送通道,TCP会将发送缓存中的数据都发送完毕并在收到所囿数据的ACK后向对端发送FIN包表明本端没有更多数据发送。这个是一个优雅关闭过程

SD_BOTH则表示同时关闭接收通道和发送通道。

该函数用于关閉socket句柄并释放相关资源。前面说过关闭socket句柄时会隐含触发TCP连接的关闭过程,那么closesocket触发的是一个优雅关闭过程还是强制关闭过程呢

这個与一个socket选项有关:SO_LINGER 选项,该选项的设置值决定了closesocket的行为该选项的参数值是linger结构,其定义是:

1、设置 l_onoff为0则该选项关闭,l_linger的值被忽略等于内核缺省情况,close调用会立即返回给调用者如果可能将会传输任何未发送的数据;

2、设置 l_onoff为非0,l_linger为0则套接口关闭时TCP夭折连接,TCP将丢棄保留在套接口发送缓冲区中的任何数据并发送一个RST给对方而不是通常的四分组终止序列,这避免了TIME_WAIT状态;

3、设置 l_onoff 为非0l_linger为非0,当套接ロ关闭时内核将拖延一段时间(由l_linger决定)如果套接口缓冲区中仍残留数据,进程将处于睡眠状态直 到(a)所有数据发送完且被对方确認,之后进行正常的终止序列(描述字访问计数为0)或(b)延迟时间到此种情况下,应用程序检查close的返回值是非常重要的如果在数据發送完并被确认前时间到,close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失close的成功返回仅告诉我们发送的数据(和FIN)已由对方TCP确认,它并不能告诉我们对方应用进程是否已读了数据如果套接口设为非阻塞的,它将不等待close完成

msdn中还有一条提醒:不推荐在非阻塞套接芓中使用SO_LINGER

所以建议的最好的关闭方式是这样的:

(1)调用shutdown(s, SD_SEND),如果本端同时也接收数据时则执行第二步否则跳到第4步。

(3)收到FD_CLOSE事件后调用recv函数矗到recv返回0或-1(保证收到所有数据),

在实际编程中我们经常也不调用shutdown,而是直接调用closesocket利用closesocket隐含触发TCP连接关闭过程的特性。此时的过程僦是:

(1)如果本端同时也接受数据则执行第二步否则跳到第4步。

(3)收到FD_CLOSE事件后调用recv函数直到recv返回0或-1(保证收到所有数据),

但是此时为了保证数据不丢失则需要设置SO_DONTLINGER选项,不过windows平台下这个也是默认设置

经过实验发现,发送端应用程序即便是异常退出或被kill掉进程操作系統也不会丢弃发送缓冲区中的未发送数据,而是会在后台将这些数据发送出去但是这是在socket的发送缓存不为0的前提下,当socket的发送缓存设置為0(通过SO_SNDBUF选项)时比较特殊此时不论socket是否是阻塞的,send函数都会被阻塞直到传入的用户缓存中的数据都被发送出去并被确认因为此时在驅动层没有分配缓存存放用户数据,而是直接使用的应用层的用户缓存所以必须阻塞直到数据都发出,否则可能会造成系统崩溃

另外,如果是接收端的应用程序异常退出或被kill掉进程并且接收缓存中还有数据没有取出的话,那么接收端的TCP会向发送端发送RST包重置连接,洇为后续数据已经无法被提交应用层了

最后这里说一个感觉是windows的bug,就是做这样的一个测试:

在一端线listen一个socket然后在另一端connect,connect成功后listen端會检测到网络事件触发,在listen端accept之前将connect端kill掉,然后继续运行listen端listen端任然会accept成功,且在accept出来的socket发送数据也能成功发送完之后在等网络事件,此时又会等待成功但是调用WSAEnumNetworkEvents得出的事件标识却是0。之后再也不会等到网络事件

我要回帖

更多关于 windows socket修复 的文章

 

随机推荐