Unity网络开发--套接字Socket(1)
Socket的重要API
Socket 套接字的作用
它是 C# 提供给我们用于网络通信的一个类 (在其它语言当中也有对应的 Socket 类)
类名: Socket
命名空间: System.Net.Sockets
Socket 套接字是支持 TCP/IP 网络通信的基本操作单位
一个套接字对象包含以下关键信息
-
本机的 IP 地址和端口
-
对方主机的 IP 地址和端口
-
双方通信的协议信息
一个 Socket 对象表示一个本地或者远程套接字信息
它可以被视为一个数据通道
这个通道连接与客户端和服务端之间
数据的发送和接受均通过这个通道进行
一般在制作长连接游戏时,我们会使用 Socket 套接字作为我们的通信方案
我们通过它连接客户端和服务端,通过它来收发消息
你可以把它抽象的想象成一根管子,插在客户端和服务端应用程序上,通过这个管子来传递交换信息
Socket 的类型
Socket 套接字有 3 种不同的类型
-
流套接字 主要用于实现 TCP 通信,提供了面向连接、可靠的、有序的、数据无差错且无重复的数据传输服务
-
数据报套接字 主要用于实现 UDP 通信,提供了无连接的通信服务,数据包的长度不能大于 32KB,不提供正确性检查,不保证顺序,可能出现重发、丢失等情况
-
原始套接字 主要用于实现 IP 数据包通信,用于直接访问协议的较低层,常用于侦听和分析数据包
通过 Socket 的构造函数 我们可以申明不同类型的套接字
参数一:AddressFamily 网络寻址 枚举类型,决定寻址方案
常用:
1.InterNetwork IPv4 寻址
2.InterNetwork6 IPv6 寻址
参数二: SocketType 套接字枚举类型,决定使用的套接字类型
常用:
1.Dgram 支持数据报,最大长度固定的无连接、不可靠的消息 (主要用于 UDP 通信)
2.Stream 支持可靠、双向、基于连接的字节流 (主要用于 TCP 通信)
参数三: ProtocolType 协议类型枚举类型,决定套接字使用的通信协议
常用:
1.TCP TCP 传输控制协议
2.UDP UDP 用户数据报协议
//TCP流套接字
Socket socketTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream,ProtocolType.Tcp);
//UDP数据报套接字
Socket sockrtUdp = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//Socket的常用属性//套接字连接状态
if (socketTcp.Connected) { }
//获取套接字类型
print(socketTcp.SocketType);
//获取套接字的协议方案
print(socketTcp.ProtocolType);
//获取套接字的寻址方案
print(socketTcp.AddressFamily);//从网络中获取准备读取的数据数据量
print(socketTcp.Available);//获取本机EndPoint对象(注意IPEndPoint继承EndPoint)
//socketTcp.LocalEndPoint as IPEndPoint//获取远程EndPoint对象
//socketTcp.RemoteEndPoint as IPEndPoint#region 知识点四 Socket 的常用方法
//1. 主要用于服务端
// 1-1: 绑定 IP 和端口
IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
socketTcp.Bind(ipPoint);
// 1-2: 设置客户端连接的最大数量
socketTcp.Listen(10);
// 1-3: 等待客户端连入
socketTcp.Accept();//2. 主要用于客户端
// 1-1: 连接远程服务端
socketTcp.Connect(IPAddress.Parse("122.12.2.3"),8080);//3. 客户端服务端都会用的
// 1-1: 同步发送和接收数据
//socketTcp.Send();
//socketTcp.SendTo();
//socketTcp.Receive();
// 1-2: 异步发送和接收数据
// 1-3: 释放连接并关闭 Socket,先与 Close 调用
socketTcp.Shutdown(SocketShutdown.Both);
// 1-4: 关闭连接,释放所有 Socket 关联资源
socketTcp.Close();
#endregion
TCP通信
同步
服务端
using System.Net;
using System.Net.Sockets;
using System.Text;namespace Program
{internal class Program{static void Main(string[] args){#region 实现服务端基本逻辑//1.创建套接字Socket(TCP)Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//2.使用Bind方法将套接字与本地地址绑定try{IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);socket.Bind(ipPoint);}catch (Exception ex) {Console.WriteLine("绑定报错:"+ex.Message);return;}//3.用Listen方法监听socket.Listen(1024);Console.WriteLine("服务端绑定监听结束,等待客户端连入");//4.用Accept方法等待客户端连接//5.建立连接,Accept返回新套接字Socket socketClient = socket.Accept();//6.用Send和Receive相关方法收发数据//发送socketClient.Send(Encoding.UTF8.GetBytes("欢迎连入服务端"));//接受byte[] bytes = new byte[1024];int receiveNum = socketClient.Receive(bytes); //receiveNum表示接受的长度Console.WriteLine("接受到{0}发来的消息:{1}",socketClient.RemoteEndPoint.ToString(),Encoding.UTF8.GetString(bytes,0,receiveNum));//7.用shutdown方法释放连接socketClient.Shutdown(SocketShutdown.Both);//8.关闭套接字socketClient.Close();#endregionConsole.WriteLine("输入任意键退出");Console.ReadKey();}}
}
客户端
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;public class lesson4 : MonoBehaviour
{// Start is called before the first frame updatevoid Start(){//1.创建套接字SocketSocket socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);//2.用Connect方法与服务端相连//确定服务端的IP和端口IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);try{socket.Connect(iPEndPoint);}catch (SocketException e){if (e.ErrorCode == 10061)print("服务器拒绝连接");elseprint("连接服务器失败" + e.ErrorCode);}//3.用Send和Receive相关方法收发数据//接受byte[] bytes = new byte[1024];int receiveNum = socket.Receive(bytes);print("收到服务端发来的消息:" + Encoding.UTF8.GetString(bytes,0,receiveNum));//发送socket.Send(Encoding.UTF8.GetBytes("Hello,我是01客户端"));//4.用ShutDown方法释放连接socket.Shutdown(SocketShutdown.Both);//5.关闭套接字socket.Close();
}
}
添加面向对象的思想和线程后
服务端
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Reflection.Emit;
using System.Text;
using System.Threading;
using System.Threading.Tasks;namespace ConsoleApp2
{internal class ClientSocket{private static int CLIENT_BEGIN_ID = 1;public int clientID;public Socket socket;public bool Connected => this.socket.Connected;public ClientSocket(Socket socket){this.clientID = CLIENT_BEGIN_ID;this.socket = socket;CLIENT_BEGIN_ID++;}//关闭public void Close(){if (socket != null){socket.Shutdown(SocketShutdown.Both);socket.Close();}}//发送public void Send(string info){if (socket != null){try {socket.Send(Encoding.UTF8.GetBytes(info));}catch (Exception e) {Console.WriteLine(e.Message);Close();}}}//接受public void Receive(){byte[] bytes = new byte[1024 * 5];try{if (socket != null && socket.Available > 0){int receiveNum = socket.Receive(bytes);ThreadPool.QueueUserWorkItem(MsgHandle,Encoding.UTF8.GetString(bytes,0,receiveNum));}} catch (Exception e){Console.WriteLine(e.Message);Close();}}private void MsgHandle(Object obj){string str = obj as string;Console.WriteLine("收到客户端{0}的消息:{1}",this.socket.RemoteEndPoint.ToString(),str);}}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;namespace ConsoleApp2
{internal class ServerSocket{public Socket socket;public Dictionary<int, ClientSocket> clientDic = new Dictionary<int, ClientSocket>();public void Start(string ip,int port,int num){socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);socket.Bind(iPEndPoint);socket.Listen(num);ThreadPool.QueueUserWorkItem(Accept);ThreadPool.QueueUserWorkItem(Receive);}public void Close(){foreach (ClientSocket item in clientDic.Values){item.Close();}socket.Shutdown(SocketShutdown.Both);socket.Close();}private void Accept(object obj){while (true){try{Socket clientSocket = socket.Accept();ClientSocket client = new ClientSocket(clientSocket);clientDic.Add(client.clientID,client);}catch (Exception e){Console.WriteLine(e.Message);}}}private void Receive(object obj){while (true){if (clientDic.Count > 0){foreach (ClientSocket client in clientDic.Values){client.Receive();}}}}public void Broadcaet(string str){foreach (ClientSocket client in clientDic.Values){client.Send(str);}}}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace ConsoleApp2
{internal class Program{static void Main(string[] args){ServerSocket serverSocket = new ServerSocket();serverSocket.Start("127.0.0.1", 8080, 1024);Console.WriteLine("服务器开启成功");while (true){string input = Console.ReadLine();if (input == "Quit"){serverSocket.Close();}if (input.Substring(0, 2) == "B:"){serverSocket.Broadcaet(input.Substring(2));}}}}
}
客户端
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
using UnityEngine.SocialPlatforms;public class lesson4_1 : MonoBehaviour
{private static lesson4_1 instance;public static lesson4_1 Instance => instance;private Socket socket;private Queue<string> sendMsgQueue = new Queue<string>();private Queue<string> receiveMsgQueue = new Queue<string>();//private Thread sendThread;// private Thread receiveThread;private byte[] reveiveBytes = new byte[1024 * 1024];private int reveiveNum;private bool isClose = false;private void Awake(){instance = this;}// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){if (receiveMsgQueue.Count > 0){print(receiveMsgQueue.Dequeue());}}public void Connect(string ip, int port){socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(ip),port);try{socket.Connect(iPEndPoint);ThreadPool.QueueUserWorkItem(SendMsg);ThreadPool.QueueUserWorkItem(ReceiveMsg);//sendThread = new Thread(SendMsg);//sendThread.Start();//receiveThread = new Thread(ReceiveMsg);//receiveThread.Start();}catch (SocketException e){Console.WriteLine(e.Message);}}public void Send(string info){sendMsgQueue.Enqueue(info);}private void SendMsg(object obj){while (!isClose){if (sendMsgQueue.Count > 0){socket.Send(Encoding.UTF8.GetBytes(sendMsgQueue.Dequeue()));}}}private void ReceiveMsg(object obj){while (!isClose){if (socket.Available > 0){reveiveNum = socket.Receive(reveiveBytes);receiveMsgQueue.Enqueue(Encoding.UTF8.GetString(reveiveBytes, 0 , reveiveNum));}}}public void Close(){if (socket != null){isClose = true;socket.Shutdown(SocketShutdown.Both);socket.Close();}}private void OnDestroy(){Close();}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class main : MonoBehaviour
{// Start is called before the first frame updatevoid Start(){if (lesson4_1.Instance == null){GameObject obj = new GameObject("Net");obj.AddComponent<lesson4_1>(); }lesson4_1.Instance.Connect("127.0.0.1",8080);}// Update is called once per framevoid Update(){}
}
效果:
区分消息类型
为发送的消息添加标识,比如添加消息ID
在所有发送的消息的头部加上消息id,通过id区分是什么类型
BaseData类可查看通信前必备知识点Unity网络开发--通信前必备知识
BaseMsg消息基类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class BaseMsg : BaseData
{public override int GetBytesNum(){throw new System.NotImplementedException();}public override int Reading(byte[] bytes, int beginIndex){throw new System.NotImplementedException();}public override byte[] Writing(){throw new System.NotImplementedException();}public virtual int GetID(){return 0;}
}
PlayerData玩家数据类
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;public class PlayerData : BaseData
{public string name;public int atk;public int lev;public override int GetBytesNum(){return 4 + 4 + 4 + Encoding.UTF8.GetBytes(name).Length;}public override int Reading(byte[] bytes, int beginIndex){int index = beginIndex;name = ReadString(bytes,ref index);atk = ReadInt(bytes,ref index);lev = ReadInt(bytes,ref index);return index - beginIndex;}public override byte[] Writing(){int index = 0;byte[] bytes = new byte[GetBytesNum()];WriteString(bytes, name,ref index);WriteInt(bytes, atk, ref index);WriteInt(bytes, lev, ref index);return bytes;}
}
玩家消息类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class PlayerMsg : BaseMsg
{public int playerID;public PlayerData playerData;public override byte[] Writing(){int index = 0;byte[] bytes = new byte[GetBytesNum()];WriteInt(bytes, GetID(), ref index);WriteInt(bytes, playerID, ref index);WriteData(bytes,playerData,ref index);return bytes;}public override int Reading(byte[] bytes, int beginIndex){int index = beginIndex;playerID = ReadInt(bytes, ref index);playerData = ReadData<PlayerData>(bytes, ref index);return index - beginIndex;}public override int GetBytesNum(){return 4 + 4 + playerData.GetBytesNum();}/// <summary>/// 自定义消息id,区分不同类型消息/// </summary>/// <returns></returns>public override int GetID(){return 1;}}
客户端解析自定义类
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;public class lesson4 : MonoBehaviour
{// Start is called before the first frame updatevoid Start(){//1.创建套接字SocketSocket socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);//2.用Connect方法与服务端相连//确定服务端的IP和端口IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);try{socket.Connect(iPEndPoint);}catch (SocketException e){if (e.ErrorCode == 10061)print("服务器拒绝连接");elseprint("连接服务器失败" + e.ErrorCode);}//3.用Send和Receive相关方法收发数据//接受byte[] bytes = new byte[1024];int receiveNum = socket.Receive(bytes);int msgID = BitConverter.ToInt32(bytes, 0);switch (msgID){case 1:PlayerMsg msg = new PlayerMsg();msg.Reading(bytes,4);print(msg.playerID);print(msg.playerData.name);print(msg.playerData.atk);print(msg.playerData.lev);break;}print("收到服务端发来的消息:" + Encoding.UTF8.GetString(bytes,0,receiveNum));//发送socket.Send(Encoding.UTF8.GetBytes("Hello,我是01客户端"));//4.用ShutDown方法释放连接socket.Shutdown(SocketShutdown.Both);//5.关闭套接字socket.Close();
}// Update is called once per framevoid Update(){}
}
服务端发送自定义类
using System.Net;
using System.Net.Sockets;
using System.Text;namespace Program
{internal class Program{static void Main(string[] args){#region 实现服务端基本逻辑//1.创建套接字Socket(TCP)Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//2.使用Bind方法将套接字与本地地址绑定try{IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);socket.Bind(ipPoint);}catch (Exception ex) {Console.WriteLine("绑定报错:"+ex.Message);return;}//3.用Listen方法监听socket.Listen(1024);Console.WriteLine("服务端绑定监听结束,等待客户端连入");//4.用Accept方法等待客户端连接//5.建立连接,Accept返回新套接字Socket socketClient = socket.Accept();//6.用Send和Receive相关方法收发数据//发送PlayerMsg msg = new PlayerMsg();msg.playerID = 666;msg.playerData = new PlayerData();msg.playerData.name = "xiaohei";msg.playerData.atk = 1;msg.playerData.lev = 2;socketClient.Send(msg.Writing());//接受byte[] bytes = new byte[1024];int receiveNum = socketClient.Receive(bytes); //receiveNum表示接受的长度Console.WriteLine("接受到{0}发来的消息:{1}",socketClient.RemoteEndPoint.ToString(),Encoding.UTF8.GetString(bytes,0,receiveNum));//7.用shutdown方法释放连接socketClient.Shutdown(SocketShutdown.Both);//8.关闭套接字socketClient.Close();#endregionConsole.WriteLine("输入任意键退出");Console.ReadKey();}}
}
效果: