前言
使用C#编写Socket通信代码方式有很多种,对面不同的编码方式,一直是困扰我们的一个难题。于是为了让Socket通信成为我们项目开发的基础设施,而不是绊脚石,我重新整理了网上Socket通信的不同编码,方便在以后的项目中使用。
实例链接:https://download.csdn.net/download/lvxingzhe3/88738052
Socket定义
Socket:套接字(socket)是一个抽象层,应用程序之间通信不会和传输层直接建立联系,而是有一个能够连接应用层和传输层之间的套件,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。
Socket通信实现方式
- 使用Socket
- 使用TcpListener
- 使用SocketAsyncEventArgs
使用Socket
服务端程序:
class SocketServer
{
static void Main(string[] args)
{
//1.创建Socket流模式
Socket tcpServe = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2.绑定IP跟端口号
IPAddress ipAddress = new IPAddress(new byte[] {172,27,1,123 });//创建IP地址
//IPEndPoint是对IP+端口号加了一层封装的类,7788是端口号
EndPoint point = new IPEndPoint(ipAddress,7788);
//向操作系统申请一个可用的IP和端口号用来做通信
tcpServe.Bind(point);
//3.开始监听(等待客户端做连接)
Console.WriteLine("开始监听了");
tcpServe.Listen(100);//参数是最大连接数
//暂停当前线程,直到有一个客户端连接过来,进行下面的代码
Socket clientSocket= tcpServe.Accept();
//使用返回的Socket和客户端做通信
Console.WriteLine("开始发送消息了");
//4.发送给客户端消息
string message = "hello,欢迎你";
//对字符串做编码,得到一个字符串的字节数组
byte[] data =Encoding.UTF8.GetBytes(message);
clientSocket.Send(data);
//5.接收来自客户端的消息
byte[] b = new byte[1024];
int length = clientSocket.Receive(b);
string message3 = Encoding.UTF8.GetString(b,0,length);
Console.WriteLine("接收一个从客户端发来的消息:"+message3);
Console.ReadKey();
}
}
客户端程序:
class SocketClient
{
static void Main(string[] args)
{
//1.创建Socket
Socket tcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2.发起建立连接的请求
//可以把一个字符串的IP地址转换为一个IPAddress的对象
IPAddress add = IPAddress.Parse("172.27.1.123");
EndPoint point = new IPEndPoint(add, 7788);
//通过IP:端口号定位到一个要连接的服务器端
tcpClient.Connect(point);
//3.接收来自服务器端的消息
byte[] data = new byte[1024];
//这里传递一个byte数组,实际上这个byte数组用来接收数据
int length = tcpClient.Receive(data);
//length返回值表示接收了多少数据
//只把接收到的数据进行转化
string message = Encoding.UTF8.GetString(data,0,length);
Console.WriteLine(message);
//4.发送给服务器端消息
string message2 = Console.ReadLine();
byte[] data1 = Encoding.UTF8.GetBytes(message2);
tcpClient.Send(data1);
Console.ReadKey();
}
}
使用TcpListener
服务端程序:
class User//创建客户端类
{
public TcpClient client { get; private set; }
//public BinaryReader br { get; private set; }
//public BinaryWriter bw { get; private set; }
public StreamReader br { get; private set; }
public StreamWriter bw { get; private set; }
public string userName { get; set; }//设备名
public string remoteEndPoint { get; set; }//设备远程地址
public User(TcpClient client)
{
this.client = client;
//this.client.ReceiveTimeout = 60000;
NetworkStream networkStream = client.GetStream();
//networkStream.ReadTimeout = 5000;
//br = new BinaryReader(networkStream);//将消息长度与消息一起发送
//bw = new BinaryWriter(networkStream);
br = new StreamReader(networkStream);//使用特殊标记分隔消息
bw = new StreamWriter(networkStream);
userName = null;
remoteEndPoint = client.Client.RemoteEndPoint.ToString();
}
public void Close()
{
br.Close();
bw.Close();
client.Close();
}
}
using System.Windows.Forms;
using System.Configuration;
using System.Collections.Specialized;
using System.Xml;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.Diagnostics;
using System.Collections;
using System.IO;
using System.Runtime.InteropServices;
private TcpListener myListener;//新建TcpListenser对象
private List<User> userList = new List<User>();// 保存连接的所有用户
private string remoteDevice = null;//远程目标地址
private int indexDevice = 0;//远程目标地址索引
User deviceUser = null;//定义目标用户
Thread myThread=null;//新建一个监听线程
/// <summary>
/// 初始化服务端
/// </summary>
private void ServerInit()
{
try
{
string ip = null;
string name = Dns.GetHostName();
IPAddress[] ipadrlist = Dns.GetHostAddresses(name);
foreach (IPAddress ipa in ipadrlist)
{
if (ipa.AddressFamily == AddressFamily.InterNetwork)
{
ip = ipa.ToString();
}
}
myListener = new TcpListener(IPAddress.Parse(ip), SystemInfo.Port);
myListener.Start();
//创建一个线程监客户端连接请求
myThread = new Thread(ListenClientConnect);
myThread.Start();
}
catch (Exception ipEX)
{
MessageBox.Show(ipEX.Message);
}
}
/// <summary>
/// 接收客户端连接
/// </summary>
private void ListenClientConnect()
{
TcpClient newClient = null;
while (true)
{
try
{
newClient = myListener.AcceptTcpClient(); //当接收到客户端连接
}
catch
{
//当单击‘停止监听’或者退出此窗体时 AcceptTcpClient()会产生异常
//因此可以利用此异常退出循环
break;
}
//每接收一个客户端连接,就创建一个对应的线程循环接收该客户端发来的信息;
User user = new User(newClient);
Thread threadReceive = new Thread(ReceiveData);
threadReceive.Start(user);
userList.Add(user);
//SendToClient(user, "ok");
try
{
//ListviewAdd(user.remoteEndPoint);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
/// <summary>
/// 处理接收的客户端信息
/// </summary>
/// <param name="userState">客户端信息</param>
private void ReceiveData(object userState)
{
User user = (User)userState;
TcpClient client = user.client;
while (true)//当没有退出
{
string receiveString = null;
try
{
//从网络流中读出字符串,此方法会自动判断字符串长度前缀
receiveString = user.br.ReadLine();
}
catch (Exception)//user失去联系
{
//RemoveUser(user);
break;
}
switch (receiveString)
{
case "ok":
break;
default: ;
break;
}
}
}
/// <summary>
/// 发送 message 给 user
/// </summary>
/// <param name="user">指定发给哪个用户</param>
/// <param name="message">信息内容</param>
private void SendToClient(User user, string message)
{
try
{
//将字符串写入网络流,此方法会自动附加字符串长度前缀
//user.bw.WriteLine(message);
user.bw.Write(message);
user.bw.Flush();
}
catch
{
}
}
/// <summary>
/// 移除用户
/// </summary>
/// <param name="user">指定要移除的用户</param>
private void RemoveUser(User user)
{
userList.Remove(user);
user.Close();
}
try
{
ServerInit();//调用Socket服务端监听程序
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
客户端程序:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using Socket_Cilent;
using System.Threading;
using System.IO;
private string ServerIP; //IP
private int port; //端口
private bool isExit = false;
private TcpClient client;
private BinaryReader br;
private BinaryWriter bw;
private void ReceiveData()//接收函数
{
string receiveString = null;
while (isExit == false)
{
try
{
//从网络流中读出字符串
//此方法会自动判断字符串长度前缀,并根据长度前缀读出字符串
receiveString = br.ReadString();
}
catch
{
if (isExit == false)
{
MessageBox.Show("与服务器失去连接");
}
break;
}
txt_recStr.Text += receiveString;
//txt_recStr.AppendText(receiveString);
}
Application.Exit();
}
try
{
ServerIP = txt_IP.Text.Trim(); //设定IP
port = Convert.ToInt32(txt_Port.Text.Trim()); //设定端口
//此处为方便演示,实际使用时要将Dns.GetHostName()改为服务器域名
//IPAddress ipAd = IPAddress.Parse("182.150.193.7");
client = new TcpClient();
client.Connect(IPAddress.Parse(ServerIP), port);
MessageBox.Show("连接成功");
}
catch (Exception ex)
{
MessageBox.Show("连接失败,原因:" + ex.Message);
}
//获取网络流
NetworkStream networkStream = client.GetStream();
//将网络流作为二进制读写对象
br = new BinaryReader(networkStream);
bw = new BinaryWriter(networkStream);
Thread threadReceive = new Thread(new ThreadStart(ReceiveData));
threadReceive.IsBackground = true;
threadReceive.Start();
SocketAsyncEventArgs
SocketAsyncEventArgs是一个套接字操作的类,主要作用是实现socket消息的异步接收和发送。由专用的高性能套接字应用程序使用的替代异步模式,主要功能是避免在大容量异步套接字 I/O 期间重复分配和同步对象。
服务端程序:
internal class AsyncUserToken
{
/// <summary>
/// 客户端IP地址
/// </summary>
public IPAddress IPAddress { get; set; }
/// <summary>
/// 远程地址
/// </summary>
public EndPoint Remote { get; set; }
/// <summary>
/// 通信SOKET
/// </summary>
public Socket Socket { get; set; }
/// <summary>
/// 连接时间
/// </summary>
public DateTime ConnectTime { get; set; }
/// <summary>
/// 数据缓存区
/// </summary>
public List<byte> Buffer { get; set; }
public string username { get; set; }
public AsyncUserToken()
{
this.Buffer = new List<byte>();
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace Socket_Server
{
/// <summary>
/// Based on example from http://msdn2.microsoft.com/en-us/library/bb517542.aspx
/// This class creates a single large buffer which can be divided up
/// and assigned to SocketAsyncEventArgs objects for use with each
/// socket I/O operation.
/// This enables bufffers to be easily reused and guards against
/// fragmenting heap memory.
/// </summary>
/// <remarks>The operations exposed on the BufferManager class are not thread safe.</remarks>
internal sealed class BufferManager
{
/// <summary>
/// The underlying Byte array maintained by the Buffer Manager.
/// </summary>
private Byte[] buffer;
/// <summary>
/// Size of the underlying Byte array.
/// </summary>
private Int32 bufferSize;
/// <summary>
/// Current index of the underlying Byte array.
/// </summary>
private Int32 currentIndex;
/// <summary>
/// Pool of indexes for the Buffer Manager.
/// </summary>
private Stack<Int32> freeIndexPool;
/// <summary>
/// The total number of bytes controlled by the buffer pool.
/// </summary>
private Int32 numBytes;
/// <summary>
/// Instantiates a buffer manager.
/// </summary>
/// <param name="totalBytes">The total number of bytes for the buffer pool.</param>
/// <param name="bufferSize">Size of the buffer pool.</param>
internal BufferManager(Int32 totalBytes, Int32 bufferSize)
{
this.numBytes = totalBytes;
this.currentIndex = 0;
this.bufferSize = bufferSize;
this.freeIndexPool = new Stack<Int32>();
}
/// <summary>
/// Removes the buffer from a SocketAsyncEventArg object.
/// This frees the buffer back to the buffer pool.
/// </summary>
/// <param name="args">SocketAsyncEventArgs where is the buffer to be removed.</param>
internal void FreeBuffer(SocketAsyncEventArgs args)
{
this.freeIndexPool.Push(args.Offset);
args.SetBuffer(null, 0, 0);
}
/// <summary>
/// Allocates buffer space used by the buffer pool.
/// </summary>
internal void InitBuffer()
{
// Create one big large buffer and divide that out to each SocketAsyncEventArg object.
this.buffer = new Byte[this.numBytes];
}
/// <summary>
/// Assigns a buffer from the buffer pool to the specified SocketAsyncEventArgs object.
/// </summary>
/// <param name="args">SocketAsyncEventArgs where is the buffer to be allocated.</param>
/// <returns>True if the buffer was successfully set, else false.</returns>
internal Boolean SetBuffer(SocketAsyncEventArgs args)
{
if (this.freeIndexPool.Count > 0)
{
args.SetBuffer(this.buffer, this.freeIndexPool.Pop(), this.bufferSize);
}
else
{
if ((this.numBytes - this.bufferSize) < this.currentIndex)
{
return false;
}
args.SetBuffer(this.buffer, this.currentIndex, this.bufferSize);
this.currentIndex += this.bufferSize;
}
return true;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace Socket_Server
{
/// <summary>
/// Based on example from http://msdn2.microsoft.com/en-us/library/system.net.sockets.socketasynceventargs.socketasynceventargs.aspx
/// Represents a collection of reusable SocketAsyncEventArgs objects.
/// </summary>
internal sealed class SocketAsyncEventArgsPool
{
/// <summary>
/// Pool of SocketAsyncEventArgs.
/// </summary>
Stack<SocketAsyncEventArgs> pool;
/// <summary>
/// Initializes the object pool to the specified size.
/// </summary>
/// <param name="capacity">Maximum number of SocketAsyncEventArgs objects the pool can hold.</param>
internal SocketAsyncEventArgsPool(Int32 capacity)
{
this.pool = new Stack<SocketAsyncEventArgs>(capacity);
}
/// <summary>
/// The number of SocketAsyncEventArgs instances in the pool.
/// </summary>
internal Int32 Count
{
get { return this.pool.Count; }
}
/// <summary>
/// Removes a SocketAsyncEventArgs instance from the pool.
/// </summary>
/// <returns>SocketAsyncEventArgs removed from the pool.</returns>
internal SocketAsyncEventArgs Pop()
{
lock (this.pool)
{
return this.pool.Pop();
}
}
/// <summary>
/// Add a SocketAsyncEventArg instance to the pool.
/// </summary>
/// <param name="item">SocketAsyncEventArgs instance to add to the pool.</param>
internal void Push(SocketAsyncEventArgs item)
{
if (item == null)
{
throw new ArgumentNullException("Items added to a SocketAsyncEventArgsPool cannot be null");
}
lock (this.pool)
{
this.pool.Push(item);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Socket_Server
{
/// <summary>
/// Based on example from http://msdn2.microsoft.com/en-us/library/system.net.sockets.socketasynceventargs.aspx
/// Implements the connection logic for the socket server.
/// After accepting a connection, all data read from the client
/// is sent back to the client with an aditional word, to demonstrate how to manipulate the data.
/// The read and echo back to the client pattern is continued until the client disconnects.
/// </summary>
internal sealed class SocketListener
{
/// <summary>
/// 所有套接字操作的大型可重用缓冲区集
/// </summary>
private BufferManager bufferManager;
/// <summary>
/// 套接字用于侦听传入的连接请求
/// </summary>
private Socket listenSocket;
/// <summary>
/// 客户端最大连接数
/// </summary>
private int maxNumConnections;
/// <summary>
/// 用于写入、读取和接受套接字操作的可重用SocketAsyncEventArgs对象池
/// </summary>
private SocketAsyncEventArgsPool readWritePool;
/// <summary>
///读取、写入(不为接受分配缓冲区空间)
/// </summary>
private const int opsToPreAlloc = 2;
/// <summary>
/// 连接到服务器的客户端总数
/// </summary>
private int numConnectedSockets;
/// <summary>
/// 用于同步服务器执行的互斥
/// </summary>
private static Mutex mutex = new Mutex();
/// <summary>
/// 控制连接到服务器的客户端总数
/// </summary>
private Semaphore semaphoreAcceptedClients;
/// <summary>
/// 服务器接收的总字节数计数器
/// </summary>
private int totalBytesRead;
/// <summary>
/// 客户端列表
/// </summary>
List<AsyncUserToken> m_clients;
/// <summary>
/// 获取客户端列表
/// </summary>
public List<AsyncUserToken> ClientList { get { return m_clients; } }
/// <summary>
/// 接收到客户端字符串委托
/// </summary>
public Action<string> ReceiveClientStrData;
/// <summary>
/// 心跳字符
/// </summary>
private static string HeartBeatStr = "pop";
/// <summary>
/// 接收到客户端的字节数据事件
/// </summary>
public event OnReceiveData ReceiveClientData;
/// <summary>
/// 客户端连接数量变化时触发
/// </summary>
/// <param name="num">当前增加客户的个数(用户退出时为负数,增加时为正数,为1)</param>
/// <param name="token">增加用户的信息</param>
public delegate void OnClientNumberChange(int num, AsyncUserToken token);
/// <summary>
/// 接收到客户端的数据
/// </summary>
/// <param name="token">客户端</param>
/// <param name="buff">客户端数据</param>
public delegate void OnReceiveData(AsyncUserToken token, byte[] buff);
/// <summary>
/// 客户端连接数量变化事件
/// </summary>
public event OnClientNumberChange ClientNumberChange;
public delegate void StopSeverEvent();
/// <summary>
/// 服务停止事件
/// </summary>
public event StopSeverEvent ServerStopedEvent;
/// <summary>
/// Create an uninitialized server instance.
/// To start the server listening for connection requests
/// call the Init method followed by Start method.
/// </summary>
/// <param name="maxNumConnections">Maximum number of connections to be handled simultaneously.</param>
/// <param name="receiveBufferSize">Buffer size to use for each socket I/O operation.</param>
internal SocketListener(int maxNumConnections, int receiveBufferSize)
{
this.totalBytesRead = 0;
this.numConnectedSockets = 0;
this.maxNumConnections = maxNumConnections;
// Allocate buffers such that the maximum number of sockets can have one outstanding read and
// write posted to the socket simultaneously .
this.bufferManager = new BufferManager(receiveBufferSize * maxNumConnections * opsToPreAlloc,
receiveBufferSize);
this.readWritePool = new SocketAsyncEventArgsPool(maxNumConnections);
this.semaphoreAcceptedClients = new Semaphore(maxNumConnections, maxNumConnections);
}
/// <summary>
/// Initializes the server by preallocating reusable buffers and
/// context objects. These objects do not need to be preallocated
/// or reused, but it is done this way to illustrate how the API can
/// easily be used to create reusable objects to increase server performance.
/// </summary>
internal void Init()
{
// Allocates one large Byte buffer which all I/O operations use a piece of. This guards
// against memory fragmentation.
this.bufferManager.InitBuffer();
m_clients = new List<AsyncUserToken>();
// Preallocate pool of SocketAsyncEventArgs objects.
SocketAsyncEventArgs readWriteEventArg;
for (int i = 0; i < this.maxNumConnections; i++)
{
// Preallocate a set of reusable SocketAsyncEventArgs.
readWriteEventArg = new SocketAsyncEventArgs();
readWriteEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(OnIOCompleted);
readWriteEventArg.UserToken = new AsyncUserToken();
// Assign a Byte buffer from the buffer pool to the SocketAsyncEventArg object.
this.bufferManager.SetBuffer(readWriteEventArg);
// Add SocketAsyncEventArg to the pool.
this.readWritePool.Push(readWriteEventArg);
}
}
/// <summary>
/// Starts the server such that it is listening for incoming connection requests.
/// </summary>
/// <param name="localEndPoint">The endpoint which the server will listening for connection requests on.</param>
internal void Start(Object data)
{
int port = (int)data;
m_clients.Clear();
// Get host related information.
IPAddress[] addressList = Dns.GetHostEntry(Environment.MachineName).AddressList;
// Get endpoint for the listener.
IPEndPoint localEndPoint = new IPEndPoint(addressList[addressList.Length - 1], port);
// Create the socket which listens for incoming connections.
this.listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
if (localEndPoint.AddressFamily == AddressFamily.InterNetworkV6)
{
// Set dual-mode (IPv4 & IPv6) for the socket listener.
// 27 is equivalent to IPV6_V6ONLY socket option in the winsock snippet below,
// based on http://blogs.msdn.com/wndp/archive/2006/10/24/creating-ip-agnostic-applications-part-2-dual-mode-sockets.aspx
this.listenSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false);
this.listenSocket.Bind(new IPEndPoint(IPAddress.IPv6Any, localEndPoint.Port));
}
else
{
// Associate the socket with the local endpoint.
this.listenSocket.Bind(localEndPoint);
}
// Start the server with a listen backlog of max connections.
this.listenSocket.Listen(this.maxNumConnections);
// Post accepts on the listening socket.
this.StartAccept(null);
mutex.WaitOne();
}
/// <summary>
/// Stop the server.
/// </summary>
internal void Stop()
{
AsyncUserToken[] clients = m_clients.ToArray();
for (int i = 0; i < clients.Length; i++)
{
try
{
AsyncUserToken token = clients[i];
token.Socket.Shutdown(SocketShutdown.Both);
}
catch (Exception) { }
}
try
{
if (m_clients.Count > 0)
listenSocket.Shutdown(SocketShutdown.Both);
}
catch (Exception e)//当socket 无连接时(即没有可用已连接的client)
{
// listenSocket.Disconnect(false);///因为无可用的socket连接,此方法不可用
Console.WriteLine(e.Message);
}
finally
{
if (listenSocket != null)
{
listenSocket.Close();
}
//通知界面,server已经停止
if (ServerStopedEvent != null)
ServerStopedEvent();
lock (m_clients)
{
m_clients.Clear();
}
mutex.ReleaseMutex();
}
}
/// <summary>
/// Close the socket associated with the client.
/// </summary>
/// <param name="e">SocketAsyncEventArg associated with the completed send/receive operation.</param>
private void CloseClientSocket(SocketAsyncEventArgs e)
{
AsyncUserToken token = e.UserToken as AsyncUserToken;
AsyncUserToken userToken = ClientList.Find(t => t.Remote == token.Remote);
if(userToken!=null)
{
lock (m_clients) { m_clients.Remove(userToken); }
}
//如果有事件,则调用事件,发送客户端数量变化通知
if (ClientNumberChange != null)
ClientNumberChange(-1, token);
try
{
token.Socket.Shutdown(SocketShutdown.Send);
}
catch (Exception ex)
{
// Throws if client process has already closed.
Console.WriteLine(ex.ToString());
}
token.Socket.Close();
Interlocked.Decrement(ref this.numConnectedSockets);
// Decrement the counter keeping track of the total number of clients connected to the server.
this.semaphoreAcceptedClients.Release();
Console.WriteLine("A client has been disconnected from the server. There are {0} clients connected to the server", this.numConnectedSockets);
e.UserToken = new AsyncUserToken();
// Free the SocketAsyncEventArg so they can be reused by another client.
this.readWritePool.Push(e);
}
/// <summary>
/// Begins an operation to accept a connection request from the client.
/// </summary>
/// <param name="acceptEventArg">The context object to use when issuing
/// the accept operation on the server's listening socket.</param>
private void StartAccept(SocketAsyncEventArgs acceptEventArg)
{
if (acceptEventArg == null)
{
acceptEventArg = new SocketAsyncEventArgs();
acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
}
else
{
// Socket must be cleared since the context object is being reused.
acceptEventArg.AcceptSocket = null;
}
this.semaphoreAcceptedClients.WaitOne();
Boolean willRaiseEvent = this.listenSocket.AcceptAsync(acceptEventArg);
if (!willRaiseEvent)
{
this.ProcessAccept(acceptEventArg);
}
}
/// <summary>
/// Process the accept for the socket listener.
/// </summary>
/// <param name="e">SocketAsyncEventArg associated with the completed accept operation.</param>
private void ProcessAccept(SocketAsyncEventArgs e)
{
try
{
if(!e.AcceptSocket.Connected)
{
return;
}
if (e.BytesTransferred > 0)
{
Interlocked.Increment(ref this.numConnectedSockets);
Console.WriteLine("Client connection accepted. There are {0} clients connected to the server",
this.numConnectedSockets);
}
// Get the socket for the accepted client connection and put it into the
// ReadEventArg object user token.
SocketAsyncEventArgs readEventArgs = this.readWritePool.Pop();
AsyncUserToken userToken = (AsyncUserToken)readEventArgs.UserToken;
//readEventArgs.UserToken = e.AcceptSocket;
userToken.username = "Mtt";
userToken.Socket = e.AcceptSocket;
userToken.ConnectTime = DateTime.Now;
userToken.Remote = e.AcceptSocket.RemoteEndPoint;
userToken.IPAddress = ((IPEndPoint)(e?.AcceptSocket?.RemoteEndPoint))?.Address;
if(!m_clients.Exists(t=>t.Remote==userToken.Remote))
{
lock (m_clients) { m_clients.Add(userToken); }
}
if (ClientNumberChange != null)
ClientNumberChange(1, userToken);
// As soon as the client is connected, post a receive to the connection.
Boolean willRaiseEvent = e.AcceptSocket.ReceiveAsync(readEventArgs);
if (!willRaiseEvent)
{
this.ProcessReceive(readEventArgs);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
// Accept the next connection request
if (e.SocketError == SocketError.OperationAborted) return;
StartAccept(e);
}
/// <summary>
/// Callback method associated with Socket.AcceptAsync
/// operations and is invoked when an accept operation is complete.
/// </summary>
/// <param name="sender">Object who raised the event.</param>
/// <param name="e">SocketAsyncEventArg associated with the completed accept operation.</param>
private void OnAcceptCompleted(object sender, SocketAsyncEventArgs e)
{
this.ProcessAccept(e);
}
/// <summary>
/// Callback called whenever a receive or send operation is completed on a socket.
/// </summary>
/// <param name="sender">Object who raised the event.</param>
/// <param name="e">SocketAsyncEventArg associated with the completed send/receive operation.</param>
private void OnIOCompleted(object sender, SocketAsyncEventArgs e)
{
// Determine which type of operation just completed and call the associated handler.
switch (e.LastOperation)
{
case SocketAsyncOperation.Receive:
this.ProcessReceive(e);
break;
case SocketAsyncOperation.Send:
this.ProcessSend(e);
break;
default:
throw new ArgumentException("The last operation completed on the socket was not a receive or send");
}
}
/// <summary>
/// This method is invoked when an asynchronous receive operation completes.
/// If the remote host closed the connection, then the socket is closed.
/// If data was received then the data is echoed back to the client.
/// </summary>
/// <param name="e">SocketAsyncEventArg associated with the completed receive operation.</param>
private void ProcessReceive(SocketAsyncEventArgs e)
{
try
{
AsyncUserToken token = (AsyncUserToken)e.UserToken;
// check if the remote host closed the connection
if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
{
int bytesTransferred = e.BytesTransferred;
// Get the message received from the listener.
string received = Encoding.UTF8.GetString(e.Buffer, e.Offset, bytesTransferred);
if(bytesTransferred==3 && received.Equals(HeartBeatStr))
{
Console.WriteLine($"接收到心跳数据->{received}");
}
else
{
// Increment the count of the total bytes receive by the server.
Interlocked.Add(ref this.totalBytesRead, bytesTransferred);
Console.WriteLine("Received: \"{0}\". The server has read a total of {1} bytes.", received, this.totalBytesRead);
ReceiveClientStrData?.Invoke(received);
}
#region 以字节方式接收
读取数据
//byte[] data = new byte[e.BytesTransferred];
//Array.Copy(e.Buffer, e.Offset, data, 0, e.BytesTransferred);
//lock (token.Buffer)
//{
// token.Buffer.AddRange(data);
//}
//do
//{
// //判断包的长度
// byte[] lenBytes = token.Buffer.GetRange(0, 4).ToArray();
// int packageLen = BitConverter.ToInt32(lenBytes, 0);
// if (packageLen > token.Buffer.Count - 4)
// { //长度不够时,退出循环,让程序继续接收
// break;
// }
// //包够长时,则提取出来,交给后面的程序去处理
// byte[] rev = token.Buffer.GetRange(4, packageLen).ToArray();
// //从数据池中移除这组数据
// lock (token.Buffer)
// {
// token.Buffer.RemoveRange(0, packageLen + 4);
// }
// if (ReceiveClientData != null)
// ReceiveClientData(token, rev);
//} while (token.Buffer.Count > 4);
#endregion
if (!token.Socket.ReceiveAsync(e))
this.ProcessReceive(e);
}
else
{
this.CloseClientSocket(e);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
/// <summary>
/// This method is invoked when an asynchronous send operation completes.
/// The method issues another receive on the socket to read any additional
/// data sent from the client.
/// </summary>
/// <param name="e">SocketAsyncEventArg associated with the completed send operation.</param>
private void ProcessSend(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
// done echoing data back to the client
AsyncUserToken token = (AsyncUserToken)e.UserToken;
// Read the next block of data send from the client.
Boolean willRaiseEvent = token.Socket.ReceiveAsync(e);
if (!willRaiseEvent)
{
this.ProcessReceive(e);
}
}
else
{
this.CloseClientSocket(e);
}
}
/// <summary>
/// 对数据进行打包,然后再发送
/// </summary>
/// <param name="token"></param>
/// <param name="message"></param>
/// <returns></returns>
public bool SendMessage(AsyncUserToken token, byte[] message)
{
bool isSuccess = false;
if (token == null || token.Socket == null || !token.Socket.Connected)
return isSuccess;
try
{
//新建异步发送对象, 发送消息
SocketAsyncEventArgs sendArg = new SocketAsyncEventArgs();
sendArg.UserToken = token;
sendArg.SetBuffer(message, 0, message.Length); //将数据放置进去.
isSuccess = token.Socket.SendAsync(sendArg);
#region 以制定简单协议发送
对要发送的消息,制定简单协议,头4字节指定包的大小,方便客户端接收(协议可以自己定)
//byte[] buff = new byte[message.Length + 4];
//byte[] len = BitConverter.GetBytes(message.Length);
//Array.Copy(len, buff, 4);
//Array.Copy(message, 0, buff, 4, message.Length);
token.Socket.Send(buff); //
新建异步发送对象, 发送消息
//SocketAsyncEventArgs sendArg = new SocketAsyncEventArgs();
//sendArg.UserToken = token;
//sendArg.SetBuffer(buff, 0, buff.Length); //将数据放置进去.
//isSuccess = token.Socket.SendAsync(sendArg);
#endregion
}
catch (Exception e)
{
Console.WriteLine("SendMessage - Error:" + e.Message);
}
return isSuccess;
}
public void CloseClient(AsyncUserToken token)
{
try
{
token.Socket.Shutdown(SocketShutdown.Both);
}
catch (Exception) { }
}
}
}
客户端程序:
/// <summary>
/// 自定义SocketAsyncEventArgs类,获取SocketAsyncEventArgs使用状态
/// </summary>
internal class MySocketEventArgs : SocketAsyncEventArgs
{
/// <summary>
/// 标识,只是一个编号而已
/// </summary>
public int ArgsTag { get; set; }
/// <summary>
/// 设置/获取使用状态
/// </summary>
public bool IsUsing { get; set; }
}
internal class BufferManager
{
int m_numBytes; // the total number of bytes controlled by the buffer pool
byte[] m_buffer; // the underlying byte array maintained by the Buffer Manager
Stack<int> m_freeIndexPool;
int m_currentIndex;
int m_bufferSize;
public BufferManager(int totalBytes, int bufferSize)
{
m_numBytes = totalBytes;
m_currentIndex = 0;
m_bufferSize = bufferSize;
m_freeIndexPool = new Stack<int>();
}
/// <summary>
/// Allocates buffer space used by the buffer pool
/// </summary>
public void InitBuffer()
{
// create one big large buffer and divide that
// out to each SocketAsyncEventArg object
m_buffer = new byte[m_numBytes];
}
// Assigns a buffer from the buffer pool to the
// specified SocketAsyncEventArgs object
// <returns>true if the buffer was successfully set, else false</returns>
public bool SetBuffer(SocketAsyncEventArgs args)
{
if (m_freeIndexPool.Count > 0)
{
args.SetBuffer(m_buffer, m_freeIndexPool.Pop(), m_bufferSize);
}
else
{
if ((m_numBytes - m_bufferSize) < m_currentIndex)
{
return false;
}
args.SetBuffer(m_buffer, m_currentIndex, m_bufferSize);
m_currentIndex += m_bufferSize;
}
return true;
}
/// <summary>
/// Removes the buffer from a SocketAsyncEventArg object.
/// This frees the buffer back to the buffer pool
/// </summary>
/// <param name="args"></param>
public void FreeBuffer(SocketAsyncEventArgs args)
{
m_freeIndexPool.Push(args.Offset);
args.SetBuffer(null, 0, 0);
}
}
internal class Client : IDisposable
{
private const int BuffSize = 1024;
// The socket used to send/receive messages.
private Socket clientSocket;
// Flag for connected socket.
private Boolean connected = false;
// Listener endpoint.
private IPEndPoint hostEndPoint;
// Signals a connection.
private static AutoResetEvent autoConnectEvent = new AutoResetEvent(false);
BufferManager m_bufferManager;
//定义接收数据的对象
List<byte> m_buffer;
//发送与接收的MySocketEventArgs变量定义
private List<MySocketEventArgs> listArgs = new List<MySocketEventArgs>();
private MySocketEventArgs receiveEventArgs = new MySocketEventArgs();
int tagCount = 0;
/// <summary>
/// 当前连接状态
/// </summary>
public bool Connected { get { return clientSocket != null && clientSocket.Connected; } }
//服务器主动发出数据受理委托及事件
public delegate void OnServerDataReceived(byte[] receiveBuff);
public event OnServerDataReceived ServerDataHandler;
/// <summary>
/// 接收到客户端字符串委托
/// </summary>
public Action<string> ReceiveClientStrData;
//服务器主动关闭连接委托及事件
public delegate void OnServerStop();
public event OnServerStop ServerStopEvent;
/// <summary>
/// Create an uninitialized client instance.
/// To start the send/receive processing call the Connect method followed by SendReceive method.
/// </summary>
internal Client(String ip, int port)
{
// Instantiates the endpoint and socket.
hostEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);
clientSocket = new Socket(hostEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
m_bufferManager = new BufferManager(BuffSize * 2, BuffSize);
m_buffer = new List<byte>();
}
/// <summary>
/// 连接到主机
/// 0.连接成功, 其他值失败,参考SocketError的值列表
/// </summary>
internal SocketError Connect()
{
SocketAsyncEventArgs connectArgs = new SocketAsyncEventArgs();
connectArgs.UserToken = clientSocket;
//AsyncUserToken userToken = new AsyncUserToken();
//userToken.username = "user";
//userToken.Socket = clientSocket;
//userToken.ConnectTime = DateTime.Now;
//userToken.Remote = clientSocket.RemoteEndPoint;
//userToken.IPAddress = ((IPEndPoint)(clientSocket?.RemoteEndPoint))?.Address;
//connectArgs.UserToken = userToken;
connectArgs.RemoteEndPoint = hostEndPoint;
connectArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnConnect);
clientSocket.ConnectAsync(connectArgs);
autoConnectEvent.WaitOne(); //阻塞. 让程序在这里等待,直到连接响应后再返回连接结果
SocketError re = connectArgs.SocketError;
return re;
}
/// <summary>
/// Disconnect from the host.
/// </summary>
internal void Disconnect()
{
clientSocket.Disconnect(true);
///将所有注册的事件也注销掉,释放相关资源
}
/// <summary>
/// Calback for connect operation
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnConnect(object sender, SocketAsyncEventArgs e)
{
// Signals the end of connection.
autoConnectEvent.Set(); //释放阻塞.
// Set the flag for socket connected.
SocketError re = e.SocketError;
connected = (e.SocketError == SocketError.Success);
//如果连接成功,则初始化socketAsyncEventArgs
if (connected)
InitArgs(e);
}
#region args
/// <summary>
/// 初始化收发参数
/// </summary>
/// <param name="e"></param>
private void InitArgs(SocketAsyncEventArgs e)
{
m_bufferManager.InitBuffer();
//发送参数
InitSendArgs();
//接收参数
receiveEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
receiveEventArgs.UserToken = e.UserToken;
receiveEventArgs.ArgsTag = 0;
m_bufferManager.SetBuffer(receiveEventArgs);
//启动接收,不管有没有,一定得启动.否则有数据来了也不知道.
if (!e.ConnectSocket.ReceiveAsync(receiveEventArgs))
ProcessReceive(receiveEventArgs);
}
/// <summary>
/// 初始化发送参数MySocketEventArgs
/// </summary>
/// <returns></returns>
MySocketEventArgs InitSendArgs()
{
MySocketEventArgs sendArg = new MySocketEventArgs();
sendArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
sendArg.UserToken = clientSocket;
sendArg.RemoteEndPoint = hostEndPoint;
sendArg.IsUsing = false;
Interlocked.Increment(ref tagCount);
sendArg.ArgsTag = tagCount;
lock (listArgs)
{
listArgs.Add(sendArg);
}
return sendArg;
}
void IO_Completed(object sender, SocketAsyncEventArgs e)
{
MySocketEventArgs mys = (MySocketEventArgs)e;
// determine which type of operation just completed and call the associated handler
switch (e.LastOperation)
{
case SocketAsyncOperation.Receive:
ProcessReceive(e);
break;
case SocketAsyncOperation.Send:
mys.IsUsing = false; //数据发送已完成.状态设为False
ProcessSend(e);
break;
default:
throw new ArgumentException("The last operation completed on the socket was not a receive or send");
}
}
#endregion
#region read and write
/// <summary>
/// This method is invoked when an asynchronous receive operation completes.
/// If the remote host closed the connection, then the socket is closed.
/// If data was received then the data is echoed back to the client.
/// </summary>
private void ProcessReceive(SocketAsyncEventArgs e)
{
try
{
// check if the remote host closed the connection
Socket token = (Socket)e.UserToken;
if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
{
//读取数据
byte[] data = new byte[e.BytesTransferred];
Array.Copy(e.Buffer, e.Offset, data, 0, e.BytesTransferred);
lock (m_buffer)
{
m_buffer.AddRange(data);
}
#region 接收字符串方法
// Get the message received from the listener.
String received = Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred);
if (!string.IsNullOrEmpty(received))
{
ReceiveClientStrData(received);
}
#endregion
#region 以协议方式接收字节,大数据接收
//do
//{
// //注意: 这里是需要和服务器有协议的,我做了个简单的协议,就是一个完整的包是包长(4字节)+包数据,便于处理,当然你可以定义自己需要的;
// //判断包的长度,前面4个字节.
// byte[] lenBytes = m_buffer.GetRange(0, 4).ToArray();
// int packageLen = BitConverter.ToInt32(lenBytes, 0);
// if (packageLen <= m_buffer.Count - 4)
// {
// //包够长时,则提取出来,交给后面的程序去处理
// byte[] rev = m_buffer.GetRange(4, packageLen).ToArray();
// //从数据池中移除这组数据,为什么要lock,你懂的
// lock (m_buffer)
// {
// m_buffer.RemoveRange(0, packageLen + 4);
// }
// //将数据包交给前台去处理
// DoReceiveEvent(rev);
// }
// else
// { //长度不够,还得继续接收,需要跳出循环
// break;
// }
//} while (m_buffer.Count > 4);
注意:你一定会问,这里为什么要用do-while循环?
如果当服务端发送大数据流的时候,e.BytesTransferred的大小就会比服务端发送过来的完整包要小,
需要分多次接收.所以收到包的时候,先判断包头的大小.够一个完整的包再处理.
如果服务器短时间内发送多个小数据包时, 这里可能会一次性把他们全收了.
这样如果没有一个循环来控制,那么只会处理第一个包,
剩下的包全部留在m_buffer中了,只有等下一个数据包过来后,才会放出一个来.
继续接收
#endregion
if (!token.ReceiveAsync(e))
this.ProcessReceive(e);
}
else
{
ProcessError(e); ///服务器断开
}
}
catch (Exception xe)
{
Console.WriteLine(xe.Message);
}
}
/// <summary>
/// This method is invoked when an asynchronous send operation completes.
/// The method issues another receive on the socket to read any additional
/// The method issues another receive on the socket to read any additional
/// </summary>
private void ProcessSend(SocketAsyncEventArgs e)
{
if (e.SocketError != SocketError.Success)
{
ProcessError(e);
}
}
/// <summary>
/// Close socket in case of failure and throws a SockeException according to the SocketError.
/// </summary>
private void ProcessError(SocketAsyncEventArgs e)
{
Socket s = (Socket)e.UserToken;
if (s.Connected)
{
// close the socket associated with the client
try
{
s.Shutdown(SocketShutdown.Both);
}
catch (Exception ex)
{
// throws if client process has already closed
Console.WriteLine(ex.Message);
}
finally
{
if (s.Connected)
{
s.Close();
}
connected = false;
}
}
//这里一定要记得把事件移走,如果不移走,当断开服务器后再次连接上,会造成多次事件触发.
foreach (MySocketEventArgs arg in listArgs)
arg.Completed -= IO_Completed;
receiveEventArgs.Completed -= IO_Completed;
if (ServerStopEvent != null)
ServerStopEvent(); //服务器断开事件
}
/// <summary>
/// Exchange a message with the host.
/// </summary>
/// <param name="sendBuffer"></param>
/// <exception cref="SocketException"></exception>
internal void Send(byte[] sendBuffer)
{
if (connected)
{
//查找有没有空闲的发送MySocketEventArgs,有就直接拿来用,没有就创建新的
MySocketEventArgs sendArgs = listArgs.Find(a => a.IsUsing == false);
if (sendArgs == null)
{
sendArgs = InitSendArgs();
}
lock (sendArgs) //要锁定,不让别的线程抢走
{
sendArgs.IsUsing = true;
sendArgs.SetBuffer(sendBuffer, 0, sendBuffer.Length);
}
clientSocket.SendAsync(sendArgs);
#region 以协议方式发送
先对数据进行包装,就是把包的大小作为头加入,这必须与服务器端的协议保持一致,否则造成服务器无法处理数据.
//byte[] buff = new byte[sendBuffer.Length + 4];
//Array.Copy(BitConverter.GetBytes(sendBuffer.Length), buff, 4);
//Array.Copy(sendBuffer, 0, buff, 4, sendBuffer.Length);
查找有没有空闲的发送MySocketEventArgs,有就直接拿来用,没有就创建新的.So easy!
//MySocketEventArgs sendArgs = listArgs.Find(a => a.IsUsing == false);
//if (sendArgs == null)
//{
// sendArgs = InitSendArgs();
//}
//lock (sendArgs) //要锁定,不锁定让别的线程抢走了就不妙了.
//{
// sendArgs.IsUsing = true;
// sendArgs.SetBuffer(buff, 0, buff.Length);
//}
//clientSocket.SendAsync(sendArgs);
#endregion
}
else
{
throw new SocketException((int)SocketError.NotConnected);
}
}
/// <summary>
/// 接收字节,使用新进程通知事件回调
/// </summary>
/// <param name="buff"></param>
private void DoReceiveEvent(byte[] buff)
{
if (ServerDataHandler == null) return;
//ServerDataHandler(buff); //可直接调用
//线程不拖延接收新数据
Thread thread = new Thread(new ParameterizedThreadStart((obj) =>
{
ServerDataHandler((byte[])obj);
}));
thread.IsBackground = true;
thread.Start(buff);
}
#endregion
#region IDisposable Members
/// <summary>
/// Disposes the instance of SocketClient.
/// </summary>
public void Dispose()
{
autoConnectEvent.Close();
if (clientSocket.Connected)
{
clientSocket.Close();
}
}
#endregion
}
internal class ClentManager
{
//定义,最好定义成静态的, 因为我们只需要一个就好
static Client client = null;
//定义事件与委托
public delegate void ReceiveData(byte[] message);
public delegate void ServerClosed();
/// <summary>
/// 接收数据委托
/// </summary>
public static event ReceiveData OnReceiveData;
/// <summary>
/// 客户端断开事件委托
/// </summary>
public static event ServerClosed OnServerClosed;
/// <summary>
/// 接收到客户端字符串委托
/// </summary>
public static Action<string> ReceiveClientStrData;
/// <summary>
/// 心跳定时器
/// </summary>
static System.Timers.Timer heartTimer = null;
/// <summary>
/// 判断是否已连接
/// </summary>
public static bool Connected
{
get { return client != null && client.Connected; }
}
#region 基本方法
/// <summary>
/// 连接到服务器
/// </summary>
/// <returns></returns>
public static SocketError Connect(string ip, int port)
{
if (Connected) return SocketError.Success;
if (string.IsNullOrWhiteSpace(ip) || port <= 1000) return SocketError.Fault;
//创建连接对象, 连接到服务器
client = new Client(ip, port);
SocketError socketError = TryConnect();
if (socketError == SocketError.Success)
{
//连接成功后,就注册事件. 最好在成功后再注册.
client.ServerDataHandler += OnReceivedServerData;
client.ReceiveClientStrData += OnReceiveClientStrData;
client.ServerStopEvent += OnServerStopEvent;
}
return socketError;
}
/// <summary>
/// 断开连接
/// </summary>
public static void Disconnect()
{
try
{
client.Disconnect();
if (heartTimer != null)
heartTimer = null;
}
catch (Exception)
{
Console.WriteLine("未能关闭socket连接");
}
}
/// <summary>
/// 尝试连接server,成功则返回true
/// </summary>
/// <returns></returns>
public static SocketError TryConnect()
{
SocketError socketError = SocketError.ConnectionRefused;
try
{
do
{
socketError = client.Connect();
if (socketError == SocketError.Success)
{
//触发事件
break;
}
} while (socketError != SocketError.Success);
}
catch (Exception ex)
{
Console.WriteLine($"TryConnect-->{ex.Message}");
}
return socketError;
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="message">消息实体</param>
/// <returns>True.已发送; False.未发送</returns>
public static bool Send(string message)
{
if (!Connected) return false;
byte[] buff = Encoding.UTF8.GetBytes(message);
//加密,根据自己的需要可以考虑把消息加密
//buff = AESEncrypt.Encrypt(buff, m_aesKey);
client.Send(buff);
return true;
}
/// <summary>
/// 发送字节流
/// </summary>
/// <param name="buff"></param>
/// <returns></returns>
static bool Send(byte[] buff)
{
if (!Connected) return false;
client.Send(buff);
return true;
}
/// <summary>
/// 接收消息
/// </summary>
/// <param name="buff"></param>
private static void OnReceivedServerData(byte[] buff)
{
//你要处理的代码,可以实现把buff转化成你具体的对象, 再传给前台
if (OnReceiveData != null)
OnReceiveData(buff);
}
/// <summary>
/// 接收字符串消息
/// </summary>
/// <param name="str"></param>
private static void OnReceiveClientStrData(string str)
{
if (ReceiveClientStrData != null)
ReceiveClientStrData(str);
}
/// <summary>
/// 服务器已断开
/// </summary>
private static void OnServerStopEvent()
{
if (OnServerClosed != null)
OnServerClosed();
}
#endregion
#region 心跳包
/// <summary>
/// 启动心跳包发送心跳消息
/// </summary>
public static void StartHeartbeat()
{
if (heartTimer == null)
{
heartTimer = new System.Timers.Timer();
heartTimer.Elapsed += TimeElapsed;
}
heartTimer.AutoReset = true; //循环执行
heartTimer.Interval = 30 * 1000; //每30秒执行一次
heartTimer.Enabled = true;
heartTimer.Start();
}
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void TimeElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Send("pop");
}
#endregion
}
参考:
Socket编程的四种通信方式_socket通信有哪几种-CSDN博客
C#socket通信(服务器端与客户端实现简单的通信)_c#利用socket实现客户端之间直接通信-CSDN博客
SocketAsyncEventArgs 类 (System.Net.Sockets) | Microsoft Learn