Socket

网络游戏分为客户端与服务端,客户端与服务端之间通过网络协议进行交流,比如TCP、UDP、KCP、HTTP等

此时,每一端都被称为一个Socket 一个Socket包含网络通讯的五种必要信息:协议、本地IP、本地端口、远程IP、远程端口 Socket = 协议 + IP + 端口

TCP通讯流程

先说明一些概念

  • sequence number: 序列号
  • acknowledgement number:确认号
  • SYNchronization:同步标志
  • ACKnowlegment:确认标志
  • FINish:终止标志

  • seq 32位,用来标识从A发送到B的数据包的序号,计算机发送数据时对此进行标记。
  • ack 32位,客户端和服务器端都可以发送,ack = seq + 1。
  • 标志位 每个标志位占用1位,共有6个,分别为 URG、ACK、PSH、RST、SYN、FIN
    • ACK 确认序号有效
    • SYN 建立一个新连接
    • FIN 断开一个连接

三次握手

建立连接需要进行3次沟通

第一次握手 Client向Server发送连接请求,标志位为SYN,并带有一个随机seq假设为1000

第二次握手 Server需要干两件事情

  1. 首先向Client发送确认请求,表示收到连接,标志位为ACK,ack为1000+1=1001,此时说明Client可以向Server发送信息了
  2. 但是Server也需要向Client发送信息,所以Server一样需要一个发送一个连接请求,标志位为SYN,seq为一个随机数假设为2000

第三次握手 这次是Client确认Server的请求,标志位为ACK,ack为2000+1=2001

四次挥手

第一次挥手 Client向Server发送关闭请求,标志位为FIN,seq为随机数,假设为5000

第二次挥手 Server想Client发送确认信息,标志位为ACK,ack为5000+1=5001,此时Client向Sever的连接可以断开

第三次挥手 Server向Client发送关闭请求,标志位为FIN,seq为随机数,假设为7000

第四次挥手 Clent向Server发送确认信息,标志位为ACK,ack=7000+1=7001

基础通讯模型

客户端

新建项目NetStudy,编写如下代码,UGUI部分省略了

public class Echo : MonoBehaviour 
{  
    public InputField InputFeld;  
    public Button connect;  
    public Button send;  
  
    private Socket socket;  
  
    private void Start()  
    {  
        connect.onClick.AddListener(Connection);  
        send.onClick.AddListener(Send);  
    }  
  
    private void Connection()  
    {  
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  
        socket.Connect("127.0.0.1", 8888);  
    }  
  
    private void Send()  
    {  
        //Send  
        string sendStr = InputFeld.text;  
        byte[] sendBytes = System.Text.Encoding.Default.GetBytes(sendStr);  
        socket.Send(sendBytes);  
        //Recv  
        byte[] readBuff = new byte[1024];   
		int count = socket.Receive(readBuff);  
        string recvStr = System.Text.Encoding.Default.GetString(readBuff, 0, count);  
        Debug.Log(recvStr);  
		socket.Close();
    }  
}

new Socket ( AddressFamily , SocketType , ProtocolType )

  1. Enum: AddressFamily 地址类型
    • InterNetwork 代表使用IPV4
    • InterNetworkV6 代表使用IPV6
  2. Enum: SocketType 数据流套接字类型
  3. Enum: ProtocolType 协议类型

Socket.Connect ( Host , Port ) : void 此方法是阻塞方法,再未连接到地址之前,都会阻塞进程

  1. String: Host IP地址
  2. Int: Port 端口号

Socket.Send(Bytes) : int 此方法为阻塞方法,向服务器发送数据

  1. Byte[]: Bytes 发送的字节数据
  2. Return 返回发送数据的长度

Socket.Receive(Bytes) : int 此方法为阻塞方法

  1. Bytes[]: Bytes 用于接收数据的字节数组
  2. Return 返回接收到数据的长度

Socket.Close() 关闭连接

此时运行项目必然是报错的,因为我们压根就没有服务端程序…

服务端

新建项目NetServerStudy并编写如下代码

public class Server : MonoBehaviour  
{  
    private Socket server;  
    private IPAddress ipAdr;  
    private IPEndPoint ipEp;  
  
    private void Start()  
    {  
        server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  
        ipAdr = IPAddress.Parse("127.0.0.1");  
        ipEp = new IPEndPoint(ipAdr, 8888);  
        server.Bind(ipEp);  
        server.Listen(0);  
        //Accept  
        var connect = server.Accept();  
        Debug.Log("[服务器]Accept");  
            //Receive  
        byte[] readBuff = new byte[1024];  
        int count = connect.Receive(readBuff);  
        string readStr = System.Text.Encoding.Default.GetString(readBuff, 0, count);  
        Debug.Log("[服务器接收]" + readStr);  
  
        //Send  
        byte[] sendBytes = System.Text.Encoding.Default.GetBytes($"服务器返回数据:{readStr}");  
        connect.Send(sendBytes);  
    }  
}

IPAddress IP地址

IPEndPoint IP地址+端口

Socket.Bind ( IPEndPoint )

  1. IPEndPoint: IPEndPoint 绑定地址,IP+端口号

Socket.Listen(BackLog) 开启监听(可以接受客户端信号)

  1. Int: BackLog 最大可接受数量,0代表无限

Socket.Accept () : Socket 等待接受客户端连接,是一个阻塞方法,如果没有客户端连接,那么会一直堵塞线程

  1. Return 代表服务器访问客户端的Socket

现在可以测试一下了,首先运行服务端,会发现卡住了,很正常,因为Accept是一个阻塞方法

然后运行客户端

首先点击Connect,然后Send

客户端结果:

服务端结果: