Unity网络聊天室
起因是计网、网络编程、游戏开发三门课作业重咯。于是打算随便学学
简单总结
三天时间踩了不少坑,借助搜索引擎与人工智能,还是把这个小demo完成了
多路复用
核心代码
//填充checkRead列表
checkRead.Clear();
checkRead.Add(server);
foreach (ClientState cs in clients.Values)
{
checkRead.Add(cs.socket);
}
//Select,多路复用,同时检测多个Socket的状态
Socket.Select(checkRead, null, null, 1000);
核心思路:把以连接的Client都放进checkRead列表里,使用Socket.Select筛选出在一定时间内接收到数据的Socket,再进行处理。
解析协议
public static byte[] Pack(int type, byte[] msg)
{
byte[] container = new byte[1024];
byte[] msgByte = BitConverter.GetBytes(type);
Buffer.BlockCopy(msgByte, 0, container, 0, 4);
byte[] msgLength = BitConverter.GetBytes(msg.Length);
Buffer.BlockCopy(msgLength, 0, container, 4, 4);
Buffer.BlockCopy(msg, 0, container, 8, msg.Length);
container = container.Take(4 + 4 + msg.Length).ToArray();
return container;
}
public static Data UnPack(byte[] msg, Socket Remote)
{
int type = BitConverter.ToInt32(msg, 0);
int msgLength = BitConverter.ToInt32(msg, 4);
byte[] msgBody = msg.Skip(8).Take(msgLength).ToArray();
Console.WriteLine("msgBody.Length:" + msgBody.Length);
Console.WriteLine("msgLength:" + msgLength);
return new Data(type, msgLength, msgBody);
}
客户端服务器按照一定的格式进行打包与解包,(这只是一个最简单的案例,不过简单的框架出来了再往里加也就简单了
小坑
哎呀懒得写了,润去Java安全了,估计过段时间心血来潮再来学一波叭,溜咯。一下哎代码
Server端代码
NetProtocol.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
namespace Server
{
class Data
{
public int type;
public int length;
public byte[] msg;
public Data(int type,int length, byte[] msg)
{
this.type = type;
this.length = length;
this.msg = msg;
}
}
class NetProtocol
{
public static byte[] Pack(int type, byte[] msg)
{
byte[] container = new byte[1024];
byte[] msgByte = BitConverter.GetBytes(type);
Buffer.BlockCopy(msgByte, 0, container, 0, 4);
byte[] msgLength = BitConverter.GetBytes(msg.Length);
Buffer.BlockCopy(msgLength, 0, container, 4, 4);
Buffer.BlockCopy(msg, 0, container, 8, msg.Length);
container = container.Take(4 + 4 + msg.Length).ToArray();
return container;
}
public static Data UnPack(byte[] msg, Socket Remote)
{
int type = BitConverter.ToInt32(msg, 0);
int msgLength = BitConverter.ToInt32(msg, 4);
byte[] msgBody = msg.Skip(8).Take(msgLength).ToArray();
Console.WriteLine("msgBody.Length:" + msgBody.Length);
Console.WriteLine("msgLength:" + msgLength);
return new Data(type, msgLength, msgBody);
}
}
}
program.cs
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Text;
namespace Server
{
class ClientState
{
public Socket socket;
public byte[] recvBuffer = new byte[1024];
public string recvStr = "";
}
class Program
{
static Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
static List<Socket> checkRead = new List<Socket>();
static void Main(string[] args)
{
StartServer();
}
public static void StartServer()
{
server.Bind(new IPEndPoint(IPAddress.Any, 8888));
server.Listen(20);
Console.WriteLine("[服务器启动]");
while (true)
{
//填充checkRead列表
checkRead.Clear();
checkRead.Add(server);
foreach (ClientState cs in clients.Values)
{
checkRead.Add(cs.socket);
}
//Select,多路复用,同时检测多个Socket的状态
Socket.Select(checkRead, null, null, 1000);
//检查可读对象
foreach (Socket s in checkRead)
{
if (s == server)
{
ReadListenfd(s);
}
else
{
ReadClientfd(s);
}
}
}
}
/// <summary>
/// 读取listenfd,开始Accept
/// </summary>
/// <param name="listenfd"></param>
public static void ReadListenfd(Socket listenfd)
{
Console.WriteLine("Accept");
Socket clientfd = listenfd.Accept();
ClientState clientState = new ClientState();
clientState.socket = clientfd;
clients.Add(clientfd, clientState);
}
public static bool ReadClientfd(Socket clientfd)
{
//Console.WriteLine("Read");
ClientState clientState = clients[clientfd];
//接收
int count = 0;
try
{
count = clientfd.Receive(clientState.recvBuffer);
}
catch (SocketException ex)
{
clientfd.Close();
Console.WriteLine("Receive SocketException " + ex.ToString());
return false;
}
//客户端关闭
if (count == 0)
{
clientfd.Close();
clients.Remove(clientfd);
Console.WriteLine("Socket Colse");
return false;
}
//Console.WriteLine("Copy");
byte[] receivedData = new byte[count];
Buffer.BlockCopy(clientState.recvBuffer, 0, receivedData, 0, count);
// 解析协议
Data data=NetProtocol.UnPack(receivedData, clientfd);
//Console.WriteLine( data.type);
if(data.type==1)
{
//Console.WriteLine(data.length);
Console.WriteLine(Encoding.UTF8.GetString(data.msg,0,data.length));
}
else if(data.type==2)
{
Console.WriteLine(Encoding.UTF8.GetString(data.msg, 0, data.length));
byte[] msg = NetProtocol.Pack(2, data.msg);
foreach(ClientState cs in clients.Values)
{
cs.socket.Send(msg);
}
//clientfd.Send(msg);
}
return true;
}
}
}
Client端代码
NetProtocol.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
namespace Server
{
class Data
{
public int type;
public int length;
public byte[] msg;
public Data(int type,int length, byte[] msg)
{
this.type = type;
this.length = length;
this.msg = msg;
}
}
class NetProtocol
{
public static byte[] Pack(int type, byte[] msg)
{
byte[] container = new byte[1024];
byte[] msgByte = BitConverter.GetBytes(type);
Buffer.BlockCopy(msgByte, 0, container, 0, 4);
byte[] msgLength = BitConverter.GetBytes(msg.Length);
Buffer.BlockCopy(msgLength, 0, container, 4, 4);
Buffer.BlockCopy(msg, 0, container, 8, msg.Length);
container = container.Take(4 + 4 + msg.Length).ToArray();
return container;
}
public static Data UnPack(byte[] msg, Socket Remote)
{
int type = BitConverter.ToInt32(msg, 0);
int msgLength = BitConverter.ToInt32(msg, 4);
byte[] msgBody = msg.Skip(8).Take(msgLength).ToArray();
Console.WriteLine("msgBody.Length:" + msgBody.Length);
Console.WriteLine("msgLength:" + msgLength);
return new Data(type, msgLength, msgBody);
}
}
}
Login.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
using UnityEngine;
using TMPro;
using network;
using System;
public class Login : MonoBehaviour
{
public TMP_Text text;
public void login()
{
Client.ClientName = text.text;
SceneManager.LoadScene("chat");
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
Client.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Net.Sockets;
using System.Text;
using System;
namespace network
{
public class Client
{
public static String ClientName;
public static Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
public static byte[] buffer = new byte[1024];
// Start is called before the first frame update
/// <summary>
/// 点击连接按钮
/// </summary>
public static void Connection()
{
try
{
if(string.IsNullOrEmpty(ClientName))
{
ClientName = "client" + new System.Random().Next().ToString();
}
socket.Connect("127.0.0.1", 8888);
}
catch (Exception ex)
{
Console.WriteLine("Connection failed: " + ex.Message);
}
}
public static void Send(int type,string s)
{
byte[] msg = NetProtocol.Pack(type, Encoding.UTF8.GetBytes(s));
socket.Send(msg);
}
}
}
Chat.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using network;
using System;
using System.Text;
using System.Net.Sockets;
public class Chat : MonoBehaviour
{
public GameObject ListItemPrefeb;
public GameObject Content;
ScrollRect scrollRect ;
public TMP_Text test;
public TMP_Text sendText;
public TMP_InputField inputField;
public Queue<Data> datas = new Queue<Data>();
public void chat()
{
try
{
//if(String.IsNullOrEmpty(sendText.text))
//{
// return;
//}
string s = inputField.text;
Debug.Log("s.Length:" + s.Length);
Debug.Log("Encoding.UTF8.GetBytes(s):" + Encoding.UTF8.GetBytes(s).Length);
//Client.Send(s.Substring(0,s.Length-1));
Client.Send(2,"["+Client.ClientName+"]: "+s);
}
catch (Exception ex)
{
test.text = "Send Failed"+ex.Message;
}
}
// Start is called before the first frame update
void Start()
{
scrollRect = Content.GetComponent<ScrollRect>();
try
{
Client.Connection();
ReceiveMessageFromServer();
test.text = "Connect : success";
}
catch(Exception ex)
{
test.text = "Connect : Fail\n" + ex.Message;
}
}
// Update is called once per frame
void Update()
{
if(datas.Count!=0)
{
Data data = datas.Dequeue();
HandleMessage(data);
}
}
public void ReceiveMessageFromServer()
{
//Debug.Log("开始接收数据");
Array.Clear(Client.buffer,0,Client.buffer.Length);
Client.socket.BeginReceive(Client.buffer, 0, Client.buffer.Length, SocketFlags.None, ReceiveMessageCallback, "");
}
public void ReceiveMessageCallback(IAsyncResult ar)
{
//Debug.Log("接收结束!!");
//结束接收
int length = Client.socket.EndReceive(ar);
Debug.Log("接收的长度是:" + length);
byte[] msg = Client.buffer;
Data data = NetProtocol.UnPack(msg,Client.socket);
datas.Enqueue(data);
Debug.Log("服务器发过来的消息是:" + msg);
//开启下一次消息的接收
ReceiveMessageFromServer();
}
public void HandleMessage(Data data)
{
if(data.type == 2)
{
addListItem(Encoding.UTF8.GetString(data.msg));
}
}
public void addListItem(string s)
{
Debug.Log("addListItem");
GameObject newItem = Instantiate(ListItemPrefeb, new Vector3(0, 0, 0), Quaternion.identity);
newItem.transform.SetParent(Content.transform);
newItem.transform.GetComponent<TMP_Text>().text = s;
Debug.Log("addListItem over");
}
}