对一个奇怪SOCKET问题的研究

   今天测试网络服务程序时发现这样一个现象:客户端登录到服务器,服务器如果验证发现用户名不存在,就返回客户端错误信息,并断开与客户端的连接。但是实际测试时却发现客户端并没有接收到用户名不存在的错误信息,并且明明服务器端关闭了连接,甚至停止了服务,但是客户端仍然显示是连接状态。

   调试,发现在断开连接操作之前(即CLOSE SOCKET之前),加断点或者写LOG或者SLEEP几毫秒后,客户端都可接收到错误信息,并成功断开。于是分析觉得问题可能出在SOCKET的IO处理上,可能SOCKET IO中的数据没有足够的时间完全发送,SOCKET就被关闭了。

   仔细检查代码发现CLOSE SOCKET前做了这样的操作:

LINGER lingerStruct;
lingerStruct.l_onoff  = 1;    
lingerStruct.l_linger = 0;
setsockopt( IoSocket, SOL_SOCKET, SO_LINGER,    (char *)&lingerStruct, sizeof(lingerStruct) );
CancelIo((HANDLE) IoSocket);
closesocket( IoSocket );
   
   在MSDN中查找setsockeopt关于LINGER的解释如下:

Setting the SO_DONTLINGER option prevents blocking on member function Close while waiting for unsent data to be sent. Setting this option is equivalent to setting SO_LINGER with l_onoff set to 0.

    若设置了SO_LINGER,并设置了零超时间隔,则closesocket()不被阻塞立即执行,不论是否有排队数据未发送或未被确认。这种关闭方式称为“强制”或“失效”关闭,因为套接口的虚电路立即被复位,且丢失了未发送的数据。在远端的recv()调用将以WSAECONNRESET出错。
   若设置了SO_LINGER并确定了非零的超时间隔,则closesocket()调用阻塞进程,直到所剩数据发送完毕或超时。这种关闭称为“优雅的”关闭。请注意如果套接口置为非阻塞且SO_LINGER设为非零超时,则closesocket()调用将以WSAEWOULDBLOCK错误返回。
   若在一个流类套接口上设置了SO_DONTLINGER,则closesocket()调用立即返回。但是,如果可能,排队的数据将在套接口关闭前发送。请注意,在这种情况下WINDOWS套接口实现将在一段不确定的时间内保留套接口以及其他资源,这对于想用所以套接口的应用程序来说有一定影响。
  简言之,setsockeopt函数使用SO_LINGER规定了断开SOCKET时处理未发送完的数据的动作。


   查询UNIX文档中关于SO_LINGER参数的解释更加详细:

   SO_LINGER
   此选项指定函数close对面向连接的协议如何操作(如TCP)。缺省close操作是立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方。

SO_LINGER选项用来改变此缺省设置。使用如下结构:
struct linger {
     int l_onoff; /* 0 = off, nozero = on */
     int l_linger; /* linger time */
};

有下列三种情况:

  1. l_onoff为0,则该选项关闭,l_linger的值被忽略,等于缺省情况,close立即返回;
  2. l_onoff为非0,l_linger为0,则套接口关闭时TCP夭折连接,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST给对方,而不是通常的四分组终止序列,这避免了TIME_WAIT状态;
  3. l_onoff 为非0,l_linger为非0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。如果套接口缓冲区中仍残留数据,进程将处于睡眠状态,直 到(a)所有数据发送完且被对方确认,之后进行正常的终止序列(描述字访问计数为0)或(b)延迟时间到。此种情况下,应用程序检查close的返回值是 非常重要的,如果在数据发送完并被确认前时间到,close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失。close的成功返 回仅告诉我们发送的数据(和FIN)已由对方TCP确认,它并不能告诉我们对方应用进程是否已读了数据。如果套接口设为非阻塞的,它将不等待close完 成。
l_linger的单位依赖于实现,4.4BSD假设其单位是时钟滴答(百分之一秒),但Posix.1g规定单位为秒。

   在了解了原理之后,将代码中的lingerStruct.l_linger 设置为非零值,问题立即被解决。

   这里把这个问题写出来,希望能够给大家带来点启示。

posted on 2007-11-14 11:45 迷宫の未来 阅读(3127) 评论(6)  编辑 收藏 引用

评论

# re: 关闭SOCKET时需注意的问题[未登录] 2007-11-14 15:45 heroboy

shutdown(...) first.  回复  更多评论   

# re: 关闭SOCKET时需注意的问题 2007-11-14 16:12 追梦时代

@heroboy
谢谢,刚测试了shutdown也可以解决问题  回复  更多评论   

# re: 关闭SOCKET时需注意的问题 2007-11-14 16:21 追梦时代

这里贴上MSDN对于shutdown的注意事项,shutdown不管SO_LINGER如何设置都不会堵塞。

The shutdown function is used on all types of sockets to disable reception, transmission, or both.

If the how parameter is SD_RECEIVE, subsequent calls to the recv function on the socket will be disallowed. This has no effect on the lower protocol layers. For TCP sockets, if there is still data queued on the socket waiting to be received, or data arrives subsequently, the connection is reset, since the data cannot be delivered to the user. For UDP sockets, incoming datagrams are accepted and queued. In no case will an ICMP error packet be generated.

If the how parameter is SD_SEND, subsequent calls to the send function are disallowed. For TCP sockets, a FIN will be sent after all data is sent and acknowledged by the receiver.

Setting how to SD_BOTH disables both sends and receives as described above.

The shutdown function does not close the socket. Any resources attached to the socket will not be freed until closesocket is invoked.

To assure that all data is sent and received on a connected socket before it is closed, an application should use shutdown to close connection before calling closesocket. For example, to initiate a graceful disconnect:
1. Call WSAAsyncSelect to register for FD_CLOSE notification.
2. Call shutdown with how=SD_SEND.
When FD_CLOSE received, call recv until zero returned, or SOCKET_ERROR.
3. Call closesocket.

Note The shutdown function does not block regardless of the SO_LINGER setting on the socket.

An application should not rely on being able to reuse a socket after it has been shut down. In particular, a Windows Sockets provider is not required to support the use of connect on a socket that has been shut down.
  回复  更多评论   

# re: 关闭SOCKET时需注意的问题 2007-11-14 16:34 追梦时代

http://hi.baidu.com/developer_chen/blog/item/53208b4594f4bf25cefca322.html
这篇文章描述了如何安全的关闭SOCKET  回复  更多评论   

# re: 对一个奇怪SOCKET问题的研究 2007-12-23 17:21 秦歌

shutdown管用  回复  更多评论   

# re: 对一个奇怪SOCKET问题的研究 2008-01-31 22:00 abettor

默认情况下,linger是保持TIME_WAIT状态的。
如果设置了linger选项,closesocket的时候就会以粗暴的形式实现。也就是,在断开连接的三次握手时,一旦接受到了对方的ACK后,发送一个RST就立即清理资源,而并不等待TCP/IP协议栈真的将全部数据发出,更不会等上两个MSL的TIME_WAIT状态。这样的话,也许RST根本就还没有发送出去,所以对等端意识不到连接已经中断。  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


<2007年11月>
28293031123
45678910
11121314151617
18192021222324
2526272829301
2345678

导航

统计

常用链接

留言簿(10)

随笔档案

文章档案

最新随笔

搜索

积分与排名

最新随笔

最新评论

阅读排行榜

评论排行榜