Use Sockets to send and receive data over TCP

Before you can use a socket to communicate with remote devices, the socket must be initialized with protocol and network address information. The constructor for the Socket class has parameters that specify the address family, socket type, and protocol type that the socket uses to make connections. When connecting a client socket to a server socket, the client will use an IPEndPoint object to specify the network address of the server.

Create an IP endpoint

When working with System.Net.Sockets, you represent a network endpoint as an IPEndPoint object. The IPEndPoint is constructed with an IPAddress and its corresponding port number. Before you can initiate a conversation through a Socket, you create a data pipe between your app and the remote destination.

TCP/IP uses a network address and a service port number to uniquely identify a service. The network address identifies a specific network destination; the port number identifies the specific service on that device to connect to. The combination of network address and service port is called an endpoint, which is represented in the .NET by the EndPoint class. A descendant of EndPoint is defined for each supported address family; for the IP address family, the class is IPEndPoint.

The Dns class provides domain-name services to apps that use TCP/IP internet services. The GetHostEntryAsync method queries a DNS server to map a user-friendly domain name (such as "host.contoso.com") to a numeric Internet address (such as 192.168.1.1). GetHostEntryAsync returns a Task<IPHostEntry> that when awaited contains a list of addresses and aliases for the requested name. In most cases, you can use the first address returned in the AddressList array. The following code gets an IPAddress containing the IP address for the server host.contoso.com.

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

Tip

For manual testing and debugging purposes, you can typically use the GetHostEntryAsync method with the resulting host name from the Dns.GetHostName() value to resolve the localhost name to an IP address. Consider the following code snippet:

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

The Internet Assigned Numbers Authority (IANA) defines port numbers for common services. For more information, see IANA: Service Name and Transport Protocol Port Number Registry). Other services can have registered port numbers in the range 1,024 to 65,535. The following code combines the IP address for host.contoso.com with a port number to create a remote endpoint for a connection.

IPEndPoint ipEndPoint = new(ipAddress, 11_000);

After determining the address of the remote device and choosing a port to use for the connection, the app can establish a connection with the remote device.

Create a Socket client

With the endPoint object created, create a client socket to connect to the server. Once the socket is connected, it can send and receive data from the server socket connection.

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);

The preceding C# code:

  • Instantiates a new Socket object with a given endPoint instances address family, the SocketType.Stream, and ProtocolType.Tcp.

  • Calls the Socket.ConnectAsync method with the endPoint instance as an argument.

  • In a while loop:

    • Encodes and sends a message to the server using Socket.SendAsync.
    • Writes the sent message to the console.
    • Initializes a buffer to receive data from the server using Socket.ReceiveAsync.
    • When the response is an acknowledgment, it is written to the console and the loop is exited.
  • Finally, the client socket calls Socket.Shutdown given SocketShutdown.Both, which shuts down both send and receive operations.

Create a Socket server

To create the server socket, the endPoint object can listen for incoming connections on any IP address but the port number must be specified. Once the socket is created, the server can accept incoming connections and communicate with clients.

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|>"
}

The preceding C# code:

  • Instantiates a new Socket object with a given endPoint instances address family, the SocketType.Stream, and ProtocolType.Tcp.

  • The listener calls the Socket.Bind method with the endPoint instance as an argument to associate the socket with the network address.

  • The Socket.Listen() method is called to listen for incoming connections.

  • The listener calls the Socket.AcceptAsync method to accept an incoming connection on the handler socket.

  • In a while loop:

    • Calls Socket.ReceiveAsync to receive data from the client.
    • When the data is received, it's decoded and written to the console.
    • If the response message ends with <|EOM|>, an acknowledgment is sent to the client using the Socket.SendAsync.

Run the sample client and server

Start the server application first, and then start the client application.

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...

The client application will send a message to the server, and the server will respond with an acknowledgment.

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...

See also