|
最近研究了一下Unity的网络框架,踩了很多的坑,所以想在这里写篇文章作为总结,文章只针对实现,不讲过多的原理,后续会陆续更新完整的服务器框架。
要想实现服务器框架,首先就是要使用Socket进行通信,Socket通信大致逻辑:服务端暴露端口号和IP地址,客户端通过访问这个IP地址,进行数据传输,传输的数据为二进制数据。接下来为大家讲讲怎么实现。文章最后放出源码。
需要工具:unity、vs2019
1、首先在服务器端创建服务端脚本Server,我这直接用本地:
大致内容:暴露服务器ip地址、端口号、最大连接数,等待客户端连接后发送给客户端信息,接收客户端发来的信息。
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace Server
{
class Program
{
private static string ipAdress = "127.0.0.1"; //ip地址
private static int port = 5000; //端口号
private static int maxConnect = 20; //最大连接数
private static Socket _serverSocket;
private static Socket _clientSocket;
static void Main(string[] args)
{
//1、创建一个服务端socket对象
_serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2、绑定一个ip和端口
IPAddress ipaddress = IPAddress.Parse(ipAdress);//将一个字符串类型的ip地址,转换成一个IPAdress对象
EndPoint endPoint = new IPEndPoint(ipaddress, port);
_serverSocket.Bind(endPoint);//向操作系统申请一个可用的ip和端口号来通讯
//3、开始监听客户端的连接请求
_serverSocket.Listen(maxConnect);//设置最大连接数
Console.WriteLine(string.Format("开启服务端{0}...", _serverSocket.LocalEndPoint));
Socket clientSocket = _serverSocket.Accept();//阻塞当前线程,直到有一个客户端连接服务器
Console.WriteLine("接受到客户端的连接请求!");
//4、发送、接收消息
string message = "hello client";
byte[] data = Encoding.UTF8.GetBytes(message);//将字符串转成字节数组
_clientSocket.Send(data);
Console.WriteLine("服务器向客户端发送了一条消息:" + message);
//5、接收客户端发来的信息
byte[] data2 = new byte[1024];//存放消息的字节数容器 1M大小
int length = _clientSocket.Receive(data2);
string messgae2 = Encoding.UTF8.GetString(data2, 0, length);//bytes转换成string
Console.WriteLine("服务器接受到客户端发来的信息" + messgae2);
}
}
}
2、创建Unity项目,并添加Client脚本。
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;
public class Client : MonoBehaviour
{
private Socket tcpClient;
private string serverIP = "127.0.0.1";//服务器ip地址
private int serverPort = 5000;//端口号
void Start()
{
//1、创建socket
tcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2、建立一个连接请求
IPAddress iPAddress = IPAddress.Parse(serverIP);
EndPoint endPoint = new IPEndPoint(iPAddress, serverPort);
tcpClient.Connect(endPoint);
Debug.Log("请求服务器连接");
//3、接受、发送消息
byte[] data = new byte[1024];
int length = tcpClient.Receive(data);
var message = Encoding.UTF8.GetString(data, 0, length);
Debug.Log("客户端收到来自服务器发来的信息" + message);
//发送消息
string message2 = "Client Say To Hello";
tcpClient.Send(Encoding.UTF8.GetBytes(message2));
Debug.Log("客户端向服务器发送消息" + message2);
}
}
3、开启服务器
4、开启客户端
将写好的脚本挂载在场景中
5、效果
服务端效果
客户端效果
最基本的通信已经实现,现在来开始重构一下代码。
服务器端:
将Client封装成一个类,要有初始化、开始接收、接收回调等方法
将Server封装成一个类,要有启动服务器,关闭服务器、接收信息等方法,并且还要有管理Client的方法。
客户端:
将Client封装成一个类,包括初始化、开始接收、接收回调等方法。
接下来实现:
服务器端:Server类
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace Server
{
class Server
{
private Socket _serverSocket;
private int _prot;
private int _maxClient;
private string ipAdress;
public List<Client> ClientList = new List<Client>();//客户端列表
public Server(string ipAdress, int prot, int maxClient)
{
this.ipAdress = ipAdress;
this._prot = prot;
this._maxClient = maxClient;
Start();//启动
}
//启动服务器
public void Start()
{
try
{
//1、创建一个socket对象
_serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2、绑定一个ip和端口
IPAddress ipaddress = IPAddress.Parse(ipAdress);//将一个字符串类型的ip地址,转换成一个IPAdress对象
EndPoint endPoint = new IPEndPoint(ipaddress, _prot);
_serverSocket.Bind(endPoint);//向操作系统申请一个可用的ip和端口号来通讯
_serverSocket.Listen(_maxClient);//设置最大连接数
Console.WriteLine(string.Format(&#34;开启服务端{0}...&#34;, _serverSocket.LocalEndPoint));
StartAccept();
}
catch (Exception)//出现异常则关闭服务器
{
//Close();
throw;
}
}
//开始接收信息
private void StartAccept()
{
OnAccept();
}
//接收信息中
private void OnAccept()
{
while (true)
{
Socket clientSocket = _serverSocket.Accept();
Console.WriteLine(string.Format(&#34;客户端 {0} 连接&#34;, clientSocket.RemoteEndPoint));
Client client = new Client(clientSocket);
client.OnSend(Encoding.Default.GetBytes(&#34;登录成功&#34;));
}
}
//关闭所有服务器
public void Close()
{
foreach (var client in ClientList)
{
client.Close();
}
_serverSocket.Close();
}
}
}
Client类:
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace Server
{
class Client
{
private Thread _recvThread;
public Socket ClientSocket;
public Client(Socket socket)
{
ClientSocket = socket;
StartRecv();
}
//开始接收
private void StartRecv()
{
_recvThread = new Thread(OnRecv);
_recvThread.IsBackground = true;
_recvThread.Start();
}
//接收中
private void OnRecv()
{
try
{
while (true)
{
byte[] buff = new byte[1024];
ClientSocket.Receive(buff);
Console.WriteLine(Encoding.Default.GetString(buff));
}
}
catch (Exception)
{
Close();
throw;
}
}
//发送信息
public void OnSend(byte[] data)
{
ClientSocket.Send(data);
}
//关闭
public void Close()
{
_recvThread.Abort();
ClientSocket.Shutdown(SocketShutdown.Both);
ClientSocket.Close();
}
}
}
Program:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace Server
{
class Program
{
private static Server server;
private static void Start()
{
server = new Server(&#34;127.0.0.1&#34;, 5000, 20);
}
static void Main(string[] args)
{
Start();//开启服务器
}
}
}
客户端 SocketClient类:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
public class ClientSocket
{
private Socket _clientSocket;
private Thread _recvThread;
private string _rIP; //ip地址
private int _rProt; //端口
public ClientSocket(string _rIP, int _rProt)
{
this._rIP = _rIP;
this._rProt = _rProt;
Connect();
}
//连接
private void Connect()
{
try
{
_clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_clientSocket.Connect(new IPEndPoint(IPAddress.Parse(_rIP), _rProt));
OnSend(Encoding.Default.GetBytes(&#34;hello,server&#34;));
StartRecv();
}
catch
{
throw;
}
}
//开始接收消息
private void StartRecv()
{
_recvThread = new Thread(OnRecv);
_recvThread.IsBackground = true;
Debug.Log(&#34;开始接收消息&#34;);
_recvThread.Start();
}
//接收中
private void OnRecv()
{
while (true)
{
byte[] buff = new byte[256];
_clientSocket.Receive(buff);
Debug.Log(Encoding.Default.GetString(buff));
}
}
//发送信息
public void OnSend(byte[] data)
{
try
{
_clientSocket.Send(data);
}
catch (Exception)
{
Close();
throw;
}
}
//关闭
public void Close()
{
_recvThread.Abort();
_clientSocket.Shutdown(SocketShutdown.Both);
_clientSocket.Close();
}
}
调用:
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;
public class Client : MonoBehaviour
{
private Socket tcpClient;
private string serverIP = &#34;127.0.0.1&#34;;//服务器ip地址
private int serverPort = 5000;//端口号
private ClientSocket c1;
void Start()
{
c1 = new ClientSocket(serverIP, serverPort);
}
}
效果:
开启服务端
开启客户端
服务端响应
注:目前客户端若是强制退出,则会导致服务端异常。
看到这里已经能实现基本的通信了,不过存在一个问题,服务器端和客户端是通过二进制数据进行通信的,而不能够传递具体的方法,要想实现这个问题,就要使用到通信协议,也就是两边设置一样的字段,接收到的二进制信息转换成字段,就调用相应字段的方法,这种方法我会在下篇文章给出。
源码链接:https://pan.baidu.com/s/1lU3f_5aYHnt1h5AjuBitlQ
提取码:e1cm |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|