先看一下效果
先看Socket的实例化
Socket s_socket= new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
参数解释:
1、AddressFamily: Socket 地址簇
2、SocketType:指定Socket类的实例表示的套接字的类型
3、ProtocolType:Socket 类支持的协议
再看这个IPEndPoint地址的实例化
IPEndPoint作用是用来封装ip和端口号 以便于指定一个唯一设备
iPEndPoint = new IPEndPoint(127.0.0.1,9000);
参数解释:
1、ip地址
2、端口号
其次就是侦听的有一个客户端的一个数量限制
s_socket.Listen(10);
参数解释:
参数为0时,是不做数量限制;其次就是参数决定数量
编写服务器端
Socket里面有同步和异步之分 本次的服务器端是异步框架
编写服务器端泛型单例脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 异步框架练习1
{
/// <summary>
/// 泛型单例
/// </summary>
/// <typeparam name="T"></typeparam>
class Singleton<T>where T:class,new()
{
private static T t = default(T);
public static T Instance
{
get
{
if (t == null)
{
t = new T();
}
return t;
}
}
}
}
编写服务器端组装消息体脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 异步框架练习1
{
class PackMsg
{
//组装消息体
public static byte[] PackMsgUtil(int id,byte[] msgBody)
{
ushort msgLen = (ushort)(4 + msgBody.Length);//获得消息的长度
byte[] msgLenBuffer = BitConverter.GetBytes(msgLen);//获得消息长度的字节数组
byte[] idBuffer = BitConverter.GetBytes(id); //获得协议id的字节数组
byte[] fullMsgBuffer = new byte[0]; //记住这里是0
fullMsgBuffer = fullMsgBuffer.Concat(msgLenBuffer).ToArray(); //组装消息头
fullMsgBuffer = fullMsgBuffer.Concat(idBuffer).ToArray(); //组装消息id
fullMsgBuffer = fullMsgBuffer.Concat(msgBody).ToArray(); //组装pb消息体
return fullMsgBuffer;
}
}
}
编写服务器端消息回调处理脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//
using PB;
using Google.Protobuf;
namespace 异步网络框架1
{
public class NetHandler:Singleton<NetHandler>
{
/// <summary>
/// 注册消息号 处理消息回调
/// </summary>
public void Register()
{
}
}
}
编写服务器端管理类脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
namespace 异步框架练习1
{
//服务器类
public class Client
{
//Soket
public Socket client_socket;
///缓冲区
public byte[] receiveBuffer = new byte[1024 * 2];
//唯一ID
public string unid;
public void SendMsg(int id,byte[] msgBody)
{
byte[] msg = PackMsg.PackMsgUtil(id, msgBody);
client_socket.BeginSend(msg, 0, msg.Length,SocketFlags.None,OnSend,null);
}
private void OnSend(IAsyncResult ar)
{
int len = client_socket.EndSend(ar);
}
}
//网络管理类
class NetMgr:Singleton<NetMgr>
{
private Socket server_Socket;
public List<Client> lstClients = new List<Client>();
public void InitGameServer(string ip, int port)
{
//构造
server_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//bind
IPEndPoint iPEnd = new IPEndPoint(IPAddress.Parse(ip), port);
server_Socket.Bind(iPEnd);
//listen
server_Socket.Listen(0);
//异步接收客户端链接
server_Socket.BeginAccept(OnAccept, server_Socket);
Console.WriteLine("异步游戏服务器启动成功!");
}
private void OnAccept(IAsyncResult ar)
{
Socket socket = server_Socket.EndAccept(ar); //回调
Client client = new Client();
client.client_socket = socket;
client.unid = Guid.NewGuid().ToString();
lstClients.Add(client);
//上线提醒
Console.WriteLine(string.Format("当前玩家的数量:{0}",lstClients.Count));
server_Socket.BeginAccept(OnAccept, server_Socket);
client.client_socket.BeginReceive(client.receiveBuffer, 0,client.receiveBuffer.Length,SocketFlags.None,OnReceive,client);
}
private void OnReceive(IAsyncResult ar)
{
Client client = ar.AsyncState as Client;
try
{
int len = client.client_socket.EndReceive(ar);
if(len>0)
{
byte[] realBuffer = new byte[len];
Buffer.BlockCopy(client.receiveBuffer, 0, realBuffer, 0, len);
//粘包处理
if(realBuffer.Length>2)
{
ushort msgLen = BitConverter.ToUInt16(realBuffer, 0);
if(realBuffer.Length-2>=msgLen)
{
byte[] oneFullMsg = new byte[msgLen];
Buffer.BlockCopy(realBuffer, 2, oneFullMsg, 0,msgLen);
int id = BitConverter.ToInt32(oneFullMsg, 0);
byte[] msgBody = new byte[msgLen - 4];
//消息中心广播
MessageManager.Instance.BrocastMsg(id, msgBody, client);
}
}
}
if(len==0)
{
NotifyOffline(client);
return;
}
}
catch (Exception)
{
throw;
}
}
///下线提醒
private void NotifyOffline(Client client)
{
Console.WriteLine(string.Format("下线客户端是:{0}",client.unid));
lstClients.Remove(client);
}
//广播消息
public void BrocastToAllClients(int id,byte[] msgBody)
{
for (int i = 0; i < lstClients.Count; i++)
{
lstClients[i].SendMsg(id, msgBody);
}
}
}
}
编写服务器端消息号的管理类脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 异步框架练习1
{
/// <summary>
/// 协议id
/// </summary>
public class MsgID
{
//C To S 是服务器发送到客户端的消息号
//S To C 是客户端发送到服务器的消息号
public const int C2S_Chat = 1001;
public const int S2C_Chat = 1002;
}
}
编写服务器端消息中心脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 异步框架练习1
{
/// <summary>
/// 消息中心
/// </summary>
class MessageManager:Singleton<MessageManager>
{
public Dictionary<int, Action<byte[], Client>> dic = new Dictionary<int, Action<byte[], Client>>();
public void AddMsg(int id,Action<byte[],Client> action)
{
if(!dic.ContainsKey(id))
{
dic.Add(id,action);
}
}
public void RemoveMsg(int id)
{
if(dic.ContainsKey(id))
{
dic.Remove(id);
}
}
public void BrocastMsg(int id,byte[] msgBody,Client client)
{
if(dic.ContainsKey(id))
{
Action<byte[], Client> action = dic[id];
if(action!=null)
{
action(msgBody, client);
}
}
}
}
}
编写客户端
编写客户端单例脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
public class Singleton<T>where T:class,new()
{
private static T t = default(T);
public static T Instance
{
get
{
if(t==null)
{
t = new T();
}
return t;
}
}
}
编写客户端管理类脚本
using System.Collections.Generic;
//
using System.Net.Sockets;
using System;
/// <summary>
/// 网络管理
/// </summary>
public class NetMgr : Singleton<NetMgr>
{
/// <summary>
/// 客户端和服务器进行通信的套接字对象
/// </summary>
public Socket c_Socket;
/// <summary>
/// 进行通信的消息缓冲区
/// </summary>
public byte[] receiveBuffer = new byte[2048];
/// <summary>
/// 存储接受的消息数据
/// </summary>
public Queue<byte[]> msgQue = new Queue<byte[]>();
/// <summary>
/// 连接服务器
/// </summary>
/// <param name="ip">ip地址</param>
/// <param name="port">端口号</param>
public void ConnectGameServer(string ip, int port)
{
c_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//构造
c_Socket.BeginConnect(ip, port, OnConnect, null);//异步连接
}
private void OnConnect(IAsyncResult ar)
{
c_Socket.EndConnect(ar);
c_Socket.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None, OnReceive, null);//开始接受消息
}
private void OnReceive(IAsyncResult ar)
{
int len = c_Socket.EndReceive(ar);
if (len > 0)
{
byte[] realBuffer = new byte[len];
Buffer.BlockCopy(receiveBuffer, 0, realBuffer, 0, len);
if (realBuffer.Length > 2)
{
ushort msgLen = BitConverter.ToUInt16(realBuffer, 0);
if (realBuffer.Length - 2 >= msgLen)
{
byte[] oneFullMsg = new byte[msgLen];
Buffer.BlockCopy(realBuffer, 2, oneFullMsg, 0, msgLen);
msgQue.Enqueue(oneFullMsg);
}
}
}
c_Socket.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None, OnReceive, null);//开始接受消息
}
/// <summary>
/// 更新处理消息队列里面的消息,记住;如果不放在继承MonoBehaviour的update函数里面去调用的化,生成模型的时候,不报错,但是没有效果
/// </summary>
public void UpdateHandleMsg()
{
if (msgQue.Count > 0)
{
byte[] oneFullMsg = msgQue.Dequeue();
int id = BitConverter.ToInt32(oneFullMsg, 0);
byte[] msgBody = new byte[oneFullMsg.Length - 4];
Buffer.BlockCopy(oneFullMsg, 4, msgBody, 0, msgBody.Length);
MsgCenter.Instance.Brocast(id, msgBody);
}
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="id">消息id</param>
/// <param name="msgBody">消息携带的pb数据</param>
public void SendMsg(int id, byte[] msgBody)
{
byte[] msgBuffer = NetMsgUntil.NetMsg(id, msgBody);
c_Socket.BeginSend(msgBuffer, 0, msgBuffer.Length, SocketFlags.None, OnSend, null);
}
private void OnSend(IAsyncResult ar)
{
int len = c_Socket.EndSend(ar);
}
/// <summary>
/// 客户端断开连接
/// </summary>
public void Disconnect()
{
if (c_Socket != null && c_Socket.Connected)//客户端套接字不为nll同时,为连接状态中
{
c_Socket.Shutdown(SocketShutdown.Both);
c_Socket.Close();
}
}
}
编写客户端组装消息体脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
/// <summary>
/// 组装消息体
/// </summary>
public class NetMsgUntil
{
public static byte[] NetMsg(int id,byte[] msgBody)
{
ushort msgLen = (ushort)(msgBody.Length + 4);
byte[] msgBuffer = BitConverter.GetBytes(msgLen);
byte[] idBuffer = BitConverter.GetBytes(id);
byte[] fullBuffer = new byte[0];
fullBuffer = fullBuffer.Concat(msgBuffer).ToArray();
fullBuffer = fullBuffer.Concat(idBuffer).ToArray();
fullBuffer = fullBuffer.Concat(msgBody).ToArray();
return fullBuffer;
}
}
编写客户端处理消息脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Pb;
using Google.Protobuf;
using UnityEngine;
public class NetHandler : Singleton<NetHandler>
{
/// <summary>
/// 注册消息号 处理消息回调
/// </summary>
public void Register()
{
}
}
编写客户端的消息号脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
public class MsgID
{
public const int C2S_Chet = 1001;
public const int S2C_Chet = 1002;
}
编写客户端的消息中心脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
/// <summary>
/// 消息中心
/// </summary>
public class MsgCenter:Singleton<MsgCenter>
{
public Dictionary<int, Action<byte[]>> dic = new Dictionary<int, Action<byte[]>>();
public void AddMsg(int id,Action<byte[]> action)
{
if(!dic.ContainsKey(id))
{
dic.Add(id, action);
}
}
public void Remove(int id)
{
if(dic.ContainsKey(id))
{
dic.Remove(id);
}
}
public void Brocast(int id,byte[] msgBody)
{
if(dic.ContainsKey(id))
{
Action<byte[]> action = dic[id];
if(action!=null)
{
action(msgBody);
}
}
}
}
编写客户端的启动脚本
用来初始化连接服务器和初始化消息回调处理
(ip和端口号一定号对得上)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
NetMgr.Instance.ConnectGameServer("127.0.0.1", 9000);
NetHandler.Instance.Register();
}
// Update is called once per frame
void Update()
{
NetMgr.Instance.UpdateHandleMsg();
}
private void OnDestroy()
{
NetMgr.Instance.Disconnect();
}
}
配置需要的库和PB数据
PB文件的编写(去VSCode上搜索proto3)
然后CTRL+N新建项目 然后CTRL+S保存选择目录和保存格式(格式为protocol Buffers类型)
然后编写PB文件即可
由于是基础的聊天功能 需要的字段和数据两个即可
然后是lib库和工具的转换(把pb文件转为C#文件)
这个库和工具可以私聊我(可以讲解用法)
转换后基本就是这样
这个文件在双端(客户端和服务器端)都拖一下
双端就可以使用在pb中定义的字段了
聊天功能逻辑编写
客户端发送消息到服务器端
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using PB;
using Google.Protobuf;
public class UIMgr : MonoBehaviour
{
public Button btn_send; //发送按钮
public InputField input_tx; //输入框
public InputField input_show; //输入框
public static UIMgr instance; //静态单例
private void Awake()
{
instance = this;
}
public void Show(string str)
{
input_show.text = str;
}
// Start is called before the first frame update
void Start()
{
btn_send.onClick.AddListener(OnSend);
}
private void OnSend()
{
if(!string.IsNullOrEmpty(input_tx.text))
{
C2S_Chet c2S_Chet = new C2S_Chet();
c2S_Chet.CChet = input_tx.text;
NetMgr.Instance.SendMsg(MsgID.C2S_Chet, c2S_Chet.ToByteArray());
}
}
}
在服务器端的处理消息的回调函数中处理消息
然后再把消息广播给所有在线的客户端
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//
using PB;
using Google.Protobuf;
namespace 异步网络框架1
{
public class NetHandler:Singleton<NetHandler>
{
/// <summary>
/// 注册消息号 处理消息回调
/// </summary>
public void Register()
{
MsgCenter.Instance.AddMsg(MsgID.C2S_Chet, OnChet);
}
private void OnChet(byte[] arg1, Client arg2)
{
Console.WriteLine(Encoding.UTF8.GetString(arg1));
C2S_Chet c2S_Chet = C2S_Chet.Parser.ParseFrom(arg1);
S2C_Chet s2C_Chet = new S2C_Chet();
s2C_Chet.SChet = c2S_Chet.CChet;
NetMgr.Instance.BocastToAll(MsgID.S2C_Chet, s2C_Chet.ToByteArray());
}
}
}
再在客户端的消息处理回调函数中处理本消息号的协议
using System.Text;
using PB;
using Google.Protobuf;
using UnityEngine;
public class NetHandler : Singleton<NetHandler>
{
/// <summary>
/// 注册消息号 处理消息回调
/// </summary>
public void Register()
{
MsgCenter.Instance.AddMsg(MsgID.S2C_Chet, OnChet);
}
StringBuilder sb = new StringBuilder();
private void OnChet(byte[] obj)
{
S2C_Chet s2C_Chet = S2C_Chet.Parser.ParseFrom(obj);
Debug.Log("发送数据成功!");
sb.AppendLine(s2C_Chet.SChet);
UIMgr.instance.Show(sb.ToString());
}
}
打包exe
测试功能
先开启服务器端 在这个脚本中开启
然后再打开客户端打成的exe运行程序(通常为2个用于测试)
然后就是整体的效果了
谢谢观看--