处理原理:

半包:即一条消息底层分几次发送,先有个头包读取整条消息的长度,当不满足长度时,将消息临时缓存起来,直到满足长度再解码

粘包:两条完整/不完整消息粘在一起,一般是解码完上一条消息,然后再判断是否有剩余字节,有的话缓存起来,循环半包处理

客户端接收代码:

    private void callReceived(object sender, SocketAsyncEventArgs args)
{
var socket = sender as Socket;
var bb = args.UserToken as ByteBuffer;
if (args.SocketError == SocketError.Success)
{
bb.WriteBytes(args.Buffer, args.Offset, args.BytesTransferred);
bb.MarkReaderIndex();
int headLength = bb.ReadInt();
int msgLength = headLength;//长度已-4
int readByteLength = bb.ReadableBytes();
//解决半包
if (msgLength > readByteLength)
{
//还原读取索引记录
bb.ResetReaderIndex();
}
else {
//是否去掉包头
byte[] filthyBytes= bb.ToArray();
System.Console.WriteLine(System.Text.Encoding.UTF8.GetString(filthyBytes)); //解决粘包剩余
bb.Clear();
int useLength = filthyBytes.Length;
int lastOffSetLength = filthyBytes.Length - useLength;
if (lastOffSetLength > ) {
bb.WriteBytes(filthyBytes, lastOffSetLength, filthyBytes.Length);
}
}
}
else {
//丢去byte处理
System.Console.WriteLine("error callReceived");
}
_socket.ReceiveAsync(args);
}

服务端发送代码:

            ByteBuffer bb = ByteBuffer.Allocate();
byte[] sendBytes=System.Text.Encoding.UTF8.GetBytes("1234567890abcdefg");
Console.WriteLine("send msg length : " + sendBytes.Length);
bb.WriteInt(sendBytes.Length);
bb.WriteBytes(sendBytes);
Send(bb.ToArray(), _clients.ToArray()); public void Send(byte[] msg, params SocketAsyncEventArgs[] sockets)
{
System.Console.WriteLine(" Send msg :");
foreach (SocketAsyncEventArgs s in sockets)
{
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.SetBuffer(msg, , msg.Length);
//args.RemoteEndPoint = s.RemoteEndPoint;
args.AcceptSocket = s.AcceptSocket; args.Completed += new EventHandler<SocketAsyncEventArgs>(this.callSended);
System.Console.WriteLine(" AcceptSocket :" + s.AcceptSocket.RemoteEndPoint.ToString()); args.AcceptSocket.SendAsync(args);
} }
ByteBuffer 类基础跟netty相同,网上复制的
using System;

public class ByteBuffer
{
//字节缓存区
private byte[] buf;
//读取索引
private int readIndex = ;
//写入索引
private int writeIndex = ;
//读取索引标记
private int markReadIndex = ;
//写入索引标记
private int markWirteIndex = ;
//缓存区字节数组的长度
private int capacity; /**
* 构造方法
*/
private ByteBuffer(int capacity)
{
buf = new byte[capacity];
this.capacity = capacity;
} /**
* 构造方法
*/
private ByteBuffer(byte[] bytes)
{
buf = bytes;
this.capacity = bytes.Length;
} /**
* 构建一个capacity长度的字节缓存区ByteBuffer对象
*/
public static ByteBuffer Allocate(int capacity)
{
return new ByteBuffer(capacity);
} /**
* 构建一个以bytes为字节缓存区的ByteBuffer对象,一般不推荐使用
*/
public static ByteBuffer Allocate(byte[] bytes)
{
return new ByteBuffer(bytes);
} /**
* 翻转字节数组,如果本地字节序列为低字节序列,则进行翻转以转换为高字节序列
*/
private byte[] flip(byte[] bytes)
{
if (BitConverter.IsLittleEndian)
{
Array.Reverse(bytes);
}
return bytes;
} /**
* 确定内部字节缓存数组的大小
*/
private int FixSizeAndReset(int currLen, int futureLen)
{
if (futureLen > currLen)
{
//以原大小的2次方数的两倍确定内部字节缓存区大小
int size = FixLength(currLen) * ;
if (futureLen > size)
{
//以将来的大小的2次方的两倍确定内部字节缓存区大小
size = FixLength(futureLen) * ;
}
byte[] newbuf = new byte[size];
Array.Copy(buf, , newbuf, , currLen);
buf = newbuf;
capacity = newbuf.Length;
}
return futureLen;
} /**
* 根据length长度,确定大于此leng的最近的2次方数,如length=7,则返回值为8
*/
private int FixLength(int length)
{
int n = ;
int b = ;
while (b < length)
{
b = << n;
n++;
}
return b;
} /**
* 将bytes字节数组从startIndex开始的length字节写入到此缓存区
*/
public ByteBuffer WriteBytes(byte[] bytes, int startIndex, int length)
{
lock (this)
{
int offset = length - startIndex;
if (offset <= ) return this;
int total = offset + writeIndex;
int len = buf.Length;
FixSizeAndReset(len, total);
for (int i = writeIndex, j = startIndex; i < total; i++, j++)
{
this.buf[i] = bytes[j];
}
writeIndex = total;
}
return this;
} /**
* 将字节数组中从0到length的元素写入缓存区
*/
public ByteBuffer WriteBytes(byte[] bytes, int length)
{
return WriteBytes(bytes, , length);
} /**
* 将字节数组全部写入缓存区
*/
public ByteBuffer WriteBytes(byte[] bytes)
{
return WriteBytes(bytes, bytes.Length);
} /**
* 将一个ByteBuffer的有效字节区写入此缓存区中
*/
public ByteBuffer Write(ByteBuffer buffer)
{
if (buffer == null) return this;
if (buffer.ReadableBytes() <= ) return this;
return WriteBytes(buffer.ToArray());
} /**
* 写入一个int16数据
*/
public ByteBuffer WriteShort(short value)
{
return WriteBytes(flip(BitConverter.GetBytes(value)));
} /**
* 写入一个uint16数据
*/
public ByteBuffer WriteUshort(ushort value)
{
return WriteBytes(flip(BitConverter.GetBytes(value)));
} /**写入字符串*/
public ByteBuffer WriteString(string value)
{
int len = value.Length;
WriteInt(len);
//System.Text.Encoding.BigEndianUnicode.GetBytes
WriteBytes(System.Text.Encoding.UTF8.GetBytes(value));
return this;
}
/**读取字符串*/
public String ReadString()
{
int len =ReadInt();
byte[] bytes =new byte[len];
ReadBytes(bytes,,len); return System.Text.Encoding.UTF8.GetString(bytes);
} /**
* 写入一个int32数据
*/
public ByteBuffer WriteInt(int value)
{
//byte[] array = new byte[4];
//for (int i = 3; i >= 0; i--)
//{
// array[i] = (byte)(value & 0xff);
// value = value >> 8;
//}
//Array.Reverse(array);
//Write(array);
return WriteBytes(flip(BitConverter.GetBytes(value)));
} /**
* 写入一个uint32数据
*/
public ByteBuffer WriteUint(uint value)
{
return WriteBytes(flip(BitConverter.GetBytes(value)));
} /**
* 写入一个int64数据
*/
public ByteBuffer WriteLong(long value)
{
return WriteBytes(flip(BitConverter.GetBytes(value)));
} /**
* 写入一个uint64数据
*/
public ByteBuffer WriteUlong(ulong value)
{
return WriteBytes(flip(BitConverter.GetBytes(value)));
} /**
* 写入一个float数据
*/
public ByteBuffer WriteFloat(float value)
{
return WriteBytes(flip(BitConverter.GetBytes(value)));
} /**
* 写入一个byte数据
*/
public ByteBuffer WriteByte(byte value)
{
lock (this)
{
int afterLen = writeIndex + ;
int len = buf.Length;
FixSizeAndReset(len, afterLen);
buf[writeIndex] = value;
writeIndex = afterLen;
}
return this;
} /**
* 写入一个double类型数据
*/
public ByteBuffer WriteDouble(double value)
{
return WriteBytes(flip(BitConverter.GetBytes(value)));
} /**
* 读取一个字节
*/
public byte ReadByte()
{
byte b = buf[readIndex];
readIndex++;
return b;
} /**
* 从读取索引位置开始读取len长度的字节数组
*/
private byte[] Read(int len)
{
byte[] bytes = new byte[len];
Array.Copy(buf, readIndex, bytes, , len);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(bytes);
}
readIndex += len;
return bytes;
} /**
* 读取一个uint16数据
*/
public ushort ReadUshort()
{
return BitConverter.ToUInt16(Read(), );
} /**
* 读取一个int16数据
*/
public short ReadShort()
{
return BitConverter.ToInt16(Read(), );
} /**
* 读取一个uint32数据
*/
public uint ReadUint()
{
return BitConverter.ToUInt32(Read(), );
} /**
* 读取一个int32数据
*/
public int ReadInt()
{
return BitConverter.ToInt32(Read(), );
} /**
* 读取一个uint64数据
*/
public ulong ReadUlong()
{
return BitConverter.ToUInt64(Read(), );
} /**
* 读取一个long数据
*/
public long ReadLong()
{
return BitConverter.ToInt64(Read(), );
} /**
* 读取一个float数据
*/
public float ReadFloat()
{
return BitConverter.ToSingle(Read(), );
} /**
* 读取一个double数据
*/
public double ReadDouble()
{
return BitConverter.ToDouble(Read(), );
} /**
* 从读取索引位置开始读取len长度的字节到disbytes目标字节数组中
* @params disstart 目标字节数组的写入索引
*/
public void ReadBytes(byte[] disbytes, int disstart, int len)
{
int size = disstart + len;
for (int i = disstart; i < size; i++)
{
disbytes[i] = this.ReadByte();
}
} /**
* 清除已读字节并重建缓存区
*/
public void DiscardReadBytes()
{
if(readIndex <= ) return;
int len = buf.Length - readIndex;
byte[] newbuf = new byte[len];
Array.Copy(buf, readIndex, newbuf, , len);
buf = newbuf;
writeIndex -= readIndex;
markReadIndex -= readIndex;
if (markReadIndex < )
{
markReadIndex = readIndex;
}
markWirteIndex -= readIndex;
if (markWirteIndex < || markWirteIndex < readIndex || markWirteIndex < markReadIndex)
{
markWirteIndex = writeIndex;
}
readIndex = ;
} /**
* 清空此对象
*/
public void Clear()
{
buf = new byte[buf.Length];
readIndex = ;
writeIndex = ;
markReadIndex = ;
markWirteIndex = ;
} /**
* 设置开始读取的索引
*/
public void SetReaderIndex(int index)
{
if (index < ) return;
readIndex = index;
} /**
* 标记读取的索引位置
*/
public int MarkReaderIndex()
{
markReadIndex = readIndex;
return markReadIndex;
} /**
* 标记写入的索引位置
*/
public void MarkWriterIndex()
{
markWirteIndex = writeIndex;
} /**
* 将读取的索引位置重置为标记的读取索引位置
*/
public void ResetReaderIndex()
{
readIndex = markReadIndex;
} /**
* 将写入的索引位置重置为标记的写入索引位置
*/
public void ResetWriterIndex()
{
writeIndex = markWirteIndex;
} /**
* 可读的有效字节数
*/
public int ReadableBytes()
{
return writeIndex - readIndex;
} /**
* 获取可读的字节数组
*/
public byte[] ToArray()
{
byte[] bytes = new byte[writeIndex];
Array.Copy(buf, , bytes, , bytes.Length);
return bytes;
} /**
* 获取缓存区大小
*/
public int GetCapacity()
{
return this.capacity;
}
}

最后小结一下:

相信大家都看过TCP编程的书,书本上的理论废话长篇,说了一堆又给出一堆无用的代码。只有看源码才知道原理是怎样,源码实现一切。。。

在看的过程发现原作者写得也不是很好,简单的事情总是搞得那么复杂。。。。。

这行真是水深火热啊

c# socket 解决粘包,半包的更多相关文章

  1. Netty 粘包/半包原理与拆包实战

    Java NIO 粘包 拆包 (实战) - 史上最全解读 - 疯狂创客圈 - 博客园 https://www.cnblogs.com/crazymakercircle/p/9941658.html 本 ...

  2. Socket解决粘包问题1

    粘包是指发送端发送的包速度过快,到接收端那边多包并成一个包的现象,比如发送端连续10次发送1个字符'a',因为发送的速度很快,接收端可能一次就收到了10个字符'aaaaaaaaaa',这就是接收端的粘 ...

  3. 网络编程基础【day09】:socket解决粘包问题之MD5(八)

    本节内容 1.概述 2.代码实现 一.概述 上一篇博客讲到的用MD5来校验还是用的之前解决粘包的方法,就是客户端发送一个请求,等待服务端的确认的这样的一个笨方法.下面我们用另外一种方法:就是客户端已经 ...

  4. Http 调用netty 服务,服务调用客户端,伪同步响应.ProtoBuf 解决粘包,半包问题.

    实际情况是: 公司需要开发一个接口给新产品使用,需求如下 1.有一款硬件设备,客户用usb接上电脑就可以,但是此设备功能比较单一,所以开发一个服务器程序,辅助此设备业务功能 2.解决方案,使用Sock ...

  5. netty解决粘包半包问题

    前言:开发者用到TCP/IP交互时,偶尔会遇到粘包或者半包的数据,这种情况有时会对我们的程序造成严重的影响,netty框架为解决这种问题提供了若干框架 1. LineBasedFrameDecoder ...

  6. Socket解决粘包问题2

    在AsynServer中对接收函数增加接收判断,如果收到客户端发送的请求信息,则发送10个测试包给发送端,否则继续接收,修改后的接收代码如下: private void AsynReceive() { ...

  7. socket编程 TCP 粘包和半包 的问题及解决办法

    一般在socket处理大数据量传输的时候会产生粘包和半包问题,有的时候tcp为了提高效率会缓冲N个包后再一起发出去,这个与缓存和网络有关系. 粘包 为x.5个包 半包 为0.5个包 由于网络原因 一次 ...

  8. Netty - 粘包和半包(上)

    在网络传输中,粘包和半包应该是最常出现的问题,作为 Java 中最常使用的 NIO 网络框架 Netty,它又是如何解决的呢?今天就让我们来看看. 定义 TCP 传输中,客户端发送数据,实际是把数据写 ...

  9. C#下利用封包、拆包原理解决Socket粘包、半包问题(新手篇)

    介于网络上充斥着大量的含糊其辞的Socket初级教程,扰乱着新手的学习方向,我来扼要的教一下新手应该怎么合理的处理Socket这个玩意儿. 一般来说,教你C#下Socket编程的老师,很少会教你如何解 ...

随机推荐

  1. fir.im Weekly - 工欲善其事,必先利其器

    这周为大家搜罗了一些优秀的 GitHub 资源.APP开发工具.产品UI设计资源等等,同时也有一些程序员个人成长的Tips,"软硬件兼备"也许事半功倍不只是梦! Open-Sour ...

  2. iOS App 研发的最后冲刺:内测与部署

    当开发者历经磨难.披荆斩棘,完成了一个iOS项目后,最后的临门一脚就是应用的内测.部署.那么,在这最后的射门动作中,都有哪些地方需要开发者注意?有哪些方式能够更好地帮助我们进行iOS应用的发布部署? ...

  3. Leetcode 168 Excel Sheet Column Title 进制数转化

    题意:将数字转化成excel表中的行中的项目 本质是10进制转化为26进制,但是在中间加入了一个不一样的操作,在每次操作前都需要n-- class Solution { public: string ...

  4. Liferay7 BPM门户开发之33: Portlet之间通信的3种方式(session、IPC Render Parameter、IPC Event、Cookies)

    文章介绍了5种方式,4种是比较常用的: Portlet session IPC Public Render Parameters IPC Event Cookies 参考地址: https://web ...

  5. 详解Bootstrap缩略图组件及警示框组件

    缩略图组件 缩略图在网站中最常用的就是产品列表页面,一行显示几张图片,有的在图片底下带有标题.描述内容.按钮等信息.bootstrap框架将这部分独立成一个模块组件,通过类名.thumbnail配合b ...

  6. hive函数 -- split 字符串分割函数

    hive字符串分割函数 split(str, regex) - Splits str around occurances that match regexTime taken: 0.769 secon ...

  7. asp.net MVC的EF与easyui DataGrid数据绑定

    页面代码 @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewpor ...

  8. 你的项目真的需要Session吗?

    在web开发中,Session这个东西一直都很重要,至少伴随我10年之久, 前一段时间发生一个性能问题,因为Redis session 问题,后来想想 其实我的项目session 是不需要的. 先看看 ...

  9. linux服务器调整参数支持高并发

    服务端调整系统的参数,在/etc/sysctl.conf中: ◦net.core.somaxconn = 2048◦net.core.rmem_default = 262144◦net.core.wm ...

  10. ZooKeeper快速搭建

    原文地址:http://nileader.blog.51cto.com/1381108/795230 下载PDF版本 本文是ZooKeeper的快速搭建,旨在帮助大家以最快的速度完成一个ZK集群的搭建 ...