TCP Socket一端关闭之后不报异常,而是接受0字节。

catchdream 2013-01-31 02:20:31
我用.net 3.5, VS 2008 开发, TCP Socket通讯,递归调用接受信息。在实际项目中发现当 Socket一端关闭(调用Socket.Close())后,另外一端不是报出异常(我期望是如此),而是不断递归调用接受0字节。我整理了下代码,下面Demo能重现此问题。期望有人能给出解释。
服务器端代码

class TestServer
{
static void Main()
{
try
{
TCPServer tcpServer = (TCPServer)TCPServer.GetInstance();
tcpServer.ServerIP = "127.0.0.1";
tcpServer.ServerPort = 3721;
tcpServer.CreatListener();
Console.WriteLine(String.Format("Server is ready with {0}:{1}", tcpServer.ServerIP, tcpServer.ServerPort));
}
catch (Exception e)
{
Console.WriteLine("Exception: {0}", e);
}
Console.WriteLine("Press Enter to Exit"); Console.ReadLine();
}
}

public abstract class PacketTransferBase
{
public PacketTransferBase()
{
}
private int count = 0;
/// <summary>
/// 接收信息
/// </summary>
/// <param name="ar"></param>
protected void ReceiveMessage(IAsyncResult ar)
{
try
{
StateObject so = (StateObject)ar.AsyncState;
Socket socket = so.workSocket;

//方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspx
var length = socket.EndReceive(ar);
byte[] actualData = new byte[length];
Array.Copy(so.buffer, 0, actualData, 0, length);

Console.WriteLine(string.Format("TCPServer.ReceiveMessage: buffer.{0} at Socket.{1}.{2} with buffer[0].{3},buffer[1].{4},buffer[2].{5}", length, socket.Handle, socket.Connected, so.buffer[0], so.buffer[1], so.buffer[2]));

bool continueReceive = true;
Console.WriteLine(Encoding.UTF8.GetString(actualData));
if (count == 0)
{
socket.Send(Encoding.UTF8.GetBytes("this is a back message from server!"));
count++;
}
socket.Close();

if (continueReceive && socket != null && socket.Connected)
{
socket.BeginReceive(so.buffer, 0, StateObject.BUFFER_SIZE, 0, new AsyncCallback(ReceiveMessage), so);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message + ex.StackTrace);
}
}
}

/// <summary>
/// TCP服务器端
/// </summary>
public class TCPServer : PacketTransferBase
{
public string ServerIP { get; set; }
public int ServerPort { get; set; }
private static TCPServer _TCPServer = new TCPServer();
Socket socketServer = null;

private TCPServer()
{
}

public static PacketTransferBase GetInstance()
{
return _TCPServer;
}

/// <summary>
/// 服务器端建立侦听
/// </summary>
public void CreatListener()
{
//创建一个新的Socket,这里我们使用最常用的基于TCP的Stream Socket(流式套接字)
socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

//将该socket绑定到主机上面的某个端口
//方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspx
socketServer.Bind(new IPEndPoint(IPAddress.Parse(ServerIP), ServerPort));

//启动监听,并且设置一个最大的队列长度
//方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspx
socketServer.Listen(100);

//开始接受客户端连接请求
//方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginaccept.aspx
socketServer.BeginAccept(new AsyncCallback(ClientAccepted), socketServer);
}

/// <summary>
/// 接收客户端
/// </summary>
/// <param name="ar"></param>
public void ClientAccepted(IAsyncResult ar)
{
try
{
var socketServer = ar.AsyncState as Socket;

//这就是客户端的Socket实例,我们后续可以将其保存起来
var socketClient = socketServer.EndAccept(ar);

//接收客户端的消息(这个和在客户端实现的方式是一样的)
StateObject so = new StateObject();
so.workSocket = socketClient;
socketClient.BeginReceive(so.buffer, 0, StateObject.BUFFER_SIZE, 0, new AsyncCallback(ReceiveMessage), so);

//准备接受下一个客户端请求
socketServer.BeginAccept(new AsyncCallback(ClientAccepted), socketServer);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message + ex.StackTrace);
}
}
}

public class StateObject
{
public Socket workSocket = null;
public EndPoint tempRemoteEP = null;
public const int BUFFER_SIZE = 1024;
public byte[] buffer = new byte[BUFFER_SIZE];
}


客户端代码

class TestClient
{
static void Main()
{
try
{
TCPClient tcpClient = (TCPClient)TCPClient.GetInstance();
tcpClient.ServerIP = "127.0.0.1";
tcpClient.ServerPort = 3721;

Socket socket = tcpClient.ConnectToServer();
socket.Send(Encoding.UTF8.GetBytes("this is a test message from client!"));
}
catch (Exception e)
{
Console.WriteLine("Exception: {0}", e);
}
Console.WriteLine("Press Enter to Exit"); Console.ReadLine();
}
}

public abstract class PacketTransferBase
{
public PacketTransferBase()
{
}

private int count = 0;
/// <summary>
/// 接收信息
/// </summary>
/// <param name="ar"></param>
protected void ReceiveMessage(IAsyncResult ar)
{
try
{
StateObject so = (StateObject)ar.AsyncState;
Socket socket = so.workSocket;

//方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspx
var length = socket.EndReceive(ar);
byte[] actualData = new byte[length];
Array.Copy(so.buffer, 0, actualData, 0, length);

Console.WriteLine(string.Format("TCPServer.ReceiveMessage: buffer.{0} at Socket.{1}.{2} with buffer[0].{3},buffer[1].{4},buffer[2].{5}", length, socket.Handle, socket.Connected, so.buffer[0], so.buffer[1], so.buffer[2]));

bool continueReceive = true;
Console.WriteLine(Encoding.UTF8.GetString(actualData));
//把以下代码去掉注释,把服务器端socket.Close()注释,则服务器端产生一样效果。
//if (count == 0)
//{
// socket.Send(Encoding.UTF8.GetBytes("this is a second message from server!"));
// count++;
//}
//socket.Close();

if (continueReceive && socket != null && socket.Connected)
{
socket.BeginReceive(so.buffer, 0, StateObject.BUFFER_SIZE, 0, new AsyncCallback(ReceiveMessage), so);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message + ex.StackTrace);
}
}
}

public class StateObject
{
public Socket workSocket = null;
public EndPoint tempRemoteEP = null;
public const int BUFFER_SIZE = 1024;
public byte[] buffer = new byte[BUFFER_SIZE];
}


/// <summary>
/// TCP 客户端
/// </summary>
public class TCPClient : PacketTransferBase
{
public string ServerIP { get; set; }
public int ServerPort { get; set; }

private static TCPClient _TCPClient = new TCPClient();

private TCPClient()
{
}

public static PacketTransferBase GetInstance()
{
return _TCPClient;
}

public Socket ConnectToServer()
{
//创建一个Socket
Socket socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

//连接到指定服务器的指定端口
//方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.connect.aspx
socketClient.Connect(ServerIP, ServerPort);

StateObject so = new StateObject();
so.workSocket = socketClient;
socketClient.BeginReceive(so.buffer, 0, StateObject.BUFFER_SIZE, 0, new AsyncCallback(ReceiveMessage), so);
return socketClient;
}
}


服务端端和客户端关闭Socket部分互换后(注释服务器端,开启客户端关闭),服务器端也能长生此同样现象。
...全文
481 9 打赏 收藏 转发到动态 举报
写回复
用AI写文章
9 条回复
切换为时间正序
请发表友善的回复…
发表回复
zhongmeiqing 2013-09-17
  • 打赏
  • 举报
回复
楼主,我也遇到了这个问题,客户端异常断开连接,服务端马上就不断的接受空消息。一直在循环。请问楼主解决了吗?
catchdream 2013-02-01
  • 打赏
  • 举报
回复
引用 7 楼 wodegege10 的回复:
接收到0字节说明连接已经断开,不再阻塞 所以在处理接收数据时一定要处理这种情况 当收到0字节时,处理连接的关闭事宜
是的,要进行处理。不过我查论坛,或者msdn都没发现此情形,难道大家都没碰到过这种情况。。。
wenbin 2013-01-31
  • 打赏
  • 举报
回复
接收到0字节说明连接已经断开,不再阻塞 所以在处理接收数据时一定要处理这种情况 当收到0字节时,处理连接的关闭事宜
足球中国 2013-01-31
  • 打赏
  • 举报
回复
引用 5 楼 catchdream 的回复:
引用 1 楼 zanfeng 的回复:这可能算是一个BUG吧。理论上是应该阻塞的。 还有更多的问题。 codeproject有一个开源东东很多也碰到过原子锁会出现负的情况。 使用心跳包吧。 我用的短连接,用完就关,耗费资源少。
这个和短连接长连接又没有关系。。。。
catchdream 2013-01-31
  • 打赏
  • 举报
回复
引用 1 楼 zanfeng 的回复:
这可能算是一个BUG吧。理论上是应该阻塞的。 还有更多的问题。 codeproject有一个开源东东很多也碰到过原子锁会出现负的情况。 使用心跳包吧。
我用的短连接,用完就关,耗费资源少。
catchdream 2013-01-31
  • 打赏
  • 举报
回复
引用 3 楼 strife 的回复:
结束时发个消息给另一端手动判断吧
我是这么做的,发现这个情形就是在一种情况有一边忘记关了,就出现上面的情况。。很奇怪。没查到有用的文档。这个也不像是 Keep-Alive数据报啊。那个应该没这么频繁吧。。
strife013 2013-01-31
  • 打赏
  • 举报
回复
结束时发个消息给另一端手动判断吧
空格键 2013-01-31
  • 打赏
  • 举报
回复
顶,接分啊
足球中国 2013-01-31
  • 打赏
  • 举报
回复
这可能算是一个BUG吧。理论上是应该阻塞的。 还有更多的问题。 codeproject有一个开源东东很多也碰到过原子锁会出现负的情况。 使用心跳包吧。
一、WinSock简介 Socket(套接字)最初是由加利福尼亚大学Berkeley(伯克利)分校为UNIX操作系统开发的网络通信接口,随着UNIX的广泛使用,Socket成为当前最流行的网络通信应用程序接口之一。20世纪90年代初,由Sun Microsystems,JSB,FTP software,Microdyne和Microsoft等几家公司共同定制了一套标准,即Windows Socket规范,简称WinSock。 VB编写网络程序主要有两种方式:1.winsock控件 2.winsockAPI 二、WinSock控件的使用 1.WinSock控件的主要属性 LocalHostName属性 本地机器名 LocalIP属性 本地机器IP地址 LocalPort属性 本地机器通信程序的端口(0<端口<65536) RemoteHost属性 远程机器名 RemotePort属性 远程机器的通信程序端口 Protocol属性 通过Protocol属性可以设置WinSock控件连接远程计算机使用的协议。可选的协议是TCP和UDP对应的VB的常量分别是sckTCPProtocol和sckUDPProtocol,Winsock控件默认协议是TCP。注意:虽然可以在运行时设置协议,但必须在连接未建立或断开连接后。 SocketHandle属性 返回当前socket连接的句柄,这是只读属性。 RemoteHostIP属性 属性返回远程计算机的IP地址。在客户端,当使用了控件的Connect方法后,远程计算机的IP地址就赋给了RemoteHostIP属性,而在服务器端,当ConnectRequest事件后,远程计算机(客户端)的IP地址就赋给了这个属性。如果使用的是UDP协议那么当DataArrival事件后,发送UDP报文的计算机的IP才赋给了这个属性。 ByteReceived属性 返回当前接收缓冲区中的字节数 State属性 返回WinSock控件当前的状态 常数 值 描述 sckClosed 0 缺省值,关闭。 SckOpen 1 打开。 SckListening 2 侦听 sckConnectionPending 3 连接挂起 sckResolvingHost 4 识别主机。 sckHostResolved 5 已识别主机 sckConnecting 6 正在连接。 sckConnected 7 已连接。 sckClosing 8 同级人员正在关闭连接。 sckError 9   错误 2.WinSock主要方法 Listen方法 方法用于服务器程序,等待客户访问。格式:Winsock对象.listen Connect方法 用于向远程主机发出连接请求。格式:Winsock对象.connect [远程主机IP,远程端口] Accept方法 用于接受一个连接请求。格式:Winsock对象.accept Request ID Senddata方法 用于发送数据。格式:Winsock对象.senddata 数据 Getdata方法 用来取得接收到的数据。格式:Winsock对象.getdata 变量 [,数据类型 [,最大长度]] Close方法 关闭当前连接。格式:Winsock对象.close Bind方法 用Bind方法可以把一个端口号固定为本控件使用,使得别的应用程序不能再使用这个端口。 Listen方法Listen方法只在使用TCP协议时有用。它将应用程序置于监听检测状态。 Connect方法 当本地计算机希望和远程计算机建立连接时,就可以调用Connect方法。Connect方法调用的规范为:Connect RemoteHost,RemotePort Accept方法 当服务器接收到客户端的连接请求后,服务器有权决定是否接受客户端的请求。 SendData方法当连接建立后,要发送数据就可以调用SendData方法,该方法只有一个参数,就是要发送的数据。 GetData方法 当本地计算机接收到远程计算机的数据时,数据存放在缓冲区中,要从缓冲区中取出数据,可以使用GetData方法。GetData方法调用规范如下:GetData

110,566

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧