网络开发基础(游戏)之 Socket API
Socket简介
Socket (套接字)是网络编程的基础,在 C# 中通过 System.Net.Sockets
命名空间提供了一套完整的 API 来实现网络通信。
网络上的两个程序通过一个双向的通信连接实现数据交换, 这个连接的一端称为一个Socket。 一个Socket包含了进行网络通信必需的五种信息:连接使用的协议、 本地主机的IP地址、 本地的协议端口、 远程主机的IP地址和远程协议端口。
网络通信流程
服务端
1、注册绑定阶段:服务端初始化一个Socket对象,绑定服务器的IP地址和端口号(使用Bind方法)。
2、开启监听 ,使用Listen方法。
3、等待客户端连接,使用Accept方法。
4、收发数据:成功连接后,可以进行接收数据(使用Receive方法)和发送数据(使用Send方法)。
5、关闭连接,使用Close方法。
客户端
1、注册绑定阶段:客户端初始化一个Socket对象,绑定服务器的IP地址和端口号(使用Bind方法)。
2、连接服务器,使用Connect方法。
3、收发数据:成功连接后,可以进行接收数据(使用Receive方法)和发送数据(使用Send方法)。
4、关闭连接,使用Close方法。
Socket API
常用属性
API | 说明 |
AddressFamily | 指定Socket类的实例可以使用的寻址方案,一般知道以下两个即可: InterNetwork:IPv4地址 nterNetworkV6 :IPv6地址 |
SocketType | 指定Socket类的实例表示的套接字类型,一般知道以下两个即可: Stream:支持可靠、双向、基于连接的字节流,而不重复数据,也不保留边界。 此类型的 Socket 与单个对方主机通信,并且在通信开始之前需要建立远程主机连接。 Stream 使用传输控制协议 (Tcp) 和 Dgram:支持数据报,即最大长度固定(通常很小)的无连接、不可靠消息。 消息可能会丢失或重复并可能在到达时不按顺序排列。 Socket 类型的 Dgram 在发送和接收数据之前不需要任何连接,并且可以与多个对方主机进行通信。 Dgram 使用 Datagram 协议 (Udp) 和 |
ProtocolType | 指定Socket类支持的协议b一般知道以下两个即可: Tcp:传输控制协议 Udp:用户数据报协议 |
Available | 获取已经从网络接收且可供读取的数据量。 |
Blocking | |
Connected | 如果 Socket 在最近操作时连接到远程资源,则为 true;否则为 false。 |
IsBound | |
SendBufferSize | |
SendTimeout | |
ReceiveBufferSize | |
ReceiveTimeout | |
常用方法
API | 说明 |
Bind | 使Socket与一个本地终结点相关联 |
Listen | 将Socket置于监听状态 |
Close | 关闭Socket连接并释放所有关联的资源 |
Shutdown | 禁用某Socket上的发送和接收 |
GetSocketOption | 获取Socket选项的值 |
SetSocketOption | 设置Socket选项 |
Poll | 确定Socket状态 |
同步方法
均为阻塞方法,程序会卡住直到成功响应
API | 说明 |
Connect | 建立与远端主机的连接 |
Accept | 为新建连接创建新的Socket |
Send | 发送数据 |
Receive | 接收数据 |
Disconnect | 关闭套接字连接并允许重用套接字 |
异步方法
与同步方法不同的是,异步方法不会阻塞线程。
只有在主线程中才能操作UI组件,由于异步回调是在其他线程执行的。可以通过缓存接收数据,再在Update中进行读取并传递给UI组件。
两种异步方式说明
在 C# 中,xxxAsync 和 Beginxxx/Endxxx 都是用于异步网络通信的方法,但它们属于不同的异步编程模型。
xxxAsync
ReceiveAsync
是 .NET Framework 4.5+ 和 .NET Core/.NET 5+ 引入的现代异步方法,基于 Task
的异步模式(TAP),适合 async/await
编程。
适用场景
-
推荐在新项目中使用,代码更简洁,可读性更好。
-
适用于 .NET Core / .NET 5+ 和 .NET Framework 4.5+。
-
配合
async/await
使用,避免回调地狱。
特点
1、简洁:直接 await
等待数据,无需回调。
2、可取消:可配合 CancellationToken
使用。
3、现代推荐:适用于新代码。
Beginxxx
/ Endxxx
BeginReceive
和 EndReceive
是 .NET 早期的异步编程模型(APM,Asynchronous Programming Model),基于 IAsyncResult
和回调机制。
适用场景
-
旧版 .NET Framework(4.0 及以下) 兼容代码。
-
需要手动管理
IAsyncResult
和回调。
特点
1、回调模式:需要手动处理 IAsyncResult
和回调函数。
2、较旧 API:不推荐在新代码中使用,除非维护旧项目。
3、无 async/await
支持:代码结构可能更复杂。
对比总结
特性 | ReceiveAsync (TAP) | BeginReceive /EndReceive (APM) |
---|---|---|
编程模型 | async/await (推荐) | 回调模式(IAsyncResult ) |
代码可读性 | 高(线性执行) | 低(嵌套回调) |
适用版本 | .NET 4.5+, .NET Core | .NET Framework 所有版本 |
取消支持 | 支持 (CancellationToken ) | 不支持 |
推荐使用场景 | 新项目、现代异步代码 | 旧代码维护 |
如何选择?
-
新项目 ➔
ReceiveAsync
+async/await
(代码更清晰,维护方便)。 -
旧项目维护 ➔
BeginReceive
/EndReceive
(兼容旧版 .NET)。 -
高性能场景 ➔ 也可以考虑
SocketAsyncEventArgs
(更低级别的异步模型)。
结论
-
优先使用
ReceiveAsync
(现代、简洁、可取消)。 -
仅在旧代码中保留
BeginReceive
/EndReceive
(兼容性需求)。
Beginxxx/Endxxx
API | 说明 |
BeginConnect | 开启一个与远端主机的连接的异步请求 |
EndConnect | 结束挂起的异步连接请求 |
BeginAccept | 开始一个异步操作来接受一个传入的连接尝试 |
EndAccept | 异步接受传入的连接尝试 |
BeginSend | 将数据异步发送到连接的 Socket |
EndSend | 结束挂起的异步发送 |
BeginReceive | 开始从连接的 Socket 中异步接收数据 |
EndReceive | 结束挂起的异步读取 |
BeginDisconnect | 开始异步请求从远程终结点断开连接 |
EndDisconnect | 结束挂起的异步断开连接请求 |
BeginConnect/EndConnect
用于客户端异步连接服务服务端
public void ConnectToServer(string host, int port)
{Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);// 开始异步连接clientSocket.BeginConnect(host, port, ConnectCallback, clientSocket);
}private void ConnectCallback(IAsyncResult ar)
{try{Socket clientSocket = (Socket)ar.AsyncState;// 完成连接操作clientSocket.EndConnect(ar);Console.WriteLine("Connected to server");// 连接成功后开始接收数据StartReceiving(clientSocket);}catch (SocketException ex){Console.WriteLine($"Connection failed: {ex.SocketErrorCode}");}catch (Exception ex){Console.WriteLine($"Connection error: {ex.Message}");}
}
BeginAccept/EndAccept
用于服务端异步接受客户端连入
public void StartServer(int port)
{Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);listener.Bind(new IPEndPoint(IPAddress.Any, port));listener.Listen(100);// 开始异步接受连接listener.BeginAccept(AcceptCallback, listener);
}private void AcceptCallback(IAsyncResult ar)
{Socket listener = (Socket)ar.AsyncState;try{// 完成接受连接操作Socket clientSocket = listener.EndAccept(ar);Console.WriteLine($"New connection from {clientSocket.RemoteEndPoint}");// 开始接收数据StartReceiving(clientSocket);// 继续接受新连接listener.BeginAccept(AcceptCallback, listener);}catch (SocketException ex){Console.WriteLine($"Accept failed: {ex.SocketErrorCode}");}catch (ObjectDisposedException){// 监听器已关闭}catch (Exception ex){Console.WriteLine($"Accept error: {ex.Message}");}
}
BeginSend/EndSend
用于客户端/服务端异步发送数据
public void SendData(Socket socket, byte[] data)
{try{// 开始异步发送socket.BeginSend(data, 0, data.Length, SocketFlags.None, SendCallback, socket);}catch (SocketException ex){Console.WriteLine($"Send error: {ex.SocketErrorCode}");socket.Close();}
}private void SendCallback(IAsyncResult ar)
{Socket socket = (Socket)ar.AsyncState;try{// 完成发送操作int bytesSent = socket.EndSend(ar);Console.WriteLine($"Sent {bytesSent} bytes");}catch (SocketException ex){Console.WriteLine($"Send completion error: {ex.SocketErrorCode}");socket.Close();}catch (ObjectDisposedException){// Socket已关闭}catch (Exception ex){Console.WriteLine($"Send error: {ex.Message}");socket.Close();}
}
BeginReceive/EndReceive
用于客户端/服务端异步接收数据
private void StartReceiving(Socket socket)
{byte[] buffer = new byte[8192];try{// 开始异步接收socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, new ReceiveState { Socket = socket, Buffer = buffer });}catch (SocketException ex){Console.WriteLine($"Receive error: {ex.SocketErrorCode}");socket.Close();}
}private void ReceiveCallback(IAsyncResult ar)
{ReceiveState state = (ReceiveState)ar.AsyncState;Socket socket = state.Socket;try{// 完成接收操作int bytesRead = socket.EndReceive(ar);if (bytesRead > 0){// 处理接收到的数据byte[] receivedData = new byte[bytesRead];Array.Copy(state.Buffer, 0, receivedData, 0, bytesRead);Console.WriteLine($"Received: {Encoding.UTF8.GetString(receivedData)}");// 继续接收数据StartReceiving(socket);}else{// 连接已关闭Console.WriteLine("Connection closed by remote host");socket.Close();}}catch (SocketException ex){Console.WriteLine($"Receive error: {ex.SocketErrorCode}");socket.Close();}catch (ObjectDisposedException){// Socket已关闭}catch (Exception ex){Console.WriteLine($"Receive error: {ex.Message}");socket.Close();}
}// 用于传递状态的辅助类
class ReceiveState
{public Socket Socket { get; set; }public byte[] Buffer { get; set; }
}
BeginDisconnect/EndDisconnect
用于客户端异步断开连接服务端
public void DisconnectSocket(Socket socket)
{try{// 开始异步断开连接socket.BeginDisconnect(false, DisconnectCallback, socket);}catch (SocketException ex){Console.WriteLine($"Disconnect error: {ex.SocketErrorCode}");socket.Close();}
}private void DisconnectCallback(IAsyncResult ar)
{Socket socket = (Socket)ar.AsyncState;try{// 完成断开连接操作socket.EndDisconnect(ar);Console.WriteLine("Disconnected successfully");socket.Close();}catch (SocketException ex){Console.WriteLine($"Disconnect error: {ex.SocketErrorCode}");socket.Close();}catch (ObjectDisposedException){// Socket已关闭}catch (Exception ex){Console.WriteLine($"Disconnect error: {ex.Message}");socket.Close();}
}
xxxAsync
如果异步套接字方法 (xxxAsync) 返回 true,请在Completed 回调中查询完成状态、获取操作结果。如果异步套接字方法 (xxxAsync) 返回 false,则操作同步完成,将不会引发 e 参数的 Completed 事件。 可查询上下文属性获取操作结果。
API | 说明 |
AcceptAsync | 开始一个异步操作来接受一个传入的连接尝试 |
ConnectAsync | 开启一个与远端主机的连接的异步请求 |
SendAsync | 将数据异步发送到连接的 Socket |
ReceiveAsync | 开始从连接的 Socket 中异步接收数据 |
DisconnectAsync | 开始异步请求从远程终结点断开连接 |
SocketAsyncEventArgs
SocketAsyncEventArgs
类通过重用操作上下文和缓冲区来减少内存分配,主要特点包括:
-
重用操作上下文对象
-
支持缓冲区池
-
减少异步操作中的内存分配
-
提供更细粒度的控制
通过合理使用 SocketAsyncEventArgs
,可以构建出高性能、可扩展的网络应用程序,特别适合需要处理大量并发连接的场景。
关键属性
// 缓冲区相关
public byte[] Buffer { get; } // 当前使用的缓冲区
public int Offset { get; } // 缓冲区起始偏移
public int Count { get; } // 可操作字节数
public int BytesTransferred { get; } // 已传输字节数// 连接信息
public EndPoint RemoteEndPoint { get; set; }
public EndPoint LocalEndPoint { get; }// 操作状态
public SocketError SocketError { get; set; } // 操作结果
public SocketFlags SocketFlags { get; set; } // 特殊标志// 用户自定义数据
public object UserToken { get; set; } // 用户自定义对象
重要方法
// 缓冲区管理
public void SetBuffer(byte[] buffer, int offset, int count);
public void SetBuffer(int offset, int count);// 内存缓冲区管理
public void SetBuffer(Memory<byte> buffer); // .NET Core 新增// 操作结果获取
public Socket AcceptSocket { get; set; } // 用于Accept操作
AcceptAsync
var acceptArgs = new SocketAsyncEventArgs();
acceptArgs.UserToken = acceptSocket;
acceptArgs.Completed += OnAcceptCompleted;if (!listener.AcceptAsync(acceptArgs))
{// 操作同步完成OnAcceptCompleted(listener, acceptArgs);
}private void OnAcceptCompleted(object sender, SocketAsyncEventArgs e)
{if (e.SocketError == SocketError.Success){Socket clientSocket = e.AcceptSocket;Console.WriteLine($"Client connected from: {clientSocket.RemoteEndPoint}");// 开始接收数据 ReceiveAsync// 准备接受下一个连接 AcceptAsynce.AcceptSocket = null; // 必须重置}else{Console.WriteLine($"Accept error: {e.SocketError}");}
}
ConnectAsync
var connectArgs = new SocketAsyncEventArgs();
connectArgs.RemoteEndPoint = new DnsEndPoint(host, port);
connectArgs.Completed += OnConnectCompleted;if (!clientSocket.ConnectAsync(connectArgs))
{// 操作同步完成OnConnectCompleted(clientSocket, connectArgs);
}private void OnConnectCompleted(object sender, SocketAsyncEventArgs e)
{if (e.SocketError == SocketError.Success){Console.WriteLine("Connected to server");// 连接成功后可以开始发送或接收数据}else{Console.WriteLine($"Connection failed: {e.SocketError}");}
}
SendAsync
public void SendData(Socket socket, byte[] data)
{var sendArgs = new SocketAsyncEventArgs();sendArgs.SetBuffer(data, 0, data.Length);sendArgs.Completed += OnSendCompleted;sendArgs.UserToken = socket;if (!socket.SendAsync(sendArgs)){// 操作同步完成OnSendCompleted(socket, sendArgs);}
}private void OnSendCompleted(object sender, SocketAsyncEventArgs e)
{if (e.SocketError == SocketError.Success){Console.WriteLine($"Sent {e.BytesTransferred} bytes");}else{Console.WriteLine($"Send error: {e.SocketError}");}
}
ReceiveAsync
private void StartReceiving(Socket socket)
{var receiveArgs = new SocketAsyncEventArgs();var buffer = new byte[4096];receiveArgs.SetBuffer(buffer, 0, buffer.Length);receiveArgs.Completed += OnReceiveCompleted;receiveArgs.UserToken = socket;if (!socket.ReceiveAsync(receiveArgs)){// 操作同步完成OnReceiveCompleted(socket, receiveArgs);}
}private void OnReceiveCompleted(object sender, SocketAsyncEventArgs e)
{Socket socket = (Socket)e.UserToken;if (e.SocketError == SocketError.Success && e.BytesTransferred > 0){// 处理接收到的数据byte[] data = new byte[e.BytesTransferred];Buffer.BlockCopy(e.Buffer, e.Offset, data, 0, e.BytesTransferred);Console.WriteLine($"Received {e.BytesTransferred} bytes: {Encoding.UTF8.GetString(data)}");// 继续接收}else{// 连接已关闭或出错Console.WriteLine("Connection closed or error occurred");socket.Close();}
}
DisconnectAsync
public void DisconnectSocket(Socket socket)
{var disconnectArgs = new SocketAsyncEventArgs();disconnectArgs.Completed += OnDisconnectCompleted;disconnectArgs.UserToken = socket;disconnectArgs.DisconnectReuseSocket = false; // 是否重用socketif (!socket.DisconnectAsync(disconnectArgs)){// 操作同步完成OnDisconnectCompleted(socket, disconnectArgs);}
}private void OnDisconnectCompleted(object sender, SocketAsyncEventArgs e)
{if (e.SocketError == SocketError.Success){Console.WriteLine("Disconnected successfully");Socket socket = (Socket)e.UserToken;socket.Close();}else{Console.WriteLine($"Disconnect error: {e.SocketError}");}
}
Socket同步
第一版 Echo程序
此版本主要实现基础通信流程步骤
Echo 程序是最基础的网络通信案例,它能够将客户端发送的消息原样返回。
同步案例均使用同步API实现,是阻塞方法,会卡住程序进程直到成功响应。
客户端
using UnityEngine;
using System.Net;
using System.Net.Sockets;
using UnityEngine.UI;
using System.Text;public class EchoClient : MonoBehaviour
{public InputField inputTxt;public Text txtShow;private Socket clientSocket;public void OnConnect(){if (clientSocket != null) return;clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);clientSocket.Connect(endPoint);txtShow.text = "成功连接服务器!";}//发送数据public void OnSend(){if (clientSocket == null) return;var str = inputTxt.text;var sendBytes = Encoding.UTF8.GetBytes(str);clientSocket.Send(sendBytes);txtShow.text = "成功发送消息!";if (str == "Close"){OnClose();}else{OnReceive();}}//接收数据public void OnReceive(){if (clientSocket == null) return;var buffer = new byte[1024];var index = clientSocket.Receive(buffer);var recStr = Encoding.UTF8.GetString(buffer, 0, index);txtShow.text = recStr;}//关闭Socketpublic void OnClose(){if (clientSocket == null) return;clientSocket.Shutdown(SocketShutdown.Both);clientSocket.Close();}
}
服务端
using System.Text;
using System.Net;
using System.Net.Sockets;public class EchoServer
{private Socket serverSocket;public byte[] bufferArray;public void Start(){if (serverSocket == null){serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);}if (bufferArray == null){bufferArray = new byte[1024];}IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);//绑定服务器IP地址和端口号serverSocket.Bind(endPoint);//开启监听,等待客户端连接//参数backlog指定队列中最多可容纳等待接受的连接数,0表示不限制serverSocket.Listen(0);Console.WriteLine("服务器启动成功!");//成功接收一个客户端连接var clientSocket = serverSocket.Accept();Console.WriteLine("有一个客户端连入!");Array.Clear(bufferArray);while (true){//接收客户端发来的消息var index = clientSocket.Receive(bufferArray);var recStr = Encoding.UTF8.GetString(bufferArray, 0, index);Console.WriteLine($"客户端发来消息:{recStr}");//向客户端发送消息var sendBytes = Encoding.UTF8.GetBytes($"我收到了你的消息:{recStr}");clientSocket.Send(sendBytes);Array.Clear(bufferArray);if (recStr == "Close"){break;}}Console.WriteLine("服务器关闭!");serverSocket.Close();}
}
第二版 多人聊天室
此版本主要实现需求:
1、多个客户端可连接
2、服务器可处理多个客户端的连入和消息广播
3、因为使用同步方法,为避免阻塞主线,使用多线程来处理对应的同步逻辑。
4、服务器使用一个容器来缓存已连接的客户端Socket,用于处理广播和持续通信。
客户端
using System.Collections.Generic;
using UnityEngine;
using System.Net;
using System.Net.Sockets;
using UnityEngine.UI;
using System.Text;
using System.Threading;public class SimpleClient : MonoBehaviour
{public InputField inputTxt;public Text txtShow;private Socket clientSocket;private bool isClose = true;private Queue<string> msgList;private StringBuilder sb;private void Update(){if (msgList == null) return;if (msgList.Count > 0){sb.Clear();sb.Append(txtShow.text);while (msgList.Count > 0){sb.Append(msgList.Dequeue());sb.Append("\n");}txtShow.text = sb.ToString();}}public void OnConnect(){if (clientSocket != null) return;if (msgList == null){msgList = new Queue<string>();}sb = new StringBuilder();clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);clientSocket.Connect(endPoint);txtShow.text = "成功连接服务器!\n";isClose = false;ThreadPool.QueueUserWorkItem(OnReceive);}//发送数据public void OnSend(){if (clientSocket == null) return;var str = inputTxt.text;var sendBytes = Encoding.UTF8.GetBytes(str);clientSocket.Send(sendBytes);inputTxt.text = "";Debug.Log("成功发送消息!");}//接收数据public void OnReceive(object obj){while (!isClose){if (clientSocket == null) return;var buffer = new byte[1024];var index = clientSocket.Receive(buffer);var recStr = Encoding.UTF8.GetString(buffer, 0, index);msgList.Enqueue(recStr);}}//关闭Socketpublic void OnClose(){if (clientSocket == null){Application.Quit();return;}var sendBytes = Encoding.UTF8.GetBytes("Quit");clientSocket.Send(sendBytes);Debug.Log("关闭Socket");isClose = true;clientSocket.Shutdown(SocketShutdown.Both);clientSocket.Close();Application.Quit();}
}
服务端
using System.Text;
using System.Net;
using System.Net.Sockets;public class BetterServer()
{private Socket serverSocket;private Dictionary<int, ClientSocket> clientList;private bool isColse = true;private int clientFlag = 0;public void Start(string ip,int port){if (serverSocket != null) return;if(clientList == null){clientList = new Dictionary<int, ClientSocket>(); }serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);serverSocket.Bind(iPEndPoint);isColse = false;serverSocket.Listen(0);Console.WriteLine("服务器启动成功!");ThreadPool.QueueUserWorkItem(Accect);}//等待接收客户端连接请求public void Accect(object obj){while (!isColse){var client = serverSocket.Accept();clientFlag++;ClientSocket clientSocket = new ClientSocket(clientFlag, client);clientList.TryAdd(clientFlag, clientSocket);var connectTip = $"有一个客户端:{clientSocket.id}连入!";Console.WriteLine(connectTip); Broadcast(connectTip);ThreadPool.QueueUserWorkItem((obj) =>{while (!isColse){var str = clientSocket.Receive(() =>{clientList.Remove(clientSocket.id); clientSocket.Close();Console.WriteLine($"还有{clientList.Count}个客户端连接中"); });if (!string.IsNullOrEmpty(str)){Broadcast(str); }; }});}}//向客户端广播消息public void Broadcast(string info){foreach (ClientSocket client in clientList.Values){client.Send(info);}}//关闭服务器public void Close(){if (serverSocket == null) return;isColse = true; foreach (var client in clientList.Values){client.Close(); }serverSocket.Shutdown(SocketShutdown.Both);serverSocket.Close();}
}public class ClientSocket
{public int id;private Socket clientSocket; public ClientSocket(int clientId,Socket instance){id = clientId;clientSocket = instance;}public void Send(string str){if (clientSocket == null) return;ThreadPool.QueueUserWorkItem((a) =>{var sendBytes = Encoding.UTF8.GetBytes(str); clientSocket.Send(sendBytes);});}public string Receive(Action callback){if (clientSocket != null && clientSocket.Available > 0){var buffer = new byte[1024];var index = clientSocket.Receive(buffer);var recvStr = Encoding.UTF8.GetString(buffer, 0, index);var isQuit = recvStr == "Quit";if (isQuit){callback?.Invoke();}var msg = isQuit ? $"客户端{id}离开了" : $"客户端{id}:{recvStr}";Console.WriteLine(msg);return msg;}return "";}public void Close(){if (clientSocket == null) return;clientSocket.Shutdown(SocketShutdown.Both); clientSocket.Close();clientSocket = null;}
}
BetterServer server = new BetterServer();
server.Start("127.0.0.1",8080);
while (true)
{var flag = Console.ReadLine(); if(flag == "Close"){server.Close(); break; }else if(flag == "广播"){server.Broadcast("好好学习,天天向上");}
}
Socket异步
Async方法实现
客户端
using System;
using System.Collections.Generic;
using UnityEngine;
using System.Net;
using System.Net.Sockets;
using UnityEngine.UI;
using System.Text;public class SimpleClient : MonoBehaviour
{public InputField inputTxt;public Text txtShow;private Socket clientSocket;//只有在主线程中才能操作UI组件,由于异步回调是在其他线程执行的。通过缓存接收的数据,再在Update中进行读取并传递给UI组件private Queue<string> msgList = new Queue<string>();private StringBuilder _stringBuilder = new StringBuilder();private void Update(){if (msgList.Count > 0){_stringBuilder.Clear();_stringBuilder.Append(txtShow.text);while (msgList.Count > 0){_stringBuilder.Append(msgList.Dequeue());_stringBuilder.Append("\n");}txtShow.text = _stringBuilder.ToString();}}//连接服务器public void OnConnect(){if (clientSocket != null) return;clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);var connectArgs = new SocketAsyncEventArgs();connectArgs.RemoteEndPoint = endPoint;connectArgs.Completed += ConnectCallback;if (!clientSocket.ConnectAsync(connectArgs)){//操作同步完成ConnectCallback(clientSocket, connectArgs);}}//连接服务器异步回调private void ConnectCallback(object sender, SocketAsyncEventArgs args){var socket = (Socket)sender;if (args.SocketError == SocketError.Success){msgList.Enqueue("成功连接服务器!");OnReceive(socket);}else{Debug.LogError($"连接服务器失败:{args.SocketError}");}}//发送数据public void OnSend(){if (clientSocket == null) return;var str = inputTxt.text;if (string.IsNullOrEmpty(str)){return;}var sendBytes = Encoding.UTF8.GetBytes(str);var sendArgs = new SocketAsyncEventArgs();sendArgs.SetBuffer(sendBytes,0,sendBytes.Length);sendArgs.Completed += SendCallback;if (!clientSocket.SendAsync(sendArgs)){//操作同步完成SendCallback(clientSocket, sendArgs);}inputTxt.text = "";}//发送数据异步回调private void SendCallback(object sender, SocketAsyncEventArgs args){if (args.SocketError == SocketError.Success){Debug.Log("发送消息成功!");}else{Debug.LogError($"发送消息失败:{args.SocketError}");}}//接收数据public void OnReceive(Socket socket){if (socket == null) return;var receiveArgs = new SocketAsyncEventArgs();var readBuff = new byte[1024];receiveArgs.SetBuffer(readBuff,0,readBuff.Length);receiveArgs.Completed += ReceiveCallback;if (!socket.ReceiveAsync(receiveArgs)){ReceiveCallback(socket, receiveArgs);}}//接收数据异步回调private void ReceiveCallback(object sender, SocketAsyncEventArgs args){if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success){var dataLength = args.BytesTransferred;byte[] data = new byte[dataLength];Buffer.BlockCopy(args.Buffer,args.Offset,data,0,dataLength);var recvMsg = Encoding.UTF8.GetString(data, 0, dataLength);msgList.Enqueue(recvMsg);// 继续接收下一条消息OnReceive((Socket)sender);}else{Debug.LogError($"接收数据失败:{args.SocketError}");OnClose();}}//关闭Socketpublic void OnClose(){if (clientSocket == null) return;clientSocket.Shutdown(SocketShutdown.Both);clientSocket.Close();Application.Quit();}
}
服务端
using System.Text;
using System.Net;
using System.Net.Sockets;public class EchoServer
{private Socket serverSocket;private class ClientInfo{public Socket clientSocket;public byte[] readBuff;public ClientInfo(Socket socket){clientSocket = socket; readBuff = new byte[1024];}}private Dictionary<Socket, ClientInfo> clientList;public void Start(){if (serverSocket == null){serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);}if (clientList == null){clientList = new Dictionary<Socket, ClientInfo>();}IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);//绑定服务器IP地址和端口号serverSocket.Bind(endPoint);//开启监听,等待客户端连接//参数backlog指定队列中最多可容纳等待接受的连接数,0表示不限制serverSocket.Listen(0);Console.WriteLine("服务器启动成功!");//成功接收一个客户端连接StartAccept();Console.ReadLine(); Console.WriteLine("服务器关闭!");serverSocket.Close();}private void StartAccept(){//成功接收一个客户端连接var acceptArgs = new SocketAsyncEventArgs();acceptArgs.Completed += AcceptCallback;if (!serverSocket.AcceptAsync(acceptArgs)){AcceptCallback(serverSocket, acceptArgs);}}//成功接收一个客户连接回调private void AcceptCallback(object sender, SocketAsyncEventArgs args){var mainSocket = (Socket)sender; if(args.SocketError == SocketError.Success){var socket = args.AcceptSocket;var clientInfo = new ClientInfo(socket);clientList.TryAdd(socket, clientInfo);Console.WriteLine($"有一个客户端连入!在线人数:{clientList.Count}");//开始接收客户端数据StartReceive(clientInfo);//继续等待接收下一个客户端连入args.AcceptSocket = null; // 必须重置if (!mainSocket.AcceptAsync(args)){AcceptCallback(mainSocket, args);};}else{Console.WriteLine($"Accept error: {args.SocketError}");}}//接收从客户端发来的消息private void StartReceive(ClientInfo clientInfo){var receiveArgs = new SocketAsyncEventArgs();var readBuff = new byte[1024]; receiveArgs.SetBuffer(readBuff,0,readBuff.Length);receiveArgs.Completed += ReceiveCallback;var socket = clientInfo.clientSocket;if (!socket.ReceiveAsync(receiveArgs)){ReceiveCallback(socket, receiveArgs);}}//接收数据异步回调private void ReceiveCallback(object sender, SocketAsyncEventArgs args){var clientSocket = (Socket)sender;var clientInfo = clientList[clientSocket];if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success){var dataLength = args.BytesTransferred;byte[] data = new byte[dataLength];Buffer.BlockCopy(args.Buffer, args.Offset, data, 0, dataLength);var recvMsg = Encoding.UTF8.GetString(data, 0, dataLength);Console.WriteLine($"接收客户端消息:{recvMsg}");//把接收的消息重新返回给客户端var sendBytes = Encoding.UTF8.GetBytes($"我收到了你的消息:{recvMsg}");StartSend(clientInfo, sendBytes);//继续接收客户端消息if (!clientSocket.ReceiveAsync(args)){ReceiveCallback(clientSocket, args);}}else{Console.WriteLine($"接收数据失败:{args.SocketError}");CloseClient(clientInfo);}}//发送消息给客户端private void StartSend(ClientInfo clientInfo, byte[] data){if (clientInfo == null) return;var sendArgs = new SocketAsyncEventArgs();sendArgs.SetBuffer(data,0,data.Length);sendArgs.Completed += SendCallback;var socket = clientInfo.clientSocket;if (!socket.SendAsync(sendArgs)){SendCallback(socket, sendArgs); } }//发送消息成功回调private void SendCallback(object sender,SocketAsyncEventArgs args){if(args.SocketError == SocketError.Success){Console.WriteLine($"成功告知客户端");}else{Console.WriteLine($"发送数据失败:{args.SocketError}");}}//关闭客户端连接private void CloseClient(ClientInfo clientInfo){if(clientInfo == null) return;clientInfo.clientSocket.Close();clientList.Remove(clientInfo.clientSocket);Console.WriteLine($"客户端断开连接了 在线人数:{clientList.Count}");}
}
Begin/End方法实现
客户端
using System;
using System.Collections.Generic;
using UnityEngine;
using System.Net;
using System.Net.Sockets;
using UnityEngine.UI;
using System.Text;public class SimpleClient : MonoBehaviour
{public InputField inputTxt;public Text txtShow;private Socket clientSocket;//只有在主线程中才能操作UI组件,由于异步回调是在其他线程执行的。通过缓存接收的数据,再在Update中进行读取并传递给UI组件private Queue<string> msgList = new Queue<string>();private StringBuilder _stringBuilder = new StringBuilder();private byte[] readBuff = new byte[1024];private void Update(){if (msgList.Count > 0){_stringBuilder.Clear();_stringBuilder.Append(txtShow.text);while (msgList.Count > 0){_stringBuilder.Append(msgList.Dequeue());_stringBuilder.Append("\n");}txtShow.text = _stringBuilder.ToString();}}//连接服务器public void OnConnect(){if (clientSocket != null) return;clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);clientSocket.BeginConnect(endPoint, ConnectCallback, clientSocket);}//连接服务器异步回调private void ConnectCallback(IAsyncResult ar){try{var socket = (Socket)ar.AsyncState;socket.EndConnect(ar);msgList.Enqueue("成功连接服务器!");OnReceive();}catch (SocketException ex){Debug.LogError($"连接服务器失败:{ex}");}}//发送数据public void OnSend(){if (clientSocket == null) return;var str = inputTxt.text;var sendBytes = Encoding.UTF8.GetBytes(str);clientSocket.BeginSend(sendBytes, 0, sendBytes.Length,SocketFlags.None, SendCallback, clientSocket);}//发送数据异步回调private void SendCallback(IAsyncResult ar){try{var socket = (Socket)ar.AsyncState;int count = socket.EndSend(ar);}catch (SocketException ex){Debug.LogError($"发送消息失败:{ex}");}}//接收数据public void OnReceive(){if (clientSocket == null) return;clientSocket.BeginReceive(readBuff,0,readBuff.Length,SocketFlags.None,ReceiveCallback,clientSocket);}//接收数据异步回调private void ReceiveCallback(IAsyncResult ar){try{var socket = (Socket)ar.AsyncState;int count = socket.EndReceive(ar);var recStr = Encoding.UTF8.GetString(readBuff, 0, count);msgList.Enqueue(recStr);clientSocket.BeginReceive(readBuff,0,readBuff.Length,SocketFlags.None,ReceiveCallback,clientSocket);}catch (SocketException ex){Debug.LogError($"接收数据失败:{ex}");}}//关闭Socketpublic void OnClose(){if (clientSocket == null) return;clientSocket.Shutdown(SocketShutdown.Both);clientSocket.Close();}
}
服务端
using System.Text;
using System.Net;
using System.Net.Sockets;public class EchoServer
{private Socket serverSocket;private class ClientInfo{public Socket clientSocket;public byte[] readBuff;public ClientInfo(Socket socket){clientSocket = socket; readBuff = new byte[1024];}}private Dictionary<Socket, ClientInfo> clientList;public void Start(){if (serverSocket == null){serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);}if (clientList == null){clientList = new Dictionary<Socket, ClientInfo>();}IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);//绑定服务器IP地址和端口号serverSocket.Bind(endPoint);//开启监听,等待客户端连接//参数backlog指定队列中最多可容纳等待接受的连接数,0表示不限制serverSocket.Listen(0);Console.WriteLine("服务器启动成功!");//成功接收一个客户端连接serverSocket.BeginAccept(AcceptCallback, serverSocket);Console.ReadLine(); Console.WriteLine("服务器关闭!");serverSocket.Close();}private void AcceptCallback(IAsyncResult ar){try{var socket = (Socket)ar.AsyncState;var client = socket.EndAccept(ar);var clientInfo = new ClientInfo(client);clientList.TryAdd(client, clientInfo); clientInfo.clientSocket.BeginReceive(clientInfo.readBuff, 0, clientInfo.readBuff.Length, SocketFlags.None, ReceiveCallback, clientInfo);socket.BeginAccept(AcceptCallback, socket);Console.WriteLine($"有一个客户端连入!在线人数:{clientList.Count}");}catch (SocketException ex){Console.WriteLine($"发送消息失败:{ex}");}}//发送数据异步回调private void SendCallback(IAsyncResult ar){try{var clientInfo = (ClientInfo)ar.AsyncState;int count = clientInfo.clientSocket.EndSend(ar);Console.WriteLine($"发送消息成功!");}catch (SocketException ex){Console.WriteLine($"发送消息失败:{ex}");}}//接收数据异步回调private void ReceiveCallback(IAsyncResult ar){try{var clientInfo = (ClientInfo)ar.AsyncState;int count = clientInfo.clientSocket.EndReceive(ar);//如果收到客户端关闭连接信号:“if(count)== 0”,断开连接if(count == 0){clientInfo.clientSocket.Close();clientList.Remove(clientInfo.clientSocket);Console.WriteLine($"客户端断开连接了 在线人数:{clientList.Count}");return;}var recStr = Encoding.UTF8.GetString(clientInfo.readBuff, 0, count);Console.WriteLine($"客户端发来消息:{recStr}");var sendBytes = Encoding.UTF8.GetBytes($"我收到了你的消息:{recStr}");clientInfo.clientSocket.BeginSend(sendBytes, 0, sendBytes.Length, SocketFlags.None, SendCallback, clientInfo);clientInfo.clientSocket.BeginReceive(clientInfo.readBuff, 0, clientInfo.readBuff.Length, SocketFlags.None, ReceiveCallback, clientInfo);}catch (SocketException ex){Console.WriteLine($"接收数据失败:{ex}");}}
}
到底了~