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

[复制链接]
发表于 2025-9-17 09:08:58 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

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

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

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

1. UDP 的特点剖析


  • 无毗连:无需提前创建毗连,如同“不打电话直接寄快递”,镌汰了毗连创建的时间开销,想发就发。
  • 不可靠:不保证数据一定到达、不检测错误、不排序。但这也让它省去了复杂的确认机制,得当视频通话、在线游戏等场景,偶尔卡顿或丢包可担当。
  • 数据报传输:以“块”为单位传输(数据报),发送端发多少,接收端收多少,保持原始界限。比方发送“abc”和“def”,接收端不会归并为“abcdef”。
  • 速率快、开销小:头部仅 8 字节(TCP 为 20 字节),额外负担少,传输服从高,得当直播、DNS 查询等“抢时间”场景。
  • 不顺应网络拥塞控制:无“减速”机制,网络拥堵时大概丢包更多,但能保持快速发送,与 TCP 的自顺应减速形成对比。
  • 支持多种通讯方式:可单播(一对一)、广播/组播(一对多),如网课直播向多个设备同时发送数据。
一句话总结:UDP 如同“快递急件”,速率快但不确保签收,得当实时性要求高或简朴传输的场景。
2. UDP 网络编程代码剖析

服务器端(ser.c)
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <unistd.h>  
  5. #include <sys/socket.h>  
  6. #include <netinet/in.h>  
  7. #include <arpa/inet.h>  
  8. int main() {  
  9.     // 创建 UDP 套接字,AF_INET 表示 IPv4,SOCK_DGRAM 表示 UDP 类型,0 表示默认协议  
  10.     int sockfd = socket(AF_INET, SOCK_DGRAM, 0);  
  11.     if (sockfd == -1) {  
  12.         perror("socket err");  
  13.         exit(1);  
  14.     }  
  15.     struct sockaddr_in saddr, caddr;  
  16.     memset(&saddr, 0, sizeof(saddr));  
  17.     saddr.sin_family = AF_INET;  
  18.     saddr.sin_port = htons(6000); // 端口号转网络字节序  
  19.     saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 绑定本地回环地址  
  20.     // 绑定套接字到指定地址和端口  
  21.     int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));  
  22.     if (res == -1) {  
  23.         perror("bind err\n");  
  24.         exit(1);  
  25.     }  
  26.     while (1) {  
  27.         char buff[128] = {0};  
  28.         int len = sizeof(caddr);  
  29.         // 接收数据,recvfrom 用于 UDP,可获取发送方地址  
  30.         int n = recvfrom(sockfd, buff, 128, 0, (struct sockaddr*)&caddr, &len);  
  31.         printf("recvfrom(%s) buff:%s\n", inet_ntoa(caddr.sin_addr), buff);  
  32.         // 向发送方回复“ok”  
  33.         sendto(sockfd, "ok", 2, 0, (struct sockaddr*)&caddr, sizeof(caddr));  
  34.     }  
  35.     close(sockfd);  
  36. }  
复制代码
客户端(cli.c)
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <unistd.h>  
  5. #include <sys/socket.h>  
  6. #include <netinet/in.h>  
  7. #include <arpa/inet.h>  
  8. int main() {  
  9.     int sockfd = socket(AF_INET, SOCK_DGRAM, 0);  
  10.     if (sockfd == -1) {  
  11.         perror("socket err");  
  12.         exit(1);  
  13.     }  
  14.     struct sockaddr_in saddr;  
  15.     saddr.sin_family = AF_INET;  
  16.     saddr.sin_port = htons(6000);  
  17.     saddr.sin_addr.s_addr = inet_addr("127.0.0.1");  
  18.     while (1) {  
  19.         printf("input:\n");  
  20.         char buff[128] = {0};  
  21.         fgets(buff, 128, stdin);  
  22.         if (strncmp(buff, "end", 3) == 0) {  
  23.             break;  
  24.         }  
  25.         // 向服务器发送数据  
  26.         sendto(sockfd, buff, strlen(buff) - 1, 0, (struct sockaddr*)&saddr, sizeof(saddr));  
  27.         memset(buff, 0, 128);  
  28.         int len = sizeof(saddr);  
  29.         // 接收服务器响应  
  30.         recvfrom(sockfd, buff, 128, 0, (struct sockaddr*)&saddr, &len);  
  31.         printf("buff(%s):%s\n", inet_ntoa(saddr.sin_addr), buff);  
  32.     }  
  33.     close(sockfd);  
  34. }  
复制代码

  • 服务器端:创建 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 协议本身不纪录客户端状态,通过 Cookie、Session 等机制实现会话跟踪。比方,用户登录后,服务器通过 Cookie 辨认用户后续哀求。
  • http哀求报文如下图  :

  • http应答报文如下图

2. 多线程 HTTP 服务器代码详解
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <unistd.h>  
  5. #include <sys/socket.h>  
  6. #include <netinet/in.h>  
  7. #include <arpa/inet.h>  
  8. #include <pthread.h>  
  9. #include <fcntl.h>  
  10. #define PATH "/home/stu/0101/http"  
  11. int socket_init();  
  12. char *strtok_fun(char buff[]) {  
  13.     char *saveptr;  
  14.     // 解析请求头第一行,获取请求的文件名。例如,对于“GET /index.html HTTP/1.1”,提取“/index.html”  
  15.     char *p = strtok_r(buff, " ", &saveptr);  
  16.     if (p != NULL) {  
  17.         p = strtok_r(NULL, " ", &saveptr);  
  18.         return p;  
  19.     }  
  20.     return NULL;  
  21. }  
  22. void *thread_fun(void *arg) {  
  23.     int c = *((int *)arg);  
  24.     free(arg);  
  25.     while (1) {  
  26.         char buff[1024] = {0};  
  27.         int n = recv(c, buff, 1024, 0);  
  28.         if (n == 0) { // 客户端关闭连接,recv 返回 0  
  29.             break;  
  30.         }  
  31.         if (n == -1) {  
  32.             perror("recv err");  
  33.             break;  
  34.         }  
  35.         char *filename = strtok_fun(buff);  
  36.         if (filename == NULL) {  
  37.             break;  
  38.         }  
  39.         if (strcmp(filename, "/") == 0) {  
  40.             filename = "/index.html"; // 若请求根路径,默认返回 index.html  
  41.         }  
  42.         char path[256];  
  43.         strcpy(path, PATH);  
  44.         strcat(path, filename);  
  45.         printf("path:%s\n", path);  
  46.         int file_id = open(path, O_RDONLY);  
  47.         if (file_id == -1) { // 文件不存在,构造 404 响应头  
  48.             char head[256] = {"HTTP/1.1 404 NOT FOUND\r\n"};  
  49.             strcat(head, "Server: myhttp\r\n");  
  50.             sprintf(head + strlen(head), "Content-Length: 0\r\n");  
  51.             strcat(head, "\r\n");  
  52.             send(c, head, strlen(head), 0);  
  53.             break;  
  54.         }  
  55.         int filesize = lseek(file_id, 0, SEEK_END);  
  56.         lseek(file_id, 0, SEEK_SET);  
  57.         char head[256] = {"HTTP/1.1 200 OK\r\n"};  
  58.         strcat(head, "Server: myhttp\r\n");  
  59.         sprintf(head + strlen(head), "Content-Length: %d\r\n", filesize);  
  60.         strcat(head, "\r\n");  
  61.         send(c, head, strlen(head), 0); // 发送 200 响应头,包含服务器信息、内容长度等  
  62.         char data[1024] = {0};  
  63.         int num = 0;  
  64.         while ((num = read(file_id, data, 1024)) > 0) {  
  65.             send(c, data, num, 0); // 分块读取文件内容并发送给客户端  
  66.         }  
  67.         close(file_id);  
  68.     }  
  69.     printf("cli close");  
  70.     close(c);  
  71.     pthread_exit(NULL);  
  72. }  
  73. int main() {  
  74.     int sockfd = socket_init();  
  75.     if (sockfd == -1) {  
  76.         exit(1);  
  77.     }  
  78.     while (1) {  
  79.         int c = accept(sockfd, NULL, NULL);  
  80.         if (c == -1) {  
  81.             perror("accept err");  
  82.             continue;  
  83.         }  
  84.         pthread_t id;  
  85.         int *p = (int *)malloc(sizeof(int));  
  86.         *p = c;  
  87.         if (pthread_create(&id, NULL, thread_fun, (void *)p) != 0) {  
  88.             perror("pthread_create err");  
  89.             free(p);  
  90.             close(c);  
  91.         } else {  
  92.             pthread_detach(id); // 分离线程,使其结束后自动释放资源,避免内存泄漏  
  93.         }  
  94.     }  
  95. }  
  96. int socket_init() {  
  97.     int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建 TCP 套接字,SOCK_STREAM 表示面向连接的 TCP  
  98.     if (sockfd == -1) {  
  99.         perror("socket err");  
  100.         return -1;  
  101.     }  
  102.     struct sockaddr_in saddr;  
  103.     saddr.sin_family = AF_INET;  
  104.     saddr.sin_port = htons(80); // 绑定 80 端口,HTTP 协议默认端口  
  105.     saddr.sin_addr.s_addr = inet_addr("127.0.0.1");  
  106.     int res = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));  
  107.     if (res == -1) {  
  108.         perror("bind err");  
  109.         return -1;  
  110.     }  
  111.     if (listen(sockfd, 5) == -1) {  
  112.         perror("listen err");  
  113.         return -1;  
  114.     }  
  115.     return sockfd;  
  116. }  
复制代码

  • 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 通讯的紧张支柱。深入明确这些技能,能更好地应对网络编程寻衅,构建强大稳固的网络应用。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表