Linux网络服务器编程:TCP与UDP详解

Linux网络服务器编程中,TCP和UDP是两种主要的传输层协议。本文将详细分析TCP和UDP在服务器编程中的使用、原理、代码示例、数据流动,以及一些异常情况的处理方式。

一、TCP与UDP概述

1.1 TCP的原理

TCP是一种面向连接的协议,它通过三次握手建立连接,然后在连接上进行可靠的数据传输。TCP使用序列号和确认应答(ACK)来保证数据的可靠传输,通过滑动窗口和拥塞控制算法进行流量控制和拥塞控制。

1.2 UDP的原理

相比于TCP,UDP是一种更简单的协议。UDP是无连接的,它直接在IP协议之上发送数据报,不提供数据的可靠传输、流量控制或拥塞控制。因此,UDP的延迟和开销较小,适用于对实时性要求高的应用,如语音和视频通信。

1.3 数据流动

在TCP和UDP通信中,数据是从客户端流向服务器的。客户端首先建立连接(TCP)或直接发送数据报(UDP),然后服务器接收并处理这些数据,可能会返回响应给客户端。在TCP通信中,数据的流动是双向的,客户端和服务器都可以发送数据和接收数据。在UDP通信中,数据的流动也是双向的,但是由于UDP是无连接的,客户端和服务器可以独立地发送和接收数据。

二、Socket的使用

在Linux网络服务器编程中,我们使用socket来实现TCP和UDP通信。以下是TCP和UDP的socket使用示例:

2.1 TCP Socket示例

服务器端:

cpp 复制代码
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <iostream>

int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080);
    server_addr.sin_addr.s_addr = INADDR_ANY;

    bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    listen(server_fd, 5);

    while (true) {
        struct sockaddr_in client_addr;
        socklen_t client_addr_len = sizeof(client_addr);
        int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);

        char buffer[1024];
        ssize_t read_len = read(client_fd, buffer, sizeof(buffer) - 1);
        buffer[read_len] = '\0';
        std::cout << "Received: " << buffer << std::endl;

        write(client_fd, buffer, strlen(buffer));

        close(client_fd);
    }

    close(server_fd);
    return 0;
}

客户端:

cpp 复制代码
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <iostream>

int main() {
    int client_fd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080);
    inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);

    connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));

    const char *message = "Hello, Server!";
    write(client_fd, message, strlen(message));

    char buffer[1024];
    ssize_t read_len = read(client_fd, buffer, sizeof(buffer) - 1);
    buffer[read_len] = '\0';
    std::cout << "Received: " << buffer << std::endl;

    close(client_fd);
    return 0;
}

在Linux网络编程中,socket(), sockaddr_in 结构体和相关常量都是用于创建和配置套接字的关键组件。以下是上面代码的含义和用法:

  • AF_INET:这是一个地址族(Address Family)常量,表示我们使用的是IPv4协议。在创建套接字时,需要指定地址族以确定使用哪种协议。另一个常见的地址族是AF_INET6,表示使用IPv6协议。

  • SOCK_STREAM:这是一个套接字类型(Socket Type)常量,表示我们使用的是面向连接的、可靠的字节流。在TCP协议中,我们使用SOCK_STREAM类型的套接字。另一个常见的套接字类型是SOCK_DGRAM,表示无连接的、不可靠的数据报文,通常用于UDP协议。

  • socket(AF_INET, SOCK_STREAM, 0):这是一个系统调用,用于创建一个新的套接字。它接受三个参数:地址族(如AF_INET)、套接字类型(如SOCK_STREAM)和协议(通常设置为0,让系统自动选择协议,如TCP或UDP)。此函数返回一个套接字文件描述符,用于后续的网络操作。

  • struct sockaddr_in:这是一个用于表示IPv4套接字地址的结构体。它包含了地址族、端口号和IPv4地址。在网络编程中,我们需要使用此结构体来设置服务器和客户端的地址信息。

  • server_addr.sin_family = AF_INET:设置sockaddr_in结构体中的地址族字段为AF_INET,表示使用IPv4协议。

  • server_addr.sin_port = htons(8080):设置sockaddr_in结构体中的端口号字段。htons()函数将主机字节序(Host Byte Order)转换为网络字节序(Network Byte Order)。这里我们设置端口号为8080。

  • INADDR_ANY:这是一个特殊的IPv4地址(0.0.0.0),表示服务器将监听所有可用的网络接口。当服务器有多个网络接口时,使用INADDR_ANY可以让服务器接受来自任何接口的连接请求。

  • server_addr.sin_addr.s_addr = INADDR_ANY:设置sockaddr_in结构体中的IPv4地址字段为INADDR_ANY,表示服务器将监听所有可用的网络接口。

2.2 UDP Socket示例

服务器端:

cpp 复制代码
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <iostream>

int main() {
    int server_fd = socket(AF_INET, SOCK_DGRAM, 0);

    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080);
    server_addr.sin_addr.s_addr = INADDR_ANY;

    bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));

    while (true) {
        char buffer[1024];
        struct sockaddr_in client_addr;
        socklen_t client_addr_len = sizeof(client_addr);
        ssize_t read_len = recvfrom(server_fd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&client_addr, &client_addr_len);
        buffer[read_len] = '\0';
        std::cout << "Received: " << buffer << std::endl;

        sendto(server_fd, buffer, strlen(buffer), 0, (struct sockaddr *)&client_addr, client_addr_len);
    }

    close(server_fd);
    return 0;
}

客户端:

cpp 复制代码
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <iostream>

int main() {
    int client_fd = socket(AF_INET, SOCK_DGRAM, 0);

    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080);
    inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);

    const char *message = "Hello, Server!";
    sendto(client_fd, message, strlen(message), 0, (struct sockaddr *)&server_addr, sizeof(server_addr));

    char buffer[1024];
    struct sockaddr_in recv_addr;
    socklen_t recv_addr_len = sizeof(recv_addr);
    ssize_t read_len = recvfrom(client_fd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&recv_addr, &recv_addr_len);
    buffer[read_len] = '\0';
    std::cout << "Received: " << buffer << std::endl;

    close(client_fd);
    return 0;
}

三、数据流动时序图

以下是TCP和UDP通信的时序图,展示了客户端与服务器之间的数据流动。

3.1 TCP通信详解

在TCP通信中,我们首先需要建立一个TCP连接,然后才能在这个连接上进行数据传输。以下是TCP通信的详细步骤和时序图:

  1. 服务器执行socket()函数,创建一个新的套接字。
  2. 服务器执行bind()函数,将套接字绑定到一个指定的地址(包括IP地址和端口号)。
  3. 服务器执行listen()函数,使套接字进入监听模式,等待客户端的连接请求。
  4. 服务器执行accept()函数,阻塞并等待客户端的连接请求。当一个客户端连接请求到来时,accept()函数返回,并创建一个新的套接字与客户端进行通信。
  5. 客户端执行socket()connect()函数,向服务器发起连接请求。connect()函数会发送一个SYN(同步)数据包到服务器。
  6. 服务器收到SYN数据包,在accept()函数返回后,回复一个SYN+ACK(确认应答)数据包给客户端。
  7. 客户端收到SYN+ACK数据包,回复一个ACK数据包给服务器,完成TCP连接的建立。
  8. TCP连接建立后,客户端和服务器可以通过read()write()函数进行数据传输。

以下是TCP通信的时序图:

scss 复制代码
Server                Client
  |                     |
  | socket()            |
  |                     |
  | bind()              |
  |                     |
  | listen()            |
  |                     |
  | accept()            |
  |                     |
  |--等待客户端连接请求--->|
  |                     |
  |                     |
  | socket(), connect() | 
  |<--- SYN ------------|
  |                     |
  |-- SYN + ACK ------->|
  |                     |
  |<--- ACK ------------|
  |                     |
  |<-- Data ------------|
  | read(), write()     |
  |                     |
  |-- Data -----------> |
  | read(), write()     |
  |                     |

3.2 UDP通信详解

与TCP不同,UDP是一种无连接的协议,客户端和服务器不需要建立连接就可以直接发送数据。以下是UDP通信的详细步骤:

  1. 服务器执行socket()函数,创建一个新的套接字。
  2. 服务器执行bind()函数,将套接字绑定到一个指定的地址(包括IP地址和端口号)。
  3. 客户端执行socket()函数,创建一个新的套接字。
  4. 客户端可以直接通过sendto()函数发送数据到服务器。
  5. 服务器通过recvfrom()函数接收客户端发送的数据。

以下是UDP通信的时序图:

scss 复制代码
Server                Client
  |                     |
  | socket()            |
  |                     |
  | bind()              |
  |                     |
  |----等待客户端数据---->|
  |                     |
  |                     |
  |   	     socket()|
  | 	     sendto()| 
  |<--- Data -----------|
  | recvfrom()          |
  |                     |

在这种情况下,服务器已经准备好接受客户端的数据。当客户端执行socket()sendto()函数发送数据时,服务器会通过recvfrom()函数接收这些数据。

四、异常情况处理

在网络通信中,可能会遇到一些异常情况,如TCP握手过程中服务器ACK丢失、第三次握手的ACK丢失等。以下是这些异常情况的处理方式:

4.1 服务器ACK丢失

当服务器发送的ACK丢失时,客户端将无法收到确认,因此会重新发送SYN。服务器在收到重复的SYN后,会再次发送ACK。这个过程会持续进行,直到客户端收到ACK或达到最大重传次数。

4.2 第三次握手的ACK丢失

当第三次握手的ACK丢失时,服务器可能仍在等待客户端的ACK。然而,客户端已经认为连接建立,可能会开始发送数据。服务器在收到客户端的数据后,会认为连接已建立,并更新连接状态。因此,即使第三次握手的ACK丢失,TCP连接仍然可以正常建立。

五、总结

本文详细讨论了Linux网络服务器编程中TCP和UDP两种方式的socket使用、原理分析、代码示例、数据流动时序图,以及一些异常情况的处理方式。理解这些概念和技巧有助于更高效地进行网络服务器编程,应对各种网络通信场景。

推荐阅读

TCP与UDP:网络协议的技术原理与要点

从HTTP到QUIC:网络协议的演进与优化

HTTPS:原理、使用方法及安全威胁

相关推荐
人工智能训练42 分钟前
UE5中如何解决角色网格体“掉下去”的问题
运维·服务器·windows·容器·ue5
都叫我大帅哥44 分钟前
Docker Swarm 部署方案
后端
都叫我大帅哥1 小时前
在Swarm中部署Nacos并配置外部MySQL
后端
想摆烂的不会研究的研究生8 小时前
每日八股——Redis(1)
数据库·经验分享·redis·后端·缓存
xiaolizi5674898 小时前
安卓远程安卓(通过frp与adb远程)完全免费
android·远程工作
阿杰100018 小时前
ADB(Android Debug Bridge)是 Android SDK 核心调试工具,通过电脑与 Android 设备(手机、平板、嵌入式设备等)建立通信,对设备进行控制、文件传输、命令等操作。
android·adb
毕设源码-郭学长8 小时前
【开题答辩全过程】以 基于SpringBoot技术的美妆销售系统为例,包含答辩的问题和答案
java·spring boot·后端
猫头虎8 小时前
2025最新OpenEuler系统安装MySQL的详细教程
linux·服务器·数据库·sql·mysql·macos·openeuler
梨落秋霜8 小时前
Python入门篇【文件处理】
android·java·python
木子.李3479 小时前
ssh连接远程服务器相关总结
运维·服务器·ssh