粘包拆包服务器

服务器:

创建个控制台应用

创建Server.cs

cs 复制代码
       internal class Server
    {
        TcpListener listen;
        public Server(IPAddress ip,int port) 
        {
           listen = new TcpListener(ip, port);
        }
        public void Start()
        {
            listen.Start(100);
            StartConnect();  
        }
        Dictionary<string,TcpClient> clientDic = new Dictionary<string,TcpClient>();
        public event Action<TcpClient> 有客户端连入的事件; 
        void StartConnect()
        {
            Task.Run(() =>
            {
                while (true) 
                {
                 TcpClient client = listen.AcceptTcpClient();
                 string ip = client.Client.RemoteEndPoint.ToString(); 
                 clientDic.Add(ip, client);
                 有客户端连入的事件?.Invoke(client);
                 startRead(client);//读取粘包的数据,在这个方法进行拆包处理
                }
            });
        }
        //拆包
        public void startRead(TcpClient t1)
        {
            //接受客户端发来的数据
            NetworkStream stream = t1.GetStream();
            string ip = t1.Client.RemoteEndPoint.ToString();
            byte[] bs = new byte[1024 * 1024];
            Task.Run(() =>
            {
                try
                {
                    while (true)
                    {
                      int count=  stream.Read(bs, 0, bs.Length);
                     if (count== 0)
                     {
                            throw new Exception("客户端断了");
                     }
                        //接受数据
                        byte[] body = bs.Take(count).ToArray();
                        string s = Encoding.UTF8.GetString(body);
                        Console.WriteLine("-------------------------------");
                        Console.WriteLine("接收到消息为:"+s);
                        Console.WriteLine("-------------------------------");
                        //进行拆包1 按照特殊符号进行分割,然后遍历,
                        //对应发数据的代码  buffer = Encoding.UTF8.GetBytes("张三你真牛逼了#");
                        //for (int i = 0; i < s.Split('#').Length-1; i++)
                        //{
                        //    Console.WriteLine("接收到消息为:" + s.Split('#')[i]);
                        //    Send(s.Split('#')[i]);
                        //}
                        if (上一个数据半包 != null) //判断是否有半包 如果有,把半包取出来和当前包合并
                        {
                            body =  上一个数据半包.Concat(body).ToArray();//把俩个数组合成一个数组
                            上一个数据半包 = null; //清空半包
                        }
                        //开始拆包 封装一个拆包方法
                        //参数1  当前要拆的数据
                        //参数2 第几个位置开始拆
                        //参数3 当前客户端
                        ChaiBao(body, 0, t1);
                    }
                }
                catch (Exception ex)
                {
                    clientDic.Remove(ip);
                }
            });
        }
        byte[] 上一个数据半包 = null;
        public void ChaiBao(byte[] bytes ,int startIndex, TcpClient client)
        {
            if(startIndex+4>bytes.Length) 
            {
                //如果开始位置加上4大于该数据包的长度时候 说明当前包是一个半包
                //跳过之前元素,取出剩余的数据
                上一个数据半包 = bytes.Skip(startIndex).ToArray();
                return;
            }
            //计算第一个包长度 获取从开始到startIndex之间的长度
            int len = BitConverter.ToInt32 (bytes,startIndex);

            // 取出对应位置包的总体大小
            // 之前的包长度总和
            int abc = len + startIndex + 4;

            //判断当前包是整包还是半包或者是多包
            if (abc == bytes.Length) 
            { //如果之前所有包的大小加上当前数据包大小等于全包的长度,证明当前是一个整包
                 byte[] bs1  = bytes.Skip(startIndex+4).ToArray();
                接受到消息的事件?.Invoke(client, bs1);
               
            }else if (abc < bytes.Length)
            {
                 byte[] bs2 =   bytes.Skip(startIndex+4).Take(len).ToArray();
                接受到消息的事件?.Invoke(client, bs2);
                //如果之前包加上当前包小于全包的长度,说明当前包是多包。如果是多包,再进行拆包
                ChaiBao(bytes, abc, client);
            }
            else
            { //目前是一个半包
                上一个数据半包 = bytes.Skip (startIndex).ToArray();
            }
        }
        public event Action<string> 客户端断开事件; 
        public event Action<TcpClient, byte[]> 接受到消息的事件;
        public Server()
        {
        }
        //群发方法 向所有的客户端发消息
        public void Send(string content)
        {
            byte[] bs = Encoding.UTF8.GetBytes(content);
            foreach (var item in clientDic) //遍历所有的客户端
            {
                item.Value.GetStream().Write(bs, 0, bs.Length);
            }
        }
        //指定给谁发
        public void Send(string content,string ip) {
            byte[] bs = Encoding.UTF8.GetBytes(content);
            //根据ip取出客户端,从字典取
            clientDic[ip].GetStream().Write(bs, 0, bs.Length);
        }
        //指定给哪些客户端发
        //send("你好", ["192.","127"])
        public void Send(string content, string[] ips)
        {
            byte[] bs = Encoding.UTF8.GetBytes(content);
            foreach (var item in clientDic) //所有客户端
            {
                //item.key 键 ip字符串
                //item.value 值 客户端对象
                if (ips.Contains(item.Key))
                {
                    //如果ips数组包含目标客户端
                    item.Value.GetStream().Write(bs, 0, bs.Length);
                }
            }
        }
    }

Program.cs

cs 复制代码
 internal class Program
 {
    static Server s;
     static void Main(string[] args)
     {
          s = new Server(IPAddress.Any,8080);
         s.有客户端连入的事件 += f1;
         s.接受到消息的事件 += f2;
         s.Start();
         Console.ReadKey();
     }
     public static void f1(TcpClient t1)
     {
         Console.WriteLine(t1.Client.RemoteEndPoint.ToString()+"连接到服务器");
     }
     public static void f2(TcpClient t2, byte[] bs)
     {
         Console.WriteLine(t2.Client.RemoteEndPoint.ToString()+"发来的消息为+++++++++++:"+ Encoding.UTF8.GetString(bs,0,bs.Length));
        
     }
 }

战报拆包开客户端:

窗体使用Button方法

cs 复制代码
 private void button1_Click(object sender, EventArgs e)
 {
     TcpClient  client = new TcpClient();
     client.Connect("192.168.107.83", 8080);
     NetworkStream stream = client.GetStream();
    

     //发消息
     // 第一种方式发消息的时候 每一条消息的结束时候以特殊符合作为结束标志 例如#、\n等符号
     // 其他端拿到数据之后 对数据进行特殊符号的分割, 

     //byte[] buffer = Encoding.UTF8.GetBytes("我是如来佛祖玉皇大帝观音菩萨指定取西经特派使者花果山水帘洞美猴王齐天大圣孙悟空啊!帅到掉渣!!#");
     //stream.Write(buffer,0,buffer.Length);

     //buffer = Encoding.UTF8.GetBytes("张三你真牛逼了#");
     //stream.Write(buffer, 0, buffer.Length);

     //buffer = Encoding.UTF8.GetBytes("李四你真牛逼了#");
     //stream.Write(buffer, 0, buffer.Length);

     //buffer = Encoding.UTF8.GetBytes("王五你真牛逼了#");
     //stream.Write(buffer, 0, buffer.Length);

     //buffer = Encoding.UTF8.GetBytes("马六你真牛逼了#");
     //stream.Write(buffer, 0, buffer.Length);

     //调用send方法
     Send(stream, "世界");
     Send(stream, "你好埃及文物已到达中国上海");
     Send(stream, "1111");
     Send(stream, "222222222");
 }
      
 //第二种发送数据时候 出现粘包现象,后台处粘包
 //包头当中添加数据长度解决粘包
 //Send(stream,"123")
 void Send(NetworkStream stream,string msg)
 {
     //1定义一个字节数组获取发送数据字节流
     byte[] bs = Encoding.UTF8.GetBytes(msg);

     //2 创建一个内存流,存在内存当中,可以理解为虚拟的文件流
     MemoryStream ms = new MemoryStream();

     //3 创建一个二进制读写对象 写入内存流
     BinaryWriter bw = new BinaryWriter(ms);

     //4写入数据的长度的
     bw.Write(bs.Length);

     //5 写入数据字节数组
     bw.Write(bs);

     //6 发送数据 把数据长度和数据内容同时发给服务器
     stream.Write(ms.ToArray(), 0, ms.ToArray().Length);

     bw.Close();
     ms.Close();
        
相关推荐
九河云23 分钟前
AWS账号注册费用详解:新用户是否需要付费?
服务器·云计算·aws
Lary_Rock28 分钟前
RK3576 LINUX RKNN SDK 测试
linux·运维·服务器
CoderIsArt29 分钟前
Redis的三种模式:主从模式,哨兵与集群模式
数据库·redis·缓存
△曉風殘月〆31 分钟前
WPF MVVM入门系列教程(二、依赖属性)
c#·wpf·mvvm
幺零九零零1 小时前
【计算机网络】TCP协议面试常考(一)
服务器·tcp/ip·计算机网络
XiaoLeisj1 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
云飞云共享云桌面2 小时前
8位机械工程师如何共享一台图形工作站算力?
linux·服务器·网络
励志成为嵌入式工程师2 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
逐·風2 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
师太,答应老衲吧2 小时前
SQL实战训练之,力扣:2020. 无流量的帐户数(递归)
数据库·sql·leetcode