Linux Network Programming: TCP and UDP Protocols

While we often use serial ports for information printing in embedded systems, this approach isn't suitable for multi-host network environments. This introduces the concept of network programming. In Linux, network programming involves using sockets for inter-process communication, particularly between processes on different hosts. Sockets provide a standardized interface for data transmission across different hosts, regardless of their operating systems or network protocols.

Network Communication Fundamentals

Three Elements of Data Transmission

  • Source: The starting point of data transmission, i.e., the data sender. In network communication, this is typically a specific device or application responsible for generating and sending data. For example, in a client-server architecture, the client acts as the source when sending requests to the server.
  • Destination: The endpoint of data transmission, i.e., the data receiver. Corresponding to the source, this is a device or application in the network responsible for receiving and processing data sent from the source. In the previous example, the server is the destination as it receives and responds to client requests.
  • Length: The size or quantity of data to be transmitted, typically measured in bytes. This is an important parameter in data transmission that helps the receiver properly receive and parse data, and helps intermediate devices (such as routers or switches) effectively process and forward data.

Network Communication Entities

In network communication, clients and servers are the two primary entities that play different roles:

  • Client: The party that initiates requests. It's typically a user-operated device or software used to access resources or services on a server. Clients can take various forms such as personal computers, smartphones, tablets, or applications running on these devices like web browsers, email clients, and instant messaging software.
  • Server: The party that responds to client requests. It provides data, resources, or services to meet client needs. Servers are typically high-performance computers or clusters running specialized service software to handle concurrent requests from multiple clients.

In the client-server architecture, communication typically follows a request-response pattern:

  1. The client sends a request to the server, which may contain data, operations to perform, or other relevant information.
  2. The server processes the request, retrieving data, performing calculations, or executing other necessary operations based on the request type and content.
  3. The server packages the processing result into a response and sends it back to the client.
  4. The client receives the response, parses and displays it, or performs further processing as needed.

TCP/UDP Protocols

TCP (Transmission Control Protocol) and UDP (User Datagram Protocol) are two commonly used transport layer protocols in computer networks. They play important roles in network communication but have different characteristics and application scenarios.

TCP Protocol

TCP is a connection-oriented, reliable, byte-stream-based transport layer protocol. It provides various features such as data stream transmission, reliability, effective flow control, full-duplex operation, and multiplexing. TCP ensures reliable data transmission through connection establishment (three-way handshake) and acknowledgment mechanisms. This protocol is suitable for applications requiring high reliability, such as file transfer, email, and web browser communication.

The TCP connection establishment process is known as the "three-way handshake," which ensures that both the client and server are ready for data transmission. Here are the detailed steps:

  1. First handshake: The client sends a SYN packet (synchronization packet) to the server, containing the client's initial sequence number. The client enters the SYN_SEND state, waiting for the server's acknowledgment.
  2. Second handshake: After receiving the SYN packet, the server acknowledges the client's SYN (by sending an ACK packet with ack=client's sequence number+1) and sends its own SYN packet containing the server's initial sequence number. This packet is typically called a SYN+ACK packet. The server enters the SYN_RECV state.
  3. Third handshake: After receiving the server's SYN+ACK packet, the client sends another ACK packet to confirm receipt of the server's SYN packet (ack=server's sequence number+1). After sending this ACK packet, both the client and server enter the ESTABLISHED state, indicating a successful connection.

UDP Protocol

UDP is a connectionless, unreliable, datagram-based transport layer protocol. Compared to TCP, UDP is simpler and more efficient because it doesn't require connection establishment and state maintenance. UDP's advantages include low overhead and fast transmission speed, making it suitable for applications with high real-time requirements, such as video calls, audio streaming, and real-time games. Additionally, UDP supports broadcast and multicast capabilities, allowing data to be sent to multiple destination hosts.

In summary, TCP and UDP each have their advantages and suitable application scenarios. TCP is suitable for data transmission requiring high reliability and integrity, while UDP is more suitable for applications with high real-time requirements or needing broadcast/multicast functionality. When choosing which protocol to use, it's necessary to balance based on specific application requirements and network environment.

Socket Programming Implementation

First, we need to introduce the concept of sockets, which are endpoints for network communication. They provide a programming interface that allows applications to send and receive data over networks. Sockets are programming interfaces for network communication in computer networks, enabling data transmission either on the same computer or between different computers.

In Linux network programming, sockets can be of different types, such as stream sockets (SOCK_STREAM, used for TCP protocol) and datagram sockets (SOCK_DGRAM, used for UDP protocol). Stream sockets provide reliable, bidirectional, connection-based data transmission services, suitable for applications requiring stable and reliable transmission, such as file transfer and email sending. Datagram sockets provide connectionless, unreliable data transmission services, suitable for applications with high real-time requirements that can tolerate some data loss, such as real-time audio/video transmission and online games.

Essential Socket Functions

Socket programming involves using specific system calls to create, connect, read, write, and close sockets. Common socket programming functions include socket(), bind(), listen(), accept(), connect(), send(), recv(), and more. These functions allow developers to create sockets in applications and bind them to specific IP addresses and port numbers, enabling network communication functionality.

socket() Function

// Creates a socket (AF_INET) specifying protocol (SOCK_STREAM) and type (0)
#include <sys/types.h>          
#include <sys/socket.h> // Required header file
int socket(int domain, int type, int protocol);

Parameters:

  • domain can be selected as:
    • AF_INET: IPv4 internet domain
    • AF_INET6: IPv6 internet domain
    • AF_UNIX: Unix domain
    • AF_ROUTE: Routing socket
    • AF_KEY: Key socket
    • AF_UNSPEC: Unspecified
  • type can be selected as:
    • SOCK_STREAM: Stream sockets provide reliable, connection-oriented communication streams, using TCP protocol to ensure correctness and order of data transmission
    • SOCK_DGRAM: Datagram sockets define a connectionless service where data is transmitted through independent messages, which are unordered and not guaranteed to be reliable or error-free. It uses datagram protocol
  • protocol: Usually set to "0"
    • 0 selects the default protocol corresponding to the type
    • IPPROTO_TCP: TCP transport protocol
    • IPPROTO_UDP: UDP transport protocol
    • IPPROTO_SCTP: SCTP transport protocol
    • IPPROTO_TIPC: TIPC transport protocol

Return value: Returns a non-negative socket descriptor on success, -1 on failure

bind() Function

// Binds IP address and port number to socket
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

Parameters:

  • sockfd: Socket descriptor
  • addr: Pointer to a sockaddr type containing local IP address and port number information, pointing to the protocol address structure to be bound to sockfd. This address structure varies depending on the address protocol family used when creating the socket.
  • addrlen: Length of the address, typically expressed as sizeof(struct sockaddr_in)

Return value: Returns 0 on success, -1 on failure

listen() Function

// Listens on the bound port
int listen(int sockfd, int backlog);

Parameters:

  • sockfd: Server socket descriptor returned by the socket system call
  • backlog: Specifies the maximum number of requests allowed in the request queue, most systems default to 5

Return value: Returns 0 on success, -1 on failure

accept() Function

// Accepts connection requests, called by TCP servers to return the next completed connection from the completed connection queue. If the completed connection queue is empty, the process is put to sleep
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

Parameters:

  • sockfd: Server socket descriptor returned by the socket system call
  • addr: Used to return the protocol address of the connected peer (client)
  • addrlen: Client address length, note that the address needs to be taken

Return value: The return value is a new socket descriptor. The return value represents the connected socket descriptor, while the first parameter is the server's listening socket descriptor. A server typically creates only one listening socket that exists throughout its lifetime. The kernel creates a connected socket for each client connection accepted by the server process (indicating that the TCP three-way handshake is complete). When the server finishes serving a given client, the corresponding connected socket is closed.

connect() Function

// Sends connection request, used by the client side after binding to establish a connection with the server
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

Parameters:

  • sockfd: Created socket descriptor
  • addr: Pointer to the server's IP address and port number address structure
  • addrlen: Address length, typically set to sizeof(struct sockaddr)

Return value: Returns 0 on success, returns -1 on error, with errno containing the corresponding error code

send() Function

// TCP send information
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

Parameters:

  • sockfd: Connected socket descriptor, i.e., the return value of the accept function
  • buf: Content to be sent
  • len: Length of content to be sent
  • flags: When set to MSG_DONTWAIT, indicates non-blocking; when set to 0, it has the same function as write

Return value: Returns the actual number of bytes sent on success, -1 on failure

recv() Function

// TCP receive information
ssize_t recv(int sockfd, const void *buf, size_t len, int flags);  

Parameters:

  • sockfd: Socket on which to receive
  • buf: Starting address for storing received data
  • len: Number of bytes of data to receive
  • flags: When set to MSG_DONTWAIT, indicates non-blocking; when set to 0, it has the same function as read

Return value: Returns the actual number of bytes received on success, -1 on failure

recvfrom Function (UDP)

// recvfrom is typically used for connectionless sockets, as this function can obtain the sender's address
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                struct sockaddr *src_addr, socklen_t *addrlen);

Parameters:

  • src_addr: struct sockaddr type variable that saves the source machine's IP address and port number
  • addrlen: Usually set to sizeof (struct sockaddr)

sendto Function (UDP)

// sendto is similar to send, the difference is that sendto allows specifying a target address on a connectionless socket
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);

Parameters:

  • dest_addr: Indicates the destination machine's IP address and port number information
  • addrlen: Often assigned sizeof(struct sockaddr), note that this is the transmission address

Return value: Number of bytes sent sent or -1 when an error occurs

Auxiliary Functions

#include <arpa/inet.h>
// Converts a short integer port number to the network port number type in sockaddr_in
// Converts host byte order to network byte order
uint16_t htons(uint16_t hostshort);

TCP Protocol Programming Flow

The following code consists of server and client components, which we need to code separately.

Server Implementation

Create socket → Bind socket with IP address and port → Listen on the bound port → Accept connection requests → Read information sent by client from socket → Close socket

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>

#define SERVER_PORT 9000
#define MAX_QUEUE 10 

/************************************************************
* Function: Receive client data from port 9000
* Input: None
* Output: Print client IP and received message
* Return: None
*************************************************************/

int main(int argc, char **argv)
{
    char buffer[512];
    int data_len;
    int listener_socket;
    int client_socket;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    int ret;
    int addr_len;

    signal(SIGCHLD, SIG_IGN);
	
    /* Create server socket descriptor */
    listener_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (listener_socket == -1)
    {
        perror("socket error");
        exit(EXIT_FAILURE);
    }
    
    /* Fill server sockaddr_in structure */
    server_addr.sin_family   = AF_INET;
    /* Convert port number to network byte order */
    server_addr.sin_port     = htons(SERVER_PORT);
    /* Receive data from all network interfaces */
    server_addr.sin_addr.s_addr  = INADDR_ANY;
    memset(server_addr.sin_zero, 0, sizeof(server_addr.sin_zero));
    
    /* Bind socket descriptor */
    ret = bind(listener_socket, (const struct sockaddr *)&server_addr, sizeof(struct sockaddr));
    if (ret == -1)
    {
        perror("bind error");
        exit(EXIT_FAILURE);
    }
    
    ret = listen(listener_socket, MAX_QUEUE);
    if (ret == -1)
    {
        perror("listen error");
        exit(EXIT_FAILURE);
    }
    
    while (1)
    {
        addr_len = sizeof(struct sockaddr);
        /* Server blocks until client establishes connection */
        client_socket = accept(listener_socket, (struct sockaddr *)&client_addr, &addr_len);
        if (client_socket != -1)
        {
            printf("Connection established from %s\n", inet_ntoa(client_addr.sin_addr));
        }
        
        if (fork() == 0)  // Child process
        {
            while (1)
            {
                memset(buffer, 0, sizeof(buffer));
                /* Receive data */
                data_len = recv(client_socket, buffer, sizeof(buffer), 0);
                buffer[data_len] = '\0';
                
                if (data_len <= 0)
                {
                    close(client_socket);
                    exit(EXIT_SUCCESS);
                }
                else
                {
                    printf("Message from %s: %s\n", inet_ntoa(client_addr.sin_addr), buffer);
                }
            }
        }
        close(client_socket);  // Parent process closes the client socket
    }
    
    close(listener_socket);
    return 0;
}

Client Implementation

Create socket → Connect to specified computer port → Send messages to socket → Close socket

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#define SERVER_PORT 9000
/************************************************************
* Function: Send data to port 9000 of specified IP
* Input: Server IP in dotted decimal format
* Output: None
* Return: None
*************************************************************/

int main(int argc, char **argv)
{
    char buffer[512];
    int data_len;
    struct sockaddr_in server_addr;
    int ret;
    int client_socket;

    
    if (argc != 2)
    {
        fprintf(stderr, "Usage:\n");
        fprintf(stderr, "%s <server_ip>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    
    /* Create client socket descriptor */
    client_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (client_socket == -1)
    {
        perror("socket error");
        exit(EXIT_FAILURE);
    }
    
    /* Fill server information */
    server_addr.sin_family   = AF_INET;
    /* Convert host byte order to network byte order */
    server_addr.sin_port     = htons(SERVER_PORT);
    
    if (inet_aton(argv[1], &server_addr.sin_addr) == 0)
    {
        fprintf(stderr, "Invalid server IP\n");
        exit(EXIT_FAILURE);
    }
    
    memset(server_addr.sin_zero, 0, sizeof(server_addr.sin_zero));
    
    /* Client initiates connection request */
    ret = connect(client_socket, (const struct sockaddr *)&server_addr, sizeof(struct sockaddr));
    if (ret == -1)
    {
        perror("connect error");
        exit(EXIT_FAILURE);
    }

    
    while (1)
    {
        if (fgets(buffer, sizeof(buffer), stdin))
        {
            data_len = send(client_socket, buffer, strlen(buffer), 0);
            if (data_len <= 0)
            {
                perror("send error");
                close(client_socket);
                exit(EXIT_FAILURE);
            }
        }
    }
    
    close(client_socket);
    return 0;
}

UDP Protocol Programming Flow

UDP protocol code is relatively simple. The client can reuse the code from TCP (with the type parameter in the socket function changed). The general flow is:

Create socket → Bind socket with IP address and port → Receive information (recvfrom) → Close socket

UDP Server Implementation

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>

/* Server port is 9000 */
#define SERVER_PORT 9000

/************************************************************
* Function: Receive client data from port 9000
* Input: None
* Output: Print client IP and received message
* Return: None
*************************************************************/

int main(int argc, char **argv)
{
    char buffer[512];
    int data_len;
    int server_socket;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    int ret;
    socklen_t addr_len;

    /* Create datagram socket */
    server_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (server_socket == -1)
    {
        perror("socket error");
        exit(EXIT_FAILURE);
    }
    
    /* Fill server sockaddr_in structure */
    server_addr.sin_family   = AF_INET;
    server_addr.sin_port     = htons(SERVER_PORT);
    server_addr.sin_addr.s_addr  = INADDR_ANY;
    memset(server_addr.sin_zero, 0, sizeof(server_addr.sin_zero));
    
    /* Bind socket */
    ret = bind(server_socket, (const struct sockaddr *)&server_addr, sizeof(struct sockaddr));
    if (ret == -1)
    {
        perror("bind error");
        exit(EXIT_FAILURE);
    }

    
    while (1)
    {
        addr_len = sizeof(struct sockaddr);
        /* Receive client datagram, return value is number of bytes received */
        data_len = recvfrom(server_socket, buffer, sizeof(buffer), 0, 
                           (struct sockaddr *)&client_addr, &addr_len);
        
        if (data_len > 0)
        {
            buffer[data_len] = '\0';
            printf("Message from %s: %s\n", inet_ntoa(client_addr.sin_addr), buffer);
        }
   
    }
    
    close(server_socket);
    return 0;
}

UDP Client Implementation

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

/* Server port is 9000 */
#define SERVER_PORT 9000

/************************************************************
* Function: Send data to port 9000 of specified IP
* Input: Server IP in dotted decimal format
* Output: None
* Return: None
*************************************************************/

int main(int argc, char **argv)
{
    char buffer[512];
    int data_len;
    struct sockaddr_in server_addr;
    int ret;
    socklen_t addr_len;
    int client_socket;

    
    if (argc != 2)
    {
        fprintf(stderr, "Usage:\n");
        fprintf(stderr, "%s <server_ip>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    
    /* Create datagram socket */
    client_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (client_socket == -1)
    {
        perror("socket error");
        exit(EXIT_FAILURE);
    }
    
    server_addr.sin_family   = AF_INET;
    server_addr.sin_port     = htons(SERVER_PORT);
    
    if (inet_aton(argv[1], &server_addr.sin_addr) == 0)
    {
        fprintf(stderr, "Invalid server IP\n");
        exit(EXIT_FAILURE);
    }
    
    memset(server_addr.sin_zero, 0, sizeof(server_addr.sin_zero));
    
    /* For UDP, we can use sendto directly without connect */
    while (1)
    {
        if (fgets(buffer, sizeof(buffer), stdin))
        {
            data_len = sendto(client_socket, buffer, strlen(buffer), 0,
                             (const struct sockaddr *)&server_addr, sizeof(struct sockaddr));
            
            if (data_len <= 0)
            {
                perror("send error");
                close(client_socket);
                exit(EXIT_FAILURE);
            }
        }
    }
    
    close(client_socket);
    return 0;
}

Tags: Linux Network Programming tcp udp Sockets

Posted on Sat, 23 May 2026 20:50:48 +0000 by guzman-el-bueno