using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace _8._2day01
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("hello world");
//Socket tcp服务端
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(new IPEndPoint(IPAddress.Any, 9090));
socket.Listen(1);
//Socket client = socket.Accept();
var statu=socket.BeginAccept(new AsyncCallback(result =>
{
Console.WriteLine(result.IsCompleted);
}), null);
statu.AsyncWaitHandle.WaitOne();
Console.WriteLine("监听已启动...");
Console.ReadLine();
}
}
}
Socket通信的常用方法
Socket通信是一种网络通信方式,它允许应用程序通过底层的TCP/IP协议进行数据交换。以下是一些常用的Socket通信方法,这些方法通常在不同的编程语言中以类似的名称和功能实现:
-
创建Socket:
-
创建一个Socket实例,指定使用的协议类型(如TCP或UDP)和地址族(如IPv4或IPv6)。
-
-
绑定(Bind):
-
将Socket绑定到一个特定的IP地址和端口上,以便监听传入的连接请求。
-
-
监听(Listen):
-
对于服务器端的Socket,使用
Listen
方法来开始监听传入的连接请求。参数通常是队列中可以容纳的最大连接数。
-
-
接受连接(Accept):
-
服务器端的Socket调用
Accept
方法来接受客户端的连接请求,返回一个新的Socket实例用于与客户端通信。
-
-
连接(Connect):
-
对于客户端的Socket,使用
Connect
方法来连接到服务器端的IP地址和端口。
-
-
发送数据(Send/Write):
-
使用
Send
或Write
方法来向连接的Socket发送数据。数据可以是字节数组、字符串或其他数据类型。
-
-
接收数据(Receive/Read):
-
使用
Receive
或Read
方法来从连接的Socket接收数据。这些方法通常会返回接收到的数据量。
-
-
关闭连接(Close/Shutdown):
-
完成通信后,使用
Close
或Shutdown
方法来关闭Socket连接。Shutdown
方法可以指定是关闭发送方、接收方还是双方。
-
-
异步通信:
-
许多编程语言提供了异步Socket通信的方法,如
BeginSend
、BeginReceive
、EndSend
和EndReceive
,允许在不阻塞主线程的情况下进行网络通信。
-
-
错误处理:
-
在Socket编程中,需要妥善处理可能出现的各种异常和错误,如连接超时、数据传输错误等。
-
-
多线程或事件驱动:
-
为了提高性能和响应能力,服务器端的Socket通信通常采用多线程或事件驱动的方式来处理多个客户端的连接。
-
Socket中的方法介绍
在Socket编程中,有几个核心方法用于建立和管理网络通信。以下是一些在.NET框架中使用System.Net.Sockets命名空间时常用的Socket方法:
-
Accept:
-
服务器端Socket调用此方法来接受客户端的连接请求,返回一个新的Socket对象用于与客户端进行通信。
-
Accept
方法是服务器端 Socket 对象的一个方法,用于接受客户端的连接请求。当服务器调用 Listen
方法后,它将开始监听传入的连接请求。此时,如果客户端尝试连接到服务器,服务器就可以使用 Accept
方法来接受这个连接。
以下是 Accept
方法的一些关键点:
- 阻塞行为:
Accept
方法是同步的,它会阻塞调用线程,直到一个客户端连接请求到达。 - 返回值:
Accept
方法返回一个新的Socket
对象,代表与客户端建立的连接。 - 使用场景:通常在服务器端的监听循环中调用,以持续接受来自客户端的连接。
下面是一个简单的服务器端示例,演示如何使用 Accept
方法:
// 服务器端初始化Socket
Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
listener.Bind(new IPEndPoint(IPAddress.Any, port)); // 绑定到端口
listener.Listen(5); // 开始监听,参数为最大等待连接数
Console.WriteLine("服务器正在监听...");
try
{
// 接受客户端连接
Socket clientSocket = listener.Accept();
Console.WriteLine("客户端已连接。");
// 从这里开始,可以使用clientSocket与客户端进行通信
// ...
// 通信完成后,关闭连接
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
}
catch (Exception e)
{
Console.WriteLine("发生异常:" + e.Message);
}
finally
{
// 关闭监听Socket
listener.Close();
}
在这个例子中,服务器首先创建了一个 Socket 对象,并绑定到一个端口上。然后调用 Listen
方法开始监听连接请求。当 Accept
方法被调用时,它会阻塞直到一个客户端连接请求到达。一旦接受连接,就会返回一个新的 Socket 对象 clientSocket
,服务器可以使用这个对象来与客户端进行通信。
此外,服务器端通常会在 Accept
方法调用后立即进入一个循环,不断接受新的客户端连接,并将每个连接的处理委托给单独的线程或任务。这样可以同时处理多个客户端的连接请求
-
Bind:
-
将Socket绑定到特定的IP地址和端口上,以便监听传入的连接请求。
-
Bind
方法是 Socket 编程中的一个关键方法,用于将 Socket 关联到特定的网络地址和端口上。这通常在服务器端 Socket 初始化过程中使用,但也可以在客户端 Socket 上使用,以指定要连接到的远程地址和端口。
以下是 Bind
方法的一些关键点:
- 作用:
Bind
方法将 Socket 关联到本地地址和端口,使得 Socket 能够监听和接收特定端口上的网络请求。 - 参数:
Bind
方法通常接受一个EndPoint
对象作为参数,EndPoint
可以是IPEndPoint
(用于 IPv4 和 IPv6)或其他类型的端点。 - 使用场景:主要用于服务器端初始化,但在客户端上也可以用于指定要连接的远程地址和端口。
// 创建 Socket 对象
Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 绑定到特定的端口和地址
IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, port); // IPAddress.Any 表示接受所有网络接口的连接
listener.Bind(localEndPoint);
// 监听传入的连接请求
listener.Listen(5); // 参数指定了队列中可以容纳的最大等待连接数
Console.WriteLine("服务器绑定到端口 " + port + " 并开始监听...");
// 创建 Socket 对象
Socket sender = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 指定要连接的远程服务器地址和端口
IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Parse("192.168.1.100"), port);
// 连接到远程服务器
sender.Connect(remoteEndPoint);
Console.WriteLine("客户端连接到服务器成功。");
在这个示例中,客户端创建了一个 Socket 对象,并使用 Connect
方法连接到指定的远程服务器地址和端口。
注意事项:
- 确保端口没有被其他应用程序占用,否则
Bind
调用可能会失败并抛出SocketException
。 - 在某些操作系统或网络配置中,可能需要管理员权限才能绑定到低于1024的端口号。
- 在设计网络应用程序时,应考虑使用防火墙和网络安全组规则来控制对特定端口的访问。
Bind
方法是网络通信的基础,正确使用它可以确保 Socket 能够监听和接收预期的网络请求。
在这个示例中,服务器创建了一个 Socket 对象,并使用 Bind
方法将其绑定到端口 port
上。IPAddress.Any
表示服务器将接受所有网络接口上的连接请求。
-
Close:
-
关闭Socket连接,并释放所有相关资源。
-
Close
方法是 Socket 类的一个成员,用于关闭 Socket 连接并释放与 Socket 关联的所有资源。以下是 Close
方法的一些关键点:
-
关闭连接:
Close
方法关闭 Socket 的发送和接收通道,使 Socket 不再能够进行通信。 -
释放资源:除了关闭连接外,
Close
方法还释放与 Socket 关联的所有资源,包括内存和底层网络资源。 -
同步操作:
Close
方法是一个同步操作,调用线程将被阻塞直到 Socket 完全关闭。 -
使用时机:通常在完成通信后或不再需要 Socket 时调用
Close
方法。 -
异常安全:即使在关闭过程中发生异常,
Close
方法也会尝试关闭 Socket 并释放资源。 -
可调用多次:如果 Socket 已经被关闭,再次调用
Close
方法不会引发异常。
-
Connect:
-
客户端Socket调用此方法来连接到服务器端的IP地址和端口。
-
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
// 进行 Socket 相关的操作,如连接、发送和接收数据等
// ...
// 完成通信后关闭 Socket
socket.Close();
}
catch (Exception ex)
{
Console.WriteLine("发生异常:" + ex.Message);
}
finally
{
// 确保 Socket 在所有情况下都被关闭
if (socket != null && socket.Connected)
{
socket.Close();
}
}
我们首先创建了一个 Socket 对象,并在 try 块中执行了 Socket 操作。完成操作后,我们调用 Close
方法来关闭 Socket。如果在操作过程中发生异常,catch 块将捕获异常并输出异常信息。在 finally 块中,我们确保无论是否发生异常,Socket 都会被关闭。
-
Disconnect:
-
断开Socket连接,但不会释放Socket对象本身。
-
在 .NET 框架中,Socket
类的 Disconnect
方法用于断开 Socket 连接,但它不会释放 Socket 对象本身。这意味着调用 Disconnect
后,Socket 对象仍然存在,但是不再处于连接状态。Disconnect
方法通常用于以下场景:
-
立即断开连接:当你需要立即断开与远程主机的连接,而不是等待缓冲区中的数据发送完成时,可以使用
Disconnect
方法。 -
客户端使用:
Disconnect
方法通常在客户端 Socket 上使用,因为服务器端 Socket 通常使用Close
方法来关闭并释放资源。 -
保持 Socket 实例:如果你希望保留 Socket 实例以备后用,比如重新连接或执行其他操作,可以使用
Disconnect
而不是Close
。 -
异步操作:.NET 还提供了
BeginDisconnect
和EndDisconnect
方法,允许你在不阻塞当前线程的情况下异步断开连接。 -
使用时机:在完成通信后,如果你希望保留 Socket 对象,但不希望保持连接状态,可以调用
Disconnect
。
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
// 连接到服务器
clientSocket.Connect(remoteEndPoint);
// 进行通信操作
// ...
// 断开连接,但不释放 Socket 对象
clientSocket.Disconnect(true); // 参数 true 表示ReuseSocket,允许 Socket 重新用于连接
}
catch (Exception ex)
{
Console.WriteLine("发生异常:" + ex.Message);
}
finally
{
// 最终确保 Socket 被关闭
if (clientSocket != null)
{
clientSocket.Close();
}
}
在这个示例中,客户端 Socket 首先连接到远程服务器,完成通信后调用 Disconnect
方法断开连接。如果在操作过程中发生异常,catch 块将捕获异常并输出异常信息。在 finally 块中,无论是否发生异常,都确保 Socket 最终被关闭。
Disconnect
方法的参数 reuseSocket
允许你指定是否可以重用 Socket 对象。如果设置为 true
,则可以在稍后重新使用该 Socket 实例进行连接。如果设置为 false
(或不传参数,默认为 false
),则 Socket 不会被重用,并且在调用 Disconnect
后,你应该调用 Close
来释放资源。
-
Listen:
-
服务器端Socket调用此方法来开始监听传入的连接请求,参数指定了队列中可以容纳的最大等待连接数。
-
Listen
方法是服务器端 Socket 对象的一个成员,用于指示 Socket 开始监听传入的连接请求。以下是 Listen
方法的一些关键点:
-
开始监听:
Listen
方法使得服务器 Socket 能够接收来自客户端的连接请求。 -
参数:
Listen
方法接受一个参数,表示可以排队等待处理的最大连接数。如果请求的数量超过这个限制,额外的客户端连接请求将被拒绝,直到队列中的一些连接被接受。 -
使用时机:
Listen
方法通常在 Socket 绑定到特定的端口后调用,即在Bind
方法之后。 -
阻塞性:
Listen
方法本身是非阻塞的,但它通常与Accept
方法结合使用,后者是阻塞的,直到有客户端连接请求到达。 -
服务器端专用:
Listen
方法主要用于服务器端 Socket,客户端 Socket 不需要调用此方法。 -
状态改变:调用
Listen
后,Socket 状态改变,它不再用于发送或接收数据,而是等待新的连接请求。
int port = 12345; // 选择一个端口号
IPAddress localAddr = IPAddress.Parse("127.0.0.1"); // 使用本地地址
// 创建 Socket 对象
Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
// 绑定到本地地址和端口
listener.Bind(new IPEndPoint(localAddr, port));
// 开始监听传入的连接请求,参数为最大排队连接数
listener.Listen(10); // 这里设置为10,表示最多可以有10个连接请求在队列中等待
Console.WriteLine("服务器正在监听端口 " + port);
while (true)
{
// 接受客户端连接
Socket clientSocket = listener.Accept();
Console.WriteLine("客户端已连接。");
// 从这里开始,使用 clientSocket 与客户端进行通信
// ...
// 通信完成后,关闭客户端 Socket
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
}
}
catch (Exception e)
{
Console.WriteLine("发生异常:" + e.Message);
}
finally
{
// 关闭监听 Socket
if (listener != null)
{
listener.Close();
}
}
服务器首先创建了一个 Socket 对象,并绑定到本地地址和端口。然后调用 Listen
方法开始监听连接请求。服务器进入一个无限循环,不断调用 Accept
方法接受客户端连接,并与每个客户端建立的 Socket 对象 clientSocket
进行通信。通信完成后,关闭客户端 Socket,并继续监听新的连接请求。
-
Receive:
-
从Socket接收数据,可以指定缓冲区、偏移量、要接收的数据长度以及一个标志来指定或接收数据的方式。
-
Receive
方法是 Socket 编程中用于从已连接的 Socket 接收数据的方法。以下是 Receive
方法的一些关键特性和用法:
-
同步接收数据:
Receive
是一个同步方法,它会阻塞调用线程直到有数据可读或者发生错误。 -
缓冲区:
Receive
方法需要一个缓冲区(通常是字节数组)来存储接收到的数据。 -
接收字节数:方法返回接收到的字节数。如果返回0,则表示对端已经关闭连接。
-
偏移量:可以指定缓冲区中的偏移量,从该位置开始存储接收到的数据。
-
接收标志:可以指定一个标志,例如
SocketFlags.None
或其他SocketFlags
枚举值,以控制接收操作的行为。 -
使用场景:通常在已经建立连接的 Socket 上调用,无论是服务器端还是客户端。
-
异常处理:应妥善处理可能发生的异常,例如
SocketException
,以处理例如网络错误或连接中断的情况。
// 假设 clientSocket 是已经与服务器建立连接的客户端 Socket 对象
Socket clientSocket = ...;
byte[] buffer = new byte[1024]; // 创建一个缓冲区
int bytesRead = 0;
try
{
// 从 Socket 接收数据
bytesRead = clientSocket.Receive(buffer);
if (bytesRead > 0)
{
// 处理接收到的数据
// 例如,将字节转换为字符串或其他所需的数据格式
string receivedData = System.Text.Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine("接收到的数据: " + receivedData);
}
else if (bytesRead == 0)
{
Console.WriteLine("远程主机关闭了连接。");
}
}
catch (SocketException ex)
{
Console.WriteLine("接收数据时发生错误: " + ex.Message);
}
finally
{
// 完成数据接收后的操作,例如关闭 Socket
if (clientSocket.Connected)
{
clientSocket.Shutdown(SocketShutdown.Receive);
}
}
我们创建了一个大小为 1024 字节的缓冲区来存储接收到的数据。然后调用 Receive
方法尝试读取数据。如果 Receive
方法返回的字节数大于0,则表示成功接收到数据,我们可以对其进行处理。如果返回0,则表示对端已经关闭了连接。如果在接收过程中发生异常,我们会捕获并处理它。
-
ReceiveFrom:
-
接收数据并返回发送方的终结点信息,通常用于UDP Socket。
-
ReceiveFrom
方法是 UDP (User Datagram Protocol) Socket 编程中使用的一个方法,它用于接收数据报并获取发送方的地址信息。与面向连接的 TCP 不同,UDP 是无连接的协议,因此每个发送的数据报都是独立的,并且可能独立到达。
以下是 ReceiveFrom
方法的一些关键特性:
-
UDP 专用:
ReceiveFrom
主要用于 UDP Socket,因为 TCP Socket 使用Receive
方法。 -
接收数据和发送者信息:此方法不仅接收数据,还返回发送数据的远程主机的地址和端口信息。
-
缓冲区:需要提供一个缓冲区(通常是字节数组)来存储接收到的数据。
-
EndPoint:需要一个
EndPoint
对象(通常是IPEndPoint
),在调用之前它包含本地端点信息,在调用之后它包含远程端点信息,即发送方的地址和端口。 -
同步操作:
ReceiveFrom
是一个同步方法,它会阻塞直到数据被接收。 -
SocketFlags:可以指定
SocketFlags
枚举值来控制接收行为。
// 创建一个 UDP Socket 对象
Socket udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
try
{
// 绑定到本地端口
udpSocket.Bind(new IPEndPoint(IPAddress.Any, 12345));
// 创建一个足够大的缓冲区来存储接收的数据
byte[] buffer = new byte[1024];
EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
// 接收数据
int bytesRead = udpSocket.ReceiveFrom(buffer, ref remoteEndPoint);
if (bytesRead > 0)
{
// 将字节转换为字符串
string receivedData = System.Text.Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine("从 " + remoteEndPoint + " 接收到数据: " + receivedData);
}
}
catch (Exception ex)
{
Console.WriteLine("接收数据时发生错误: " + ex.Message);
}
finally
{
// 关闭 Socket
udpSocket.Close();
}
首先创建了一个 UDP Socket 并绑定到本地端口。然后,我们定义了一个缓冲区和一个 EndPoint
对象来接收数据。调用 ReceiveFrom
方法后,它将填充缓冲区并更新 EndPoint
对象,以包含发送方的地址和端口信息。如果接收到数据,我们可以将其转换为字符串并打印出来。
-
Send:
-
向Socket发送数据,可以指定缓冲区、偏移量以及发送的数据长度。
-
Send
方法是 Socket 编程中用于向已连接的 Socket 发送数据的方法。以下是 Send
方法的一些关键特性和用法:
-
同步发送数据:
Send
是一个同步方法,它会阻塞调用线程直到数据被发送。 -
缓冲区:
Send
方法需要一个缓冲区(通常是字节数组)作为数据源。 -
偏移量:可以指定缓冲区中的偏移量,从该位置开始发送数据。
-
发送字节数:
Send
方法接受要发送的字节数作为参数。 -
SocketFlags:可以指定
SocketFlags
枚举值来控制发送操作的行为。 -
使用场景:通常在已经建立连接的 Socket 上调用,无论是服务器端还是客户端。
-
异常处理:应妥善处理可能发生的异常,例如
SocketException
,以处理例如网络错误或连接中断的情况。
// 假设 socket 是已经与远程主机建立连接的 Socket 对象
Socket socket = ...;
// 要发送的数据
byte[] dataBuffer = System.Text.Encoding.UTF8.GetBytes("Hello, World!");
try
{
// 发送数据
int bytesSent = socket.Send(dataBuffer, 0, dataBuffer.Length, SocketFlags.None);
Console.WriteLine("发送了 " + bytesSent + " 字节的数据。");
}
catch (SocketException ex)
{
Console.WriteLine("发送数据时发生错误: " + ex.Message);
}
首先将字符串 "Hello, World!" 转换为字节数组 dataBuffer
。然后调用 Send
方法发送整个缓冲区的数据。Send
方法返回实际发送的字节数。如果在发送过程中发生异常,我们会捕获并处理它。
实际应用中可能需要根据数据的大小和网络条件多次调用 Send
方法来发送大量数据。此外,还可以使用 Send
方法的异步版本,如 BeginSend
和 EndSend
,来提高应用程序的响应性,避免在数据发送时阻塞线程。
对于面向连接的 TCP Socket,一旦连接建立,数据就可以通过 Send
方法发送,而不需要担心数据的目的地,因为连接已经指定了目标。对于无连接的 UDP Socket,使用 SendTo
方法代替,因为它需要指定每个数据报的目的地。
-
SendTo:
-
向特定的终结点发送数据,通常用于UDP Socket。
-
SendTo
方法是 UDP (User Datagram Protocol) Socket 编程中使用的一个方法,它用于向指定的远程主机发送数据。与 TCP 不同,UDP 是无连接的协议,因此每次发送数据时都需要指定目标地址和端口。
以下是 SendTo
方法的一些关键特性:
-
UDP 专用:
SendTo
主要用于 UDP Socket,因为 TCP Socket 使用Send
方法。 -
目标地址:需要指定远程主机的地址和端口信息,这通常通过
EndPoint
对象(如IPEndPoint
)来完成。 -
缓冲区:需要提供一个缓冲区(通常是字节数组)作为数据源。
-
同步操作:
SendTo
是一个同步方法,它会阻塞直到数据被发送。 -
偏移量和长度:可以指定缓冲区中的偏移量和要发送的字节长度。
-
SocketFlags:可以指定
SocketFlags
枚举值来控制发送行为。
// 创建一个 UDP Socket 对象
Socket udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
try
{
// 要发送的数据
byte[] dataBuffer = System.Text.Encoding.UTF8.GetBytes("Hello, World!");
// 远程主机的 IPEndPoint
IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Parse("192.168.1.100"), 12345);
// 发送数据到远程主机
udpSocket.SendTo(dataBuffer, 0, dataBuffer.Length, SocketFlags.None, remoteEndPoint);
Console.WriteLine("数据已发送到远程主机。");
}
catch (Exception ex)
{
Console.WriteLine("发送数据时发生错误: " + ex.Message);
}
finally
{
// 关闭 Socket
udpSocket.Close();
}
首先创建了一个 UDP Socket 并准备要发送的数据。然后,我们定义了远程主机的 IPEndPoint
。调用 SendTo
方法后,数据将被发送到指定的远程主机和端口。如果在发送过程中发生异常,我们会捕获并处理它。
请注意,由于 UDP 是无连接的,SendTo
方法只负责将数据发送出去,但不保证数据一定会到达目的地。如果需要确认数据是否到达,应用程序需要实现自己的确认机制或使用基于连接的协议如 TCP。此外,SendTo
方法的异步版本 BeginSendTo
和 EndSendTo
可用于提高性能,避免在数据发送时阻塞线程。
-
Select:
-
检查一组Socket以确定哪些Socket已经准备好进行读取或写入操作。
-
Select
方法是 .NET Socket 编程中用于确定一个或多个 Socket 是否已经准备好进行操作(如接收或发送数据)的方法。这个方法可以用于多路复用 I/O 操作,允许单个线程同时管理多个网络连接。
以下是 Select
方法的一些关键特性:
-
多个 Socket 检查:
Select
可以同时检查多个 Socket 对象,看它们是否已经准备好进行读取或写入操作。 -
超时时间:
Select
方法允许设置一个超时时间(以毫秒为单位),如果在超时时间内没有 Socket 准备好,方法将返回。 -
返回值:
Select
方法返回一个整数,表示设置为检查的 Socket 中有多少个已经准备好了。 -
Socket 集合:
Select
方法接受三个 Socket 集合作为参数:读取集合、写入集合和异常集合。- 读取集合:包含所有需要检查是否可以读取数据的 Socket。
- 写入集合:包含所有需要检查是否可以写入数据的 Socket。
- 异常集合:包含所有需要检查是否有异常情况的 Socket。
-
使用场景:通常在需要同时监控多个网络连接的状态时使用,例如在服务器端需要同时处理多个客户端连接。
-
异步替代:虽然
Select
方法可以用于同步地检查多个 Socket,但在 .NET 中更推荐使用异步 Socket 操作,如BeginSocketBeginAccept
、BeginReceive
等。
Socket listenSocket = ...; // 已经绑定和监听的服务器 Socket
List<Socket> readSockets = new List<Socket>();
Socket[] readSocketsArray = readSockets.ToArray();
List<Socket> writeSockets = new List<Socket>();
List<Socket> exceptSockets = new List<Socket>();
// 将监听 Socket 添加到读取集合中,等待新的连接
readSockets.Add(listenSocket);
int selectResult = 0;
try
{
selectResult = Socket.Select(readSocketsArray, null, exceptSockets, 3000); // 3000 毫秒超时
if (selectResult > 0)
{
foreach (Socket sock in readSocketsArray)
{
if (sock == listenSocket)
{
// 接受新的连接
var clientSocket = listenSocket.Accept();
readSockets.Add(clientSocket);
}
else
{
// 处理数据读取或其他操作
}
}
}
}
catch (SocketException ex)
{
Console.WriteLine("Select 调用失败:" + ex.Message);
}
我们首先创建了一个服务器 Socket 并将其添加到读取集合中。然后调用 Select
方法,并设置了一个 3000 毫秒的超时时间。如果 Select
返回值大于 0,我们将遍历数组,检查哪些 Socket 已经准备好。如果是监听 Socket 准备好了,我们接受新的连接并将其添加到读取集合中。如果是其他 Socket 准备好了,我们可以进行数据读取或其他操作。
Select
方法有一些限制,例如它不支持选择超时小于 1 毫秒,并且它不是线程安全的。在实际应用中,可能需要考虑这些限制,并根据需要选择合适的方法来管理多个 Socket。
-
Shutdown:
-
关闭Socket的一个方向(发送或接收)的通信。
-
Shutdown
方法用于关闭 Socket 的单向或双向通信。当不再需要 Socket 进行发送或接收数据时,可以使用此方法来关闭相应的操作。以下是 Shutdown
方法的一些关键特性:
-
关闭方向:
Shutdown
方法可以关闭 Socket 的发送方向、接收方向或两者同时关闭。SocketShutdown.Receive
:关闭接收方向,不再接收数据。SocketShutdown.Send
:关闭发送方向,不再发送数据。SocketShutdown.Both
:同时关闭发送和接收方向。
-
不影响 Socket 实例:调用
Shutdown
方法不会释放 Socket 实例,它仍然存在于内存中。 -
优雅关闭:使用
Shutdown
方法可以优雅地关闭通信,允许已经发送的数据被接收方接收。 -
使用场景:
- 在客户端和服务器端都可以使用
Shutdown
方法来关闭通信。 - 通常在完成数据交换或确定不再需要通信时调用。
- 在客户端和服务器端都可以使用
-
调用时机:在调用
Shutdown
之后,通常会调用Close
方法来完全关闭 Socket 并释放资源。 -
异常处理:调用
Shutdown
可能会抛出SocketException
,需要妥善处理。
Socket socket = ...; // 已经建立连接的 Socket 对象
try
{
// 关闭 Socket 的发送方向
socket.Shutdown(SocketShutdown.Send);
// 此时 Socket 仍然可以接收数据
// ...
// 当确定不再需要接收数据时,可以关闭接收方向
socket.Shutdown(SocketShutdown.Receive);
// 完成所有操作后,关闭 Socket
socket.Close();
}
catch (SocketException ex)
{
Console.WriteLine("关闭 Socket 时发生错误: " + ex.Message);
}
finally
{
// 确保 Socket 最终被关闭
if (socket != null)
{
socket.Close();
}
}
在这个示例中,我们首先调用 Shutdown
方法关闭 Socket 的发送方向,然后根据需要接收数据。当确定不再需要接收数据时,再次调用 Shutdown
方法关闭接收方向。最后,无论是否发生异常,都确保调用 Close
方法来关闭 Socket 并释放资源。
请注意,Shutdown
方法的行为可能受到底层操作系统和网络栈的影响。在某些情况下,关闭 Socket 的发送方向可能会导致接收方向也自动关闭。因此,使用 Shutdown
方法时,需要根据具体场景和需求进行操作
// 服务器端示例
Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
listener.Bind(new IPEndPoint(IPAddress.Any, port));
listener.Listen(5);
while (true)
{
Socket clientSocket = listener.Accept();
// 处理客户端连接
// ...
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
}
// 客户端示例
Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
client.Connect(serverEndPoint);
byte[] buffer = new byte[1024];
int bytesRead = client.Receive(buffer);
// 处理接收到的数据
// ...
client.Shutdown(SocketShutdown.Send);
client.Close();