东西都很简单,把敲过的东西记下来,加深印象和理解

前端代码:

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

public class Echo : MonoBehaviour
{
    Socket socket; // 定义一个套接字 用来收发消息

    public InputField inputField;
    public Text text;
    byte[] readBuff = new byte[1024];
    string recvStr = "";
    
    public void Connection()
    {
        // 第一个参数是地址族 InterNetwork 为 IPv4  第二个参数是 字节流套接字
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        //socket.Connect("127.0.0.1", 8888); // connect 是一个阻塞方法,程序会卡住直到服务器回应
        socket.BeginConnect("127.0.0.1", 8888, ConnectCallBack, socket); // BeginConnect 是一个异步方法不会阻塞线程
    }

    // 异步方法的回调
    private void ConnectCallBack(IAsyncResult ar)
    {
        try
        {
            Socket socket = (Socket)ar.AsyncState; // BeginConnect传入的Socket
            socket.EndConnect(ar);
            Debug.Log("Socket Connect Succ");
            socket.BeginReceive(readBuff, 0, 1024, 0, ReceiveCallBack, socket);
        }
        catch (SocketException ex)
        {
            Debug.LogError($"Socket Connect fail: {ex.ToString()}");
        }
    }

    // Receive回调
    private void ReceiveCallBack(IAsyncResult ar)
    {
        try
        {
            Socket socket = (Socket)ar.AsyncState;
            int count = socket.EndReceive(ar);
            string s = System.Text.Encoding.Default.GetString(readBuff, 0, count);
            recvStr = $"{s}\n{recvStr}";    // 显示以前的聊天信息
            socket.BeginReceive(readBuff, 0, 1024, 0, ReceiveCallBack, socket);
        }
        catch (SocketException ex)
        {
            Debug.LogError($"Socket Receive fail {ex.ToString()}");
        }
    }

    public void Send()
    {
        // send
        string sendStr = inputField.text;
        byte[] sendBytes = System.Text.Encoding.Default.GetBytes(sendStr);
        socket.BeginSend(sendBytes, 0, sendBytes.Length, 0, SendCallback, socket);
        //socket.Send(sendBytes);

        // recv
        //byte[] readBuff = new byte[1024];
        //int count = socket.Receive(readBuff);
        //string recvStr = System.Text.Encoding.Default.GetString(readBuff, 0, count);
        //text.text = recvStr;
        //socket.Close();
    }

    // 异步发送回调 此回调只代表把数据放入了发送缓存区不代表发送成功
    private void SendCallback(IAsyncResult ar)
    {
        try
        {
            Socket socket = (Socket)ar.AsyncState;
            int count = socket.EndSend(ar);
            Debug.Log($"Socket Send succ: {count}");
        }
        catch (SocketException ex)
        {
            Debug.LogError($"Socket Send fail: {ex}");
        }
    }

    // 异步回调是在其他线程里执行的,所以回调只给变量赋值由Update来赋值给UI
    private void Update()
    {
        text.text = recvStr;
    }
}

前端所有的代码都在上面,大概流程为:
一、接收消息
1.实例化socket
2.socket异步连接服务器
3.连接成功后开始异步等待接收服务器信息
4.接收消息成功后会重复的等待接收消息做到一个接收循环
二、发送消息
1.文本序列化后用BeginSend放入到发送缓存区,等待发送回调
2.发送回调成功只代表成功放入了发送缓存区不代表发送成功
三、UI显示
1.用u3d的Update方法来刷新显示UI

后端代码:

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

namespace Server
{
    class Program
    {
        static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
        static Socket listenfd; // 监听Socket

        static void Main(string[] args)
        {
            Console.WriteLine("Hello word!");

            // socket
            Socket listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // Bind
            IPAddress ipAdr = IPAddress.Parse("127.0.0.1"); // 此为送回地址 一般用于测试
            IPEndPoint ipEp = new IPEndPoint(ipAdr, 8888);
            listenfd.Bind(ipEp); // 给套接字绑定接口和端口

            // Listen
            listenfd.Listen(0); // 等待客户端链接,参数表示最多可容纳等待接收的链接数,0表示不限
            Console.WriteLine("服务器启动成功");

            // Accpet
            listenfd.BeginAccept(AcceptCallBack, listenfd);
            // 等待
            Console.ReadLine();

            //while (true)
            //{
            //    // Accept
            //    Socket connfd = listenfd.Accept(); // 应答,接收客户端链接 没有客户端链接时不会往下执行,返回一个新的socket对象用于处理客户端数据
            //    Console.WriteLine("服务器 Accept");

            //    // Receive
            //    byte[] readBuff = new byte[1024];
            //    int count = connfd.Receive(readBuff);
            //    string readStr = System.Text.Encoding.Default.GetString(readBuff, 0, count);
            //    Console.WriteLine($"服务器接收: {readStr}");

            //    string timeStr = System.DateTime.Now.ToString();

            //    // Send
            //    byte[] sendBytes = System.Text.Encoding.Default.GetBytes(timeStr);
            //    connfd.Send(sendBytes);
            //}
        }

        // 处理三件事 1.给新的连接分配ClientState并加入client字典中
        // 2.异步接收客户端数据
        // 3.再次调用BegingAccept实现循环
        private static void AcceptCallBack(IAsyncResult ar)
        {
            try
            {
                Console.WriteLine($"服务器 Accept");
                Socket listenfd = (Socket)ar.AsyncState;
                Socket clientfd = listenfd.EndAccept(ar);
                // clients 列表 
                ClientState state = new ClientState();
                state.socket = clientfd;
                clients.Add(clientfd, state);
                // 接收数据 BeginReceive
                clientfd.BeginReceive(state.readBuff, 0, 1024, 0, ReceiveCallback, state);
                // 继续Accept
                listenfd.BeginAccept(AcceptCallBack, listenfd);
            }
            catch (SocketException ex)
            {
                Console.WriteLine($"Socket Accpet fail: {ex}");
            }
        }

        // 1. 服务端收到消息后,回应客户端
        // 2. 如果收到客户端关闭连接的信号 if count == 0 断开连接(小于等于0表示断开连接,但也有特例)
        // 3. 继续调用BeginReceive接收下一个数据
        private static void ReceiveCallback(IAsyncResult ar)
        {
            try
            {
                ClientState state = (ClientState)ar.AsyncState;
                Socket clientfd = state.socket;
                int count = clientfd.EndReceive(ar);
                // 客户端关闭
                if (count == 0)
                {
                    clientfd.Close();
                    clients.Remove(clientfd);
                    Console.WriteLine($"Socket Close");
                    return;
                }

                string recvStr = System.Text.Encoding.Default.GetString(state.readBuff, 0, count);
                byte[] sendBytes = System.Text.Encoding.Default.GetBytes("echo" + recvStr);
                foreach (var client in clients.Values) // 聊天室广播给所有的客户端
                {
                    client.socket.Send(sendBytes);
                }
                //clientfd.Send(sendBytes); // 减少代码量不用异步
                clientfd.BeginReceive(state.readBuff, 0, 1024, 0, ReceiveCallback, state);
            }
            catch (SocketException ex)
            {
                Console.WriteLine($"Socket Receive fail: {ex}");
            }
        }
    }
}

// 代表一个客户端链接
class ClientState
{
    public Socket socket;
    public byte[] readBuff = new byte[1024];
}

后端代码大概流程:
1.new一个TCPSocket
2.绑定服务器IP和端口
3.socket开始监听
4.BeginAccept异步等待客户端传入
5.AcceptCallBack客户端传入,listenfd.EndAccept()方法处理socket通信
6.BeginReceive开始接收数据
7.BeginAccept继续侦听客户端连接
8.ReceiveCallback接收回调,在这里处理接收的数据并BeginReceive准备再次接收数据