C#的Socket

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通信方法,这些方法通常在不同的编程语言中以类似的名称和功能实现:

  1. 创建Socket:

    • 创建一个Socket实例,指定使用的协议类型(如TCP或UDP)和地址族(如IPv4或IPv6)。

  2. 绑定(Bind):

    • 将Socket绑定到一个特定的IP地址和端口上,以便监听传入的连接请求。

  3. 监听(Listen):

    • 对于服务器端的Socket,使用Listen方法来开始监听传入的连接请求。参数通常是队列中可以容纳的最大连接数。

  4. 接受连接(Accept):

    • 服务器端的Socket调用Accept方法来接受客户端的连接请求,返回一个新的Socket实例用于与客户端通信。

  5. 连接(Connect):

    • 对于客户端的Socket,使用Connect方法来连接到服务器端的IP地址和端口。

  6. 发送数据(Send/Write):

    • 使用SendWrite方法来向连接的Socket发送数据。数据可以是字节数组、字符串或其他数据类型。

  7. 接收数据(Receive/Read):

    • 使用ReceiveRead方法来从连接的Socket接收数据。这些方法通常会返回接收到的数据量。

  8. 关闭连接(Close/Shutdown):

    • 完成通信后,使用CloseShutdown方法来关闭Socket连接。Shutdown方法可以指定是关闭发送方、接收方还是双方。

  9. 异步通信:

    • 许多编程语言提供了异步Socket通信的方法,如BeginSendBeginReceiveEndSendEndReceive,允许在不阻塞主线程的情况下进行网络通信。

  10. 错误处理:

    • 在Socket编程中,需要妥善处理可能出现的各种异常和错误,如连接超时、数据传输错误等。

  11. 多线程或事件驱动:

    • 为了提高性能和响应能力,服务器端的Socket通信通常采用多线程或事件驱动的方式来处理多个客户端的连接。

Socket中的方法介绍

在Socket编程中,有几个核心方法用于建立和管理网络通信。以下是一些在.NET框架中使用System.Net.Sockets命名空间时常用的Socket方法:

  1. 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 方法调用后立即进入一个循环,不断接受新的客户端连接,并将每个连接的处理委托给单独的线程或任务。这样可以同时处理多个客户端的连接请求

  1. 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 表示服务器将接受所有网络接口上的连接请求。

  1. Close:

    • 关闭Socket连接,并释放所有相关资源。

Close 方法是 Socket 类的一个成员,用于关闭 Socket 连接并释放与 Socket 关联的所有资源。以下是 Close 方法的一些关键点:

  1. 关闭连接Close 方法关闭 Socket 的发送和接收通道,使 Socket 不再能够进行通信。

  2. 释放资源:除了关闭连接外,Close 方法还释放与 Socket 关联的所有资源,包括内存和底层网络资源。

  3. 同步操作Close 方法是一个同步操作,调用线程将被阻塞直到 Socket 完全关闭。

  4. 使用时机:通常在完成通信后或不再需要 Socket 时调用 Close 方法。

  5. 异常安全:即使在关闭过程中发生异常,Close 方法也会尝试关闭 Socket 并释放资源。

  6. 可调用多次:如果 Socket 已经被关闭,再次调用 Close 方法不会引发异常。

  1. 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 都会被关闭。

  1. Disconnect:

    • 断开Socket连接,但不会释放Socket对象本身。

在 .NET 框架中,Socket 类的 Disconnect 方法用于断开 Socket 连接,但它不会释放 Socket 对象本身。这意味着调用 Disconnect 后,Socket 对象仍然存在,但是不再处于连接状态。Disconnect 方法通常用于以下场景:

  1. 立即断开连接:当你需要立即断开与远程主机的连接,而不是等待缓冲区中的数据发送完成时,可以使用 Disconnect 方法。

  2. 客户端使用Disconnect 方法通常在客户端 Socket 上使用,因为服务器端 Socket 通常使用 Close 方法来关闭并释放资源。

  3. 保持 Socket 实例:如果你希望保留 Socket 实例以备后用,比如重新连接或执行其他操作,可以使用 Disconnect 而不是 Close

  4. 异步操作:.NET 还提供了 BeginDisconnectEndDisconnect 方法,允许你在不阻塞当前线程的情况下异步断开连接。

  5. 使用时机:在完成通信后,如果你希望保留 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 来释放资源。

  1. Listen:

    • 服务器端Socket调用此方法来开始监听传入的连接请求,参数指定了队列中可以容纳的最大等待连接数。

Listen 方法是服务器端 Socket 对象的一个成员,用于指示 Socket 开始监听传入的连接请求。以下是 Listen 方法的一些关键点:

  1. 开始监听Listen 方法使得服务器 Socket 能够接收来自客户端的连接请求。

  2. 参数Listen 方法接受一个参数,表示可以排队等待处理的最大连接数。如果请求的数量超过这个限制,额外的客户端连接请求将被拒绝,直到队列中的一些连接被接受。

  3. 使用时机Listen 方法通常在 Socket 绑定到特定的端口后调用,即在 Bind 方法之后。

  4. 阻塞性Listen 方法本身是非阻塞的,但它通常与 Accept 方法结合使用,后者是阻塞的,直到有客户端连接请求到达。

  5. 服务器端专用Listen 方法主要用于服务器端 Socket,客户端 Socket 不需要调用此方法。

  6. 状态改变:调用 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,并继续监听新的连接请求。 

  1. Receive:

    • 从Socket接收数据,可以指定缓冲区、偏移量、要接收的数据长度以及一个标志来指定或接收数据的方式。

Receive 方法是 Socket 编程中用于从已连接的 Socket 接收数据的方法。以下是 Receive 方法的一些关键特性和用法:

  1. 同步接收数据Receive 是一个同步方法,它会阻塞调用线程直到有数据可读或者发生错误。

  2. 缓冲区Receive 方法需要一个缓冲区(通常是字节数组)来存储接收到的数据。

  3. 接收字节数:方法返回接收到的字节数。如果返回0,则表示对端已经关闭连接。

  4. 偏移量:可以指定缓冲区中的偏移量,从该位置开始存储接收到的数据。

  5. 接收标志:可以指定一个标志,例如 SocketFlags.None 或其他 SocketFlags 枚举值,以控制接收操作的行为。

  6. 使用场景:通常在已经建立连接的 Socket 上调用,无论是服务器端还是客户端。

  7. 异常处理:应妥善处理可能发生的异常,例如 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,则表示对端已经关闭了连接。如果在接收过程中发生异常,我们会捕获并处理它。

  1. ReceiveFrom:

    • 接收数据并返回发送方的终结点信息,通常用于UDP Socket。

ReceiveFrom 方法是 UDP (User Datagram Protocol) Socket 编程中使用的一个方法,它用于接收数据报并获取发送方的地址信息。与面向连接的 TCP 不同,UDP 是无连接的协议,因此每个发送的数据报都是独立的,并且可能独立到达。

以下是 ReceiveFrom 方法的一些关键特性:

  1. UDP 专用ReceiveFrom 主要用于 UDP Socket,因为 TCP Socket 使用 Receive 方法。

  2. 接收数据和发送者信息:此方法不仅接收数据,还返回发送数据的远程主机的地址和端口信息。

  3. 缓冲区:需要提供一个缓冲区(通常是字节数组)来存储接收到的数据。

  4. EndPoint:需要一个 EndPoint 对象(通常是 IPEndPoint),在调用之前它包含本地端点信息,在调用之后它包含远程端点信息,即发送方的地址和端口。

  5. 同步操作ReceiveFrom 是一个同步方法,它会阻塞直到数据被接收。

  6. 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 对象,以包含发送方的地址和端口信息。如果接收到数据,我们可以将其转换为字符串并打印出来。 

  1. Send:

    • 向Socket发送数据,可以指定缓冲区、偏移量以及发送的数据长度。

Send 方法是 Socket 编程中用于向已连接的 Socket 发送数据的方法。以下是 Send 方法的一些关键特性和用法:

  1. 同步发送数据Send 是一个同步方法,它会阻塞调用线程直到数据被发送。

  2. 缓冲区Send 方法需要一个缓冲区(通常是字节数组)作为数据源。

  3. 偏移量:可以指定缓冲区中的偏移量,从该位置开始发送数据。

  4. 发送字节数Send 方法接受要发送的字节数作为参数。

  5. SocketFlags:可以指定 SocketFlags 枚举值来控制发送操作的行为。

  6. 使用场景:通常在已经建立连接的 Socket 上调用,无论是服务器端还是客户端。

  7. 异常处理:应妥善处理可能发生的异常,例如 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 方法的异步版本,如 BeginSendEndSend,来提高应用程序的响应性,避免在数据发送时阻塞线程。

对于面向连接的 TCP Socket,一旦连接建立,数据就可以通过 Send 方法发送,而不需要担心数据的目的地,因为连接已经指定了目标。对于无连接的 UDP Socket,使用 SendTo 方法代替,因为它需要指定每个数据报的目的地。

  1. SendTo:

    • 向特定的终结点发送数据,通常用于UDP Socket。

SendTo 方法是 UDP (User Datagram Protocol) Socket 编程中使用的一个方法,它用于向指定的远程主机发送数据。与 TCP 不同,UDP 是无连接的协议,因此每次发送数据时都需要指定目标地址和端口。

以下是 SendTo 方法的一些关键特性:

  1. UDP 专用SendTo 主要用于 UDP Socket,因为 TCP Socket 使用 Send 方法。

  2. 目标地址:需要指定远程主机的地址和端口信息,这通常通过 EndPoint 对象(如 IPEndPoint)来完成。

  3. 缓冲区:需要提供一个缓冲区(通常是字节数组)作为数据源。

  4. 同步操作SendTo 是一个同步方法,它会阻塞直到数据被发送。

  5. 偏移量和长度:可以指定缓冲区中的偏移量和要发送的字节长度。

  6. 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 方法的异步版本 BeginSendToEndSendTo 可用于提高性能,避免在数据发送时阻塞线程。

  1. Select:

    • 检查一组Socket以确定哪些Socket已经准备好进行读取或写入操作。

Select 方法是 .NET Socket 编程中用于确定一个或多个 Socket 是否已经准备好进行操作(如接收或发送数据)的方法。这个方法可以用于多路复用 I/O 操作,允许单个线程同时管理多个网络连接。

以下是 Select 方法的一些关键特性:

  1. 多个 Socket 检查Select 可以同时检查多个 Socket 对象,看它们是否已经准备好进行读取或写入操作。

  2. 超时时间Select 方法允许设置一个超时时间(以毫秒为单位),如果在超时时间内没有 Socket 准备好,方法将返回。

  3. 返回值Select 方法返回一个整数,表示设置为检查的 Socket 中有多少个已经准备好了。

  4. Socket 集合Select 方法接受三个 Socket 集合作为参数:读取集合、写入集合和异常集合。

    • 读取集合:包含所有需要检查是否可以读取数据的 Socket。
    • 写入集合:包含所有需要检查是否可以写入数据的 Socket。
    • 异常集合:包含所有需要检查是否有异常情况的 Socket。
  5. 使用场景:通常在需要同时监控多个网络连接的状态时使用,例如在服务器端需要同时处理多个客户端连接。

  6. 异步替代:虽然 Select 方法可以用于同步地检查多个 Socket,但在 .NET 中更推荐使用异步 Socket 操作,如 BeginSocketBeginAcceptBeginReceive 等。

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。

  1. Shutdown:

    • 关闭Socket的一个方向(发送或接收)的通信。

Shutdown 方法用于关闭 Socket 的单向或双向通信。当不再需要 Socket 进行发送或接收数据时,可以使用此方法来关闭相应的操作。以下是 Shutdown 方法的一些关键特性:

  1. 关闭方向Shutdown 方法可以关闭 Socket 的发送方向、接收方向或两者同时关闭。

    • SocketShutdown.Receive:关闭接收方向,不再接收数据。
    • SocketShutdown.Send:关闭发送方向,不再发送数据。
    • SocketShutdown.Both:同时关闭发送和接收方向。
  2. 不影响 Socket 实例:调用 Shutdown 方法不会释放 Socket 实例,它仍然存在于内存中。

  3. 优雅关闭:使用 Shutdown 方法可以优雅地关闭通信,允许已经发送的数据被接收方接收。

  4. 使用场景

    • 在客户端和服务器端都可以使用 Shutdown 方法来关闭通信。
    • 通常在完成数据交换或确定不再需要通信时调用。
  5. 调用时机:在调用 Shutdown 之后,通常会调用 Close 方法来完全关闭 Socket 并释放资源。

  6. 异常处理:调用 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();

  • 21
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值