C#的异步socket编程碰到几个很无语的问题。。。求解脱

diablox0147 2012-05-01 12:27:04
问题1:
在服务端经常遇到EndReceive先是返回0字节然后再次接受才能获得真正的包内容,但是客户端那边明明没有断开连接,而且之后的通讯也一直能收发,代码有点长我简约下。
大概的结构是这样:

private void AsynReceiveHead(IAsyncResult ar)
{
int octs = 0;
Socket sock = (Socket)ar.AsyncState;
octs = sock.EndReceive(ar);//就是这里几乎每次都返回0字节,必须再次接受才能获得真正的包,而有时候连真正的包也接收不到
if(xxx)
{
.
.
.
sock.BeginReceive(BUFF, 0, SIZE, SocketFlags.None, AsynReceiveHead, sock);//继续接受下个包
}
else if(yyy)
{
.
.
.
sock.BeginReceive(BUFF, 0, ContentSize, SocketFlags.None, AsynReceiveContent, sock);//接受包的内容
}

sock.BeginReceive(BUFF, 0, SIZE, SocketFlags.None, AsynReceiveHead, sock);//再其他情况下都继续接收下个包
}

private void AsynReceiveContent(IAsyncResult ar)
{
int octs = 0;
Socket sock = (Socket)ar.AsyncState;
octs = sock.EndReceive(ar);//而在这里几乎不会返回0字节的包
if(xxx)
{
.
.
.
sock.BeginReceive(BUFF, 0, SIZE, SocketFlags.None, AsynReceiveHead, sock);//继续接收下个包
}
else if(yyy)
{
.
.
.
.
sock.BeginSend(BUFF, 0, SIZE, SocketFlags.None, AsynSend, sock);//发送消息结果
}
}

差不多就是这样了,基本上每次在接收的时候在AsynReceiveHead里都是先返回一个0字节然后再次接收才能接收真正的包。。。

问题2:
在客户端那边有时候接收不到来自服务端的消息。几率是20%左右。。。客户端是2个线程一个线程不停的接收来自服务端的消息另外一个则不停的检查有没有新的消息包并且发送消息包到服务端。
而在客户端却没有像在服务端一样会先返回0字节
我也把代码大概的结构写下来


List<Request> listRequest;//储存收到来自服务端的消息列表

void threadReceive()
{
while(true)
{
socket.Receive(buff, 0, length, SocketFlags.None);
.
.
.
if(消息头里有内容)
{
socket.Receive(buff, 0, length, SocketFlags.None);
}
.
.//继续处理消息包
.
lock(listRequest)
{
listRequest.Add(new Request(buff));//添加到消息包列表里
}
}
}


void threadHandleAndSend()
{
List<Request> requests;

while(true)
{
lock(listRequest)
{
if(listRequest.Count>0)
{
//那么就把listRequest复制到request里并且清空listRequest
.
.
.//继续处理消息
}
}
if(需要发送消息)
{
sock.Send(data, 0, data.Length, SocketFlags.None);
}

}
}


代码大概的结构就是这样的了。。。真的很奇怪很无语,被这2个问题郁闷了好几天了。。就是看不出来哪里的问题,
有时候啥事都没有时候就很奇怪的接收不到消息。。。
...全文
409 15 打赏 收藏 转发到动态 举报
写回复
用AI写文章
15 条回复
切换为时间正序
请发表友善的回复…
发表回复
diablox0147 2012-05-01
  • 打赏
  • 举报
回复
现在想想看的话,在客户端收不到来自服务端的消息是因为在服务端根本就没收到消息包。。。所以当然就不会处理了。。。
diablox0147 2012-05-01
  • 打赏
  • 举报
回复
我现在就是想破脑袋也想不出来为什么会这样,有时候会收到0包有时候收不到正确的包。。。

而且在用户登录后第一次发送的确认包带消息的是另外一个专门接收用户登录的线程处理的,也是分2次接受。唯一不同的是在接收包体内容里用的是Receive直接获得而不是BeginReceive、、
但是在用户第一次登录发送的确认包一次都没发生过0包的情况。。。
diablox0147 2012-05-01
  • 打赏
  • 举报
回复
嗯,那个不过是临时写的。,
不过大概接收的逻辑是这样。 里面的包量名有点乱用了
  • 打赏
  • 举报
回复
然后再接收到1000字节 --> ,然后再接收到100字节


[Quote=引用 10 楼 的回复:]

哦,在这个代码里的确是没有但是在真正的文件里是有的,先是接收固定大小的包头然后根据包头里的标识符看看是不是还需要接收一定长度的消息包内容。
真正的代码是这样的:
C# code


player.SOCKET.BeginReceive(player.BUFF, 0, MessageHead.SIZE, SocketFlags.None, AsynReceiveHead, player);/……
[/Quote]

那么你的代码前边接收“包头长度”代码的所有的 BUFF、SIZE也是错的?
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 的回复:]
所有接收的消息都按照发送顺序储存在底层的缓冲区了,我只需要一个个的提取就可以了
[/Quote]

我不太确定你是什么具体意思。

tcp会先把数据缓存在本地,然后等待一段时间,然后把缓冲的数据发出去。打个比方把,你的客户端首先发送5个字节,然后发送1000个字节,然后发送5个字节,然后再发送1000个字节,而服务器端则可能首先接收到800个字节,然后再接收到1200字节,然后再接收到1000字节。这就是你说的“按照所发送循序储存在在底层缓冲区了”。
diablox0147 2012-05-01
  • 打赏
  • 举报
回复
哦,在这个代码里的确是没有但是在真正的文件里是有的,先是接收固定大小的包头然后根据包头里的标识符看看是不是还需要接收一定长度的消息包内容。
真正的代码是这样的:


player.SOCKET.BeginReceive(player.BUFF, 0, MessageHead.SIZE, SocketFlags.None, AsynReceiveHead, player);//接受包头

if (head.size!= 0)
{
player.SOCKET.BeginReceive(player.BUFF, MessageHead.SIZE, head.size, SocketFlags.None, AsynReceiveContent, player);//继续接收内容,在buff里的offset是从包头之后开始的
}
  • 打赏
  • 举报
回复
至少在你的代码中,你何时“接收这个标识符长度的大小”了?你的AsynReceiveHead方法中那一个接受“包体”的代码,明明是再次使用BUFF、SIZE,哪一个代码说明你是按照包头大小来接收的?

diablox0147 2012-05-01
  • 打赏
  • 举报
回复
对了,还有就是,发现在服务端经常还收不到消息内容。 有时候会在返回了0字节后不再接收真正的消息。真是奇怪,我的log里是这样的:

///下面是DD发送的消息,他先是进入空闲状态然后变成等待状态等待其他玩家,这个类别的消息是不带包内容的,就是一个消息头,
///所以这2包都不会调用ReceiveContent
///可以看到只有一个包是返回了空包然后再获得真正的消息包,不过因为出现在中间所以也不知道是先返回0的包还是接收完消息包后才返回0

16 Information: DD is in free... -14:19:40
17 ERROR: DD has send a empty message, ID Player: 2 -14:19:40
18 Information: DD is in waiting... -14:19:40
///下面是SS发送的3个包,但是只有出现一次返回空包的情况。
///19号Log是只有包头的消息报
///21号是带消息内容的消息包
///22号是带消息内容的消息包
///看的出来这里也出现了一次先返回空消息,不知道是不是不带消息内容也就是不调用ReceiveContent的就不会出现先返回空包的情况
19 Information: Player SS ask for information... -14:19:41
20 Error: SS has send a empty message, ID Player: 3 -14:19:43
21 Information: SS ask for play with DD -14:19:43
22 Information: SS is ready, now he is waiting for DD -14:19:44
///之后这个本来是DD发送了同意的消息包,这个包是带内容的,就是和22号Log一样的包,就是不知道为什么
///就返回了0包后就没其他动作了、、、
23 Error: DD has send a empty message, ID Player: 2 -14:19:46


上面的这些看起来都非常随机阿。。。 但是不知道是不是每次发送没有消息内容的包就会返回一次0包。。。
diablox0147 2012-05-01
  • 打赏
  • 举报
回复
其实我每次在接受的时候都是先接收固定的包头大小然后看下包头里代表内容长度的标识,如果大于0就再次接收这个标识符长度的大小。
因为我认为 消息的接受和发送不是TCP底层已经帮你做好了么? 所有接收的消息都按照发送顺序储存在底层的缓冲区了,我只需要一个个的提取就可以了。 根据这样的想法才会觉得先接收包头。然后根据包头的内容选择是否接收内容。
  • 打赏
  • 举报
回复
实际的代码在 octs = sock.EndReceive(ar) 和最后一条代码这两个地方,需要有try...catch,因为socket(客户端)可能已经不能访问了。

并且假设你的通讯协议支持长连接异步通讯,那么
    foreach(byte[] cmd in commands)
{
解析命令并且返回结果(sock, cmd);
} foreach(byte[] cmd in commands)
{
解析命令并且返回结果(sock, cmd);
}
这这个方法都可以异步并发执行,而不需要按照客户端发送消息的次序来返回各个消息处理结果。
  • 打赏
  • 举报
回复
接收消息的程序大致可以写成(仅仅按照你的代码修改):
private void AsynReceiveMessages(IAsyncResult ar)
{
int octs = 0;
Socket sock = (Socket)ar.AsyncState;
octs = sock.EndReceive(ar);
if(octs>0)
{
将BUFF中前octs个字节累积起来//例如放到一个MemoryStream中,或者List<byte[]>集合中。
}
if(octs==0 || BUFF[octs-1]附近的字节是以消息结束符结束的)
{
var commands = 从这个MemoryStream中或者List<byte[]>集合中取出一个或者多个消息内容;
foreach(byte[] cmd in commands)
{
解析命令并且返回结果(sock, cmd);
}
}
sock.BeginReceive(BUFF, 0, SIZE, SocketFlags.None, AsynReceiveMessages, sock);
}



你使用BUFF、SIZE接收消息的时候,BUFF中的数据可能既包含前边消息体,也包括下一个消息的部分消息头,你怎样区分呢?

所以要将信息全都收集起来,然后从收集的内容中解析出前边的消息执行。注意这时候收集的消息可能不止一条——因为你 EndReceive 的数据中有所谓“沾包”的数据。

你的代码太复杂了,反而问题很多。实际上最终代码应该比你写的简单多了,用不着分成什么Head、Content两部分,而且也不能分开。
足球中国 2012-05-01
  • 打赏
  • 举报
回复
.net 的bug。多线程的元子操作的那个也有BUG。
diablox0147 2012-05-01
  • 打赏
  • 举报
回复
难道没有人知道这么回事吗?
diablox0147 2012-05-01
  • 打赏
  • 举报
回复
难道收不到消息报也是正常的吗。。。是TCP而且还是在本地测试的哦
stonespace 2012-05-01
  • 打赏
  • 举报
回复
这是正常的,你可以写程序去适应这种情况,

111,126

社区成员

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

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

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