前言
主要是基于TcpListener与TcpClient这两个类来完成tpc连接与通讯。为了避免发送快避免连包,做了简单的拆包操作。还有与unity的主线程来显示收到的信息。
一、服务器端
二、使用步骤
1.服务器类
代码如下(示例):
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.PlayerLoop;
using UnityEngine.UI;
public class SocketServer : MonoBehaviour
{
public TcpListener serverSocket;
static SocketServer server;
ConcurrentQueue<Action> ActionQueue = new ConcurrentQueue<Action>();
public Action<TcpClient, string> 收到消息;
public event Action<TcpClient> 连接到客户端;
public Action<TcpClient> 客户端断开连接;
public List<TcpClient> Clients = new List<TcpClient>();
public static SocketServer Server
{
get
{
if (server == null)
{
GameObject g = new GameObject("SocketServer");
server=g.AddComponent<SocketServer>();
}
return server;
}
}
public static IPAddress GetIP(bool IPv6)
{
if (IPv6 && !Socket.OSSupportsIPv6)
{
return null;
}
IPAddress output = null;
foreach (NetworkInterface item in NetworkInterface.GetAllNetworkInterfaces())
{
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
NetworkInterfaceType _type1 = NetworkInterfaceType.Wireless80211;
NetworkInterfaceType _type2 = NetworkInterfaceType.Ethernet;
if ((item.NetworkInterfaceType == _type1 || item.NetworkInterfaceType == _type2) && item.OperationalStatus == OperationalStatus.Up)
#endif
{
foreach (UnicastIPAddressInformation ip in item.GetIPProperties().UnicastAddresses)
{
//IPv4
if (!IPv6)
{
if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
{
output = ip.Address;
//Debug.Log("IP:" + output);
}
}
//IPv6
else
{
if (ip.Address.AddressFamily == AddressFamily.InterNetworkV6)
{
output = ip.Address;
}
}
}
}
}
return output;
}
// Update is called once per frame
private void Awake()
{
if (server != null)
{
Destroy(gameObject);
return;
}
server = this;
DontDestroyOnLoad(gameObject);
IPAddress ip = GetIP(false);
Debug.Log(ip);
IPEndPoint ip_end_point = new IPEndPoint(ip, 9999);
//创建服务器Socket对象,并设置相关属性
serverSocket = new TcpListener(ip_end_point);
}
void Update()
{
while (ActionQueue.TryDequeue(out Action a))
{
a();
}
}
public SocketServer StartServer()
{
//serverSocket.BeginAccept(OnConnectRequest, null);
serverSocket.Start();
serverSocket.BeginAcceptTcpClient(OnConnectRequest, null);
return this;
}
void OnConnectRequest(IAsyncResult ar)
{
//初始化一个SOCKET,用于其它客户端的连接
TcpClient client = serverSocket.EndAcceptTcpClient(ar);
Clients.Add(client);
ActionQueue.Enqueue(() => {
连接到客户端?.Invoke(client);
});
Debug.Log("收到连接"+client.Client.RemoteEndPoint);
Byte[] bytes = new Byte[1024];
NetworkStream stream = client.GetStream();
AsyncCallback callback = null; ;
callback=(IAsyncResult ar1)=>{
int recvLenth = stream.EndRead(ar1);
if (client == null) return;
if (recvLenth > 0)
{
string msg = Encoding.UTF8.GetString(bytes, 0, recvLenth);
print("原始数据" + msg);
string[] ss = msg.Split(new char[] { '@' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string item in ss)
{
ActionQueue.Enqueue(() => {
收到消息?.Invoke(client, item);
});
}
bytes = new byte[1024];
stream.BeginRead(bytes, 0, bytes.Length, callback, null);
}
else
{
IPEndPoint ip = client.Client.RemoteEndPoint as IPEndPoint;
Debug.LogWarning(ip + "断开连接!");
//stream.Dispose();
Clients.Remove(client);
ActionQueue.Enqueue(() => {
客户端断开连接?.Invoke(client);
});
}
};
stream.BeginRead(bytes, 0, bytes.Length, callback, null);
serverSocket.BeginAcceptTcpClient(OnConnectRequest, null);
}
public bool SendAllMessage(string data)
{
bool b = true;
foreach (TcpClient item in Clients)
{
if (!SendClientMessage(item, data))
{
b = false;
}
}
return b;
}
public bool SendClientMessage(TcpClient client,string data)
{
if (!client.Connected)
{
Debug.LogWarning("发送失败!客户端"+ client+"断开连接!");
Clients.Remove(client);
ActionQueue.Enqueue(() => {
客户端断开连接?.Invoke(client);
});
return false;
}
NetworkStream stream = client.GetStream();
try
{
byte[] message = Encoding.UTF8.GetBytes(data);
stream.Write(message, 0, message.Length);
}
catch (Exception e)
{
Debug.LogWarning("发松失败" + e);
Debug.LogWarning(client.Connected);
client.Dispose();
Clients.Remove(client);
ActionQueue.Enqueue(() => {
客户端断开连接?.Invoke(client);
});
return false;
}
return true;
}
private void OnDestroy()
{
serverSocket.Stop();
serverSocket = null;
}
}
2.客户端类
代码如下(示例):
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;
public class SocketClient : MonoBehaviour
{
static SocketClient client;
public TcpClient clientSocket;
public Action<string> 收到消息;
ConcurrentQueue<Action> ActionQueue = new ConcurrentQueue<Action>();
public static SocketClient Client
{
get
{
if (client == null)
{
GameObject g = new GameObject("SocketServer");
client = g.AddComponent<SocketClient>();
}
return client;
}
}
public bool Connected{
get
{
return clientSocket != null && clientSocket.Connected;
}
}
private void Awake()
{
if (client != null)
{
Destroy(gameObject);
return;
}
client = this;
DontDestroyOnLoad(gameObject);
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
while (ActionQueue.TryDequeue(out Action a))
{
a();
}
}
public bool ConnectServer(string ip, int port)
{
try
{
clientSocket = new TcpClient(ip, port);
Debug.Log("连接服务器成功");
}
catch (Exception e)
{
Debug.LogError("连接服务器失败" + e);
return false;
}
Receive();
return true;
}
private void Receive()
{
Byte[] bytes = new Byte[1024];
NetworkStream stream = clientSocket.GetStream();
AsyncCallback callback = null; ;
callback = (IAsyncResult ar1) => {
int recvLenth = stream.EndRead(ar1);
if (client == null) return;
if (recvLenth > 0)
{
string msg = Encoding.UTF8.GetString(bytes, 0, recvLenth);
print("原始数据" + msg);
string[] ss = msg.Split(new char[] { '@' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string item in ss)
{
ActionQueue.Enqueue(() => {
收到消息?.Invoke(item);
});
}
bytes = new byte[1024];
stream.BeginRead(bytes, 0, bytes.Length, callback, null);
}
else
{
stream.Dispose();
clientSocket.Dispose();
client = null;
Debug.LogWarning("与服务器断开!");
}
};
stream.BeginRead(bytes, 0, bytes.Length, callback, null);
}
public bool SendTcpMessage(string data)
{
if (!clientSocket.Connected)
return false;
NetworkStream stream = clientSocket.GetStream();
try
{
byte[] message = Encoding.UTF8.GetBytes("@"+data);
stream.Write(message, 0, message.Length);
}
catch (Exception e)
{
Debug.LogWarning("发松失败" + e);
clientSocket.Dispose();
client = null;
return false;
}
return true;
}
private void OnDestroy()
{
if (clientSocket!=null)
{
clientSocket.Dispose();
}
}
}
为了使客户端能够找到服务器的IP然后连接服务器,我还在服务器启动时启动一个定时器发送一段广播来通知客户端连接那个ip。具体的广播类如下:
using System;
using System.Text;
using System.Threading;
using UnityEngine;
using System.Net.Sockets;
using System.Net;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Net.NetworkInformation;
public class UdpBroad : MonoBehaviour
{
private UdpClient UDPrecv ;
int port = 9999;
ConcurrentQueue<Action> ActionQueue = new ConcurrentQueue<Action>();
public event Action<string> 收到广播消息;
static UdpBroad broad;
public static UdpBroad Broad
{
get
{
if (broad==null)
{
GameObject g = new GameObject("udp广播");
g.AddComponent<UdpBroad>();
}
return broad;
}
}
private void Awake()
{
if (broad != null)
{
Destroy(gameObject);
return;
}
broad = this;
DontDestroyOnLoad(broad);
try
{
UDPrecv = new UdpClient(port);
}
catch
{
UDPrecv = new UdpClient(new IPEndPoint(GetIP(false), port));
}
}
public static IPAddress GetIP(bool IPv6)
{
if (IPv6 && !Socket.OSSupportsIPv6)
{
return null;
}
IPAddress output = null;
foreach (NetworkInterface item in NetworkInterface.GetAllNetworkInterfaces())
{
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
NetworkInterfaceType _type1 = NetworkInterfaceType.Wireless80211;
NetworkInterfaceType _type2 = NetworkInterfaceType.Ethernet;
if ((item.NetworkInterfaceType == _type1 || item.NetworkInterfaceType == _type2) && item.OperationalStatus == OperationalStatus.Up)
#endif
{
foreach (UnicastIPAddressInformation ip in item.GetIPProperties().UnicastAddresses)
{
//IPv4
if (!IPv6)
{
if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
{
output = ip.Address;
//Debug.Log("IP:" + output);
}
}
//IPv6
else
{
if (ip.Address.AddressFamily == AddressFamily.InterNetworkV6)
{
output = ip.Address;
}
}
}
}
}
return output;
}
public void 开始接收广播(Action<string> 收到广播消息)
{
this.收到广播消息 = 收到广播消息;
UDPrecv.BeginReceive(ReceiveCallback, null);
}
private void Update()
{
while (ActionQueue.TryDequeue(out Action a))
{
a();
}
}
private void OnDestroy()
{
UDPrecv.Dispose();
}
public void 发送广播(string mes)
{
//UdpClient UDPsend = new UdpClient(new IPEndPoint(IPAddress.Any, 0));
IPEndPoint endpoint = new IPEndPoint(IPAddress.Broadcast, port);
byte[] message = Encoding.UTF8.GetBytes(mes);
UDPrecv.Send(message, message.Length, endpoint);
}
private void ReceiveCallback(IAsyncResult ar)
{
IPEndPoint endpoint = null;
byte[] recvBuf = UDPrecv.EndReceive(ar, ref endpoint);
string msg = Encoding.UTF8.GetString(recvBuf);
ActionQueue.Enqueue(() => { 收到广播消息?.Invoke(msg);});
UDPrecv.BeginReceive(new AsyncCallback(ReceiveCallback), null);
}
}
总结
以上就是主要的类接下来可以根据自身需求来实现不同的效果,基于以上的类我写了一个简单的聊天室功能:https://download.youkuaiyun.com/download/chunyu90225/13084303