實(shí)現(xiàn)聊天功能分為兩部分:服務(wù)端,客戶端。話不多說,上代碼。
1. 服務(wù)端 (用VS寫的控制臺(tái)程序),主要實(shí)現(xiàn)異步通信,及連接池
1.1 ConnectClient (客戶端連接類)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
namespace TestSocketServer
{
/// <summary>
/// 客戶端連接對(duì)象類
/// </summary>
class ConnectClient
{
//緩沖區(qū)大小+
public const int BUFFER_SIZE = 1024;
public Socket socket;
//是否使用
public bool isUse = false;
//緩沖區(qū)
public byte[] readBuff = new byte[BUFFER_SIZE];
//數(shù)據(jù)大小
public int bufferCount;
//構(gòu)造
public ConnectClient()
{
readBuff = new byte[BUFFER_SIZE];
}
/// <summary>
/// 初始化數(shù)據(jù)
/// </summary>
/// <param name="soc"></param>
public void Init(Socket soc)
{
this.socket = soc;
isUse = true;
bufferCount = 0;
}
/// <summary>
/// 緩沖區(qū)剩余字節(jié)空間
/// </summary>
/// <returns></returns>
public int BufferRemain()
{
return BUFFER_SIZE - bufferCount;
}
/// <summary>
/// 獲取socket連接地址
/// </summary>
/// <returns></returns>
public string Address()
{
if (!isUse)
{
return null;
}
else
{
return socket.RemoteEndPoint.ToString();
}
}
/// <summary>
/// 關(guān)閉連接
/// </summary>
public void Close()
{
if (!isUse)
{
return;
}
else
{
Console.WriteLine(Address() + " [ 斷開連接 ]");
socket.Close();
isUse = false;
}
}
}
}
1.2 SocketServer (服務(wù)端控制類)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
namespace TestSocketServer
{
/// <summary>
/// 服務(wù)器類
/// </summary>
class SocketServer
{
//監(jiān)聽socket對(duì)象
public Socket listenSocket;
//客戶端連接池
public ConnectClient[] clientList;
//容納客戶端數(shù)量
public int maxClient = 50;
/// <summary>
/// 從連接池中 獲取客戶端連接對(duì)象 ,如果列表中以滿 則獲取失敗
/// </summary>
/// <returns></returns>
public int GetIndex()
{
//如果連接池為空 則新建連接池 返回第一個(gè)連接對(duì)象
if (clientList == null)
{
clientList = new ConnectClient[maxClient];
clientList[0] = new ConnectClient();
return 0;
}
else
{
//遍歷連接池 , 返回未使用連接對(duì)象的索引
for (int i = 0; i < clientList.Length; i++)
{
if (clientList[i] == null)
{
clientList[i] = new ConnectClient();
return i;
}
else if (clientList[i].isUse == false)
{
return i;
}
}
//如果都有對(duì)象且在使用中,則返回-1. 代表獲取失敗
return -1;
}
}
//接收回調(diào)
private void AcceptCallBack(IAsyncResult ar)
{
try
{
Socket socket = listenSocket.EndAccept(ar);
int index = GetIndex();
if (index< 0)
{
socket.Close();
Console.WriteLine("服務(wù)器連接已滿,請(qǐng)稍候再試");
}
else
{
ConnectClient client = clientList[index];
client.Init(socket);
client.socket.BeginReceive(client.readBuff, client.bufferCount, client.BufferRemain(), SocketFlags.None, ReceiveCallBack,client);
Console.WriteLine("客戶端連接成功 = " + client.Address());
}
//重新開始異步接收請(qǐng)求
listenSocket.BeginAccept(AcceptCallBack, null);
}
catch (Exception e)
{
Console.WriteLine("客戶端請(qǐng)求異常! Exception = " + e.Message);
}
}
//返回回調(diào)
private void ReceiveCallBack(IAsyncResult ar)
{
ConnectClient client = (ConnectClient)ar.AsyncState;
try
{
int count = client.socket.EndReceive(ar);
//斷開連接
if (count <= 0)
{
Console.WriteLine("斷開連接 = " + client.Address());
client.Close();
}
else
{
string receiveString = System.Text.Encoding.UTF8.GetString(client.readBuff, 0, count);
Console.WriteLine("接收 " + client.Address() + " 的數(shù)據(jù) = " + receiveString);
byte[] sendBytes = System.Text.Encoding.UTF8.GetBytes(client.Address() + " : " + receiveString);
//廣播信息
for (int i = 0; i < clientList.Length; i++)
{
if (clientList[i] == null)
{
continue;
}
if (clientList[i].isUse == false)
{
continue;
}
clientList[i].socket.Send(sendBytes);
Console.WriteLine("廣播 " + client.Address() + " 的數(shù)據(jù) 給 " + clientList[i].Address());
}
}
//繼續(xù)接收數(shù)據(jù)
client.socket.BeginReceive(client.readBuff, client.bufferCount, client.BufferRemain(), SocketFlags.None, ReceiveCallBack, client);
}
catch (Exception e)
{
Console.WriteLine("[接收數(shù)據(jù)異常] client = " + client.Address());
Console.WriteLine(" Execption = " + e.Message);
client.Close();
}
}
/// <summary>
/// 開啟服務(wù)
/// </summary>
/// <param name="host">主機(jī)地址</param>
/// <param name="port">端口</param>
/// <param name="maxClient">容納客戶端數(shù)量 (默認(rèn)50)</param>
public void Start(string host , int port , int maxClient = 50)
{
//初始化連接池
this.maxClient = maxClient;
clientList = new ConnectClient[this.maxClient];
listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ipa = IPAddress.Parse(host);
IPEndPoint ipe = new IPEndPoint(ipa, port);
listenSocket.Bind(ipe);
listenSocket.Listen(maxClient);
//開啟異步接收連接
listenSocket.BeginAccept(AcceptCallBack, null);
Console.WriteLine("服務(wù)器啟動(dòng)成功!");
}
}
}
啟動(dòng)服務(wù)端:
static void Main(string[] args)
{
Console.WriteLine("請(qǐng)輸入服務(wù)端IP地址:");
string host = Console.ReadLine();
Console.WriteLine("請(qǐng)輸入服務(wù)端端口號(hào):");
string port = Console.ReadLine();
SocketServer server = new SocketServer();
//只是本機(jī)測試,可以寫127.0.0.1 , 但是要讓其他機(jī)器連接的話,要寫實(shí)際ip地址
//server.Start("192.168.0.171", 1234);
server.Start(host, int.Parse(port));
while (true)
{
string write = Console.ReadLine();
switch (write)
{
case "quit":
return;
}
}
}
2. 客戶端,聊天Demo 及Socket通信。
場景界面如下:

圖片.png
功能控制腳本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.Sockets;
using System.Net;
using UnityEngine.UI;
using System;
public class TestClient : MonoBehaviour {
private Socket m_Socket;
public InputField HostField;
public InputField PortField;
public InputField MessageField;
public InputField LinkMessageField;
public InputField ReceiveFiled;
private byte[] readBuff = new byte[1024];
private string reveString; //接收的字符數(shù)據(jù)
private bool isReceived = false; //數(shù)據(jù)接收完成
// Use this for initialization
void Start ()
{
//設(shè)置后臺(tái)運(yùn)行,數(shù)據(jù)就會(huì)立馬同步更新。否則其他客戶端發(fā)送一條消息,服務(wù)端進(jìn)行廣播,但是Unity進(jìn)程被掛起了,就無法實(shí)時(shí)更新
Application.runInBackground = true;
}
public void LinkServer()
{
m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
m_Socket.Connect(HostField.text, int.Parse(PortField.text));
LinkMessageField.text = "連接成功-----" + m_Socket.LocalEndPoint.ToString();
}
catch (Exception)
{
LinkMessageField.text = "連接失?。?!";
throw;
}
m_Socket.BeginReceive(readBuff, 0, 1024, SocketFlags.None, ReceiveCallBack, null);
}
public void SendMessageToServer()
{
try
{
byte[] sendBytes = System.Text.Encoding.UTF8.GetBytes(MessageField.text);
m_Socket.Send(sendBytes);
}
catch (Exception)
{
throw;
}
}
//服務(wù)器返回回調(diào)
private void ReceiveCallBack(IAsyncResult ar)
{
try
{
int count = m_Socket.EndReceive(ar);
reveString = System.Text.Encoding.UTF8.GetString(readBuff, 0, count);
isReceived = true;
//之所以不直接在這里賦值,是因?yàn)榫€程問題,會(huì)報(bào)錯(cuò),該回調(diào)不是在unity主線程中執(zhí)行的,所以賦值放在update中
//if (ReceiveFiled.text.Length > 500)
//{
// ReceiveFiled.text = "";
//}
//ReceiveFiled.text += reveString + '\n';
//繼續(xù)接收返回信息
m_Socket.BeginReceive(readBuff, 0, 1024, SocketFlags.None, ReceiveCallBack, null);
}
catch (Exception)
{
reveString = m_Socket.LocalEndPoint.ToString() + "連接斷開";
isReceived = true;
m_Socket.Close();
throw;
}
}
private void Update()
{
if (isReceived)
{
if (ReceiveFiled.text.Length > 500)
{
ReceiveFiled.text = "";
}
ReceiveFiled.text += reveString + '\n';
isReceived = false;
}
}
}
運(yùn)行效果如下:

圖片.png
最后:
以上純屬個(gè)人總結(jié),如有不對(duì)或者更好的方法,歡迎指正,交流。
工程文件鏈接 : https://github.com/IongX/Unity_Socket