欢迎光临
我们一直在努力

Linux软件编程:TCP通信

1. TCP协议简介

TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。它在网络编程中广泛应用于需要可靠数据传输的场景,如文件传输、网页访问、邮件传输等。TCP通过三次握手建立连接,通过四次挥手释放连接,并提供了确认机制、流量控制、拥塞控制等功能,保证数据有序、无差错、不重复地传输。

在Linux系统中,TCP网络编程主要通过套接字(Socket)接口实现。本文将详细介绍TCP通信的基本流程、关键函数接口,并通过一个文件传输的示例代码帮助读者理解实际应用。

2. TCP通信流程

TCP通信分为服务器端和客户端两部分。服务器端通常被动等待连接,客户端主动发起连接。基本流程如下:

服务器端(收端)流程:

  • 创建套接字(socket)

  • 绑定地址和端口(bind)

  • 监听连接请求(listen)

  • 接受客户端连接(accept)

  • 接收数据(recv)或发送数据(send)

  • 关闭连接(close)

  • 客户端(发端)流程:

  • 创建套接字(socket)

  • 连接服务器(connect)

  • 发送数据(send)或接收数据(recv)

  • 关闭连接(close)

  • 下面用流程图示意:

    text

    服务器端:
    socket() → bind() → listen() → accept() → recv()/send() → close()

    客户端:
    socket() → connect() → send()/recv() → close()

    3. 主要函数接口详解

    以下函数均定义在 <sys/socket.h> 中,使用时需包含相关头文件。

    3.1 socket() — 创建套接字

    c

    int socket(int domain, int type, int protocol);

    • 功能:创建一个套接字,返回文件描述符。

    • 参数:

      • domain:协议族,IPv4使用 AF_INET。

      • type:套接字类型,TCP使用 SOCK_STREAM(流式套接字)。

      • protocol:协议,通常填0,表示自动选择对应协议(TCP对应IPPROTO_TCP)。

    • 返回值:成功返回新套接字的文件描述符,失败返回-1。

    3.2 bind() — 绑定地址

    c

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    • 功能:将套接字与本地IP地址和端口号绑定。

    • 参数:

      • sockfd:socket返回的文件描述符。

      • addr:指向 struct sockaddr_in 结构体的指针,包含IP地址和端口号(注意类型转换)。

      • addrlen:地址结构体的长度。

    • 返回值:成功返回0,失败返回-1。

    • 注意:只能绑定自己的IP地址,端口号不能重复绑定。

    3.3 listen() — 监听连接

    c

    int listen(int sockfd, int backlog);

    • 功能:将套接字设置为监听状态,等待客户端连接。

    • 参数:

      • sockfd:已绑定的套接字。

      • backlog:未处理的三次握手请求的最大排队个数(即已完成三次握手但尚未被accept的连接队列长度)。

    • 返回值:成功返回0,失败返回-1。

    3.4 accept() — 接受连接

    c

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    • 功能:从监听队列中取出第一个连接请求,并返回一个新的套接字用于与该客户端通信。

    • 参数:

      • sockfd:监听套接字。

      • addr:用于存放客户端地址信息的缓冲区指针,可为NULL。

      • addrlen:传入时指定addr缓冲区大小,返回时实际地址长度。

    • 返回值:成功返回新的通信套接字文件描述符,失败返回-1。

    3.5 connect() — 发起连接

    c

    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    • 功能:客户端向服务器发起连接请求(三次握手)。

    • 参数:

      • sockfd:客户端套接字。

      • addr:服务器地址信息。

      • addrlen:地址长度。

    • 返回值:成功返回0,失败返回-1。

    3.6 send() — 发送数据

    c

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

    • 功能:通过已连接的套接字发送数据。

    • 参数:

      • sockfd:通信套接字(服务器端accept返回的套接字或客户端socket)。

      • buf:待发送数据的缓冲区。

      • len:发送的数据长度。

      • flags:标志位,通常设为0。

    • 返回值:成功返回实际发送的字节数,失败返回-1。

    3.7 recv() — 接收数据

    c

    ssize_t recv(int sockfd, void *buf, size_t len, int flags);

    • 功能:从已连接的套接字接收数据。

    • 参数:

      • sockfd:通信套接字。

      • buf:接收缓冲区。

      • len:缓冲区大小。

      • flags:标志位,通常设为0。

    • 返回值:成功返回实际接收的字节数,若对方关闭连接则返回0,失败返回-1。

    3.8 close() — 关闭套接字

    c

    int close(int fd);

    • 功能:关闭套接字,释放资源。

    4. 代码实例:使用TCP实现文件传输

    下面实现一个简单的文件传输程序:客户端将本地文件发送给服务器,服务器接收并保存为另一个文件。

    服务器端代码(tcp_server.c)

    c

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

    #define PORT 8888
    #define BUFFER_SIZE 1024

    int main(int argc, char *argv[]) {
    if (argc != 2) {
    fprintf(stderr, "Usage: %s <save_filename>\\n", argv[0]);
    exit(1);
    }

    int listen_fd, conn_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    char buffer[BUFFER_SIZE];
    FILE *fp;

    // 1. 创建套接字
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd == -1) {
    perror("socket");
    exit(1);
    }

    // 2. 绑定地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听所有网卡
    server_addr.sin_port = htons(PORT);

    if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
    perror("bind");
    close(listen_fd);
    exit(1);
    }

    // 3. 监听
    if (listen(listen_fd, 5) == -1) {
    perror("listen");
    close(listen_fd);
    exit(1);
    }

    printf("Server listening on port %d…\\n", PORT);

    // 4. 接受连接
    conn_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &client_len);
    if (conn_fd == -1) {
    perror("accept");
    close(listen_fd);
    exit(1);
    }
    printf("Client connected: %s:%d\\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

    // 打开文件准备写入
    fp = fopen(argv[1], "wb");
    if (fp == NULL) {
    perror("fopen");
    close(conn_fd);
    close(listen_fd);
    exit(1);
    }

    // 5. 接收数据并写入文件
    int bytes;
    while ((bytes = recv(conn_fd, buffer, BUFFER_SIZE, 0)) > 0) {
    fwrite(buffer, 1, bytes, fp);
    }
    if (bytes == 0) {
    printf("File transfer completed.\\n");
    } else {
    perror("recv");
    }

    fclose(fp);
    close(conn_fd);
    close(listen_fd);
    return 0;
    }

    客户端代码(tcp_client.c)

    c

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

    #define PORT 8888
    #define BUFFER_SIZE 1024

    int main(int argc, char *argv[]) {
    if (argc != 3) {
    fprintf(stderr, "Usage: %s <server_ip> <filename>\\n", argv[0]);
    exit(1);
    }

    int sock_fd;
    struct sockaddr_in server_addr;
    char buffer[BUFFER_SIZE];
    FILE *fp;

    // 1. 创建套接字
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd == -1) {
    perror("socket");
    exit(1);
    }

    // 2. 设置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    if (inet_pton(AF_INET, argv[1], &server_addr.sin_addr) <= 0) {
    perror("inet_pton");
    close(sock_fd);
    exit(1);
    }

    // 3. 连接服务器
    if (connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
    perror("connect");
    close(sock_fd);
    exit(1);
    }
    printf("Connected to server %s:%d\\n", argv[1], PORT);

    // 打开待发送文件
    fp = fopen(argv[2], "rb");
    if (fp == NULL) {
    perror("fopen");
    close(sock_fd);
    exit(1);
    }

    // 4. 发送文件内容
    int bytes;
    while ((bytes = fread(buffer, 1, BUFFER_SIZE, fp)) > 0) {
    if (send(sock_fd, buffer, bytes, 0) == -1) {
    perror("send");
    break;
    }
    }
    printf("File sent.\\n");

    fclose(fp);
    close(sock_fd);
    return 0;
    }

    编译与运行:

    bash

    gcc tcp_server.c -o server
    gcc tcp_client.c -o client

    # 先运行服务器(指定保存的文件名)
    ./server received_file

    # 再运行客户端(指定服务器IP和要发送的文件)
    ./client 192.168.1.100 myfile.txt

    5. UDP与TCP的区别

    PDF中总结了UDP和TCP的主要区别,这里整理成表格形式:

    特性UDPTCP
    资源开销 小(UDP包头8字节) 大(TCP包头至少20字节)
    传输机制 简单,无连接 复杂,面向连接(三次握手、四次挥手)
    可靠性 不可靠,可能丢包、乱序 可靠,通过确认、重传、排序保证
    流量控制 有滑动窗口机制
    拥塞控制 有拥塞控制算法
    应用场景 实时性要求高、允许丢包(如音视频、DNS) 要求可靠传输(如文件、网页、邮件)

    TCP的复杂性带来了可靠性,但也增加了开销和延迟。UDP则轻量高效,适合实时通信。

    6. 总结

    本文详细介绍了Linux下TCP网络编程的基本流程和核心函数接口,并通过一个文件传输示例展示了实际应用。TCP的面向连接特性保证了数据传输的可靠性,适用于对数据完整性要求高的场景。掌握TCP编程是学习网络通信的基础,读者可以在此基础上进一步探索多线程并发服务器、IO多路复用等高级主题。

    练习:客户端与服务端的双向通信

    client:

    serve:

    赞(0)
    未经允许不得转载:171主机测评 » Linux软件编程:TCP通信
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址