实现聊天功能分为两部分:服务端,客户端。话不多说,上代码。
1. 服务端 (用VS写的控制台程序),主要实现异步通信,及连接池
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>
/// 客户端连接对象类
/// </summary>
class ConnectClient
{
//缓冲区大小+
public const int BUFFER_SIZE = 1024;
public Socket socket;
//是否使用
public bool isUse = false;
//缓冲区
public byte[] readBuff = new byte[BUFFER_SIZE];
//数据大小
public int bufferCount;
//构造
public ConnectClient()
{
readBuff = new byte[BUFFER_SIZE];
}
/// <summary>
/// 初始化数据
/// </summary>
/// <param name="soc"></param>
public void Init(Socket soc)
{
this.socket = soc;
isUse = true;
bufferCount = 0;
}
/// <summary>
/// 缓冲区剩余字节空间
/// </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>
/// 关闭连接
/// </summary>
public void Close()
{
if (!isUse)
{
return;
}
else
{
Console.WriteLine(Address() + " [ 断开连接 ]");
socket.Close();
isUse = false;
}
}
}
}
1.2 SocketServer (服务端控制类)
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>
/// 服务器类
/// </summary>
class SocketServer
{
//监听socket对象
public Socket listenSocket;
//客户端连接池
public ConnectClient[] clientList;
//容纳客户端数量
public int maxClient = 50;
/// <summary>
/// 从连接池中 获取客户端连接对象 ,如果列表中以满 则获取失败
/// </summary>
/// <returns></returns>
public int GetIndex()
{
//如果连接池为空 则新建连接池 返回第一个连接对象
if (clientList == null)
{
clientList = new ConnectClient[maxClient];
clientList[0] = new ConnectClient();
return 0;
}
else
{
//遍历连接池 , 返回未使用连接对象的索引
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;
}
}
//如果都有对象且在使用中,则返回-1. 代表获取失败
return -1;
}
}
//接收回调
private void AcceptCallBack(IAsyncResult ar)
{
try
{
Socket socket = listenSocket.EndAccept(ar);
int index = GetIndex();
if (index< 0)
{
socket.Close();
Console.WriteLine("服务器连接已满,请稍候再试");
}
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());
}
//重新开始异步接收请求
listenSocket.BeginAccept(AcceptCallBack, null);
}
catch (Exception e)
{
Console.WriteLine("客户端请求异常! Exception = " + e.Message);
}
}
//返回回调
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() + " 的数据 = " + 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() + " 的数据 给 " + clientList[i].Address());
}
}
//继续接收数据
client.socket.BeginReceive(client.readBuff, client.bufferCount, client.BufferRemain(), SocketFlags.None, ReceiveCallBack, client);
}
catch (Exception e)
{
Console.WriteLine("[接收数据异常] client = " + client.Address());
Console.WriteLine(" Execption = " + e.Message);
client.Close();
}
}
/// <summary>
/// 开启服务
/// </summary>
/// <param name="host">主机地址</param>
/// <param name="port">端口</param>
/// <param name="maxClient">容纳客户端数量 (默认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("服务器启动成功!");
}
}
}
启动服务端:
static void Main(string[] args)
{
Console.WriteLine("请输入服务端IP地址:");
string host = Console.ReadLine();
Console.WriteLine("请输入服务端端口号:");
string port = Console.ReadLine();
SocketServer server = new SocketServer();
//只是本机测试,可以写127.0.0.1 , 但是要让其他机器连接的话,要写实际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通信。
场景界面如下:
功能控制脚本:
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; //接收的字符数据
private bool isReceived = false; //数据接收完成
// Use this for initialization
void Start ()
{
//设置后台运行,数据就会立马同步更新。否则其他客户端发送一条消息,服务端进行广播,但是Unity进程被挂起了,就无法实时更新
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;
}
}
//服务器返回回调
private void ReceiveCallBack(IAsyncResult ar)
{
try
{
int count = m_Socket.EndReceive(ar);
reveString = System.Text.Encoding.UTF8.GetString(readBuff, 0, count);
isReceived = true;
//之所以不直接在这里赋值,是因为线程问题,会报错,该回调不是在unity主线程中执行的,所以赋值放在update中
//if (ReceiveFiled.text.Length > 500)
//{
// ReceiveFiled.text = "";
//}
//ReceiveFiled.text += reveString + '\n';
//继续接收返回信息
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;
}
}
}
运行效果如下:
最后:
以上纯属个人总结,如有不对或者更好的方法,欢迎指正,交流。
工程文件链接 : https://github.com/IongX/Unity_Socket