网络开发基础(游戏)之 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}");}}
}
到底了~
相关文章:
网络开发基础(游戏)之 Socket API
Socket简介 Socket (套接字)是网络编程的基础,在 C# 中通过 System.Net.Sockets 命名空间提供了一套完整的 API 来实现网络通信。 网络上的两个程序通过一个双向的通信连接实现数据交换, 这个连接的一端称为一个Socket。 一个Socket包含了进行网络通信必…...
行为审计软件:企业合规与内部监控的数字守门人
在当今高度数字化的商业环境中,企业运营产生的电子数据呈指数级增长,员工行为也日益复杂多样。行为审计软件应运而生,成为现代企业管理不可或缺的工具。这类软件通过系统化记录、分析和报告组织内部用户活动,帮助企业管理风险、确…...
bat脚本转换为EXE应用程序文件
很多时候,我们使用电脑时会编辑bat脚本文件 很多时候,我们制作的玩笑,病毒也会使用这个格式. 但这个格式也有很多缺点 1,如果是需要管理员运行的程序,需要费劲的自己使用管理员身份运行 2,文件并不为大家所熟知,认同度不高 3,可以非常轻松的看到原代…...
细说STM32单片机FreeRTOS任务管理API函数vTaskList()的使用方法
目录 一、函数vTaskList() 1、 函数说明 2、返回的字符串表格说明 3、函数的使用方法 二、 vTaskList()的应用示例 1、示例功能、项目设置 2、软件设计 (1)main.c (2)freertos.c (3)FreeRTOSConf…...
DNS主从同步
安装软件 主配置中完成DNS解析:192.168.131.134 [rootlocalhost ~]# mount /dev/sr0 /mnt [rootlocalhost ~]# vim /etc/yum.repos.d/myrepo.repo [base] namebase baseurl/mnt/BaseOS gpgchcek0 enable1 [base2] namebase2 baseurl/mnt/AppStream gpgchcek0 enab…...
双指针算法(部分例题解析)
快慢指针左右指针 前言 双指针,它通过设置两个指针来遍历数据,从而实现高效的查找、排序、去重等操作。双指针算法的核心在于通过合理地移动这两个指针,减少不必要的遍历,提高算法的效率。 283. 移动零 - 力扣(LeetCo…...
解决Windows打印问题的集成软件
家里或公司电脑经常为连不上打印机而烦恼,今天给大家推荐一款修复打印工具,该工具是采用易语言开发的集成化打印机故障修复软件,专为解决 Windows 系统(含 32/64 位 Windows 7/10/11)中因权限配置、服务异常、补丁缺失…...
神经网络模型应用到机器学习时的难点
虽然神经网络具有非常强的表达能力,但是当应用神经网络模型到机器学习时依然存在一些难点问题。主要分为两大类: 优化问题:深度神经网络的优化十分困难。 首先,神经网络的损失函数是一个非凸函数,找到全局最优解通常比较困难。 …...
警惕阿里云中的yum update操作不当导致:/sbin/init被清空导致Linux无法正常启动
由于使用阿里云进行部署测试,因而会对yum update进行操作,这两天更新了systemd-239-82.0.3.4.al8.2.x86_64,但存在报错,然后进行yum history undo和清空yum cache,但出现操作Linux命令行无效。具体来说,几个…...
关系型数据库MYSQL(续)
目录 三.MySQL事务原理分析 1.事务是什么? 2.执行事务的目的是什么? 3.事务是由什么组成的? 4.事务的特征是什么? 5.事务控制语句 6.ACID特性 6.1原子性(A) 6.2隔离性(I) …...
WInform当今技术特性分析
Windows Forms (WinForms) 技术特性分析 引言 Windows Forms (WinForms) 作为微软最早推出的基于.NET的图形用户界面开发框架,已经存在了20多年。在如今充满了各种现代UI框架的软件开发生态系统中,WinForms仍然保持着其独特的地位。本文将深入分析WinF…...
day46——两数之和-输入有序数组(LeetCode-167)
题目描述 给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 < index1 < index2 &l…...
Python 一等函数( 把函数视作对象)
把函数视作对象 示例 5-1 中的控制台会话表明,Python 函数是对象。这里我们创建了一 个函数,然后调用它,读取它的 doc 属性,并且确定函数对象本 身是 function 类的实例。 示例 5-1 创建并测试一个函数,然后读取它的…...
运筹学之模拟退火
目录 一、历史二、精髓思想三、案例与代码实现 一、历史 问:谁在什么时候提出模拟退火?答:模拟退火算法(Simulated Annealing,SA)是由斯图尔特柯尔斯基(Scott Kirkpatrick) 等人在 …...
PHP实现简单的爬虫功能
<?php// 目标URL $url https://example.com;// 初始化cURL $ch curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_USERAGENT, Mozilla/5…...
树莓派5-开发应用笔记
0.树莓派系统目录 /home:用户目录。 除了root用户外,其他所有的使用者的数据都存放在这个目录下,在树莓派的系统中,/home目录中有一个pi的子目录,这个就是pi用户的默认目录。 /bin: 主要放置系统的必备执行文件目录。 …...
PostgreSQL 通过 copy 命令导入几何数据 及 通过 CopyManager.copyIn() 导入几何数据
COPY命令介绍 copy是postgresql提供的一个专门用于快速导入导出数据的命令,通常用于从文件(TXT、CSV等)或标准输入输出中读取或写入数据。适合批量导入导出数据,速度快。 默认情况下,如果在处理过程中遇到错误,COPY将失败。 COPY只能用于表,不能用于视图!!! COPY…...
8.5/Q1,Charls最新文章解读
文章题目:Atherogenic index of plasma, high sensitivity C-reactive protein and incident diabetes among middle-aged and elderly adults in China: a national cohort study DOI:10.1186/s12933-025-02653-4 中文标题:中国中老年人群血…...
k8s 调整Node节点 Max_Pods
默认情况下,Kubernetes集群中一个Node最多能起110个Pod。 这是基于性能和资源管理的考虑,以确保Kubernetes集群的稳定性和可靠性。 查看kht125节点上支持的最大pod数量: kubectl describe node kht125 | grep -i “Capacity|Allocatable” -A 6 调整…...
深度补全网络:CSPN++ 有哪些开源项目
关于 CSPN(Convolutional Spatial Propagation Network) 的开源项目,目前官方或社区维护的完整实现较为有限,但以下资源可作为研究深度补全任务的参考: 1. 官方实现 & 相关论文 原始论文与代码 CSPN 的…...
使用Service发布前后端应用程序
使用Service发布前后端应用程序 文章目录 使用Service发布前后端应用程序[toc]一、创建并发布后端应用程序二、创建并发布前端应用程序三、通过前端发送流量进行测试 部署前端(Frontend)微服务和后端(Backend)微服务是比较常见的应…...
Ubuntu20.04下Docker方案实现多平台SDK编译
0 前言 熟悉嵌入式平台Linux SDK编译流程的小伙伴都知道,假如平台a要求必须在Ubuntu18.04下编译,平台b要求要Ubuntu22.04的环境,那我只有Ubuntu20.04,或者说我的电脑硬件配置最高只能支持Ubuntu20.04怎么办?强行在Ubuntu20.04下编译,编又编不过,换到旧版本我又不愿意,…...
-SSRF 服务端请求Gopher 伪协议无回显利用黑白盒挖掘业务功能点
1 、 SSRF 漏洞原理 SSRF(Server-Side Request Forgery: 服务器端请求伪造 ) 一种由攻击者构造形成由服务端发起请求的一个安全漏洞 ; 一般情况下, SSRF 攻击的目标是从外网无法访问的内部系统。 (正是因为它是由服务端发起的,所以它能…...
事件冒泡与捕获
一、事件流基础:事件冒泡与捕获的起源 事件流概念 事件发生时在DOM节点上的传播顺序,触发一个节点的事件会连锁触发相关节点的事件。 两种对立模型 事件捕获(微软提出):事件从文档根节点(如document&#…...
《AI大模型应知应会100篇》第27篇:模型温度参数调节:控制创造性与确定性
第27篇:模型温度参数调节:控制创造性与确定性 摘要 在大语言模型的使用中,“温度”(Temperature)是一个关键参数,它决定了模型输出的创造性和确定性之间的平衡。通过调整温度参数,您可以根据任…...
聊聊Doris的数据模型,如何用结构化设计解决实时分析难题
传统 OLAP 系统的局限 在大数据实时分析领域,数据模型设计直接决定了系统的查询性能、存储效率与业务适配性。Apache Doris作为新一代MPP分析型数据库,通过独创的多模型融合架构,在业内率先实现了"一份数据支持多种分析范式"的能力…...
LNA设计
设计目的 为后级提供足够的增益以克服后级电路噪声 尽可能小的噪声和信号失真 确保输入和输出端的阻抗匹配 确保信号线性度 评价标准 噪声系数 功率增益 工作频率和带宽 输入信号功率动态范围 端口电压驻波比 稳定性 基于SP模型的LNA设计 直流分析 S参数分析 设计指标 …...
小红书爬虫,小红书api,小红书数据挖掘
背景: 小红书(Xiaohongshu)是一款结合社交、购物和内容分享的移动应用,近年来在中国以及全球范围内拥有大量的用户群体。小红书上的内容包括用户的消费体验、生活方式、旅行分享、时尚搭配等。通过这些内容,用户可以了…...
C++ STL 环形队列模拟实现
C STL 环形队列模拟实现 下面是一个使用C STL实现的环形队列(Circular Queue)的完整示例: #include <iostream> #include <vector> #include <stdexcept>template <typename T> class CircularQueue { private:std…...
C++中unique_lock和lock_guard区别
目录 1.自动锁定与解锁机制 2.灵活性 3.所有权转移 4.可与条件变量配合使用 5.性能开销 在 C 中,std::unique_lock 和 std::lock_guard 都属于标准库 <mutex> 中的互斥锁管理工具,用于简化互斥锁的使用并确保线程安全。但它们存在一些显著区别…...







