使用套接字通过 TCP 发送和接收数据

在使用套接字与远程设备通信之前,必须使用协议和网络地址信息初始化套接字。 Socket 类的构造函数有一些参数,这些参数指定了套接字用于建立连接的地址族、套接字类型和协议类型。 将客户端套接字连接到服务器套接字时,客户端将使用对象 IPEndPoint 来指定服务器的网络地址。

创建 IP 终结点

使用 System.Net.Sockets时,将网络终结点表示为对象 IPEndPointIPEndPoint 是由 IPAddress 和其相应的端口号组成的。 在通过某个 Socket会话启动对话之前,可以在应用和远程目标之间创建数据管道。

TCP/IP 使用网络地址和服务端口号来唯一标识服务。 网络地址标识特定的网络目标;端口号标识要连接到的设备上的特定服务。 网络地址和服务端口的组合称为终结点,该终结点由 EndPoint 类在 .NET 中表示。 为每个受支持的地址系列定义子代 EndPoint ;对于 IP 地址系列,类为 IPEndPoint

Dns 类为使用 TCP/IP Internet 服务的应用提供域名服务。 该方法 GetHostEntryAsync 查询 DNS 服务器,将用户友好的域名(如“host.contoso.com”)映射到数字 Internet 地址(例如 192.168.1.1)。 GetHostEntryAsync 返回一个 Task<IPHostEntry>,其在等待时包含所请求名称的地址和别名的列表。 在大多数情况下,可以使用数组中 AddressList 返回的第一个地址。 下面的代码获取一个包含服务器 IPAddress 的 IP 地址的 host.contoso.com

IPHostEntry ipHostInfo = await Dns.GetHostEntryAsync("host.contoso.com");
IPAddress ipAddress = ipHostInfo.AddressList[0];

小窍门

出于手动测试和调试目的,通常可以使用GetHostEntryAsync方法,与从Dns.GetHostName()值生成的主机名结合,将 localhost 名称解析为 IP 地址。 请思考以下代码片段:

var hostName = Dns.GetHostName();
IPHostEntry localhost = await Dns.GetHostEntryAsync(hostName);
// This is the IP address of the local machine
IPAddress localIpAddress = localhost.AddressList[0];

Internet 分配号码机构 (IANA) 定义常见服务的端口号。 有关详细信息,请参阅 IANA:服务名称和传输协议端口号注册表。 其他服务可以在 1,024 到 65,535 的范围内注册端口号。 以下代码将 IP 地址 host.contoso.com 与端口号组合在一起,为连接创建远程终结点。

IPEndPoint ipEndPoint = new(ipAddress, 11_000);

确定远程设备的地址并选择用于连接的端口后,应用可以与远程设备建立连接。

创建 Socket 客户端

创建 endPoint 对象后,创建客户端套接字用于连接到服务器。 连接套接字后,它可以从服务器套接字连接发送和接收数据。

using Socket client = new(
    ipEndPoint.AddressFamily, 
    SocketType.Stream, 
    ProtocolType.Tcp);

await client.ConnectAsync(ipEndPoint);
while (true)
{
    // Send message.
    var message = "Hi friends 👋!<|EOM|>";
    var messageBytes = Encoding.UTF8.GetBytes(message);
    _ = await client.SendAsync(messageBytes, SocketFlags.None);
    Console.WriteLine($"Socket client sent message: \"{message}\"");

    // Receive ack.
    var buffer = new byte[1_024];
    var received = await client.ReceiveAsync(buffer, SocketFlags.None);
    var response = Encoding.UTF8.GetString(buffer, 0, received);
    if (response == "<|ACK|>")
    {
        Console.WriteLine(
            $"Socket client received acknowledgment: \"{response}\"");
        break;
    }
    // Sample output:
    //     Socket client sent message: "Hi friends 👋!<|EOM|>"
    //     Socket client received acknowledgment: "<|ACK|>"
}

client.Shutdown(SocketShutdown.Both);

上述 C# 代码:

  • 实例化一个新的 Socket 对象,其具有给定的 endPoint 实例地址族、SocketType.StreamProtocolType.Tcp

  • 调用 Socket.ConnectAsync 方法,并将 endPoint 实例作为参数。

  • while 循环中:

    • 使用 Socket.SendAsync 对消息进行编码并将消息发送到服务器。
    • 将发送的消息写入控制台。
    • 初始化缓冲区以通过Socket.ReceiveAsync从服务器接收数据。
    • response为确认信息时,它会被写入到控制台,并且循环会退出。
  • 最后,client 套接字调用 Socket.Shutdown 给定的 SocketShutdown.Both,此操作会关闭发送和接收操作。

创建 Socket 服务器

若要创建服务器套接字,该 endPoint 对象可以侦听任何 IP 地址上的传入连接,但必须指定端口号。 创建套接字后,服务器可以接受传入连接并与客户端通信。

using Socket listener = new(
    ipEndPoint.AddressFamily,
    SocketType.Stream,
    ProtocolType.Tcp);

listener.Bind(ipEndPoint);
listener.Listen(100);

var handler = await listener.AcceptAsync();
while (true)
{
    // Receive message.
    var buffer = new byte[1_024];
    var received = await handler.ReceiveAsync(buffer, SocketFlags.None);
    var response = Encoding.UTF8.GetString(buffer, 0, received);
    
    var eom = "<|EOM|>";
    if (response.IndexOf(eom) > -1 /* is end of message */)
    {
        Console.WriteLine(
            $"Socket server received message: \"{response.Replace(eom, "")}\"");

        var ackMessage = "<|ACK|>";
        var echoBytes = Encoding.UTF8.GetBytes(ackMessage);
        await handler.SendAsync(echoBytes, 0);
        Console.WriteLine(
            $"Socket server sent acknowledgment: \"{ackMessage}\"");

        break;
    }
    // Sample output:
    //    Socket server received message: "Hi friends 👋!"
    //    Socket server sent acknowledgment: "<|ACK|>"
}

上述 C# 代码:

  • 实例化一个新的 Socket 对象,其具有给定的 endPoint 实例地址族、SocketType.StreamProtocolType.Tcp

  • listener 调用 Socket.Bind 方法,并将 endPoint 实例作为参数,以将套接字与网络地址相关联。

  • 调用 Socket.Listen() 该方法以侦听传入连接。

  • listener 调用 Socket.AcceptAsync 方法以接受 handler 套接字上的传入连接。

  • while 循环中:

    • 调用 Socket.ReceiveAsync 以接收来自客户端的数据。
    • 收到数据后,会对其进行解码并写入控制台。
    • 如果消息以 response 结束,则会使用 <|EOM|> 向客户端发送确认信息。

运行示例客户端和服务器

首先启动服务器应用程序,然后启动客户端应用程序。

dotnet run --project socket-server
Socket server starting...
Found: 172.23.64.1 available on port 9000.
Socket server received message: "Hi friends 👋!"
Socket server sent acknowledgment: "<|ACK|>"
Press ENTER to continue...

客户端应用程序将向服务器发送消息,服务器将响应确认。

dotnet run --project socket-client
Socket client starting...
Found: 172.23.64.1 available on port 9000.
Socket client sent message: "Hi friends 👋!<|EOM|>"
Socket client received acknowledgment: "<|ACK|>"
Press ENTER to continue...

另请参阅