Many Internet applications use a client/server model for communication: a server listens for connections; and a client initiates connections to the server. How are these client and server programs implemented? In this chapter you will learn the basic programming constructs, called sockets, to create a client and server program. You can use these programming constructs to implement your own client/server application. This chapter first explains sockets using the C programming language as an example. Sections 19.3 to 19.4 provide detailed examples of socket application in C. Then Sections 19.5 to 19.7 provide examples using Python programming language. All the source code is available for download via https://sandilands.info/nsl/source/.
This chapter assumes you have knowledge of:
Basic Linux command line skills, as covered in Chapter 4, are assumed. You will need to be able to:
All of the practical tasks in this chapter can be completed on a single Linux computer, however it is preferable to use two computers (client and server).
The recommended virtnet topology is:
Sockets are programming constructs used to communicate between processes. There are different types of systems that sockets can be used for, the main one of interest to us are Internet-based sockets (the other commonly used socket is Unix sockets).
Sockets for Internet programming were created in early versions of Unix (written in C code). Due to the popularity of Unix for network computing at the time, these Unix/C based sockets become quite common. Now, the same concept has been extended to other languages and other operating systems. So although we use C code and a Unix-based system (Ubuntu Linux), the principles can be applied to almost any computer system.
There are two main Internet socket types, corresponding to the two main Internet transport protocols:
Here we will focus on stream sockets, but examples of datagram sockets are given in later sections.
The basic procedure is shown in Figure 19.1. The server must first create a socket, then associate or bind an IP address and port number to that socket. Then the server listens for connections.
The client creates a socket and then connects to the server. The connect() system call from the client triggers a TCP SYN segment from client to server.
The server accepts the connection from the client. The accept() system call is actually a blocking function—when the program calls accept(), the server does not return from the function until it receives a TCP SYN segment from a client, and completes the 3-way handshake.
After the client returns from the connect() system call, and the server returns from the accept() system call, a connection has been established. Now the two can send data.
Sending and receiving data is performed using the write() and read() functions. read() is a blocking function—it will only return when the socket receives data. You (the application programmer) must correctly coordinate reads and writes between the client and server. If a client calls the read() function, but no data is sent from the server, then the client will wait forever!
It is common for a server to be implemented such that it can handle multiple connections at a time. The most common way to do this is for a main server process to listen for connections, and when a connection is established, to create a child process to handle that connection (while the parent process returns to listening for connections). In our example, we use the fork() system call.
The fork() system call creates a new process, which is the child process of the current process. Both the parent and child process execute the next command following the call to fork(). fork() returns a process ID, which may be:
Hence we can use the process ID returned from fork() to determine what to do next—the parent process (pid > 0) will end the current loop and go back to waiting for connections. The child process (pid = 0) will perform the data exchange with the client.
The next sections provide examples of programming with sockets in both C and Python programming languages. You should read the source code for the corresponding client and server, run the code, and then try to understand the output produced.
For the C programming language, most of the socket system calls are described in detail in their individual man pages. You should use the man pages for finding out further details of each function. Note that you may have to specify the section of man pages to use (which is section 2, the System Calls section):
$ man -S2 accept
ACCEPT(2) Linux Programmer's Manual ACCEPT(2)
NAME
accept - accept a connection on a socket
...
Now we present example implementations of clients and servers that can exchange data across the Internet. They are implemented in C. There is a TCP version and a UDP version. The source code is quite old (there are newer, better constructs available), and may produce warnings when compiled, however it still executes as intended. The purpose of this code is to show a simple example of using sockets in C to create an Internet client/server application. If you want to create your own application, it is recommended you look for other (better) ways to implement in C.
On one computer compile the server and then start it. The server takes a port number as a command line argument:
$ gcc -o tcpserver socket_tcp_server.c
$ ./tcpserver 5001
On another computer compile the client and then start it. The client takes the IP address of the server and the port number it uses as command line arguments:
$ gcc -o tcpclient socket_tcp_client.c
$ ./tcpclient 127.0.0.1 5001
The client prompts for a message. Type in a message and press Enter. The result should be the message being displayed at the server and then the client printing “I got your message”. The client exits, but the server keeps running (other clients can connect).
An example on the client:
$ ./tcpclient 127.0.0.1 5001
Please enter the message: Hello from Steve
I got your message
$
And on the server:
$ ./tcpserver 5001
Here is the message: Hello from Steve
On one computer compile the server and then start it. The server takes a port number as a command line argument:
$ gcc -o udpserver socket_udp_server.c
$ ./udpserver 5001
On another computer compile the client and then start it. The client takes the IP address of the server and the port number it uses as command line arguments:
$ gcc -o udpclient socket_udp_client.c
$ ./udpclient 127.0.0.1 5001
The client prompts for a message. Type in a message and press Enter. The result should be the message being displayed at the server and then the client printing “Got your message”. The client exits, but the server keeps running (other clients can connect).
An example on the client:
$ ./udpclient 127.0.0.1 5002
Please enter the message: a udp test
Got an ack: Got your message
$
And on the server:
$ ./udpserver 5002
Received a datagram: a udp test
Now we present example implementions of clients and servers that can exchange data across the Internet, but implemented using Python. There is a TCP version and a UDP version of the client/server application. In addition there is an application that uses raw sockets to generate and send packets of any type.
The example application contains the server IP address (127.0.0.1), port (5005) and message (Hello World!) hardcoded into the Python source. The address used means the client and server run on the same computer (easy for testing, but not very useful). You should change them to the values appropriate for your setup.
Start the server in one terminal, and then start the client in another terminal. The client exchanges data with the server and then exits. The server remains running. The output on the server is:
$ python socket_tcp_server.py
Hello, World! from 127.0.0.1:56279
The output on the client is:
$ python socket_tcp_client.py
Connected to 127.0.0.1:5005
Thank you.
$
Similar to the TCP Python example, the addresses and messages are hardcoded in the source for the UDP example. You should change them to values appropriate for your setup.
Start the server in one terminal, and then start the client in another terminal. The client sends a message to the server and the server returns an acknowledgement. The client waits for 0.5 seconds and then repeats. To exit the client/server press Ctrl-C.
The output on the server is:
$ python socket_udp_server.py
received message: Hello, World!
received message: Hello, World!
received message: Hello, World!
received message: Hello, World!
received message: Hello, World!
received message: Hello, World!
received message: Hello, World!
The output on the client is:
$ python socket_udp_client.py
UDP target IP: 127.0.0.1
UDP target port: 5006
message: Hello, World!
received message: Ack
received message: Ack
received message: Ack
received message: Ack
received message: Ack
received message: Ack
received message: Ack
^C
TCP and UDP sockets provide an interface for an application to send/receive data using the respective transport protocol. In turn, both TCP and UDP use IP, which creates an IP datagram and sends it via the NIC. Raw sockets provide an interface for an application to create any type of packet and send via a chosen network interface. It provides the application direct access to send a data link layer packet (e.g. Ethernet frame), rather than having to go via TCP/IP.
Most applications don’t need raw sockets, as TCP or UDP sockets provide a much simpler interface for the service required by the application. However there are special cases when raw sockets may be used. For example, you can create packets of any format to send via a network interface for testing purposes (testing the network, testing the security of a system). Also you can capture packets of any type using raw sockets (e.g. implement your own “tcpdump”).
The following code provides an example of using raw sockets to create two types of packets:
The example Python application demonstrates how to create the two frames to be sent. The code creates the frames in their raw binary format (although using hexadecimal values instead of binary). The frames, including source/destination MAC addresses, source/destination IP addresses, packet sizes, and checksums, are hardcoded in the Python source. This wil not run on your computer: you will at least need to change the addresses and checksums. Read the source code to see suggestions on how to do this.
The application sends the two frames and then exits. To test whether it worked you should capture using tcpdump on both the sending computer and the destination computer.