Network Sockets and Ports
What is a network socket?
A socket was defined in 1971 in RFC147 (The Definition of a Socket) as:
"The unique identification to or from which information is transmitted in the network."
Sockets have evolved since then, but this definition still holds true. This article will discuss network sockets (also called Internet sockets) used for communication as part of the TCP/IP stack and based on the Berkeley sockets standard. We will discuss datagram sockets, stream sockets and raw sockets. We will not cover Unix sockets for inter-process communication or web sockets - there are links at the end of the article if you want to read more about them, though.
We can think of network sockets as software structures that represent the endpoints of a network connection. A pair of sockets fully specify a network connection. Network connections between sockets enable communication in both directions - sockets allow full-duplex data transmission. Different types of network socket (datagram, stream and raw) have different properties, which will be covered later in the article. We will begin by focusing on stream sockets used for TCP connections by protocols such as HTTP and SSH.
Network sockets can be thought of as analogous to physical sockets, where a connection is made between two physical plug sockets by using a cable. In the same way that physical connections can be made between different physical ports/connectors on a single device, network connections can be made to different service ports on a given host.
Datagram and stream sockets operate at the transport layer in the TCP/IP model and the session layer in the OSI stack, whilst raw sockets operate at the Internet layer in TCP/IP and the network layer in the OSI stack.
The socket address, or handle, for datagram and stream sockets is the IP address combined with the port number. When creating the socket, we must also specify what type of socket to initialise and the protocol to use - for example, a datagram socket using UDP or a stream socket using TCP. The network connection, or flow, can be fully described by the combination of source IP address, destination IP address, source port, destination port and protocol (e.g. 6 - TCP, or 17 - UDP) - this information is also known as '5-tuple' data.
RFC793 (Transmission Control Protocol) states the following about sockets and ports:
To identify the separate data streams that a TCP may handle, the TCP provides a port identifier. Since port identifiers are selected independently by each TCP they might not be unique. To provide for unique addresses within each TCP, we concatenate an internet address identifying the TCP with a port identifier to create a socket that will be unique throughout all networks connected together.
A connection is fully specified by the pair of sockets at the ends. A local socket may participate in many connections to different foreign sockets. A connection can be used to carry data in both directions, that is, it is "full duplex".
Operating systems, such as Linux or Windows, present a uniform socket API that developers can use. Programming languages may provide additional abstraction to make it easy for developers to send and receive data using sockets. Often, libraries will be available, enabling higher layers of abstraction so that developers don't need to consider the socket implementation and instead focus on the application layer protocols (such as HTTP), with the lower level sending and receiving of data using sockets handled transparently.
The difference between a socket and a port
Ports are transport layer features present in TCP and UDP - they allow us to distinguish between multiple services hosted on the same IP address. In contrast, network sockets are software implementations that enable network connections. For stream and datagram sockets, port numbers are combined with IP addresses and protocol to describe a specific network socket.
How do sockets work?
Modern internet sockets derive from the Berkeley sockets API (application programming interface) originally developed for the BSD Unix Operating system. On Windows, socket functionality is implemented by the Winsock API (formerly known as the Windows Sockets API), which generally uses the conventions established by Berkeley sockets.
The Berkeley sockets model provides a set of primitives (building blocks) which can be used to establish, maintain and tear down connections. Some of the key functions used with Berkeley sockets for TCP connections are:
- SOCKET - create a new endpoint for network communication.
- BIND - attach a socket to a specific local address (specified by IP address and port number).
- LISTEN - indicate that the socket can accept new connections.
- ACCEPT - begin a network connection following an incoming connection request.
- CONNECT - attempt to establish a connection.
- SEND - transmit data over the connection.
- RECEIVE - receive data over the connection.
- CLOSE - release the connection.
Many network protocols use a client-server approach. In this model, a server is always listening for new connections from clients. For example, with HTTP (the Hyper Text Transfer Protocol used for serving websites), web servers listen for connections from clients - web browsers. Once a connection has been established, clients send requests, and servers reply with responses. Client and server sockets behave differently, as outlined below.
First, a new endpoint is created using the SOCKET primitive. During socket creation, we must specify which type of socket should be created and which protocol should be used. For a socket to communicate using TCP, the socket type will be a stream socket, and the protocol will be TCP (IP protocol number 6).
Next, the socket must be bound to a specific address using the BIND function so that clients can connect. For a local server, we might choose to bind to the IP address 127.0.0.1 with the TCP port number 8080.
Now we LISTEN for incoming connections. Incoming connection requests will be placed on a queue, ready to be accepted.
From the current state, the server can ACCEPT incoming requests, creating a new connection for each client. The server will create a new socket (with the same properties) for each client connection.
The server can now SEND and RECEIVE data with each client.
Finally, when both client and server issue the CLOSE primitive, the connection will be torn down.
The operation of a client is slightly more straightforward.
The client also starts by calling SOCKET to create a new socket.
Next, the client will attempt to CONNECT to the server. The client doesn't need to be explicitly told to BIND to a specific address - it will pick an appropriate IP and port (usually a random high port).
After the server has accepted the connection, the client can SEND and RECEIVE data according to the protocols being used. With HTTP, the client will send GET and POST requests and receive HTTP responses back from the server.
Finally, when both client and server issue the CLOSE primitive, the connection will be torn down.
There are three main types of Internet socket in use:
- Datagram sockets
- Stream sockets
- Raw sockets
The differences between these different types of socket are outlined below.
Stream Sockets (SOCK_STREAM)
Most of what we have discussed so far applies to stream sockets. This type of socket is 'connection-oriented', which means that an ongoing network connection is negotiated between the two endpoints for data to be sent and received. With stream sockets, the server must accept the connection before the client will send data.
The most common transport protocol used with stream sockets is the Transmission Control Protocol. The Stream Control Transmission Protocol (SCTP) and Datagram Congestion Control Protocol (DCCP) may also be used.
Datagram Sockets (SOCK_DGRAM)
Datagram sockets are 'connectionless' and use the User Datagram Protocol (UDP) to transmit and receive data. Because the connection is connectionless, there is no acknowledgement by either party when data is received and no ongoing connection to tear down. Consequently, the communication process is more straightforward - the client does not need to wait for a LISTEN and ACCEPT from the server before sending data. However, communication over datagram sockets is unreliable as the sending party will not know to resend data if the data was lost in transit.
Raw Sockets (SOCK_RAW)
Unlike datagram and stream sockets, raw sockets act at the Internet/network layer and provide direct sending and receiving of IP (Internet Protocol) packets. Raw sockets are used when you need to use a protocol such as ICMP, which doesn't use TCP or UDP, or if you require access to the underlying IP packet data.
A Socket example in Python
The following code snippets illustrate how sockets can be used in Python 3. The example is taken from the official Python documentation, with some additional comments added. There is a link to the original at the end of the article.
We will create a simple socket-based server that will accept clients' connections and echo incoming data back to them. Most real-world examples will get more complex than this, but it illustrates the concepts we have covered above.
Server Socket Example
# server.py import socket HOST = '127.0.0.1' # We're just making the service available on localhost PORT = 50007 # We pick a random high TCP port to listen on # Now we create the socket - socket.AF_INET represents IPv4 socket.AF_INET6 for IPv6 sockets # socket.SOCK_STREAM represents a STREAM socket, in this case, using TCP with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: # we bind the socket to the IP address and port number defined above s.bind((HOST, PORT)) # as this is the server, we next instruct the socket to listen for incoming connections s.listen(1) # incoming connections will be accepted and provide a new socket to communicate # with a given client (conn) and the address of the client (addr) conn, addr = s.accept() # after a connection is established, the server prints out the client address # and then waits for data. Any data that is received will just be sent back # to the client (by conn.sendall) with conn: print('Connected by', addr) while True: data = conn.recv(1024) if not data: break conn.sendall(data)
Client Socket Example
We can then use a simple client to connect to the echo server above.
# client.py import socket SERVER_IP_ADDRESS = '127.0.0.1' # The remote host we to connect to PORT = 50007 # The port number used by the server # Again, we need to create an IPv4 stream socket with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: # We don't need to bind the client to a local address, # we can just attempt to connect to the server s.connect((SERVER_IP_ADDRESS, PORT)) # After the connection is accepted, we send the string "Hello, world" s.sendall(b'Hello, world') # After sending, we wait to receive data back. data = s.recv(1024) # Finally, print the data that was received print('Received', repr(data))
If we run the example, we get the following sequence of events.
- Run the server (it is now listening for incoming connections)
- Run the client
- The client will connect, causing the server to print 'Connected by' with the client details
- The client sends data ('Hello, world'), which the server echoes back to the client
- The client prints the data received from the server
The output from running the above Python scripts is shown below. Notice that the client is automatically assigned a random high TCP port (55987).
$ python3 server.py Connected by ('127.0.0.1', 55987)
$ python3 client.py Received b'Hello, world'