深入探讨 UDP 协议与多线程 HTTP 服务器

深入探讨 UDP 协议与多线程 HTTP 服务器

一、UDP 协议:高效但"不羁"的传输使者

UDP 协议以其独特的特性在网络传输中占据一席之地,适用于对实时性要求高、能容忍少量数据丢失的场景。

1. UDP 的特点解析

  • 无连接:无需提前建立连接,如同"不打电话直接寄快递",减少了连接建立的时间开销,想发就发。
  • 不可靠:不保证数据一定到达、不检测错误、不排序。但这也让它省去了复杂的确认机制,适合视频通话、在线游戏等场景,偶尔卡顿或丢包可接受。
  • 数据报传输:以"块"为单位传输(数据报),发送端发多少,接收端收多少,保持原始边界。例如发送"abc"和"def",接收端不会合并为"abcdef"。
  • 速度快、开销小:头部仅 8 字节(TCP 为 20 字节),额外负担少,传输效率高,适合直播、DNS 查询等"抢时间"场景。
  • 不适应网络拥塞控制:无"减速"机制,网络拥堵时可能丢包更多,但能保持快速发送,与 TCP 的自适应减速形成对比。
  • 支持多种通信方式:可单播(一对一)、广播/组播(一对多),如网课直播向多个设备同时发送数据。

一句话总结:UDP 如同"快递急件",速度快但不确保签收,适合实时性要求高或简单传输的场景。

2. UDP 网络编程代码解析

服务器端(ser.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>  

int main() {  
    // 创建 UDP 套接字,AF_INET 表示 IPv4,SOCK_DGRAM 表示 UDP 类型,0 表示默认协议  
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);  
    if (sockfd == -1) {  
        perror("socket err");  
        exit(1);  
    }  

    struct sockaddr_in saddr, caddr;  
    memset(&saddr, 0, sizeof(saddr));  
    saddr.sin_family = AF_INET;  
    saddr.sin_port = htons(6000); // 端口号转网络字节序  
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 绑定本地回环地址  

    // 绑定套接字到指定地址和端口  
    int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));  
    if (res == -1) {  
        perror("bind err\n");  
        exit(1);  
    }  

    while (1) {  
        char buff[128] = {0};  
        int len = sizeof(caddr);  
        // 接收数据,recvfrom 用于 UDP,可获取发送方地址  
        int n = recvfrom(sockfd, buff, 128, 0, (struct sockaddr*)&caddr, &len);  
        printf("recvfrom(%s) buff:%s\n", inet_ntoa(caddr.sin_addr), buff);  
        // 向发送方回复"ok"  
        sendto(sockfd, "ok", 2, 0, (struct sockaddr*)&caddr, sizeof(caddr));  
    }  
    close(sockfd);  
}  
客户端(cli.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>  

int main() {  
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);  
    if (sockfd == -1) {  
        perror("socket err");  
        exit(1);  
    }  

    struct sockaddr_in saddr;  
    saddr.sin_family = AF_INET;  
    saddr.sin_port = htons(6000);  
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");  

    while (1) {  
        printf("input:\n");  
        char buff[128] = {0};  
        fgets(buff, 128, stdin);  
        if (strncmp(buff, "end", 3) == 0) {  
            break;  
        }  
        // 向服务器发送数据  
        sendto(sockfd, buff, strlen(buff) - 1, 0, (struct sockaddr*)&saddr, sizeof(saddr));  
        memset(buff, 0, 128);  
        int len = sizeof(saddr);  
        // 接收服务器响应  
        recvfrom(sockfd, buff, 128, 0, (struct sockaddr*)&saddr, &len);  
        printf("buff(%s):%s\n", inet_ntoa(saddr.sin_addr), buff);  
    }  
    close(sockfd);  
}  
  • 服务器端 :创建 UDP 套接字,绑定到 127.0.0.1:6000,循环接收客户端数据,打印发送方 IP 和数据,回复"ok"。
  • 客户端:创建 UDP 套接字,循环读取用户输入,发送给服务器,接收并打印服务器响应,输入"end"时退出。

二、多线程 HTTP 服务器:构建 Web 通信枢纽

HTTP 协议(端口 80,HTTPS 为 443)是应用层的核心协议,基于 TCP 协议,具有无状态、简单灵活等特点,广泛用于 Web 通信。多线程设计使其能并发处理多个客户端请求,提升性能。

1. HTTP 协议深度解析

  • 请求方法 :常见的有 GET(获取资源)、POST(提交数据)、PUT(更新资源)、DELETE(删除资源)等。例如,浏览器访问网页使用 GET 请求获取页面内容。

  • 状态码

    • 2xx(如 200 OK):表示请求成功。
    • 4xx(如 404 NOT FOUND):客户端错误,资源不存在。
    • 5xx(如 500 Internal Server Error):服务器内部错误。
  • 长连接与短连接

    • 短连接:每次请求都新建 TCP 连接,完成后关闭。适用于请求不频繁的场景,如普通网页浏览。
    • 长连接(Keep-Alive):多个请求复用一个 TCP 连接,减少连接建立开销,提升效率,适用于频繁交互的场景,如单页应用(SPA)。
  • 无状态特性 :HTTP 协议本身不记录客户端状态,通过 CookieSession 等机制实现会话跟踪。例如,用户登录后,服务器通过 Cookie 识别用户后续请求。

  • http请求报文如下图

  • http应答报文如下图

2. 多线程 HTTP 服务器代码详解

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>  
#include <pthread.h>  
#include <fcntl.h>  

#define PATH "/home/stu/0101/http"  
int socket_init();  
char *strtok_fun(char buff[]) {  
    char *saveptr;  
    // 解析请求头第一行,获取请求的文件名。例如,对于"GET /index.html HTTP/1.1",提取"/index.html"  
    char *p = strtok_r(buff, " ", &saveptr);  
    if (p != NULL) {  
        p = strtok_r(NULL, " ", &saveptr);  
        return p;  
    }  
    return NULL;  
}  

void *thread_fun(void *arg) {  
    int c = *((int *)arg);  
    free(arg);  
    while (1) {  
        char buff[1024] = {0};  
        int n = recv(c, buff, 1024, 0);  
        if (n == 0) { // 客户端关闭连接,recv 返回 0  
            break;  
        }  
        if (n == -1) {  
            perror("recv err");  
            break;  
        }  
        char *filename = strtok_fun(buff);  
        if (filename == NULL) {  
            break;  
        }  
        if (strcmp(filename, "/") == 0) {  
            filename = "/index.html"; // 若请求根路径,默认返回 index.html  
        }  

        char path[256];  
        strcpy(path, PATH);  
        strcat(path, filename);  
        printf("path:%s\n", path);  

        int file_id = open(path, O_RDONLY);  
        if (file_id == -1) { // 文件不存在,构造 404 响应头  
            char head[256] = {"HTTP/1.1 404 NOT FOUND\r\n"};  
            strcat(head, "Server: myhttp\r\n");  
            sprintf(head + strlen(head), "Content-Length: 0\r\n");  
            strcat(head, "\r\n");  
            send(c, head, strlen(head), 0);  
            break;  
        }  

        int filesize = lseek(file_id, 0, SEEK_END);  
        lseek(file_id, 0, SEEK_SET);  

        char head[256] = {"HTTP/1.1 200 OK\r\n"};  
        strcat(head, "Server: myhttp\r\n");  
        sprintf(head + strlen(head), "Content-Length: %d\r\n", filesize);  
        strcat(head, "\r\n");  
        send(c, head, strlen(head), 0); // 发送 200 响应头,包含服务器信息、内容长度等  

        char data[1024] = {0};  
        int num = 0;  
        while ((num = read(file_id, data, 1024)) > 0) {  
            send(c, data, num, 0); // 分块读取文件内容并发送给客户端  
        }  
        close(file_id);  
    }  
    printf("cli close");  
    close(c);  
    pthread_exit(NULL);  
}  

int main() {  
    int sockfd = socket_init();  
    if (sockfd == -1) {  
        exit(1);  
    }  
    while (1) {  
        int c = accept(sockfd, NULL, NULL);  
        if (c == -1) {  
            perror("accept err");  
            continue;  
        }  
        pthread_t id;  
        int *p = (int *)malloc(sizeof(int));  
        *p = c;  
        if (pthread_create(&id, NULL, thread_fun, (void *)p) != 0) {  
            perror("pthread_create err");  
            free(p);  
            close(c);  
        } else {  
            pthread_detach(id); // 分离线程,使其结束后自动释放资源,避免内存泄漏  
        }  
    }  
}  

int socket_init() {  
    int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建 TCP 套接字,SOCK_STREAM 表示面向连接的 TCP  
    if (sockfd == -1) {  
        perror("socket err");  
        return -1;  
    }  
    struct sockaddr_in saddr;  
    saddr.sin_family = AF_INET;  
    saddr.sin_port = htons(80); // 绑定 80 端口,HTTP 协议默认端口  
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");  

    int res = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));  
    if (res == -1) {  
        perror("bind err");  
        return -1;  
    }  
    if (listen(sockfd, 5) == -1) {  
        perror("listen err");  
        return -1;  
    }  
    return sockfd;  
}  
  • socket_init 函数 :初始化 TCP 套接字,绑定到 127.0.0.1:80,通过 listen 开始监听,使服务器处于等待客户端连接状态。
  • main 函数 :循环调用 accept 接收客户端连接。每收到一个连接,创建新线程处理(thread_fun),通过 pthread_detach 分离线程,确保线程结束后资源自动释放,避免服务器资源泄漏。
  • thread_fun 函数
    • 读取客户端请求数据,解析请求头获取文件名。
    • 根据文件名构造文件路径,尝试打开文件。若文件不存在,发送 404 响应头;若存在,发送 200 响应头(包含 HTTP 协议版本、状态码、服务器名称、内容长度等),然后分块读取文件内容并发送给客户端。
    • 处理完一个客户端请求后,关闭连接套接字,线程退出。

3. 多线程 HTTP 服务器的优势

  • 并发处理:每个客户端连接独立分配线程,多个客户端请求可同时处理,提升服务器吞吐量,避免单个请求阻塞影响整体服务。
  • 资源利用:充分利用多核 CPU 资源,线程间相互独立,提高系统资源利用率。
  • 响应速度:及时处理客户端请求,减少等待时间,提升用户体验,尤其适合高并发场景,如电商网站、新闻门户等。

UDP 协议以其高效简洁适用于特定场景,而多线程 HTTP 服务器通过并发处理与 HTTP 协议特性结合,成为 Web 通信的重要支柱。深入理解这些技术,能更好地应对网络编程挑战,构建强大稳定的网络应用。

相关推荐
2501_9160137411 分钟前
日常开发中,iOS 性能调优我们怎么做?
websocket·网络协议·tcp/ip·http·网络安全·https·udp
努力也学不会java38 分钟前
【HTTP】《HTTP 全原理解析:从请求到响应的奇妙之旅》
java·网络·网络协议·http
半路_出家ren1 小时前
传输层协议 1.TCP 2.UDP
网络·网络安全·udp·wireshark·kali·tcp·gns3
gbase_lmax1 小时前
gbase8s数据库 tcp连接不同阶段的超时处理
网络·数据库·网络协议·tcp/ip
熬夜学编程的小王1 小时前
【Linux篇】多线程编程中的互斥与同步:深入理解锁与条件变量的应用
linux·条件变量·线程同步·线程互斥
芯辰则吉--模拟芯片2 小时前
模拟Sch LVS Sch 方法
服务器·数据库·lvs
zzr9152 小时前
TCP/IP协议深度解析:从分层架构到TCP核心机制
网络协议·tcp/ip·架构
techdashen3 小时前
性能比拼: HTTP/2 vs. HTTP/3
网络·网络协议·http
Chat_zhanggong3454 小时前
AI训练服务器概述
运维·服务器·人工智能
伊织code4 小时前
AWS MCP Servers
服务器·python·ai·云计算·aws·mcp