Socket/TCP练习 脑图

鸽了 鸽了... 之前说要写socket的文章,但是没空啊... 

先把练习的Demo贴出来... 最最最最最基本的socket应用,里面没有buff利用的优化,也没有粘包分包的处理...

纯属最基本的学习的代码

先发出来鞭策自己,回头再来添加。

代码很简单:

C#服务端:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace Server
{
    public class MyTestServer
    {
        static Socket listen;
        static Dictionary clients = new Dictionary();

        //开启服务器
        static void Main(string[] args)
        {
            Console.WriteLine("Main Start !!! ");
            //监听用socket实例
            listen = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //绑定本机 ip 以及 端口
            IPAddress ipAdr = IPAddress.Parse("192.168.1.115");
            IPEndPoint ipEnd = new IPEndPoint(ipAdr, 8888);
            listen.Bind(ipEnd);

            listen.Listen(0);

            Console.WriteLine("服务器启动 !!! ");
            listen.BeginAccept(AcceptCallBack, listen);

            Console.ReadKey();
        }

        //接收回调
        static void AcceptCallBack(IAsyncResult ar)
        {
            try
            {
                //分配state 加入clients列表
                Console.WriteLine("Accepted");
                Socket listen = (Socket)ar.AsyncState;
                Socket client = listen.EndAccept(ar);
                ClientState state = new ClientState
                {
                    socket = client
                };
                clients.Add(client, state);
                client.BeginReceive(state.readBuff, 0, 1024, 0, ReceiveCallback, state);
            }
            catch (SocketException ex)
            {
                Console.WriteLine(ex);
            }

        }

        static void ReceiveCallback(IAsyncResult ar)
        {
            try
            {
                ClientState state = (ClientState)ar.AsyncState;
                Socket client = state.socket;
                int count = client.EndReceive(ar);
                if (count == 0)
                {
                    //暂时 为0就断开
                    client.Close();
                    clients.Remove(client);
                    Console.WriteLine("socket close");
                    return;
                }

                string recvStr = System.Text.Encoding.Default.GetString(state.readBuff, 0, count);
                Console.WriteLine("recv : " + recvStr);

                byte[] sendBytes = System.Text.Encoding.Default.GetBytes("echo" + recvStr + recvStr + recvStr + recvStr + recvStr);
                client.Send(sendBytes);
                client.BeginReceive(state.readBuff, 0, 1024, 0, ReceiveCallback, state);
            }
            catch (SocketException ex)
            {
                Console.WriteLine("Socket Receive Fail " + ex.ToString());
            }
        }
    }

    class ClientState
    {
        public Socket socket;
        public byte[] readBuff = new byte[1024];
    }
}

Unity 客户端:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Sockets;
using UnityEngine;
using UnityEngine.UI;

public class MyTestClient : MonoBehaviour
{
    public InputField inputF;
    public Text text;
    Socket socket;

    byte[] readBuff = new byte[1024];
    string recvStr = "";
    List msgs = new List();
    private void Update()
    {
        if (msgs.Count > 0)
        {
            for(int i = 0; i < msgs.Count; i++)
            {
                text.text += msgs[i] + "\r\n";
            }
            msgs.Clear();
        }
    }
    public void Connection()
    {
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        socket.BeginConnect("192.168.1.115", 8888, ConnectCallback, socket);
    }

    void ConnectCallback(IAsyncResult ar)
    {
        Debug.Log("ConnectCallback");
        try
        {
            Socket socket = (Socket)ar.AsyncState;
            socket.EndConnect(ar);
            Debug.Log("Socket Connect Success ... ");
            
            socket.BeginReceive(readBuff, 0, 1024, SocketFlags.None, ReceiveCallBack, socket);
        }
        catch (SocketException ex)
        {
            Debug.LogError(ex);
        }
    }

    void ReceiveCallBack(IAsyncResult ar)
    {
        try
        {
            Socket socket = (Socket)ar.AsyncState;
            int count = socket.EndReceive(ar);

            recvStr = System.Text.Encoding.Default.GetString(readBuff, 0, count);

            msgs.Add(recvStr);

            Debug.Log("recvStr: " + msgs);

            socket.BeginReceive(readBuff, 0, 1024, SocketFlags.None, ReceiveCallBack, socket);
        }
        catch (SocketException ex)
        {
            Debug.LogError(ex);
        }
    }

    public void Send()
    {
        Debug.Log("Send : " + inputF.text);
        byte[] b = System.Text.Encoding.Default.GetBytes(inputF.text);
        socket.Send(b);
    }
}

最最最最最基本的应用,写的也很鄙陋~ 做个练习就好啦。

实现的功能就是给服务器发个啥,服务器给你回“echo”然后把你发的东西回5遍~

GIT地址:https://github.com/PatrickBoomBoom/SocketTest.git

 

===============================================================================================

分包粘包处理

咳咳... 回来了,今天练习一下分包粘包处理 ~ ps:大佬有更好的方法还请斧正~

先跑一下之前的练习项目~ 

1. 启动C# 服务器~

2. 启动unity客户端~

Socket/TCP练习 脑图_第1张图片

连接服务器!

Socket/TCP练习 脑图_第2张图片

服务器收到连接请求,到这tcp就算连上了 ~ 

3. 给服务器发送一个“a”

服务器收到了“a”

Socket/TCP练习 脑图_第3张图片

并给你回了5个“a” ~

Socket/TCP练习 脑图_第4张图片

好了,到这运行良好,可是如果会遇到下面这种情况:

你给服务器发了个“asokl;awjiovjnasl;903t7908asydfioh2nasoidfnaso;dfjizlxdkfj;9 ... ...  ”非常长的一个字符串,

服务器以五倍返回给你的时候,鸭!超了容量了,那么socket会把这东西给你分成两个或者多个包返回给你,就像这样:

Socket/TCP练习 脑图_第5张图片

我们现在只是把byte[]反序列化成 字符串 放到ui上,不会有什么问题,

如果实际工作中传输的是一个自定义的数据结构,当只有一条消息回来时,我们拿着一半的byte[]去反序列化那肯定就会出问题了哇~ 这就是分包问题。

粘包问题 正好反过来,是短时间内两个流 都进入Socket的sendbuff要发送,Socket就把它们当成一条消息一起发出去了。我们接收时要把它们拆出来分别反序列化才可以。

解: 加个头,告诉接收方,你发的这个东西一共多长。搞定~ (因为使用的是tcp所以接受一定是按顺序的,如果是UDP的话,还要加上本次的消息是从哪儿到哪儿的,这样接收方才能拼接出正确完整的 流)

举栗子:服务器加头,客户端来解析(反过来也一样的道理,就不做两遍了)

下面我们来改一下服务器给客户端发送消息处的代码:

Socket/TCP练习 脑图_第6张图片

之前的sendbytes现在是消息主体;

头部我用的int32转成的byte[] ,支持长度为2,147,483,647的消息主体,发啥玩楞都够用了~ 客户端取前4byte用来读长度。

也有用int16转成的byte[],支持长度为32767,其实一般也差不多够用了~ 客户端取前2byte来读长度。

 

下面我们来改一下客户端在接收的时候的处理:

第一种方法可以是直接扩展buff的容量,对方给你发1500长度的byte[],那你就用一个2048的长度的buff去接收,简单粗暴~

第二种方法是循环的接收,这条消息没接完,下条消息继续接(适用于比较少的特殊情况下使用,如果一直发特长消息,直接扩容比较好):

这地方逻辑比较恶心心~ 做了个脑图(如有错误大佬勿喷)

Socket/TCP练习 脑图_第7张图片

代码 先不贴了。。。 回头上传到git 。。。

先这样,待续未完 ....

 

================================================================================================

回头看看 之前的流程是没啥问题 但是实现起来 就很蠢 代码真的不够优美,因为处理在太多地方都处理过了,代码可读性也很差。

那么那么为什么不把队列引入进来,每次消息中所有的完整的包压进队列,然后每次消息读完之后把队列中所有的包解析了。

ok: 那么新鲜的流程图就出来了~

Socket/TCP练习 脑图_第8张图片

唉~ 舒服多了~ 嘻嘻

你可能感兴趣的:(自我学习路程)