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
// 准备接受下一个连接 AcceptAsync
e.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; // 是否重用socket
if (!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;
}
//关闭Socket
public 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);
}
}
//关闭Socket
public 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();
}
}
//关闭Socket
public 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}");
}
}
//关闭Socket
public 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}");
}
}
}
到底了~