DAY38 TCP Network Programming

TCP Network Programming

I. Preface

In network communication protocols, TCP (Transmission Control Protocol) has become the cornerstone of scenarios such as file transfer, web browsing, and instant messaging due to its characteristics of reliability, connection-oriented, and stream-oriented transmission. Unlike UDP's connectionless and unreliable transmission, TCP ensures the integrity and order of data transmission through a series of mechanisms, serving as the foundation of Internet applications.

II. Core Theoretical Foundations of TCP

2.1 Common Network Architecture Models

Network communication is mainly divided into three architecture models, among which TCP is widely used in the CS architecture. The characteristics of each model are as follows:

Architecture Model English Full Name Core Components Application Scenarios Core Features
CS Model Client-Server Dedicated Client + Server WeChat, QQ, Game Clients, Desktop Software 1. Dedicated client with powerful functions; 2. Supports custom/standard protocols; 3. Resources can be cached locally for fast response; 4. Relatively complex functions, requiring separate client installation
BS Model Browser-Server Universal Browser + Server Web Browsing, Online Shopping Malls, Office Systems 1. Universal client (browser) without separate installation; 2. Fixed use of HTTP/HTTPS application layer protocols; 3. Resources are provided by the server with no local cache (temporary cache available); 4. Relatively simple functions, dependent on browser environment
P2P Model Peer-to-Peer Multiple Peer Nodes (both Client and Server) Thunder, BT Download, Peer-to-Peer File Transfer 1. No central server, with equal node status; 2. Downloaders are also uploaders, improving transmission efficiency; 3. Strong network scalability, no reliance on central node support

2.2 Core Features of TCP

As a reliable transport layer protocol, TCP has the following key features, which are also the core differences from UDP:

  1. Connection-Oriented: Both communication parties must first establish a connection through the "Three-Way Handshake". Data transmission can only be performed after the connection is established, and the connection is closed through the "Four-Way Wavehand".
  2. Reliable Transmission: Ensures that data arrives in order and intact without packet loss or out-of-order issues through "Acknowledgment Mechanism (ACK)", "Timeout Retransmission", "Flow Control", and "Congestion Control".
  3. Stream Socket: Data transmission is boundaryless, transmitted continuously in the form of byte streams. Data sent multiple times by the sender may be received at once by the receiver.
  4. Full-Duplex Communication: Both communication parties have their own send and receive buffers, allowing simultaneous sending and receiving operations without interfering with each other.
  5. No Corresponding Requirement for Send/Receive Times: The sender can send data twice (e.g., sending 10 bytes and 20 bytes respectively), and the receiver can receive 30 bytes at once, without matching the number of send times.
  6. Write Blocking Exists: When the send buffer is full (about 64K by default), the send operation will block until there is free space in the buffer.
  7. Connection Status Awareness: If one party disconnects abnormally or closes normally, the other party can perceive the change in connection status through protocol mechanisms and terminate communication in a timely manner.

2.3 Explanation of Key TCP Concepts

(1) Listening Socket and Communication Socket

There are two types of sockets on the TCP server with clear division of responsibilities:

  • Listening Socket (listfd) : Created by socket(), bound by bind(), and monitored by listen(). It is only responsible for waiting for client connection requests, does not participate in actual data transmission, and its lifecycle runs through the entire operation of the server.
  • Communication Socket (connfd) : Returned by the accept() function, it is specially used for sending and receiving data with the client that has established a connection. One client corresponds to one communication socket, which needs to be closed after the client disconnects.
(2) Sticky Packet Problem

Sticky packets are a unique problem of TCP stream-oriented transmission: due to the lack of data boundaries, small data segments sent multiple times by the sender may be merged by the kernel and sent at once, and the receiver cannot distinguish the data boundaries, leading to abnormal data parsing.

  • Causes: To improve transmission efficiency, TCP uses the Nagle algorithm to merge small data packets; the receiver buffer does not read data in a timely manner, leading to the accumulation of multiple data packets.
  • Impact: Unable to correctly split complete business data (e.g., sending "username" and "password" continuously, the receiver may receive the spliced string at once and cannot distinguish them).
(3) Three-Way Handshake and Four-Way Wavehand
  • Three-Way Handshake : The process of establishing a TCP connection, ensuring that both parties have normal sending and receiving capabilities, allocating resources, and avoiding invalid connections from occupying resources.
    1. Client → Server: Sends a SYN packet to request a connection establishment;
    2. Server → Client: Sends a SYN+ACK packet to confirm receipt of the client's request and synchronize its own sequence number;
    3. Client → Server: Sends an ACK packet to confirm receipt of the server's response, and the connection is established.
  • Four-Way Wavehand : The process of closing a TCP connection, ensuring that both parties have completed data transmission and releasing resources.
    1. Active Closer → Passive Closer: Sends a FIN packet to inform the other party that it will no longer send data;
    2. Passive Closer → Active Closer: Sends an ACK packet to confirm receipt of the FIN packet. At this time, the passive closer can continue to send remaining data;
    3. Passive Closer → Active Closer: Sends a FIN packet to inform the other party that data transmission is complete and preparation for closing;
    4. Active Closer → Passive Closer: Sends an ACK packet to confirm receipt of the FIN packet, waits for a timeout and then closes the connection, and the passive closer closes the connection directly after receiving the ACK.

2.4 Detailed Explanation of Core TCP API Functions

TCP programming relies on a series of system call functions. The functions, parameters, and return values of the core functions are as follows:

Function Prototype Function Description Applicable Role Key Parameter Explanation Return Value
int socket(int domain, int type, int protocol); Creates a socket descriptor and establishes an interface between the application and the kernel network protocol stack Server/Client domain: Address family (AF_INET=IPv4); type: Socket type (SOCK_STREAM=TCP); protocol: Protocol type (0=automatic adaptation) Success: Socket ID; Failure: -1
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen); Binds the socket to the local IP + port and determines the server listening address Server sockfd: Listening socket ID; my_addr: Local address structure; addrlen: Length of the address structure Success: 0; Failure: -1
int listen(int sockfd, int backlog); Converts the listening socket to the listening state and waits for client connections Server sockfd: Listening socket ID; backlog: Length of the three-way handshake queue (maximum number of allowed pending connections) Success: 0; Failure: -1
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); Takes out the established client connection from the listening queue and connects it to the current program Server sockfd: Listening socket ID; addr: Used to store client address information (NULL=not concerned); addrlen: Length of the client address (pass in the address) Success: Communication socket ID; Failure: -1
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); Initiates a connection request to the server and completes the three-way handshake Client sockfd: Client socket ID; addr: Server address structure; addrlen: Length of the server address Success: 0; Failure: -1
ssize_t recv(int sockfd, void *buf, size_t len, int flags); Receives data from the socket to the local buffer Server/Client sockfd: Server=communication socket; Client=created socket; buf: Data receiving buffer; len: Buffer size; flags: Receiving mode (0=blocking) Success: Number of received bytes; Failure: -1; Peer closed: 0
ssize_t send(int sockfd, const void *msg, size_t len, int flags); Sends local data to the socket send buffer Server/Client sockfd: Server=communication socket; Client=created socket; msg: Data to be sent; len: Length of the sent data; flags: Sending mode (0=blocking) Success: Number of sent bytes; Failure: -1

III. TCP Programming Process

3.1 Server Process (Core: Listen → Connect → Communicate → Close)

  1. Call socket() to create a listening socket;
  2. Call bzero() to clear the address structure and initialize the server IP and port (convert to network byte order);
  3. Call bind() to bind the listening socket to the local address;
  4. Call listen() to set the socket to the listening state;
  5. Call accept() to block and wait for client connections, and return a communication socket on success;
  6. Cyclically call recv() to receive client data and send() to reply data;
  7. Close the communication socket after the client disconnects;
  8. Close the listening socket when the server exits.

3.2 Client Process (Core: Connect → Communicate → Close)

  1. Call socket() to create a client socket;
  2. Call bzero() to clear the address structure and initialize the server IP and port;
  3. Call connect() to initiate a connection request to the server;
  4. Cyclically call send() to send data and recv() to receive server replies;
  5. Call close() to close the client socket after communication ends.

3.3 Comparison Table of Server and Client Processes

Process Step Server Operation Client Operation Corresponding Functions
1 Create listening socket Create client socket socket()
2 Initialize local address structure Initialize server address structure bzero(), htons(), inet_addr()
3 Bind socket to local address Initiate connection request to server bind()
4 Set socket to listening state - listen()
5 Accept client connection and create communication socket - accept()
6 Receive client data + reply data Send data + receive server reply recv(), send()
7 Close communication socket Close client socket close(connfd)
8 Close listening socket - close(listfd)

IV. TCP Practice: Timestamp Echo Service (Complete Runable Code)

This section implements a simple TCP server that receives messages from the client, appends the current system timestamp, and replies to the client. The client cyclically sends test messages and receives replies, intuitively demonstrating the complete process of TCP communication.

4.1 Server Code (tcp_server.c)

c 复制代码
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h> /* See NOTES */
#include <time.h>
#include <unistd.h>

// Define an alias for the socket address structure pointer to simplify code writing
typedef struct sockaddr*(SA);

int main(int argc, char *argv[])
{
    // 1. Create a TCP listening socket (SOCK_STREAM specifies a stream socket, corresponding to TCP)
    int listfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == listfd)
    {
        perror("socket error"); // Print error information if an error occurs
        exit(1); // Abnormal exit
    }
    printf("TCP listening socket created successfully, socket ID: %d\n", listfd);

    // 2. Initialize server and client address structures
    struct sockaddr_in ser, cli;
    bzero(&ser, sizeof(ser)); // Clear the server address structure
    bzero(&cli, sizeof(cli)); // Clear the client address structure

    // Configure server address information
    ser.sin_family = AF_INET; // Specify the IPv4 address family
    ser.sin_addr.s_addr = INADDR_ANY; // Bind all local network card IPs without specifying a specific IP
    ser.sin_port = htons(50000); // Convert port number to network byte order (50000 is a custom port)

    // 3. Bind the listening socket to the server address (IP + port)
    int ret = bind(listfd, (SA)&ser, sizeof(ser));
    if (-1 == ret)
    {
        perror("bind error");
        exit(1);
    }
    printf("Listening socket bound successfully, listening port: 50000\n");

    // 4. Set the listening socket to the listening state and wait for client connections
    // backlog=3: Allow 3 clients to be in the three-way handshake waiting queue
    listen(listfd, 3);
    printf("Server has entered listening state, waiting for client connections...\n");

    // 5. Accept client connections and create a communication socket (blocking wait)
    socklen_t len = sizeof(cli); // Length of the client address structure
    int connfd = accept(listfd, (SA)&cli, &len);
    if (-1 == connfd)
    {
        perror("accept error");
        exit(1);
    }
    printf("Client connected successfully, communication socket ID: %d\n", connfd);

    // 6. Cyclically receive client data and reply with timestamps
    while (1)
    {
        char buf[512] = {0}; // Initialize the receive buffer
        // Receive client data from the communication socket (blocking mode)
        int n = recv(connfd, buf, sizeof(buf), 0);
        if (n <= 0)
        {
            // n=0 means the client is closed normally, n=-1 means reception failed
            printf("client close or recv error\n");
            break;
        }
        printf("Received data from client: %s\n", buf);

        // Get the current system time and append it to the received data
        time_t tm;
        time(&tm); // Get the current timestamp
        struct tm* info = localtime(&tm); // Convert to local time structure
        // Format the string and append the timestamp (hour:minute:second)
        sprintf(buf, "%s %d:%d:%d", buf, info->tm_hour, info->tm_min, info->tm_sec);

        // Send the spliced reply data to the client
        send(connfd, buf, strlen(buf), 0);
        printf("Replied data to client: %s\n", buf);
    }

    // 7. Close the sockets and release resources
    close(connfd); // Close the communication socket
    close(listfd); // Close the listening socket
    printf("Server exited normally, sockets closed\n");
    return 0;
}
Key Analysis of Server Code
  • INADDR_ANY: Binds all local network card IPs without manually specifying a specific IP address, adapting to multi-network card environments;
  • listen(listfd, 3): Sets the length of the listening queue, and client connections exceeding the queue will be rejected;
  • accept(listfd, (SA)&cli, &len): Blocks and waits for client connections, and stores client address information in the cli structure after success;
  • recv(connfd, buf, sizeof(buf), 0): Receives data through the communication socket, where connfd is the unique communication identifier corresponding to the client;
  • Loop exit condition: n<=0 indicates that the client closes the connection or reception fails, at which point communication is terminated and the socket is closed.

4.2 Client Code (tcp_client.c)

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h> /* See NOTES */
#include <netinet/ip.h>
#include <time.h>

// Define an alias for the socket address structure pointer
typedef struct sockaddr*(SA);

int main(int argc, char *argv[])
{
    // 1. Create a TCP client socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd)
    {
        perror("socket");
        exit(-1);
    }
    printf("TCP client socket created successfully, socket ID: %d\n", sockfd);

    // 2. Initialize the server address structure and configure connection target information
    struct sockaddr_in ser;
    bzero(&ser, sizeof(ser)); // Clear the server address structure
    ser.sin_family = AF_INET; // IPv4 address family
    ser.sin_port = htons(50000); // Server port (consistent with the server)
    ser.sin_addr.s_addr = INADDR_ANY; // Connect to the local server (can be replaced with the actual server IP)

    // 3. Initiate a connection request to the server and complete the three-way handshake
    int ret = connect(sockfd, (SA)&ser, sizeof(ser));
    if (-1 == ret)
    {
        perror("connect");
        exit(-1);
    }
    printf("Client connected to server successfully, starting communication...\n");

    // 4. Cyclically send test data and receive server replies (loop 100 times)
    int i = 100;
    while (i--)
    {
        char buf[512] = "this is tcp test"; // Test data to be sent
        // Send data to the server
        send(sockfd, buf, strlen(buf), 0);
        printf("Sent data to server: %s (remaining send times: %d)\n", buf, i);

        bzero(buf, sizeof(buf)); // Clear the buffer to prepare for receiving replies
        // Receive reply data from the server
        recv(sockfd, buf, sizeof(buf), 0);
        printf("Received reply from server: %s\n", buf);

        sleep(1); // Send once every 1 second to avoid sending too fast
    }

    // 5. Close the client socket and release resources
    close(sockfd);
    printf("Client communication ended, socket closed\n");
    return 0;
}
Key Analysis of Client Code
  • connect(sockfd, (SA)&ser, sizeof(ser)): Actively initiates a connection to the server with parameters as the server address information. After successfully completing the three-way handshake, the connection is established;
  • ser.sin_addr.s_addr = INADDR_ANY: Connects to the local server (local communication). For cross-machine communication, it can be replaced with the actual server IP (e.g., inet_addr("192.168.14.128"));
  • sleep(1): Controls the sending rate to avoid excessive pressure on the server due to frequent sending, and facilitates observing the communication process.

V. Compilation and Running Steps (Ubuntu Virtual Machine Environment)

5.1 Environment Preparation

  1. Ensure that the gcc compiler is installed in the Ubuntu system (if not installed, execute: sudo apt update && sudo apt install gcc -y);
  2. Open two terminals (Terminal 1 runs the server, Terminal 2 runs the client), and ensure that the current directory contains the tcp_server.c and tcp_client.c files.

5.2 Compile the Code

bash 复制代码
# Compile the server code
gcc tcp_server.c -o tcp_server
# Compile the client code
gcc tcp_client.c -o tcp_client

5.3 Run the Program

Step 1: Start the Server (Terminal 1)
bash 复制代码
./tcp_server

Server Output (Example):

复制代码
TCP listening socket created successfully, socket ID: 3
Listening socket bound successfully, listening port: 50000
Server has entered listening state, waiting for client connections...
Client connected successfully, communication socket ID: 4
Received data from client: this is tcp test
Replied data to client: this is tcp test 16:20:30
Received data from client: this is tcp test
Replied data to client: this is tcp test 16:20:31
...
Step 2: Start the Client (Terminal 2)
bash 复制代码
./tcp_client

Client Output (Example):

复制代码
TCP client socket created successfully, socket ID: 3
Client connected to server successfully, starting communication...
Sent data to server: this is tcp test (remaining send times: 99)
Received reply from server: this is tcp test 16:20:30
Sent data to server: this is tcp test (remaining send times: 98)
Received reply from server: this is tcp test 16:20:31
...
Client communication ended, socket closed
Step 3: Stop the Program
  • The client will exit automatically after completing 100 sends, and the server will also exit after detecting that the client is closed;
  • To stop manually, use Ctrl+C to terminate the program.

VI. Troubleshooting Common Problems

6.1 bind() Failed: Address already in use

  • Cause: The port bound by the server (e.g., 50000) is already occupied by another process;
  • Solutions:
    1. Use netstat -anp | grep 50000 to find the process ID occupying the port;
    2. Use kill -9 <Process ID> to terminate the occupying process;
    3. Modify the port number in the code (e.g., change to 50001) and recompile and run.

6.2 connect() Failed: Connection refused

  • Cause: The server is not started, the server port configuration is incorrect, or the server listening address does not match the client connection address;
  • Solutions:
    1. Start the server first, then start the client;
    2. Ensure that the port numbers of the client and server are consistent;
    3. For cross-machine communication, the client must fill in the actual IP of the server instead of INADDR_ANY.

6.3 Sticky Packet Problem Occurs

  • Phenomenon: The "this is tcp test" sent multiple times by the client is received by the server at once, resulting in data splicing;
  • Temporary Solutions:
    1. Add sleep() after each send by the sender to avoid kernel merging of data packets;
    2. The receiver processes data in a timely manner after each reception and clears the buffer;
    3. Long-term Solution: Add data boundary identifiers at the application layer (e.g., fixed length, delimiters, header+body format) to achieve correct data splitting.

6.4 The Server Can Only Handle One Client Connection

  • Cause: The sample code is a single-client model, and accept() only accepts one client connection, and subsequent clients will be rejected;
  • Solution: Use pthread multi-threading or fork multi-processing to allow the server to handle multiple client connections simultaneously.

VII. Summary and Expansion

7.1 Article Summary

  1. Core TCP Features: Connection-oriented, reliable transmission, stream sockets, full-duplex communication, with connection lifecycle managed through three-way handshake and four-way wavehand;
  2. Key Concepts: Listening sockets (responsible for listening) and communication sockets (responsible for communication) have clear divisions of labor, and the sticky packet problem is an inherent problem of TCP stream-oriented transmission;
  3. Programming Process: Server (Create → Bind → Listen → Accept → Communicate → Close), Client (Create → Connect → Communicate → Close);
  4. Practical Verification: The timestamp echo service implements basic TCP communication, verifying the reliable sending and receiving of data and connection management.

7.2 Expansion Directions

  1. Multi-Client Concurrent Processing: Use pthread multi-threading or fork multi-processing to enable the server to handle multiple client connections simultaneously;
  2. Thorough Solution to Sticky Packet Problem: Design data formats at the application layer (e.g., "data length + data content") to achieve correct data splitting;
  3. Non-Blocking IO and Multiplexing : Use fcntl to set non-blocking sockets, and combine select/poll/epoll to implement high-concurrency servers;
  4. Advanced TCP Protocols: In-depth study of TCP flow control, congestion control, and sliding window mechanisms to understand the underlying principles of reliable transmission;
  5. Practical Application Development : Implement practical applications such as file transfer, instant messaging, and HTTP servers based on TCP to improve engineering practice capabilities.
    Through the theoretical learning and practical operations in this article, I believe you have mastered the basic process of TCP network programming. As the core protocol of the Internet, TCP has relatively complex knowledge points. In the future, you need to deepen your understanding through more practical exercises and gradually master the development skills of high-concurrency and high-performance TCP applications.
相关推荐
rannn_1112 小时前
【SQL题解】力扣高频 SQL 50题|DAY5
数据库·后端·sql·leetcode·题解
ZStack开发者社区2 小时前
VMware替代 | ZStack Cloud与NSX二层三层网络对比分析
网络
ss2732 小时前
ThreadPoolExecutor七大核心参数:从源码看线程池的设计
java·数据库·算法
川212 小时前
ZooKeeper配置+失误
linux·分布式·zookeeper
+VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue健康茶饮销售管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
陌路202 小时前
redis的哨兵模式
数据库·redis·缓存
qq_433554542 小时前
C++ 状压DP(01矩阵约束问题)
c++·算法·矩阵
ohoy2 小时前
mysql数据存在则更新、不存在插入
数据库·mysql