SocketAsyncServer数据粘包处理,急!!!

谁动了我的凌枫 2016-11-17 05:06:24
各位大神,小弟最近在做socket服务器通讯,客户端大约共有1万个,大约每隔30秒会发送一个数据包,每个数据长度大约50个字节左右。我采用的是SocketAsyncServer来实现,并且保持长连接。
现在遇到一个问题,就是担心TCP会有数据粘包的现象发生,如果有问题,我应该怎么处理,同步的socket我会处理,定义个包头和长度,然后就是内容了,可以实现正确判断。如果要是异步的怎么办呢?我用的是MSDN上的例子,这里我有一个疑问,completed事件是只要服务器端有数据就触发吗?那如果客户端A给服务器S发送数据,由于网络原因服务器端只收到了一半,会触发这个completed事件吗?等A剩下的另一半数据又接收后还会触发completed事件吗?然后客户端B又给服务器S发送了一个数据,触发completed事件后,A的数据怎么和B的数据进行区分。在ProcessReceive里应该如何实现数据粘包问题,如何处理我接收的字符串received,求各位大神给予帮助。
代码如下:

private void OnIOCompleted(object sender, SocketAsyncEventArgs e)
{
// Determine which type of operation just completed and call the associated handler.
switch (e.LastOperation)
{
case SocketAsyncOperation.Receive:
this.ProcessReceive(e);
break;
case SocketAsyncOperation.Send:
this.ProcessSend(e);
break;
default:
throw new ArgumentException("The last operation completed on the socket was not a receive or send");
}
}
private void ProcessReceive(SocketAsyncEventArgs e)
{
// Check if the remote host closed the connection.
if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
{
Socket s = e.UserToken as Socket;

Int32 bytesTransferred = e.BytesTransferred;

// Get the message received from the listener.
String received = Encoding.ASCII.GetString(e.Buffer, e.Offset, bytesTransferred);

// Increment the count of the total bytes receive by the server.
Interlocked.Add(ref this.totalBytesRead, bytesTransferred);

Console.WriteLine("Received: {0}\"{1}\". The server has read a total of {2} bytes.", s.RemoteEndPoint, received, this.totalBytesRead);
}
else
{
this.CloseClientSocket(e);
}
}

...全文
507 16 打赏 收藏 转发到动态 举报
写回复
用AI写文章
16 条回复
切换为时间正序
请发表友善的回复…
发表回复
starts_2000 2016-11-20
  • 打赏
  • 举报
回复
DotNetty https://github.com/Azure/DotNetty 看懂了就OK了
  • 打赏
  • 举报
回复
引用 13 楼 yle_sjmr 的回复:
我最近也刚学习用SocketAsyncEventArgs做即时通信,交流下我的做法的是,同一客户端Socket下,将接收的每一块数据都追加到上一块数据的末尾,根据定好的字节流格式(业务数据包格式),每分析出一个业务数据包,就加入一个队列里,再不停地弹出处理。下面我写的部分代码:
/// <summary>
        /// 处理接收数据
        /// </summary>
        /// <param name="receiveArgs"></param>
        protected void processReceive(SocketAsyncEventArgs receiveArgs)
        {
            var user = receiveArgs.UserToken as UserToken;
            // 只有当有数据且正常连接的时候才处理
            if (receiveArgs.BytesTransferred > 0 && receiveArgs.SocketError == SocketError.Success)
            {
                // 截取收到的数据包
                byte datas = new byte;
                Array.Copy(receiveArgs.Buffer, receiveArgs.Offset, datas, 0, receiveArgs.BytesTransferred);

                // 触发外部事件
                triggerOnReceived(user, datas);

                // 继续接收下个数据包
                startReceive(user.Socket, receiveArgs);
            }
            else
            {
                loger.WriteLine("Socket Receive SocketError: " + receiveArgs.SocketError);
                closeSocket(user);
            }
        }
private void _server_OnReceived(UserToken user, byte[] bytes)
        {
            var puser = _users[user.Id];
            var packetMg = puser.PacketManager;
            // 追加字节流数据包
            packetMg.Push(bytes);
            // 弹出一个业务数据包
            var packet = packetMg.PopPacket();

            while (packet != null)
            {
                triggerOnReceivePacket(puser, packet);
                packet = packetMg.PopPacket();
            }
        }
其实主要是后面的那块数据拼接处理那块不明白,应该有自定义的类吧, var puser = _users[user.Id]; var packetMg = puser.PacketManager; // 追加字节流数据包 packetMg.Push(bytes); // 弹出一个业务数据包 var packet = packetMg.PopPacket(); 这块能贴出相应代码吗,谢谢
  • 打赏
  • 举报
回复
引用 13 楼 yle_sjmr 的回复:
我最近也刚学习用SocketAsyncEventArgs做即时通信,交流下我的做法的是,同一客户端Socket下,将接收的每一块数据都追加到上一块数据的末尾,根据定好的字节流格式(业务数据包格式),每分析出一个业务数据包,就加入一个队列里,再不停地弹出处理。下面我写的部分代码:
/// <summary>
        /// 处理接收数据
        /// </summary>
        /// <param name="receiveArgs"></param>
        protected void processReceive(SocketAsyncEventArgs receiveArgs)
        {
            var user = receiveArgs.UserToken as UserToken;
            // 只有当有数据且正常连接的时候才处理
            if (receiveArgs.BytesTransferred > 0 && receiveArgs.SocketError == SocketError.Success)
            {
                // 截取收到的数据包
                byte datas = new byte;
                Array.Copy(receiveArgs.Buffer, receiveArgs.Offset, datas, 0, receiveArgs.BytesTransferred);

                // 触发外部事件
                triggerOnReceived(user, datas);

                // 继续接收下个数据包
                startReceive(user.Socket, receiveArgs);
            }
            else
            {
                loger.WriteLine("Socket Receive SocketError: " + receiveArgs.SocketError);
                closeSocket(user);
            }
        }
private void _server_OnReceived(UserToken user, byte[] bytes)
        {
            var puser = _users[user.Id];
            var packetMg = puser.PacketManager;
            // 追加字节流数据包
            packetMg.Push(bytes);
            // 弹出一个业务数据包
            var packet = packetMg.PopPacket();

            while (packet != null)
            {
                triggerOnReceivePacket(puser, packet);
                packet = packetMg.PopPacket();
            }
        }
非常感谢您的回答,能发一个完整示例吗?我学习学习,邮箱mwb1211@126.com,谢谢了~~~
  • 打赏
  • 举报
回复
你在“面向对象”方面可能是缺乏直观设计感受,比较多地用结构化、底层的思维方式考虑逻辑设计。
  • 打赏
  • 举报
回复
引用 10 楼 alon729 的回复:
[quote=引用 9 楼 tcmakebest 的回复:] 每个客户端连接都要建一个数据缓存的, 收到即往里面累积, 然后判断里面是否有一个完整的数据包, 数据包的规则是自己来定的, 存在数据包时就进行处理, 处理的时候要锁定缓存,避免处理到一半又有新的数据进来造成混乱.
您好,非常感谢您的回答,我还有一个问题,如果客户端A给服务端S发送123456,然后客户端B给服务端发送ABCDEFG,那么服务端S会不会收到123ABCDEFG456这种样式呢?多谢您的回答[/quote] 不同的 Accept/BeginAccept 得到的 client 会话对象,每一个 client 再开始 Receive/BeginReceive,怎么可能会将数据串到一起呢?
  • 打赏
  • 举报
回复
引用 9 楼 tcmakebest 的回复:
每个客户端连接都要建一个数据缓存的, 收到即往里面累积, 然后判断里面是否有一个完整的数据包, 数据包的规则是自己来定的, 存在数据包时就进行处理, 处理的时候要锁定缓存,避免处理到一半又有新的数据进来造成混乱.
您好,非常感谢您的回答,我还有一个问题,如果客户端A给服务端S发送123456,然后客户端B给服务端发送ABCDEFG,那么服务端S会不会收到123ABCDEFG456这种样式呢?多谢您的回答
yle_sjmr 2016-11-19
  • 打赏
  • 举报
回复
我最近也刚学习用SocketAsyncEventArgs做即时通信,交流下我的做法的是,同一客户端Socket下,将接收的每一块数据都追加到上一块数据的末尾,根据定好的字节流格式(业务数据包格式),每分析出一个业务数据包,就加入一个队列里,再不停地弹出处理。下面我写的部分代码:
/// <summary>
        /// 处理接收数据
        /// </summary>
        /// <param name="receiveArgs"></param>
        protected void processReceive(SocketAsyncEventArgs receiveArgs)
        {
            var user = receiveArgs.UserToken as UserToken;
            // 只有当有数据且正常连接的时候才处理
            if (receiveArgs.BytesTransferred > 0 && receiveArgs.SocketError == SocketError.Success)
            {
                // 截取收到的数据包
                byte datas = new byte;
                Array.Copy(receiveArgs.Buffer, receiveArgs.Offset, datas, 0, receiveArgs.BytesTransferred);

                // 触发外部事件
                triggerOnReceived(user, datas);

                // 继续接收下个数据包
                startReceive(user.Socket, receiveArgs);
            }
            else
            {
                loger.WriteLine("Socket Receive SocketError: " + receiveArgs.SocketError);
                closeSocket(user);
            }
        }
private void _server_OnReceived(UserToken user, byte[] bytes)
        {
            var puser = _users[user.Id];
            var packetMg = puser.PacketManager;
            // 追加字节流数据包
            packetMg.Push(bytes);
            // 弹出一个业务数据包
            var packet = packetMg.PopPacket();

            while (packet != null)
            {
                triggerOnReceivePacket(puser, packet);
                packet = packetMg.PopPacket();
            }
        }
tcmakebest 2016-11-18
  • 打赏
  • 举报
回复
每个客户端连接都要建一个数据缓存的, 收到即往里面累积, 然后判断里面是否有一个完整的数据包, 数据包的规则是自己来定的, 存在数据包时就进行处理, 处理的时候要锁定缓存,避免处理到一半又有新的数据进来造成混乱.
by_封爱 版主 2016-11-18
  • 打赏
  • 举报
回复
网上有很多使用SocketAsync写出来的可用的DLL 我建议你看下. 直接调用. 因为这部分处理的确比较费劲 而且你又不是太懂. 所以浪费时间 不如直接拿来人家写好的用而已.. 前人栽树后人乘凉 需要请call我
xian_wwq 2016-11-18
  • 打赏
  • 举报
回复
引用 楼主 alon729 的回复:
各位大神,小弟最近在做socket服务器通讯,客户端大约共有1万个,大约每隔30秒会发送一个数据包,每个数据长度大约50个字节左右。我采用的是SocketAsyncServer来实现,并且保持长连接。 现在遇到一个问题,就是担心TCP会有数据粘包的现象发生,如果有问题,我应该怎么处理,同步的socket我会处理,定义个包头和长度,然后就是内容了,可以实现正确判断。如果要是异步的怎么办呢?我用的是MSDN上的例子,这里我有一个疑问,completed事件是只要服务器端有数据就触发吗?那如果客户端A给服务器S发送数据,由于网络原因服务器端只收到了一半,会触发这个completed事件吗?等A剩下的另一半数据又接收后还会触发completed事件吗?然后客户端B又给服务器S发送了一个数据,触发completed事件后,A的数据怎么和B的数据进行区分。在ProcessReceive里应该如何实现数据粘包问题,如何处理我接收的字符串received,求各位大神给予帮助。 代码如下:

private void OnIOCompleted(object sender, SocketAsyncEventArgs e)
        {
            // Determine which type of operation just completed and call the associated handler.
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    this.ProcessReceive(e);
                    break;
                case SocketAsyncOperation.Send:
                    this.ProcessSend(e);
                    break;
                default:
                    throw new ArgumentException("The last operation completed on the socket was not a receive or send");
            }
        }
private void ProcessReceive(SocketAsyncEventArgs e)
        {
            // Check if the remote host closed the connection.
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {
                Socket s = e.UserToken as Socket;

                Int32 bytesTransferred = e.BytesTransferred;

                // Get the message received from the listener.
                String received = Encoding.ASCII.GetString(e.Buffer, e.Offset, bytesTransferred);

                // Increment the count of the total bytes receive by the server.
                Interlocked.Add(ref this.totalBytesRead, bytesTransferred);

                Console.WriteLine("Received: {0}\"{1}\". The server has read a total of {2} bytes.", s.RemoteEndPoint, received, this.totalBytesRead);
            }
            else
            {
                this.CloseClientSocket(e);
            }
        }

楼上大神其实已经说过了, 1. 网络通讯与数据解析要分离, lz担心的粘包不是网络通讯的重点,是业务数据解析的重点。 网络通讯只要收到byte[],工作就完成了,将数据存放到buffer中,立即返回。 否则服务器别说接入1万,能否承接1000路都要打个问号。 2.系统的瓶径不在网络通讯,而是数据解析。业务数据处理都是 高频的短任务,适合采用线程池进行处理。 buffer中的数据有可能是一个独立的包,有可能是多个包, 也可能是部分数据。这些都是数据解析必须处理的。 3.server端要把client的相关连接信息保存起来, 这样根据socket或者其他标识就能区分目前的数据属于谁, 否则回送数据的时候找不到对象, 同时要处理超时异常,就是client由于某种原因,不再工作,要通过 后台线程判断超时对象并进行清理,否则就只能等着server被拖死了。 个人感觉如果客户端30秒通讯一次,可以不保持长链接。能简化好多处理逻辑。
  • 打赏
  • 举报
回复
引用 3 楼 sp1234 的回复:
如果是连续、并发发送数据,其实没有什么“一半”的概念。数据是“流式”的,也就是说假设发送端有连续数据、并发多线程再发送数据,而数据自然是拆成无数个不足1k地低级TCP包,从全世界不同路由传送到接收端,最后再重新组装成连续的“流”。 这里说的“包”不是底层的什么“包”,而是 .net 的逻辑的“包”。比如说发送端“一次”发送5000字节数据,另一端可能分3次才能接收到数据。比如说发送端最终 2000、3000字节的数据,另一端则是分3次1900字节、2100字节、1000字节三个包才收到。这都是很正常的。当然你发送一次数据就会间隔很久,那么粘包的情况不太可能出现,但是分包的可能行还是存在。这是正常的! 当你 Receive 的时候,你应该把收到的字节放到 buffer 中,然后就结束 I/O 线程。之后你的 Workder 线程负责解析收到的所有的 byets,从中解析到第一条消息(并且从 buffer 中删除此消息),调用线程池注册一个任务去处理它。然后再判断 buffer 中还有没有消息....... buffer 中可能会剩下一些字节。
您好,感谢您这么详细的解答,可是我还有一个问题,就是SocketAsyncEventArgs中的completed事件是只要缓冲区有数据就触发吗?还是等数据流齐全了才会触发,如果我把临时接收到的这些流都存下来,那我怎么知道这些流哪些是客户端A发的,哪些是客户端B发的,到时候拼接字符串的时候不会出错啊。还有这个处理机制有比较成熟的封装的类吗,还是需要自己手动去写。多谢了。
  • 打赏
  • 举报
回复
引用 6 楼 diaodiaop 的回复:
网上有很多使用SocketAsync写出来的可用的DLL 我建议你看下. 直接调用. 因为这部分处理的确比较费劲 而且你又不是太懂. 所以浪费时间 不如直接拿来人家写好的用而已.. 前人栽树后人乘凉 需要请call我
哈哈哈,我觉得您说的很对,有相关程序代码示例吗?可不可以给借鉴下,学习学习,小弟在此多谢了。 邮箱:mwb1211@126.com
  • 打赏
  • 举报
回复
引用 5 楼 xian_wwq 的回复:
[quote=引用 楼主 alon729 的回复:] 各位大神,小弟最近在做socket服务器通讯,客户端大约共有1万个,大约每隔30秒会发送一个数据包,每个数据长度大约50个字节左右。我采用的是SocketAsyncServer来实现,并且保持长连接。 现在遇到一个问题,就是担心TCP会有数据粘包的现象发生,如果有问题,我应该怎么处理,同步的socket我会处理,定义个包头和长度,然后就是内容了,可以实现正确判断。如果要是异步的怎么办呢?我用的是MSDN上的例子,这里我有一个疑问,completed事件是只要服务器端有数据就触发吗?那如果客户端A给服务器S发送数据,由于网络原因服务器端只收到了一半,会触发这个completed事件吗?等A剩下的另一半数据又接收后还会触发completed事件吗?然后客户端B又给服务器S发送了一个数据,触发completed事件后,A的数据怎么和B的数据进行区分。在ProcessReceive里应该如何实现数据粘包问题,如何处理我接收的字符串received,求各位大神给予帮助。 代码如下:

private void OnIOCompleted(object sender, SocketAsyncEventArgs e)
        {
            // Determine which type of operation just completed and call the associated handler.
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    this.ProcessReceive(e);
                    break;
                case SocketAsyncOperation.Send:
                    this.ProcessSend(e);
                    break;
                default:
                    throw new ArgumentException("The last operation completed on the socket was not a receive or send");
            }
        }
private void ProcessReceive(SocketAsyncEventArgs e)
        {
            // Check if the remote host closed the connection.
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {
                Socket s = e.UserToken as Socket;

                Int32 bytesTransferred = e.BytesTransferred;

                // Get the message received from the listener.
                String received = Encoding.ASCII.GetString(e.Buffer, e.Offset, bytesTransferred);

                // Increment the count of the total bytes receive by the server.
                Interlocked.Add(ref this.totalBytesRead, bytesTransferred);

                Console.WriteLine("Received: {0}\"{1}\". The server has read a total of {2} bytes.", s.RemoteEndPoint, received, this.totalBytesRead);
            }
            else
            {
                this.CloseClientSocket(e);
            }
        }

楼上大神其实已经说过了, 1. 网络通讯与数据解析要分离, lz担心的粘包不是网络通讯的重点,是业务数据解析的重点。 网络通讯只要收到byte[],工作就完成了,将数据存放到buffer中,立即返回。 否则服务器别说接入1万,能否承接1000路都要打个问号。 2.系统的瓶径不在网络通讯,而是数据解析。业务数据处理都是 高频的短任务,适合采用线程池进行处理。 buffer中的数据有可能是一个独立的包,有可能是多个包, 也可能是部分数据。这些都是数据解析必须处理的。 3.server端要把client的相关连接信息保存起来, 这样根据socket或者其他标识就能区分目前的数据属于谁, 否则回送数据的时候找不到对象, 同时要处理超时异常,就是client由于某种原因,不再工作,要通过 后台线程判断超时对象并进行清理,否则就只能等着server被拖死了。 个人感觉如果客户端30秒通讯一次,可以不保持长链接。能简化好多处理逻辑。 [/quote] 您回答真的很专业,让我对socket理解更深入些了,非常感谢。另外,您这有相关的示例程序吗?可以发给我看看吗,学习学习。邮箱:mwb1211@126.com。再次感谢。
  • 打赏
  • 举报
回复
如果是连续、并发发送数据,其实没有什么“一半”的概念。数据是“流式”的,也就是说假设发送端有连续数据、并发多线程再发送数据,而数据自然是拆成无数个不足1k地低级TCP包,从全世界不同路由传送到接收端,最后再重新组装成连续的“流”。 这里说的“包”不是底层的什么“包”,而是 .net 的逻辑的“包”。比如说发送端“一次”发送5000字节数据,另一端可能分3次才能接收到数据。比如说发送端最终 2000、3000字节的数据,另一端则是分3次1900字节、2100字节、1000字节三个包才收到。这都是很正常的。当然你发送一次数据就会间隔很久,那么粘包的情况不太可能出现,但是分包的可能行还是存在。这是正常的! 当你 Receive 的时候,你应该把收到的字节放到 buffer 中,然后就结束 I/O 线程。之后你的 Workder 线程负责解析收到的所有的 byets,从中解析到第一条消息(并且从 buffer 中删除此消息),调用线程池注册一个任务去处理它。然后再判断 buffer 中还有没有消息....... buffer 中可能会剩下一些字节。
  • 打赏
  • 举报
回复
引用 1 楼 SomethingJack 的回复:
TCP 数据应该有协议吧?异步的话 你得把所有的tcp客户端放在一个类中,比如
 /// <summary>
        /// 客户端Socket对象类
        /// </summary>
        public class ClientInfo
        {
            public byte[] Buffer { get; set; }
            public ByteQueue Queue { get; set; }
            public Socket Socket { get; set; }
        }
加这个类有什么用,是为了把每个socket和接收到的消息(可能不全)放在一块方便处理吗?能给讲讲怎么使用吗?我这个是MSDN上的例子。
SomethingJack 2016-11-17
  • 打赏
  • 举报
回复
TCP 数据应该有协议吧?异步的话 你得把所有的tcp客户端放在一个类中,比如
 /// <summary>
        /// 客户端Socket对象类
        /// </summary>
        public class ClientInfo
        {
            public byte[] Buffer { get; set; }
            public ByteQueue Queue { get; set; }
            public Socket Socket { get; set; }
        }

110,534

社区成员

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

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

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