├── LICENSE ├── README.md ├── 第10章 信号 └── 实战 7:使用 SIGURG 信号检测带外数据是否到达 │ └── sigurg_server.cpp ├── 第11章 定时器 ├── 实战 8:使用 SO_SNDTIMEO 选项设置定时 │ ├── client │ └── connect_timeout.cpp └── 实战9:利用alarm函数周期性地触发SIGALRM信号 │ ├── client │ ├── client.cpp │ ├── lst_timer.h │ ├── server │ └── sigalrm.cpp ├── 第12章 高性能IO框架库Libevent └── 实战 10:利用 Libevent 库实现一个“Hello World”程序 │ ├── example │ └── example.cpp ├── 第13章 多进程编程 └── 实战 11: 在进程间传递文件描述符 │ ├── code │ ├── code.cpp │ └── test.txt ├── 第14章 多线程编程 ├── 实战 12:死锁举例 │ ├── test │ └── test.cpp ├── 实战 13:使用条件变量模拟实现生产者—消费者问题 │ ├── pcer │ └── producer-concumer.cpp ├── 实战 14:多线程环境中,使用fork调用产生的死锁问题 │ ├── fork_lock │ ├── fork_lock.cpp │ └── fork_lock2 ├── 实战 15:在一个线程中统一处理所有信号 │ ├── signal │ └── signal.cpp └── 线程同步机制包装类 │ └── locker.h ├── 第15章 进程池和线程池 └── 实战16:用进程池实现简单的CGI服务器 │ ├── cgi_server_2 │ ├── cgi_server_2.cpp │ ├── client │ ├── client.cpp │ └── processpool.h ├── 第5章 Linux网络编程基础API ├── 实战 1:TCP通信实现(服务器端和客户端) │ ├── client │ ├── client.cpp │ ├── server │ └── server.cpp └── 实战 2:使用 MSG_OOB 选项发送带外数据 │ ├── TCPClientSocket.h │ ├── TCPServerSocket.h │ ├── test_oob_recv.cpp │ └── test_oob_send.cpp ├── 第6章 高级IO函数 └── 实战3:实现一个简单的CGI服务器 │ ├── cgi_server │ ├── cgi_server.cpp │ ├── client │ └── client.cpp ├── 第7章 Linux服务器程序规范 ├── 实战 4:测试 UID 和 EUID 的区别 │ ├── test_uid │ └── test_uid.cpp └── 实战 5:使用 setsid() 创建一个新的会话和进程组 │ ├── create_sid │ └── create_sid.cpp └── 第9章 IO 复用 └── 实战 6:select 调用同时接收普通数据和带外数据 ├── TCPClientSocket.h ├── TCPServerSocket.h ├── client.cpp └── select.cpp /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 B. Hu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 目录 2 | 3 | - [目录](#目录) 4 | - [概述](#概述) 5 | - [《Linux 高性能服务器编程》学习笔记](#linux-高性能服务器编程学习笔记) 6 | - [书本中的样例代码](#书本中的样例代码) 7 | - [第一篇 前置知识 TCP / IP 协议](#第一篇-前置知识-tcp--ip-协议) 8 | - [第二篇 深入解析高性能服务器编程](#第二篇-深入解析高性能服务器编程) 9 | - [第三篇 高性能服务器优化与监测](#第三篇-高性能服务器优化与监测) 10 | - [其他参考](#其他参考) 11 | 12 | ## 概述 13 | 项目准备过程中,主要阅读了 **《Linux 高性能服务器编程》 游双** 一书。源码参考的是:[TinyWebServer](https://github.com/qinguoyi/TinyWebServer),我在此源码的基础上做了一定的 **优化和修改**。 14 | 15 | **我的代码:**[Github: myWebserver: 我的C++服务器项目](https://github.com/bhu619/myWebserver) 16 | 17 | **我的 Webserver 项目总结:**[WebServer 项目解读 / 代码分析 / 改进](https://www.yuque.com/u39624144/zvaea9/aqktb26g923rsiv5) 18 | 19 | **我的《Linux 高性能服务器编程》学习笔记:**[《Linux 高性能服务器编程》总结—红茶川的CSDN博客](https://blog.csdn.net/teriri_/category_12760091.html?spm=1001.2014.3001.5482) 20 | 21 | **我的 CSDN 博客:**[红茶川—CSDN博客](https://blog.csdn.net/Teriri_?spm=1000.2115.3001.5343),上面同步更新了所有内容。 22 | 23 | ## 《Linux 高性能服务器编程》学习笔记 24 | ### 书本中的样例代码 25 | 书中的部分样例代码,我都在笔记中给出了可以运行的代码,大家可以编译运行一下看看效果。 26 | 27 | 所有代码我都放在了 Github 仓库:[bhu619/Linux-high-performance-server-programming-Notebook](https://github.com/bhu619/Linux-high-performance-server-programming-Notebook) 28 | 29 | 完整笔记:[《Linux 高性能服务器编程》总结—红茶川的CSDN博客](https://blog.csdn.net/teriri_/category_12760091.html?spm=1001.2014.3001.5482) 30 | 31 | --- 32 | 33 | [实战 1:TCP通信实现(服务器端和客户端)](https://www.yuque.com/u39624144/zvaea9/xsil4chqwb5qqc0h#p2OYs) 34 | 35 | [实战 2:使用 MSG_OOB 选项发送带外数据](https://www.yuque.com/u39624144/zvaea9/xsil4chqwb5qqc0h#rvyoz) 36 | 37 | [实战 3:实现一个简单的 CGI 服务器](https://www.yuque.com/u39624144/zvaea9/coklc3naf35zmiqs#uhNNJ) 38 | 39 | [实战 4:测试 UID 和 EUID 的区别](https://www.yuque.com/u39624144/zvaea9/uykylirmss5wl757#euBnM) 40 | 41 | [实战 5:使用 setsid() 创建一个新的会话和进程组](https://www.yuque.com/u39624144/zvaea9/uykylirmss5wl757#a1Hsx) 42 | 43 | [实战 6:select 调用同时接收普通数据和带外数据](https://www.yuque.com/u39624144/zvaea9/ypvqw1ip7m8g06iw#Higr8) 44 | 45 | [实战 7:使用 SIGURG 信号检测带外数据是否到达](https://www.yuque.com/u39624144/zvaea9/lmeph1l89eka5260#iDHr9) 46 | 47 | [实战 8:使用 SO_SNDTIMEO 选项设置定时](https://www.yuque.com/u39624144/zvaea9/yp17h7vn7pyqeg8u#yGpN7) 48 | 49 | [实战 9:利用 alarm 函数周期性触发 SIGALRM 信号](https://www.yuque.com/u39624144/zvaea9/yp17h7vn7pyqeg8u#mQQlT) 50 | 51 | [实战 10:利用 Libevent 库实现一个“Hello World”程序](https://www.yuque.com/u39624144/zvaea9/bn1zz8726fc80b0g#mo5Ik) 52 | 53 | [实战 11:在进程间传递文件描述符](https://www.yuque.com/u39624144/zvaea9/uqu0tqep71gn5x5n#t0560) 54 | 55 | [实战 12:死锁举例](https://www.yuque.com/u39624144/zvaea9/gc463q2ptu28gzpc#zI94q) 56 | 57 | [实战 13:使用条件变量模拟实现生产者—消费者问题](https://www.yuque.com/u39624144/zvaea9/gc463q2ptu28gzpc#h3K6i) 58 | 59 | [实战 14:多线程环境中,使用fork调用产生的死锁问题](https://www.yuque.com/u39624144/zvaea9/gc463q2ptu28gzpc#AR15e) 60 | 61 | [实战 15:在一个线程中统一处理所有信号](https://www.yuque.com/u39624144/zvaea9/gc463q2ptu28gzpc#HA174) 62 | 63 | [实战 16:用进程池实现简单的 CGI 服务器](https://www.yuque.com/u39624144/zvaea9/qulr4t6shwwh9kmo#eKrIS) 64 | 65 | ### 第一篇 前置知识 TCP / IP 协议 66 | [1. IP协议](https://www.yuque.com/u39624144/zvaea9/ufa1lv48std8gahd) 67 | 68 | [2. TCP协议](https://www.yuque.com/u39624144/zvaea9/xbfffzg2tl63cqda) 69 | 70 | [3. 访问一个网页的全过程](https://www.yuque.com/u39624144/zvaea9/kk0kn0i6bcd2iqq0) 71 | 72 | ### 第二篇 深入解析高性能服务器编程 73 | [4. Linux网络编程基础API](https://www.yuque.com/u39624144/zvaea9/xsil4chqwb5qqc0h) 74 | 75 | [5. 高级 I/O 函数](https://www.yuque.com/u39624144/zvaea9/coklc3naf35zmiqs) 76 | 77 | [6. Linux服务器程序规范](https://www.yuque.com/u39624144/zvaea9/uykylirmss5wl757) 78 | 79 | [7. 高性能服务器程序框架](https://www.yuque.com/u39624144/zvaea9/ocl1e8vzzdes4zgn) 80 | 81 | [7. 高性能服务器程序框架](https://www.yuque.com/u39624144/zvaea9/ocl1e8vzzdes4zgn) 82 | 83 | [8. I/O 复用](https://www.yuque.com/u39624144/zvaea9/ypvqw1ip7m8g06iw) 84 | 85 | [9. 信号](https://www.yuque.com/u39624144/zvaea9/lmeph1l89eka5260) 86 | 87 | [10. 定时器](https://www.yuque.com/u39624144/zvaea9/yp17h7vn7pyqeg8u) 88 | 89 | [11. 高性能I/O框架库Libevent](https://www.yuque.com/u39624144/zvaea9/bn1zz8726fc80b0g) 90 | 91 | [12. 多进程编程](https://www.yuque.com/u39624144/zvaea9/uqu0tqep71gn5x5n) 92 | 93 | [13. 多线程编程](https://www.yuque.com/u39624144/zvaea9/gc463q2ptu28gzpc) 94 | 95 | [14. 进程池和线程池](https://www.yuque.com/u39624144/zvaea9/qulr4t6shwwh9kmo) 96 | 97 | ### 第三篇 高性能服务器优化与监测 98 | [15. 服务器调制、调试和测试](https://www.yuque.com/u39624144/zvaea9/xozozt5a6b668adg) 99 | 100 | ## 其他参考 101 | 1. [什么是 web 服务器? - 学习 Web 开发 | MDN](https://developer.mozilla.org/zh-CN/docs/Learn/Common_questions/Web_mechanics/What_is_a_web_server) 102 | 2. [基于 Linux 的 web 服务器_基于linux系统的webserve服务器开发-CSDN博客](https://blog.csdn.net/qq_42370809/article/details/126753879) 103 | 3. [代码随想录项目精讲系列-webserver.pdf](https://www.yuque.com/attachments/yuque/0/2024/pdf/40997209/1720596301277-75d1fb45-ecdf-4998-8c0c-e113aa6fba40.pdf) 密码:`dmsxlwb0624` 104 | 4. [Linux高性能服务器编程.pdf](https://www.yuque.com/attachments/yuque/0/2024/pdf/40997209/1724151873130-23d63211-4809-40ed-abb9-03a6b8441507.pdf) 105 | 5. [从零开始自制实现C++ High-Performance WebServer 全流程记录_c++ webserver项目 速成-CSDN博客](https://love6.blog.csdn.net/article/details/123754194) 106 | 6. [从零开始实现C++ TinyWebServer_JehanRio的博客-CSDN博客](https://blog.csdn.net/weixin_51322383/category_12307428.html) 107 | 108 | ## Star History 109 | 110 | [![Star History Chart](https://api.star-history.com/svg?repos=bhu619/Linux-high-performance-server-programming-Notebook&type=Date)](https://star-history.com/#bhu619/Linux-high-performance-server-programming-Notebook&Date) 111 | -------------------------------------------------------------------------------- /第10章 信号/实战 7:使用 SIGURG 信号检测带外数据是否到达/sigurg_server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define BUF_SIZE 1024 15 | 16 | static int connfd; 17 | 18 | // SIGURG信号的处理函数 19 | void sig_urg(int sig) { 20 | int save_errno = errno; 21 | char buffer[BUF_SIZE]; 22 | memset(buffer, '\0', BUF_SIZE); 23 | 24 | int ret; 25 | while ((ret = recv(connfd, buffer, BUF_SIZE - 1, MSG_OOB)) < 0) { 26 | if (errno == EWOULDBLOCK) { 27 | continue; // 如果接收缓冲区满了,继续读取,直到接收到带外数据 28 | } else { 29 | break; // 处理其他错误情况 30 | } 31 | } 32 | if (ret > 0) { 33 | printf("got %d bytes of oob data '%s'\n", ret, buffer); 34 | } 35 | 36 | errno = save_errno; 37 | } 38 | 39 | void addsig(int sig, void (*sig_handler)(int)) { 40 | struct sigaction sa; 41 | memset(&sa, '\0', sizeof(sa)); 42 | sa.sa_handler = sig_handler; 43 | sa.sa_flags |= SA_RESTART; 44 | sigfillset(&sa.sa_mask); 45 | assert(sigaction(sig, &sa, NULL) != -1); 46 | } 47 | 48 | int main(int argc, char *argv[]) { 49 | if (argc != 3) { 50 | printf("usage: %s ip_address port_number\n", basename(argv[0])); 51 | return 1; 52 | } 53 | const char *ip = argv[1]; 54 | int port = atoi(argv[2]); 55 | 56 | struct sockaddr_in address; 57 | bzero(&address, sizeof(address)); 58 | address.sin_family = AF_INET; 59 | inet_pton(AF_INET, ip, &address.sin_addr); 60 | address.sin_port = htons(port); 61 | 62 | int sock = socket(PF_INET, SOCK_STREAM, 0); 63 | assert(sock >= 0); 64 | 65 | int ret = bind(sock, (struct sockaddr *)&address, sizeof(address)); 66 | assert(ret != -1); 67 | 68 | ret = listen(sock, 5); 69 | assert(ret != -1); 70 | 71 | struct sockaddr_in client; 72 | socklen_t client_addrlength = sizeof(client); 73 | connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength); 74 | if (connfd < 0) { 75 | printf("errno is: %d\n", errno); 76 | } else { 77 | addsig(SIGURG, sig_urg); 78 | // 我们必须设置socket的宿主进程或进程组 79 | fcntl(connfd, F_SETOWN, getpid()); 80 | 81 | char buffer[BUF_SIZE]; 82 | // 循环接收普通数据 83 | while (1) { 84 | memset(buffer, '\0', BUF_SIZE); 85 | ret = recv(connfd, buffer, BUF_SIZE - 1, 0); 86 | if (ret < 0) { 87 | if(errno == EINTR) { 88 | continue; // 如果recv因信号中断,则继续读取 89 | } 90 | break; // 处理其他错误 91 | } else if (ret == 0) { 92 | printf("Client disconnected.\n"); 93 | break; 94 | } 95 | printf("get %d bytes of normal data '%s'\n", ret, buffer); 96 | } 97 | close(connfd); 98 | } 99 | close(sock); 100 | return 0; 101 | } -------------------------------------------------------------------------------- /第11章 定时器/实战 8:使用 SO_SNDTIMEO 选项设置定时/client: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhu619/Linux-high-performance-server-programming-Notebook/62ede31d2f82c18b81a9c52df7ce3d7ed2a78f87/第11章 定时器/实战 8:使用 SO_SNDTIMEO 选项设置定时/client -------------------------------------------------------------------------------- /第11章 定时器/实战 8:使用 SO_SNDTIMEO 选项设置定时/connect_timeout.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | /* 超时连接函数 15 | * 这个函数的作用是尝试与指定IP地址和端口的服务器建立连接, 16 | * 并设置一个连接超时时间。 17 | */ 18 | int timeout_connect(const char *ip, int port, int time) { 19 | int ret = 0; 20 | struct sockaddr_in address; 21 | bzero(&address, sizeof(address)); 22 | address.sin_family = AF_INET; 23 | inet_pton(AF_INET, ip, &address.sin_addr); 24 | address.sin_port = htons(port); 25 | 26 | int sockfd = socket(PF_INET, SOCK_STREAM, 0); 27 | assert(sockfd >= 0); 28 | 29 | // SO_RCVTIMEO和SO_SNDTIMEO套接字选项对应的值类型为timeval,这和select函数的超时参数类型相同 30 | struct timeval timeout; 31 | timeout.tv_sec = time; 32 | timeout.tv_usec = 0; 33 | socklen_t len = sizeof(timeout); 34 | ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, len); 35 | assert(ret != -1); 36 | 37 | /* 38 | * 如果连接在指定时间内没有成功建立,connect() 函数将返回 -1, 39 | * 并且 errno 会被设置为 EINPROGRESS,表示连接超时。 40 | */ 41 | printf("Attempting to connect...\n"); 42 | ret = connect(sockfd, (struct sockaddr *)&address, sizeof(address)); 43 | printf("Connect returned: %d\n", ret); 44 | 45 | if (ret == -1) { 46 | // 超时对应的错误号是EINPROGRESS,此时就可执行定时任务了 47 | if (errno == EINPROGRESS) { 48 | printf("conencting timeout, process timeout logic\n"); 49 | return -1; 50 | } 51 | printf("error occur when connecting to server\n"); 52 | return -1; 53 | } 54 | 55 | return sockfd; 56 | } 57 | 58 | int main(int argc, char *argv[]) { 59 | if (argc != 3) { 60 | printf("usage: %s ip_address port_number\n", basename(argv[0])); 61 | return 1; 62 | } 63 | const char *ip = argv[1]; 64 | int port = atoi(argv[2]); 65 | 66 | int sockfd = timeout_connect(ip, port, 1); 67 | if (sockfd < 0) { 68 | return 1; 69 | } 70 | return 0; 71 | } -------------------------------------------------------------------------------- /第11章 定时器/实战9:利用alarm函数周期性地触发SIGALRM信号/client: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhu619/Linux-high-performance-server-programming-Notebook/62ede31d2f82c18b81a9c52df7ce3d7ed2a78f87/第11章 定时器/实战9:利用alarm函数周期性地触发SIGALRM信号/client -------------------------------------------------------------------------------- /第11章 定时器/实战9:利用alarm函数周期性地触发SIGALRM信号/client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | int main(int argc, char *argv[]) { 10 | if (argc != 3) { 11 | printf("Usage: %s \n", argv[0]); 12 | return 1; 13 | } 14 | 15 | const char *server_ip = argv[1]; 16 | int server_port = atoi(argv[2]); 17 | 18 | // 创建 socket 19 | int sock = socket(AF_INET, SOCK_STREAM, 0); 20 | if (sock < 0) { 21 | perror("Socket creation failed"); 22 | return 1; 23 | } 24 | 25 | // 服务器地址结构 26 | struct sockaddr_in server_addr; 27 | memset(&server_addr, 0, sizeof(server_addr)); 28 | server_addr.sin_family = AF_INET; 29 | server_addr.sin_port = htons(server_port); 30 | if (inet_pton(AF_INET, server_ip, &server_addr.sin_addr) <= 0) { 31 | perror("Invalid address/ Address not supported"); 32 | return 1; 33 | } 34 | 35 | // 连接到服务器 36 | if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { 37 | perror("Connection Failed"); 38 | return 1; 39 | } 40 | 41 | printf("Connected to server, now sleeping...\n"); 42 | 43 | // 保持连接,但不进行任何通信 44 | sleep(20); // 保持连接20秒,调整时间以匹配服务器设置的超时时间 45 | 46 | // 关闭 socket 47 | close(sock); 48 | printf("Disconnected from server\n"); 49 | 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /第11章 定时器/实战9:利用alarm函数周期性地触发SIGALRM信号/lst_timer.h: -------------------------------------------------------------------------------- 1 | #ifndef LST_TIMER 2 | #define LST_TIMER 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define BUFFER_SIZE 64 9 | 10 | // 前向声明,我们需要在client_data结构中定义该结构的指针类型 11 | class util_timer; 12 | 13 | // 用户数据结构 14 | struct client_data { 15 | sockaddr_in address; /* 客户socket地址 */ 16 | int sockfd; /* socket文件描述符 */ 17 | char buf[BUFFER_SIZE]; /* 读缓冲区 */ 18 | util_timer *timer; /* 定时器 */ 19 | }; 20 | 21 | // 定时器类 22 | class util_timer { 23 | public: 24 | util_timer() : prev(NULL), next(NULL) {} 25 | 26 | // 任务执行时间,UNIX时间戳 27 | time_t expire; 28 | // 任务回调函数 29 | void (*cb_func)(client_data *); 30 | // 回调函数处理的客户数据,由定时器的执行者传给回调函数 31 | client_data *user_data; 32 | // 指向前一个定时器 33 | util_timer *prev; 34 | // 指向后一个定时器 35 | util_timer *next; 36 | }; 37 | 38 | // 定时器链表,它是一个升序、双向链表,且有头节点和尾节点 39 | class sort_timer_lst { 40 | public: 41 | sort_timer_lst() : head(NULL), tail(NULL) {} 42 | // 链表被删除时,删除其中所有定时器 43 | ~sort_timer_lst() { 44 | util_timer *tmp = head; 45 | while (tmp) { 46 | head = tmp->next; 47 | delete tmp; 48 | tmp = head; 49 | } 50 | } 51 | 52 | // 将目标定时器timer参数添加到链表中 53 | void add_timer(util_timer *timer) { 54 | if (!timer) { 55 | return; 56 | } 57 | if (!head) { 58 | head = tail = timer; 59 | return; 60 | } 61 | 62 | // 如果timer中的执行时间小于链表中所有定时器的超时时间,则将其放在链表头部 63 | if (timer->expire < head->expire) { 64 | timer->next = head; 65 | head->prev = timer; 66 | head = timer; 67 | return; 68 | } 69 | // 否则调用重载函数add_timer(util_timer, util_timer)将其放到链表中合适的位置,以保证链表的升序特性 70 | add_timer(timer, head); 71 | } 72 | 73 | // 调整定时任务的执行时间,本函数只处理执行时间延后的情况,即将该定时器向链表尾部移动 74 | void adjust_timer(util_timer *timer) { 75 | if (!timer) { 76 | return; 77 | } 78 | util_timer *tmp = timer->next; 79 | // 如果被调整的目标定时器在链表尾,或该定时器的超时值仍小于下一个定时器的超时值,则不用调整 80 | if (!tmp || (timer->expire < tmp->expire)) { 81 | return; 82 | } 83 | // 如果目标定时器在链表头,则将该定时器从链表中取出并重新插入链表 84 | if (timer == head) { 85 | head = head->next; 86 | head->prev = NULL; 87 | timer->next = NULL; 88 | add_timer(timer, head); 89 | // 如果目标定时器不是链表头节点,则将该定时器从链表中取出,然后插入其原来所在位置之后的链表中 90 | } else { 91 | timer->prev->next = timer->next; 92 | timer->next->prev = timer->prev; 93 | add_timer(timer, timer->next); 94 | } 95 | } 96 | 97 | // 将目标定时器timer从链表中删除 98 | void del_timer(util_timer *timer) { 99 | if (!timer) { 100 | return; 101 | } 102 | // 当链表中只有要删除的那个定时器时 103 | if ((timer == head) && (timer == tail)) { 104 | delete timer; 105 | head = NULL; 106 | tail = NULL; 107 | return; 108 | } 109 | // 如果链表中至少有两个定时器,且目标定时器时链表头节点,则将链表的头节点重置为原头节点的下一个节点 110 | if (timer == head) { 111 | head = head->next; 112 | head->prev = NULL; 113 | delete timer; 114 | return; 115 | } 116 | // 如果链表中至少有两个定时器,且目标定时器时链表尾节点,则将链表的尾节点重置为原尾节点的前一个节点 117 | if (timer == tail) { 118 | tail = tail->prev; 119 | tail->next = NULL; 120 | delete timer; 121 | return; 122 | } 123 | // 如果,目标定时器位于链表中间,则把它前后的定时器串联起来,然后删除目标定时器 124 | timer->prev->next = timer->next; 125 | timer->next->prev = timer->prev; 126 | delete timer; 127 | } 128 | 129 | // SIGALRM信号每次触发就在其信号处理函数(如果使用统一事件源,则是主函数)中执行一次tick函数,处理链表上的到期任务 130 | void tick() { 131 | if (!head) { 132 | return; 133 | } 134 | printf("timer tick\n"); 135 | // 获得系统当前UNIX时间戳 136 | time_t cur = time(NULL); 137 | util_timer *tmp = head; 138 | 139 | // 从头节点开始依次处理每个定时器,直到遇到一个尚未到期的定时器 140 | while (tmp) { 141 | // 每个定时器都使用绝对时间作为超时值,因此我们可把定时器的超时值和系统当前时间作比较 142 | if (cur < tmp->expire) { 143 | break; 144 | } 145 | // 调用定时器的回调函数,以执行定时任务 146 | tmp->cb_func(tmp->user_data); 147 | // 执行完定时器中的任务后,将其从链表中删除,并重置链表头节点 148 | head = tmp->next; 149 | if (head) { 150 | head->prev = NULL; 151 | } 152 | delete tmp; 153 | tmp = head; 154 | } 155 | } 156 | 157 | private: 158 | // 一个重载的辅助函数,它被公有的add_timer和adjust_timer函数调用 159 | // 该函数将目标定时器timer参数添加到节点lst_head参数后的链表中 160 | void add_timer(util_timer *timer, util_timer *lst_head) { 161 | util_timer *prev = lst_head; 162 | util_timer *tmp = prev->next; 163 | // 遍历lst_head节点后的部分链表,直到找到一个超时时间大于目标定时器超时时间的节点,并将目标定时器插入该节点前 164 | while (tmp) { 165 | if (timer->expire < tmp->expire) { 166 | prev->next = timer; 167 | timer->next = tmp; 168 | tmp->prev = timer; 169 | timer->prev = prev; 170 | break; 171 | } 172 | prev = tmp; 173 | tmp = tmp->next; 174 | } 175 | 176 | // 如果遍历完lst_head节点后的链表,仍未找到超时时间大于目标定时器的超时时间的节点,则将目标定时器作为链表尾 177 | if (!tmp) { 178 | prev->next = timer; 179 | timer->prev = prev; 180 | timer->next = NULL; 181 | tail = timer; 182 | } 183 | } 184 | 185 | util_timer *head; 186 | util_timer *tail; 187 | }; 188 | #endif 189 | -------------------------------------------------------------------------------- /第11章 定时器/实战9:利用alarm函数周期性地触发SIGALRM信号/server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhu619/Linux-high-performance-server-programming-Notebook/62ede31d2f82c18b81a9c52df7ce3d7ed2a78f87/第11章 定时器/实战9:利用alarm函数周期性地触发SIGALRM信号/server -------------------------------------------------------------------------------- /第11章 定时器/实战9:利用alarm函数周期性地触发SIGALRM信号/sigalrm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "lst_timer.h" 18 | 19 | #define FD_LIMIT 65535 20 | #define MAX_EVENT_NUMBER 1024 21 | #define TIMESLOT 5 22 | 23 | static int pipefd[2]; 24 | 25 | static sort_timer_lst timer_lst; /* 用升序链表来管理定时器 */ 26 | static int epollfd = 0; 27 | 28 | /* 将文件描述符设置为非阻塞模式 */ 29 | int setnonblocking(int fd) { 30 | int old_option = fcntl(fd, F_GETFL); 31 | int new_option = old_option | O_NONBLOCK; 32 | fcntl(fd, F_SETFL, new_option); 33 | return old_option; 34 | } 35 | 36 | /* addfd 函数把新的文件描述符添加到 epoll 监听列表中, 37 | * 并且设置为非阻塞和边缘触发模式(EPOLLET),以提高事件处理的效率。 38 | */ 39 | void addfd(int epollfd, int fd) { 40 | epoll_event event; 41 | event.data.fd = fd; 42 | event.events = EPOLLIN | EPOLLET; 43 | epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); /* 将fd注册到内核事件表epollfd中*/ 44 | setnonblocking(fd); 45 | } 46 | 47 | /* sig_handler 用于处理程序接收到的信号。 48 | * 它把信号编号发送到 pipefd 管道,这样主循环可以安全地处理信号事件。 49 | * 这种使用信号和管道的组合是多线程或多进程环境中处理信号的一种安全模式。 50 | */ 51 | void sig_handler(int sig) { 52 | int save_errno = errno; 53 | int msg = sig; 54 | // 此处还是老bug,没有考虑字节序就发送了int的低地址的1字节 55 | send(pipefd[1], (char *)&msg, 1, 0); 56 | errno = save_errno; 57 | } 58 | 59 | /* addsig 函数设置信号处理函数,并确保系统调用被中断时能自动重启,避免了部分系统调用失败的问题。 */ 60 | void addsig(int sig) { 61 | struct sigaction sa; 62 | memset(&sa, '\0', sizeof(sa)); 63 | sa.sa_handler = sig_handler; 64 | sa.sa_flags |= SA_RESTART; 65 | sigfillset(&sa.sa_mask); 66 | assert(sigaction(sig, &sa, NULL) != -1); 67 | } 68 | 69 | void timer_handler() { 70 | // 处理定时任务 71 | timer_lst.tick(); 72 | // 由于alarm函数只会引起一次SIGALRM信号,因此重新定时,以不断触发SIGALRM信号 73 | alarm(TIMESLOT); 74 | } 75 | 76 | /* 定时器回调函数,它删除非活动连接socket上的注册事件,并关闭之 */ 77 | void cb_func(client_data *user_data) { 78 | epoll_ctl(epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0); 79 | assert(user_data); 80 | close(user_data->sockfd); 81 | printf("close fd %d\n", user_data->sockfd); 82 | } 83 | 84 | int main(int argc, char *argv[]) { 85 | if (argc != 3) { 86 | printf("usage: %s ip_address port_number\n", basename(argv[0])); 87 | return 1; 88 | } 89 | const char *ip = argv[1]; 90 | int port = atoi(argv[2]); 91 | 92 | int ret = 0; 93 | struct sockaddr_in address; 94 | bzero(&address, sizeof(address)); 95 | address.sin_family = AF_INET; 96 | inet_pton(AF_INET, ip, &address.sin_addr); 97 | address.sin_port = htons(port); 98 | 99 | int listenfd = socket(PF_INET, SOCK_STREAM, 0); 100 | assert(listenfd >= 0); 101 | 102 | ret = bind(listenfd, (struct sockaddr *)&address, sizeof(address)); 103 | assert(ret != -1); 104 | 105 | ret = listen(listenfd, 5); 106 | assert(ret != -1); 107 | 108 | epoll_event events[MAX_EVENT_NUMBER]; 109 | int epollfd = epoll_create(5); /* 创建内核事件表 */ 110 | assert(epollfd != -1); 111 | addfd(epollfd, listenfd); 112 | 113 | ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd); 114 | assert(ret != -1); 115 | setnonblocking(pipefd[1]); /* fd[1]只能用于数据写入。 */ 116 | addfd(epollfd, pipefd[0]); 117 | 118 | // 设置信号处理函数 119 | addsig(SIGALRM); 120 | addsig(SIGTERM); 121 | bool stop_server = false; 122 | 123 | // 直接初始化FD_LIMIT个client_data对象,其数组索引是文件描述符 124 | client_data *users = new client_data[FD_LIMIT]; 125 | bool timeout = false; 126 | // 定时 127 | alarm(TIMESLOT); 128 | 129 | while (!stop_server) { 130 | int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1); /* 它在一段超时时间内等待一组文件描述符上的事件: */ 131 | if ((number < 0) && (errno != EINTR)) { 132 | printf("epoll failure\n"); 133 | break; 134 | } 135 | 136 | for (int i = 0; i < number; ++i) { 137 | int sockfd = events[i].data.fd; 138 | // 处理新到的客户连接 139 | if (sockfd == listenfd) { 140 | struct sockaddr_in client_address; 141 | socklen_t client_addrlength = sizeof(client_address); 142 | int connfd = accept(listenfd, (struct sockaddr *)&client_address, &client_addrlength); 143 | addfd(epollfd, connfd); 144 | users[connfd].address = client_address; 145 | users[connfd].sockfd = connfd; 146 | // 创建一个定时器,设置其回调函数和超时时间,然后绑定定时器和用户数据,并将定时器添加到timer_lst中 147 | util_timer *timer = new util_timer; 148 | timer->user_data = &users[connfd]; 149 | timer->cb_func = cb_func; 150 | time_t cur = time(NULL); 151 | timer->expire = cur + 3 * TIMESLOT; 152 | users[connfd].timer = timer; 153 | timer_lst.add_timer(timer); 154 | // 处理信号 155 | } else if ((sockfd == pipefd[0]) && (events[i].events & EPOLLIN)) { 156 | int sig; 157 | char signals[1024]; 158 | ret = recv(pipefd[0], signals, sizeof(signals), 0); 159 | if (ret == -1) { 160 | // handle the error 161 | continue; 162 | } else if (ret == 0) { 163 | continue; 164 | } else { 165 | for (int i = 0; i < ret; ++i) { 166 | switch (signals[i]) { 167 | case SIGALRM: 168 | // 先标记为有定时任务,因为定时任务优先级比IO事件低,我们优先处理其他更重要的任务 169 | timeout = true; 170 | break; 171 | 172 | case SIGTERM: 173 | stop_server = true; 174 | break; 175 | } 176 | } 177 | } 178 | // 从客户连接上接收到数据 179 | } else if (events[i].events & EPOLLIN) { 180 | memset(users[sockfd].buf, '\0', BUFFER_SIZE); 181 | ret = recv(sockfd, users[sockfd].buf, BUFFER_SIZE - 1, 0); 182 | printf("get %d bytes of client data %s from %d\n", ret, users[sockfd].buf, sockfd); 183 | 184 | util_timer *timer = users[sockfd].timer; 185 | if (ret < 0) { 186 | // 如果发生读错误,则关闭连接,并移除对应的定时器 187 | if (errno != EAGAIN) { 188 | cb_func(&users[sockfd]); 189 | if (timer) { 190 | timer_lst.del_timer(timer); 191 | } 192 | } 193 | } else if (ret == 0) { 194 | // 如果对方关闭连接,则我们也关闭连接,并移除对应的定时器 195 | cb_func(&users[sockfd]); 196 | if (timer) { 197 | timer_lst.del_timer(timer); 198 | } 199 | } else { 200 | // 如果客户连接上读到了数据,则调整该连接对应的定时器,以延迟该连接被关闭的时间 201 | if (timer) { 202 | time_t cur = time(NULL); 203 | timer->expire = cur + 3 * TIMESLOT; 204 | printf("adjust timer once\n"); 205 | timer_lst.adjust_timer(timer); 206 | } 207 | } 208 | } 209 | } 210 | 211 | // 最后处理定时事件,因为IO事件的优先级更高,但这样会导致定时任务不能精确按预期的时间执行 212 | if (timeout) { 213 | timer_handler(); 214 | timeout = false; 215 | } 216 | } 217 | 218 | close(listenfd); 219 | close(pipefd[1]); 220 | close(pipefd[0]); 221 | delete[] users; 222 | return 0; 223 | } 224 | -------------------------------------------------------------------------------- /第12章 高性能IO框架库Libevent/实战 10:利用 Libevent 库实现一个“Hello World”程序/example: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhu619/Linux-high-performance-server-programming-Notebook/62ede31d2f82c18b81a9c52df7ce3d7ed2a78f87/第12章 高性能IO框架库Libevent/实战 10:利用 Libevent 库实现一个“Hello World”程序/example -------------------------------------------------------------------------------- /第12章 高性能IO框架库Libevent/实战 10:利用 Libevent 库实现一个“Hello World”程序/example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void signal_cb(int fd, short event, void *argc) { 5 | struct event_base *base = (event_base *)argc; 6 | struct timeval delay = {2, 0}; 7 | printf("Caught an interrupt signal; exiting cleanly in two seconds...\n"); 8 | event_base_loopexit(base, &delay); 9 | } 10 | 11 | void timeout_cb(int fd, short event, void *argc) { 12 | printf("timeout\n"); 13 | } 14 | 15 | int main() { 16 | struct event_base *base = event_init(); 17 | 18 | struct event *signal_event = evsignal_new(base, SIGINT, signal_cb, base); 19 | event_add(signal_event, NULL); 20 | 21 | timeval tv = {1, 0}; 22 | struct event *timeout_event = evtimer_new(base, timeout_cb, NULL); 23 | event_add(timeout_event, &tv); 24 | 25 | event_base_dispatch(base); 26 | 27 | event_free(timeout_event); 28 | event_free(signal_event); 29 | event_base_free(base); 30 | } -------------------------------------------------------------------------------- /第13章 多进程编程/实战 11: 在进程间传递文件描述符/code: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhu619/Linux-high-performance-server-programming-Notebook/62ede31d2f82c18b81a9c52df7ce3d7ed2a78f87/第13章 多进程编程/实战 11: 在进程间传递文件描述符/code -------------------------------------------------------------------------------- /第13章 多进程编程/实战 11: 在进程间传递文件描述符/code.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static const int CONTROL_LEN = CMSG_LEN(sizeof(int)); 10 | // 发送文件描述符,fd参数是用来传递信息的UNIX域socket,fd_to_send参数是待发送的文件描述符 11 | void send_fd(int fd, int fd_to_send) { 12 | struct iovec iov[1]; 13 | struct msghdr msg; 14 | char buf[0]; 15 | 16 | iov[0].iov_base = buf; 17 | iov[0].iov_len = 1; 18 | msg.msg_name = NULL; 19 | msg.msg_namelen = 0; 20 | msg.msg_iov = iov; 21 | msg.msg_iovlen = 1; 22 | 23 | cmsghdr cm; 24 | cm.cmsg_len = CONTROL_LEN; 25 | cm.cmsg_level = SOL_SOCKET; 26 | cm.cmsg_type = SCM_RIGHTS; 27 | *(int *)CMSG_DATA(&cm) = fd_to_send; 28 | msg.msg_control = &cm; /* 设置辅助数据 */ 29 | msg.msg_controllen = CONTROL_LEN; 30 | 31 | sendmsg(fd, &msg, 0); /* 通用数据读 */ 32 | } 33 | 34 | // 接收目标文件描述符 35 | int recv_fd(int fd) { 36 | struct iovec iov[1]; 37 | struct msghdr msg; 38 | char buf[0]; 39 | 40 | iov[0].iov_base = buf; 41 | iov[0].iov_len = 1; 42 | msg.msg_name = NULL; 43 | msg.msg_namelen = 0; 44 | msg.msg_iov = iov; 45 | msg.msg_iovlen = 1; 46 | 47 | cmsghdr cm; 48 | msg.msg_control = &cm; 49 | msg.msg_controllen = CONTROL_LEN; 50 | 51 | recvmsg(fd, &msg, 0); /* 通用数据写 */ 52 | 53 | int fd_to_read = *(int *)CMSG_DATA(&cm); 54 | return fd_to_read; 55 | } 56 | 57 | int main() { 58 | int pipefd[2]; 59 | int fd_to_pass = 0; 60 | /* 创建父、子进程间的管道,文件描述符pipefd[0]和pipefd[1]都是UNIX域socket */ 61 | int ret = socketpair(PF_UNIX, SOCK_DGRAM, 0, pipefd); 62 | assert(ret != -1); 63 | 64 | pid_t pid = fork(); /* 创建子进程 */ 65 | assert(pid >= 0); 66 | 67 | if (pid == 0) { 68 | close(pipefd[0]); /* 子进程关闭读 */ 69 | fd_to_pass = open("test.txt", O_RDWR, 0666); /* 打开文件 */ 70 | send_fd(pipefd[1], (fd_to_pass > 0) ? fd_to_pass : 0); /* 子进程通过管道将文件描述符发送到父进程,如果文件打开失败,则子进程将标准输入发送到父进程 */ 71 | close(fd_to_pass); 72 | exit(0); 73 | } 74 | 75 | close(pipefd[1]); /* 父进程关闭写 */ 76 | 77 | fd_to_pass = recv_fd(pipefd[0]); /* 父进程从管道接收目标文件描述符 */ 78 | char buf[1024]; /* 存放数据 */ 79 | memset(buf, '\0', 1024); 80 | // 读目标文件描述符,验证其有效性 81 | read(fd_to_pass, buf, 1024); 82 | printf("I got fd %d and data %s\n", fd_to_pass, buf); 83 | close(fd_to_pass); 84 | } -------------------------------------------------------------------------------- /第13章 多进程编程/实战 11: 在进程间传递文件描述符/test.txt: -------------------------------------------------------------------------------- 1 | 123 2 | -------------------------------------------------------------------------------- /第14章 多线程编程/实战 12:死锁举例/test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhu619/Linux-high-performance-server-programming-Notebook/62ede31d2f82c18b81a9c52df7ce3d7ed2a78f87/第14章 多线程编程/实战 12:死锁举例/test -------------------------------------------------------------------------------- /第14章 多线程编程/实战 12:死锁举例/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int a = 0; 6 | int b = 0; 7 | pthread_mutex_t mutex_a; 8 | pthread_mutex_t mutex_b; 9 | 10 | void *another (void *arg) { 11 | pthread_mutex_lock(&mutex_b); /* 子线程上锁 mutex_b */ 12 | printf("in child thread, got mutex b, waiting for mutex a\n"); 13 | sleep(5); 14 | ++b; 15 | pthread_mutex_lock(&mutex_a); /* 子线程上锁 mutex_a */ 16 | b += a++; 17 | 18 | pthread_mutex_unlock(&mutex_a); /* 解锁 */ 19 | pthread_mutex_unlock(&mutex_b); 20 | pthread_exit(NULL); 21 | } 22 | 23 | int main () { 24 | pthread_t id; 25 | 26 | pthread_mutex_init(&mutex_a, NULL); /* 初始化互斥锁 */ 27 | pthread_mutex_init(&mutex_b, NULL); 28 | pthread_create(&id, NULL, another, NULL); /* 创建线程 */ 29 | 30 | pthread_mutex_lock(&mutex_a); /* 主线程上锁 mutex_a */ 31 | printf("in parent thread, got mutex a, waiting for mutex b\n"); 32 | sleep(5); 33 | ++a; 34 | 35 | pthread_mutex_lock(&mutex_b); /* 主线程上锁 mutex_b */ 36 | a += b++; 37 | 38 | pthread_mutex_unlock(&mutex_b); 39 | pthread_mutex_unlock(&mutex_a); 40 | 41 | /* 主线程等待子线程结束,然后销毁互斥锁以释放资源 */ 42 | pthread_join(id, NULL); 43 | pthread_mutex_destroy(&mutex_a); 44 | pthread_mutex_destroy(&mutex_b); 45 | 46 | return 0; 47 | } -------------------------------------------------------------------------------- /第14章 多线程编程/实战 13:使用条件变量模拟实现生产者—消费者问题/pcer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhu619/Linux-high-performance-server-programming-Notebook/62ede31d2f82c18b81a9c52df7ce3d7ed2a78f87/第14章 多线程编程/实战 13:使用条件变量模拟实现生产者—消费者问题/pcer -------------------------------------------------------------------------------- /第14章 多线程编程/实战 13:使用条件变量模拟实现生产者—消费者问题/producer-concumer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; /* 初始化互斥锁 */ 8 | pthread_cond_t cond = PTHREAD_COND_INITIALIZER; /* 初始化条件变量 */ 9 | int buffer = 0; /* 产品,代表共享资源 */ 10 | 11 | /* 生产者线程 */ 12 | void* producer(void* arg) { 13 | pthread_mutex_lock(&mutex); 14 | buffer = 1; // 生产产品 15 | printf("Producer: Produced an item\n"); 16 | pthread_cond_signal(&cond); // 通知消费者 17 | pthread_mutex_unlock(&mutex); 18 | sleep(rand() % 3); // 随机延时0-2秒 19 | return NULL; 20 | } 21 | 22 | /* 消费者线程 */ 23 | void* consumer(void* arg) { 24 | pthread_mutex_lock(&mutex); 25 | while (buffer == 0) { 26 | printf("Consumer: Waiting for item\n"); 27 | pthread_cond_wait(&cond, &mutex); // 等待产品,自动释放互斥锁并使线程进入等待状态 28 | } 29 | buffer = 0; // 消费产品 30 | printf("Consumer: Consumed an item\n"); 31 | pthread_mutex_unlock(&mutex); 32 | sleep(rand() % 3); // 随机延时0-2秒 33 | return NULL; 34 | } 35 | 36 | int main() { 37 | srand(time(NULL)); // 初始化随机数生成器 38 | 39 | /* 创建线程 */ 40 | pthread_t prod, cons; 41 | pthread_create(&prod, NULL, producer, NULL); 42 | pthread_create(&cons, NULL, consumer, NULL); 43 | 44 | /* 等待线程结束 */ 45 | pthread_join(prod, NULL); 46 | pthread_join(cons, NULL); 47 | 48 | /* 资源释放 */ 49 | pthread_mutex_destroy(&mutex); 50 | pthread_cond_destroy(&cond); 51 | 52 | return 0; 53 | } -------------------------------------------------------------------------------- /第14章 多线程编程/实战 14:多线程环境中,使用fork调用产生的死锁问题/fork_lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhu619/Linux-high-performance-server-programming-Notebook/62ede31d2f82c18b81a9c52df7ce3d7ed2a78f87/第14章 多线程编程/实战 14:多线程环境中,使用fork调用产生的死锁问题/fork_lock -------------------------------------------------------------------------------- /第14章 多线程编程/实战 14:多线程环境中,使用fork调用产生的死锁问题/fork_lock.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | pthread_mutex_t mutex; 8 | 9 | void prepare () { 10 | pthread_mutex_lock ( &mutex ); 11 | } 12 | 13 | void infork () { 14 | pthread_mutex_unlock ( &mutex ); 15 | } 16 | 17 | /* 子线程运行的函数,它首先获得互斥锁 mutex ,然后暂停5s,再释放该互斥锁 */ 18 | void *another(void *arg) { 19 | printf("in child thread, lock the mutex\n"); 20 | pthread_mutex_lock(&mutex); 21 | sleep(5); 22 | pthread_mutex_unlock(&mutex); 23 | } 24 | 25 | int main() { 26 | pthread_mutex_init(&mutex, NULL); 27 | pthread_t id; 28 | pthread_create(&id, NULL, another, NULL); 29 | /* 父进程中的主线程暂停1s,以确保在执行 fork 前,子线程已经开始运行并获得了互斥量 mutex */ 30 | sleep(1); 31 | 32 | pthread_atfork ( prepare, infork, infork ); 33 | 34 | int pid = fork(); 35 | if (pid < 0) { 36 | pthread_join(id, NULL); 37 | pthread_mutex_destroy(&mutex); 38 | return 1; 39 | } else if (pid == 0) { 40 | printf("I am in the child, want to get the lock\n"); 41 | /* 子进程从父进程继承了互斥锁 mutex 的状态,该互斥锁处于锁住的状态 */ 42 | /* 这是由父进程中的子线程执行 pthread_mutex_lock 引起的,因此以下加锁操作会一直阻塞 */ 43 | /* 尽管从逻辑上来说它是不应该阻塞的 */ 44 | pthread_mutex_lock(&mutex); 45 | printf("I can not run to here, oop...\n"); 46 | pthread_mutex_unlock(&mutex); 47 | exit(0); 48 | } else { 49 | wait(NULL); 50 | } 51 | pthread_join(id, NULL); 52 | pthread_mutex_destroy(&mutex); 53 | return 0; 54 | } -------------------------------------------------------------------------------- /第14章 多线程编程/实战 14:多线程环境中,使用fork调用产生的死锁问题/fork_lock2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhu619/Linux-high-performance-server-programming-Notebook/62ede31d2f82c18b81a9c52df7ce3d7ed2a78f87/第14章 多线程编程/实战 14:多线程环境中,使用fork调用产生的死锁问题/fork_lock2 -------------------------------------------------------------------------------- /第14章 多线程编程/实战 15:在一个线程中统一处理所有信号/signal: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhu619/Linux-high-performance-server-programming-Notebook/62ede31d2f82c18b81a9c52df7ce3d7ed2a78f87/第14章 多线程编程/实战 15:在一个线程中统一处理所有信号/signal -------------------------------------------------------------------------------- /第14章 多线程编程/实战 15:在一个线程中统一处理所有信号/signal.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // perror函数根据全局errno值打印其相应的错误信息到标准错误 9 | #define handle_error_en(en, msg) \ 10 | do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0) 11 | 12 | /* 在 sig_thread 函数中,线程循环调用 sigwait 来等待信号 */ 13 | static void *sig_thread(void *arg) { 14 | sigset_t *set = (sigset_t *)arg; 15 | int s, sig; 16 | 17 | for (; ; ) { 18 | // 第二步,调用sigwait等待信号 19 | s = sigwait(set, &sig); 20 | if (s != 0) { 21 | handle_error_en(s, "sigwait"); 22 | } 23 | printf("Signal handling thread got signal %d\n", sig); 24 | } 25 | } 26 | 27 | int main(int argc, char *argv[]) { 28 | printf("The PID of this process is: %d\n", getpid()); /* 获取进程 PID */ 29 | 30 | pthread_t thread; /* 线程 */ 31 | sigset_t set; /* 信号集 */ 32 | int s; 33 | 34 | /* 第一步,在主线程中设置信号掩码,信号集set被初始化并添加了SIGQUIT和SIGUSR1信号: */ 35 | sigemptyset(&set); 36 | sigaddset(&set, SIGQUIT); 37 | sigaddset(&set, SIGUSR1); 38 | 39 | /* 使用 pthread_sigmask 来阻塞这些信号 */ 40 | s = pthread_sigmask(SIG_BLOCK, &set, NULL); 41 | if (s != 0) { 42 | handle_error_en(s, "pthread_sigmask"); 43 | } 44 | 45 | /* 创建处理信号的线程 */ 46 | s = pthread_create(&thread, NULL, &sig_thread, (void *)&set); 47 | if (s != 0) { 48 | handle_error_en(s, "thread_create"); 49 | } 50 | 51 | pause(); 52 | } -------------------------------------------------------------------------------- /第14章 多线程编程/线程同步机制包装类/locker.h: -------------------------------------------------------------------------------- 1 | #ifndef LOCKER_H 2 | #define LOCKER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | // 封装信号量的类 9 | class sem { 10 | public: 11 | // 创建并初始化信号量 12 | sem() { 13 | if (sem_init(&m_sem, 0, 0) != 0) { 14 | // 构造函数没有返回值,可通过抛出异常来报告错误 15 | throw std::exception(); 16 | } 17 | } 18 | 19 | // 销毁信号量 20 | ~sem() { 21 | sem_destroy(&m_sem); 22 | } 23 | 24 | // 等待信号量 25 | bool wait() { 26 | return sem_wait(&m_sem) == 0; 27 | } 28 | 29 | // 增加信号量 30 | bool post() { 31 | return sem_post(&m_sem) == 0; 32 | } 33 | 34 | private: 35 | sem_t m_sem; 36 | }; 37 | 38 | // 封装互斥锁的类 39 | class locker { 40 | public: 41 | // 创建并初始化互斥锁 42 | locker() { 43 | if (pthread_mutex_init(&m_mutex, NULL) != 0) { 44 | throw std::exception(); 45 | } 46 | } 47 | 48 | // 销毁互斥锁 49 | ~locker() { 50 | pthread_mutex_destroy(&m_mutex); 51 | } 52 | 53 | // 获取互斥锁 54 | bool lock() { 55 | return pthread_mutex_lock(&m_mutex) == 0; 56 | } 57 | 58 | // 释放互斥锁 59 | bool unlock() { 60 | return pthread_mutex_unlock(&m_mutex) == 0; 61 | } 62 | 63 | private: 64 | pthread_mutex_t m_mutex; 65 | }; 66 | 67 | // 封装条件变量的类 68 | class cond { 69 | public: 70 | // 创建并初始化条件变量 71 | cond() { 72 | if (pthread_mutex_init(&m_mutex, NULL) != 0) { 73 | throw std::exception(); 74 | } 75 | if (pthread_cond_init(&m_cond, NULL) != 0) { 76 | // 构造函数中一旦出现问题,就应立即释放已经成功分配的资源 77 | pthread_mutex_destroy(&m_mutex); 78 | throw std::exception(); 79 | } 80 | } 81 | 82 | // 销毁条件变量 83 | ~cond() { 84 | pthread_mutex_destroy(&m_mutex); 85 | pthread_cond_destroy(&m_cond); 86 | } 87 | 88 | // 等待条件变量 89 | bool wait() { 90 | int ret = 0; 91 | // 作者在此处对互斥锁加锁,保护了什么?这导致其他人无法使用该封装类 92 | pthread_mutex_lock(&m_mutex); 93 | ret = pthread_cond_wait(&m_cond, &m_mutex); 94 | pthread_mutex_unlock(&m_mutex); 95 | return ret == 0; 96 | } 97 | 98 | // 唤醒等待条件变量的线程 99 | bool signal() { 100 | return pthread_cond_signal(&m_cond) == 0; 101 | } 102 | 103 | private: 104 | pthread_mutex_t m_mutex; 105 | pthread_cond_t m_cond; 106 | }; 107 | 108 | #endif -------------------------------------------------------------------------------- /第15章 进程池和线程池/实战16:用进程池实现简单的CGI服务器/cgi_server_2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhu619/Linux-high-performance-server-programming-Notebook/62ede31d2f82c18b81a9c52df7ce3d7ed2a78f87/第15章 进程池和线程池/实战16:用进程池实现简单的CGI服务器/cgi_server_2 -------------------------------------------------------------------------------- /第15章 进程池和线程池/实战16:用进程池实现简单的CGI服务器/cgi_server_2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | /* 引用进程池 */ 17 | #include "processpool.h" 18 | 19 | // 用于处理客户CGI请求的类,它可作为processpool类的模板参数 20 | class cgi_conn { 21 | public: 22 | cgi_conn() { } 23 | 24 | ~cgi_conn() { } 25 | 26 | // 初始化客户连接,清空读缓冲区 27 | void init(int epollfd, int sockfd, const sockaddr_in& client_addr) { 28 | m_epollfd = epollfd; 29 | m_sockfd = sockfd; 30 | m_address = client_addr; 31 | memset(m_buf, '\0', BUFFER_SIZE); 32 | m_read_idx = 0; 33 | } 34 | 35 | void process() { 36 | int idx = 0; 37 | int ret = -1; 38 | // 循环读取和分析客户数据 39 | while (true) { 40 | idx = m_read_idx; 41 | ret = recv(m_sockfd, m_buf + idx, BUFFER_SIZE - 1 - idx, 0); 42 | // 如果读操作发生错误,则关闭客户连接;如果暂时无数据可读,则退出循环 43 | if (ret < 0) { 44 | if (errno != EAGAIN) { 45 | removefd(m_epollfd, m_sockfd); 46 | } 47 | break; 48 | // 如果对方关闭连接,则服务器也关闭连接 49 | } else if (ret == 0) { 50 | removefd(m_epollfd, m_sockfd); 51 | break; 52 | } else { 53 | m_read_idx += ret; 54 | printf("user content is: %s\n", m_buf); 55 | // 如果遇到字符\r\n,则开始处理客户请求 56 | for (; idx < m_read_idx; ++idx) { 57 | if ((idx >= 1) && (m_buf[idx - 1] == '\r') && (m_buf[idx] == '\n')) { 58 | break; 59 | } 60 | } 61 | // 如没有遇到\r\n,则需要读取更多数据 62 | if (idx == m_read_idx) { 63 | continue; 64 | } 65 | m_buf[idx - 1] = '\0'; 66 | 67 | char *file_name = m_buf; 68 | // 判断客户要运行的CGI程序是否存在 69 | // access函数用于检测file_name参数表示的文件,F_OK表示检测文件是否存在 70 | if (access(file_name, F_OK) == -1) { 71 | removefd(m_epollfd, m_sockfd); 72 | break; 73 | } 74 | // 创建子进程执行CGI程序 75 | ret = fork(); 76 | if (ret == -1) { 77 | removefd(m_epollfd, m_sockfd); 78 | break; 79 | } else if (ret > 0) { 80 | // 父进程只需关闭连接 81 | removefd(m_epollfd, m_sockfd); 82 | break; 83 | } else { 84 | // 子进程将标准输出重定向到m_sockfd,并执行CGI程序 85 | close(STDOUT_FILENO); 86 | dup(m_sockfd); 87 | execl(m_buf, m_buf, 0); 88 | exit(0); 89 | } 90 | } 91 | } 92 | } 93 | 94 | private: 95 | static const int BUFFER_SIZE = 1024; 96 | static int m_epollfd; 97 | int m_sockfd; 98 | sockaddr_in m_address; 99 | char m_buf[BUFFER_SIZE]; 100 | // 标记读缓冲中已经读入的客户数据的最后一个字节的下一个位置 101 | int m_read_idx; 102 | }; 103 | int cgi_conn::m_epollfd = -1; 104 | 105 | int main(int argc, char *argv[]) { 106 | if (argc <= 2) { 107 | printf("usage: %s ip_address port_number\n", basename(argv[0])); 108 | return 1; 109 | } 110 | const char *ip = argv[1]; 111 | int port = atoi(argv[2]); 112 | 113 | int listenfd = socket(PF_INET, SOCK_STREAM, 0); 114 | assert(listenfd >= 0); 115 | 116 | int ret = 0; 117 | struct sockaddr_in address; 118 | bzero(&address, sizeof(address)); 119 | address.sin_family = AF_INET; 120 | inet_pton(AF_INET, ip, &address.sin_addr); 121 | address.sin_port = htons(port); 122 | 123 | ret = bind(listenfd, (struct sockaddr *)&address, sizeof(address)); 124 | assert(ret != -1); 125 | 126 | processpool *pool = processpool::create(listenfd); 127 | if (pool) { 128 | pool->run(); 129 | delete pool; 130 | } 131 | close(listenfd); // main函数创建了listenfd,就由它来关闭 132 | return 0; 133 | } -------------------------------------------------------------------------------- /第15章 进程池和线程池/实战16:用进程池实现简单的CGI服务器/client: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhu619/Linux-high-performance-server-programming-Notebook/62ede31d2f82c18b81a9c52df7ce3d7ed2a78f87/第15章 进程池和线程池/实战16:用进程池实现简单的CGI服务器/client -------------------------------------------------------------------------------- /第15章 进程池和线程池/实战16:用进程池实现简单的CGI服务器/client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /* TCP客户端实现 */ 8 | int main() { 9 | 10 | // 1.创建套接字: AF_INET 表示使用 IPv4 地址,SOCK_STREAM 表示使用 TCP 连接。 11 | int fd = socket(AF_INET, SOCK_STREAM, 0); 12 | if(fd == -1) { 13 | perror("socket"); 14 | exit(-1); 15 | } 16 | 17 | // 2.连接服务器端: 设定服务器的 IP 地址和端口号(此处为 "192.168.177.146" 和 9999)。 18 | struct sockaddr_in serveraddr; 19 | serveraddr.sin_family = AF_INET; 20 | inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr.s_addr); 21 | serveraddr.sin_port = htons(8080); 22 | int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); 23 | 24 | if(ret == -1) { 25 | perror("connect"); 26 | exit(-1); 27 | } 28 | 29 | 30 | // 3.通信 31 | char recvBuf[1024] = {0}; 32 | while(1) { 33 | 34 | char * data = "hello,i am client"; 35 | // 通过 write 函数发送消息 "hello,i am client" 到服务器。 36 | write(fd, data , strlen(data)); 37 | 38 | sleep(1); 39 | 40 | int len = read(fd, recvBuf, sizeof(recvBuf)); 41 | if(len == -1) { 42 | perror("read"); 43 | exit(-1); 44 | } else if(len > 0) { 45 | printf("recv server data : %s\n", recvBuf); 46 | } else if(len == 0) { 47 | // 如果 read 返回 0,表示服务器关闭了连接,客户端程序将结束循环并关闭连接。 48 | printf("server closed..."); 49 | break; 50 | } 51 | 52 | } 53 | 54 | // 关闭连接 55 | close(fd); 56 | 57 | return 0; 58 | } 59 | 60 | -------------------------------------------------------------------------------- /第15章 进程池和线程池/实战16:用进程池实现简单的CGI服务器/processpool.h: -------------------------------------------------------------------------------- 1 | /* filename: processpool.h */ 2 | /* 模板化设计:使用模板类可以很容易地适应不同类型的请求处理,使得进程池更加通用 */ 3 | /* 信号处理:集成信号处理以处理如SIGCHLD、SIGTERM等信号,以实现进程管理和平滑退出 */ 4 | /* 非阻塞I/O与Epoll:使用非阻塞I/O和 Epoll 事件驱动模型,提高了I/O处理的效率 */ 5 | /* 子进程管理:通过管道与子进程通信,并用Round Robin策略分配客户端请求,均衡负载 */ 6 | 7 | #ifndef PROCESSPOOL_H 8 | #define PROCESSPOOL_H 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | // 描述一个子进程的类 27 | class process { 28 | public: 29 | process() : m_pid(-1) { } 30 | 31 | pid_t m_pid; // 目标子进程的PID 32 | int m_pipefd[2]; // 父进程和子进程通信用的管道 33 | }; 34 | 35 | // 进程池类,将它定义为模板类是为了代码复用,其模板参数是处理逻辑任务的类 36 | template 37 | class processpool { 38 | private: 39 | // 私有构造函数,只能通过后面的create静态方法来创建processpool实例 40 | processpool(int listenfd, int process_number = 8); 41 | 42 | public: 43 | // 单例模式,以保证进程最多创建一个processpool实例,这是程序正确处理信号的必要条件 44 | static processpool *create(int listenfd, int process_number = 8) { 45 | // 此处有bug,默认new失败会抛异常,而非返回空指针 46 | if (!m_instance) { 47 | m_instance = new processpool(listenfd, process_number); 48 | } 49 | return m_instance; 50 | } 51 | 52 | ~processpool() { 53 | delete[] m_sub_process; 54 | } 55 | 56 | void run(); // 启动进程池 57 | 58 | private: 59 | void setup_sig_pipe(); 60 | void run_parent(); 61 | void run_child(); 62 | 63 | private: 64 | // 进程池允许的最大子进程数量 65 | static const int MAX_PROCESS_NUMBER = 16; 66 | // 每个子进程最多能处理的客户数量 67 | static const int USER_PER_PROCESS = 65536; 68 | // epoll最多能处理的事件数 69 | static const int MAX_EVENT_NUMBER = 10000; 70 | // 进程池中的进程总数 71 | int m_process_number; 72 | // 子进程在池中的序号,从0开始 73 | int m_idx; 74 | // 每个进程都有一个epoll内核事件表,用m_epollfd标识 75 | int m_epollfd; 76 | // 监听socket 77 | int m_listenfd; 78 | // 子进程通过m_stop决定是否停止运行 79 | int m_stop; 80 | // 保存所有子进程的描述信息 81 | process *m_sub_process; 82 | // 进程池静态实例 83 | static processpool* m_instance; 84 | }; 85 | 86 | template 87 | processpool *processpool::m_instance = NULL; 88 | 89 | // 用来处理信号的管道,以实现统一事件源,后面称之为信号管道 90 | static int sig_pipefd[2]; 91 | 92 | static int setnonblocking(int fd) { 93 | int old_option = fcntl(fd, F_GETFL); 94 | int new_option = old_option | O_NONBLOCK; 95 | fcntl(fd, F_SETFL, new_option); 96 | return old_option; 97 | } 98 | 99 | static void addfd(int epollfd, int fd) { 100 | epoll_event event; 101 | event.data.fd = fd; 102 | event.events = EPOLLIN | EPOLLET; 103 | epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); 104 | setnonblocking(fd); 105 | } 106 | 107 | // 从epollfd参数标识的epoll内核事件表中删除fd上的所有注册事件 108 | static void removefd(int epollfd, int fd) { 109 | epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, 0); 110 | close(fd); 111 | } 112 | 113 | static void sig_handler(int sig) { 114 | int save_errno = errno; 115 | int msg = sig; 116 | // 发送的sig的低位1字节,如果主机字节序是大端字节序,则发送的永远是0 117 | send(sig_pipefd[1], (char *)&msg, 1, 0); 118 | errno = save_errno; 119 | } 120 | 121 | static void addsig(int sig, void handler(int), bool restart = true) { 122 | struct sigaction sa; 123 | memset(&sa, '\0', sizeof(sa)); 124 | sa.sa_handler = handler; 125 | if (restart) { 126 | sa.sa_flags |= SA_RESTART; 127 | } 128 | sigfillset(&sa.sa_mask); 129 | assert(sigaction(sig, &sa, NULL) != -1); 130 | } 131 | 132 | // 进程池的构造函数,参数listenfd是监听socket,它必须在创建进程池前被创建 133 | // 否则子进程无法直接引用它,参数process_number指定进程池中子进程的数量 134 | template processpool::processpool(int listenfd, int process_number) 135 | : m_listenfd(listenfd), m_process_number(process_number), m_idx(-1), m_stop(false) { 136 | assert((process_number > 0) && (process_number <= MAX_PROCESS_NUMBER)); 137 | 138 | // 此处有bug,默认new失败会抛异常,而非返回空指针 139 | m_sub_process = new process[process_number]; 140 | assert(m_sub_process); 141 | 142 | // 创建process_number个子进程,并建立它们和父进程之间的管道 143 | for (int i = 0; i < process_number; ++i) { 144 | int ret = socketpair(PF_UNIX, SOCK_STREAM, 0, m_sub_process[i].m_pipefd); 145 | assert(ret == 0); 146 | 147 | m_sub_process[i].m_pid = fork(); 148 | assert(m_sub_process[i].m_pid >= 0); 149 | if (m_sub_process[i].m_pid > 0) { 150 | close(m_sub_process[i].m_pipefd[1]); 151 | continue; 152 | } else { 153 | close(m_sub_process[i].m_pipefd[0]); 154 | m_idx = i; 155 | break; 156 | } 157 | } 158 | } 159 | 160 | // 统一事件源 161 | template void processpool::setup_sig_pipe() { 162 | // 创建epoll事件监听表 163 | m_epollfd = epoll_create(5); 164 | assert(m_epollfd != -1); 165 | 166 | // 创建信号管道 167 | int ret = socketpair(PF_UNIX, SOCK_STREAM, 0, sig_pipefd); 168 | assert(ret != -1); 169 | 170 | setnonblocking(sig_pipefd[1]); 171 | addfd(m_epollfd, sig_pipefd[0]); 172 | 173 | // 设置信号处理函数 174 | addsig(SIGCHLD, sig_handler); 175 | addsig(SIGTERM, sig_handler); 176 | addsig(SIGINT, sig_handler); 177 | addsig(SIGPIPE, SIG_IGN); 178 | } 179 | 180 | // 父进程中m_idx值为-1,子进程中m_idx值大于等于0,我们据此判断要运行的是父进程代码还是子进程代码 181 | template void processpool::run() { 182 | if (m_idx != -1) { 183 | run_child(); 184 | return; 185 | } 186 | run_parent(); 187 | } 188 | 189 | template 190 | void processpool::run_child() { 191 | setup_sig_pipe(); 192 | 193 | // 每个子进程都通过其在进程池中的序号值m_idx找到与父进程通信的管道 194 | int pipefd = m_sub_process[m_idx].m_pipefd[1]; 195 | // 子进程需要监听管道文件描述符pipefd,因为父进程将通过它通知子进程accept新连接 196 | addfd(m_epollfd, pipefd); 197 | 198 | epoll_event events[MAX_EVENT_NUMBER]; 199 | // 此处有bug,默认new失败会抛异常,而非返回空指针 200 | T *users = new T[USER_PER_PROCESS]; 201 | assert(users); 202 | int number = 0; 203 | int ret = -1; 204 | 205 | while (!m_stop) { 206 | number = epoll_wait(m_epollfd, events, MAX_EVENT_NUMBER, -1); 207 | if ((number < 0) && (errno != EINTR)) { 208 | printf("epoll failure\n"); 209 | break; 210 | } 211 | 212 | for (int i = 0; i < number; ++i) { 213 | int sockfd = events[i].data.fd; 214 | if ((sockfd == pipefd) && (events[i].events & EPOLLIN)) { 215 | int client = 0; 216 | ret = recv(sockfd, (char *)&client, sizeof(client), 0); 217 | if (((ret < 0) && (errno != EAGAIN)) || ret == 0) { 218 | continue; 219 | } else { 220 | struct sockaddr_in client_address; 221 | socklen_t client_addrlength = sizeof(client_address); 222 | int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, 223 | &client_addrlength); 224 | if (connfd < 0) { 225 | printf("errno is: %d\n", errno); 226 | continue; 227 | } 228 | addfd(m_epollfd, connfd); 229 | // 模板类T必须实现init方法,以初始化一个客户连接,我们直接使用connfd来索引逻辑处理对象(T对象) 230 | // 这样效率较高,但比较占用空间(在子进程的堆内存中创建了65535个T对象) 231 | users[connfd].init(m_epollfd, connfd, client_address); 232 | } 233 | // 处理子进程接收到的信号 234 | } 235 | else if ((sockfd == sig_pipefd[0]) && (events[i].events & EPOLLIN)) 236 | { 237 | int sig; 238 | char signals[1024]; 239 | ret = recv(sig_pipefd[0], signals, sizeof(signals), 0); 240 | if (ret <= 0) 241 | { 242 | continue; 243 | } 244 | else 245 | { 246 | for (int i = 0; i < ret; ++i) 247 | { 248 | switch (signals[i]) 249 | { 250 | case SIGCHLD: 251 | pid_t pid; 252 | int stat; 253 | while ((pid = waitpid(-1, &stat, WNOHANG)) > 0) 254 | { 255 | continue; 256 | } 257 | break; 258 | 259 | case SIGTERM: 260 | case SIGINT: 261 | m_stop = true; 262 | break; 263 | 264 | default: 265 | break; 266 | } 267 | } 268 | } 269 | // 如果是客户发来的请求,则调用逻辑处理对象的process方法处理之 270 | } 271 | else if (events[i].events & EPOLLIN) 272 | { 273 | users[sockfd].process(); 274 | } else 275 | { 276 | continue; 277 | } 278 | } 279 | } 280 | 281 | delete[] users; 282 | users = NULL; 283 | close(pipefd); 284 | // 我们将关闭监听描述符的代码注释掉,以提醒读者:应由m_listenfd的创建者来关闭这个文件描述符 285 | // 即所谓的对象(如文件描述符或一段堆内存)应由创建函数来销毁 286 | // close(m_listenfd); 287 | close(m_epollfd); 288 | } 289 | 290 | template 291 | void processpool::run_parent() 292 | { 293 | setup_sig_pipe(); 294 | 295 | // 父进程监听m_listenfd 296 | addfd(m_epollfd, m_listenfd); 297 | 298 | epoll_event events[MAX_EVENT_NUMBER]; 299 | int sub_process_counter = 0; 300 | int new_conn = 1; 301 | int number = 0; 302 | int ret = -1; 303 | 304 | while (!m_stop) 305 | { 306 | number = epoll_wait(m_epollfd, events, MAX_EVENT_NUMBER, -1); 307 | if ((number < 0) && (errno != EINTR)) 308 | { 309 | printf("epoll failure\n"); 310 | break; 311 | } 312 | 313 | for (int i = 0; i < number; ++i) 314 | { 315 | int sockfd = events[i].data.fd; 316 | if (sockfd == m_listenfd) 317 | { 318 | // 如果有新连接到来,就用Round Robin方式将其分配给一个子进程处理 319 | int i = sub_process_counter; 320 | do 321 | { 322 | if (m_sub_process[i].m_pid != -1) 323 | { 324 | break; 325 | } 326 | i = (i + 1) % m_process_number; 327 | } 328 | while (i != sub_process_counter); 329 | 330 | if (m_sub_process[i].m_pid == -1) 331 | { 332 | m_stop = true; 333 | break; 334 | } 335 | sub_process_counter = (i + 1) % m_process_number; 336 | send(m_sub_process[i].m_pipefd[0], (char *)&new_conn, sizeof(new_conn), 0); 337 | printf("send request to child %d\n", i); 338 | } 339 | else if ((sockfd == sig_pipefd[0]) && (events[i].events & EPOLLIN)) 340 | { 341 | int sig; 342 | char signals[1024]; 343 | ret = recv(sig_pipefd[0], signals, sizeof(signals), 0); 344 | if (ret <= 0) 345 | { 346 | continue; 347 | } 348 | else 349 | { 350 | for (int i = 0; i < ret; ++i) 351 | { 352 | switch (signals[i]) 353 | { 354 | case SIGCHLD: 355 | { 356 | pid_t pid; 357 | int stat; 358 | while ((pid = waitpid(-1, &stat, WNOHANG)) > 0) 359 | { 360 | for (int i = 0; i < m_process_number; ++i) 361 | { 362 | // 如果进程池中第i个进程退出 363 | if (m_sub_process[i].m_pid == pid) 364 | { 365 | printf("child %d join\n", i); 366 | // 关闭与该子进程的通信管道 367 | close(m_sub_process[i].m_pipefd[0]); 368 | // 将该子进程的m_pid设为-1,表示该子进程已退出 369 | m_sub_process[i].m_pid = -1; 370 | } 371 | } 372 | } 373 | 374 | // 如果所有子进程都已退出,则父进程也退出 375 | m_stop = true; 376 | for (int i = 0; i < m_process_number; ++i) 377 | { 378 | if (m_sub_process[i].m_pid != -1) 379 | { 380 | m_stop = false; 381 | } 382 | } 383 | break; 384 | } 385 | case SIGTERM: 386 | case SIGINT: 387 | { 388 | // 如果父进程接收到终止信号,就杀死所有子进程,并等待它们全部结束 389 | // 通知子进程结束更好的方式是向父子进程之间的通信管道发送特殊数据 390 | printf("kill all the child now\n"); 391 | for (int i = 0; i < m_process_number; ++i) 392 | { 393 | int pid = m_sub_process[i].m_pid; 394 | if (pid != -1) 395 | { 396 | kill(pid, SIGTERM); 397 | } 398 | } 399 | break; 400 | } 401 | default: 402 | { 403 | break; 404 | } 405 | } 406 | } 407 | } 408 | } 409 | else 410 | { 411 | continue; 412 | } 413 | } 414 | } 415 | 416 | // close(m_listenfd); /* 由创建者关闭这个文件描述符 */ 417 | close( m_epollfd ); 418 | } 419 | 420 | #endif -------------------------------------------------------------------------------- /第5章 Linux网络编程基础API/实战 1:TCP通信实现(服务器端和客户端)/client: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhu619/Linux-high-performance-server-programming-Notebook/62ede31d2f82c18b81a9c52df7ce3d7ed2a78f87/第5章 Linux网络编程基础API/实战 1:TCP通信实现(服务器端和客户端)/client -------------------------------------------------------------------------------- /第5章 Linux网络编程基础API/实战 1:TCP通信实现(服务器端和客户端)/client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /* TCP客户端实现 */ 8 | int main() { 9 | 10 | // 1.创建套接字: AF_INET 表示使用 IPv4 地址,SOCK_STREAM 表示使用 TCP 连接。 11 | int fd = socket(AF_INET, SOCK_STREAM, 0); 12 | if(fd == -1) { 13 | perror("socket"); 14 | exit(-1); 15 | } 16 | 17 | // 2.连接服务器端: 设定服务器的 IP 地址和端口号(此处为 "192.168.177.146" 和 9999)。 18 | struct sockaddr_in serveraddr; 19 | serveraddr.sin_family = AF_INET; 20 | inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr.s_addr); 21 | serveraddr.sin_port = htons(8080); 22 | int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); 23 | 24 | if(ret == -1) { 25 | perror("connect"); 26 | exit(-1); 27 | } 28 | 29 | 30 | // 3.通信 31 | char recvBuf[1024] = {0}; 32 | while(1) { 33 | 34 | char * data = "hello,i am client"; 35 | // 通过 write 函数发送消息 "hello,i am client" 到服务器。 36 | write(fd, data , strlen(data)); 37 | 38 | sleep(1); 39 | 40 | int len = read(fd, recvBuf, sizeof(recvBuf)); 41 | if(len == -1) { 42 | perror("read"); 43 | exit(-1); 44 | } else if(len > 0) { 45 | printf("recv server data : %s\n", recvBuf); 46 | } else if(len == 0) { 47 | // 如果 read 返回 0,表示服务器关闭了连接,客户端程序将结束循环并关闭连接。 48 | printf("server closed..."); 49 | break; 50 | } 51 | 52 | } 53 | 54 | // 关闭连接 55 | close(fd); 56 | 57 | return 0; 58 | } 59 | 60 | -------------------------------------------------------------------------------- /第5章 Linux网络编程基础API/实战 1:TCP通信实现(服务器端和客户端)/server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhu619/Linux-high-performance-server-programming-Notebook/62ede31d2f82c18b81a9c52df7ce3d7ed2a78f87/第5章 Linux网络编程基础API/实战 1:TCP通信实现(服务器端和客户端)/server -------------------------------------------------------------------------------- /第5章 Linux网络编程基础API/实战 1:TCP通信实现(服务器端和客户端)/server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /* TCP服务器端实现 */ 8 | int main() { 9 | 10 | /* 11 | * 1.创建socket(用于监听的套接字): 12 | * 使用 socket 函数创建一个套接字 listen_fd 13 | * AF_INET: 表示使用 IPv4 协议 14 | * SOCK_STREAM: 表示使用 TCP 连接。 15 | */ 16 | int listen_fd = socket(AF_INET, SOCK_STREAM, 0); 17 | 18 | if(listen_fd == -1) { 19 | perror("socket"); 20 | exit(-1); 21 | } 22 | 23 | /* 24 | * 2.绑定/命名 socket: 25 | * struct sockaddr_in 用于指定服务器的地址和端口。 26 | * INADDR_ANY 让服务器绑定到所有可用的网络接口(即可以接受任何 IP 地址的连接)。 27 | * 使用 bind 函数将套接字与指定的 IP 地址和端口绑定。 28 | * 如果 bind 返回 -1,表示绑定失败,输出错误信息并退出程序。 29 | */ 30 | struct sockaddr_in saddr; 31 | saddr.sin_family = AF_INET; 32 | // inet_pton(AF_INET, "192.168.193.128", &saddr.sin_addr.s_addr); 33 | saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0 34 | saddr.sin_port = htons(8080); 35 | int ret = bind(listen_fd, (struct sockaddr *)&saddr, sizeof(saddr)); 36 | 37 | if(ret == -1) { 38 | perror("bind"); 39 | exit(-1); 40 | } 41 | 42 | /* 43 | * 3.监听连接 44 | * listen 函数使套接字进入监听状态,准备接收来自客户端的连接请求。 45 | * 第二个参数 8 指定了连接队列的最大长度,即最多可以有 8 个连接请求排队等待处理。 46 | * 如果 listen 返回 -1,表示监听失败,输出错误信息并退出程序。 47 | */ 48 | ret = listen(listen_fd, 8); 49 | if(ret == -1) { 50 | perror("listen"); 51 | exit(-1); 52 | } 53 | 54 | /* 55 | * 4.接收客户端连接 56 | * 使用 accept 函数接收客户端的连接请求,accept 返回一个新的套接字 cfd,用于与客户端通信。 57 | * clientaddr 用于存储客户端的地址信息。 58 | * 如果 accept 返回 -1,表示接收连接失败,输出错误信息并退出程序。 59 | */ 60 | struct sockaddr_in clientaddr; 61 | socklen_t len = sizeof(clientaddr); 62 | int cfd = accept(listen_fd, (struct sockaddr *)&clientaddr, &len); 63 | 64 | if(cfd == -1) { 65 | perror("accept"); 66 | exit(-1); 67 | } 68 | 69 | /* 70 | * 5. 输出客户端的信息 71 | * inet_ntop 函数将客户端的 IP 地址从网络字节序转换为点分十进制字符串格式,并输出。 72 | * ntohs 函数将客户端的端口号从网络字节序转换为主机字节序,并输出。 73 | */ 74 | char clientIP[16]; 75 | inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP)); 76 | unsigned short clientPort = ntohs(clientaddr.sin_port); 77 | printf("client ip is %s, port is %d\n", clientIP, clientPort); 78 | 79 | // 6.通信 80 | char recvBuf[1024] = {0}; 81 | while(1) { 82 | 83 | // 获取客户端的数据 84 | int num = read(cfd, recvBuf, sizeof(recvBuf)); 85 | if(num == -1) { 86 | perror("read"); 87 | exit(-1); 88 | } else if(num > 0) { 89 | printf("recv client data : %s\n", recvBuf); 90 | } else if(num == 0) { 91 | // 表示客户端断开连接 92 | printf("clinet closed..."); 93 | break; 94 | } 95 | 96 | char * data = "hello,i am server"; 97 | // 给客户端发送数据 98 | write(cfd, data, strlen(data)); 99 | } 100 | 101 | // 7. 关闭文件描述符 102 | close(cfd); 103 | close(listen_fd); 104 | 105 | return 0; 106 | } 107 | -------------------------------------------------------------------------------- /第5章 Linux网络编程基础API/实战 2:使用 MSG_OOB 选项发送带外数据/TCPClientSocket.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include // 用于抛出异常 8 | 9 | class TCPClientSocket { 10 | private: 11 | int sockfd; 12 | struct sockaddr_in server_addr; 13 | 14 | public: 15 | TCPClientSocket(const char* server_ip, int server_port) { 16 | sockfd = socket(PF_INET, SOCK_STREAM, 0); 17 | if (sockfd < 0) { 18 | throw std::runtime_error("Failed to create socket"); 19 | } 20 | 21 | memset(&server_addr, 0, sizeof(server_addr)); 22 | server_addr.sin_family = AF_INET; 23 | server_addr.sin_port = htons(server_port); 24 | if (inet_aton(server_ip, &server_addr.sin_addr) == 0) { 25 | throw std::runtime_error("Invalid server IP address"); 26 | } 27 | } 28 | 29 | ~TCPClientSocket() { 30 | close(sockfd); 31 | } 32 | 33 | // 连接到服务器 34 | void connectToServer() { 35 | if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { 36 | throw std::runtime_error("Failed to connect to server"); 37 | } 38 | } 39 | 40 | // 发送数据(示例方法,需要具体实现) 41 | // void sendData(const char* data, size_t length); 42 | 43 | // 接收数据(示例方法,需要具体实现) 44 | // ssize_t receiveData(char* buffer, size_t buffer_size); 45 | 46 | // 获取socket文件描述符 47 | int getSocketFd() const { 48 | return sockfd; 49 | } 50 | }; -------------------------------------------------------------------------------- /第5章 Linux网络编程基础API/实战 2:使用 MSG_OOB 选项发送带外数据/TCPServerSocket.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // 创建IPv4 服务器端 socket 9 | class TCPServerSocket{ 10 | 11 | private: 12 | struct sockaddr_in address; 13 | int sockfd; 14 | 15 | public: 16 | TCPServerSocket(const char *ip, int port){ 17 | sockfd = socket( PF_INET, SOCK_STREAM, 0 ); 18 | assert(sockfd >= 0); 19 | memset(&address, 0, sizeof(address)); 20 | address.sin_family = AF_INET; 21 | address.sin_port = htons(port); 22 | assert(inet_aton(ip, &address.sin_addr) == 1); 23 | } 24 | ~TCPServerSocket() { 25 | close(sockfd); 26 | } 27 | int bindSocket(); 28 | int listenSocket(int backlog); 29 | int acceptConnection(struct sockaddr_in* client, socklen_t* client_addrlength); 30 | int getSocketFd() const { return sockfd; } // 获取socket文件描述符 31 | }; 32 | 33 | // 命名socket 34 | int TCPServerSocket::bindSocket(){ 35 | return bind(sockfd, (struct sockaddr*)&address, sizeof(address)); 36 | } 37 | 38 | // 监听socket 39 | int TCPServerSocket::listenSocket(int backlog){ 40 | return listen(sockfd, backlog); 41 | } 42 | 43 | // 接受连接 44 | int TCPServerSocket::acceptConnection(struct sockaddr_in* client, socklen_t* client_addrlength){ 45 | // 注意:这里应该是传递client的指针的地址给accept 46 | int newsockfd = accept(sockfd, (struct sockaddr*)client, client_addrlength); 47 | if (newsockfd < 0) { 48 | // 可以选择抛出异常或返回错误码 49 | return -1; // 或者 throw std::runtime_error("Failed to accept connection"); 50 | } 51 | return newsockfd; // 返回新的socket文件描述符 52 | } 53 | 54 | // 注意:在实际使用中,您可能还需要检查bind和listen的返回值以确保它们成功执行。 55 | // 此外,对于accept函数,通常我们会检查它的返回值以确认连接是否被成功接受。 56 | // 如果需要处理多个客户端连接,您可能需要将accept调用放在一个循环中,或者使用多线程/多进程来处理每个连接。 57 | -------------------------------------------------------------------------------- /第5章 Linux网络编程基础API/实战 2:使用 MSG_OOB 选项发送带外数据/test_oob_recv.cpp: -------------------------------------------------------------------------------- 1 | #include "TCPServerSocket.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #define BUF_SIZE 1024 7 | 8 | // 服务器接收TCP报文 9 | int main(int argc, char *argv[]){ 10 | if(argc <= 2){ 11 | std::cout << "Usage: " << basename(argv[0]) << " ip_address port_number" << std::endl; 12 | return 1; 13 | } 14 | 15 | const char* ip = argv[1]; // ip地址 16 | int port = atoi(argv[2]); // 端口号 17 | 18 | TCPServerSocket server(ip, port); 19 | 20 | // 绑定socket到指定的地址和端口 21 | if (server.bindSocket() < 0) { 22 | throw std::runtime_error("Failed to bind socket"); 23 | } 24 | 25 | // 开始监听连接,backlog参数指定了可以挂起(等待被accept)的连接数 26 | if (server.listenSocket(5) < 0) { 27 | throw std::runtime_error("Failed to listen on socket"); 28 | } 29 | 30 | // 等待并接受一个客户端连接 31 | struct sockaddr_in client_addr; 32 | socklen_t client_addrlen = sizeof(client_addr); 33 | int client_sockfd = server.acceptConnection(&client_addr, &client_addrlen); 34 | 35 | if (client_sockfd < 0) { 36 | throw std::runtime_error("Failed to accept connection"); 37 | } 38 | 39 | // 可以使用client_sockfd来与客户端进行通信 40 | 41 | char buffer[BUF_SIZE]; 42 | memset(buffer, '\0', BUF_SIZE); 43 | int ret = recv(client_sockfd, buffer, BUF_SIZE - 1, 0); 44 | std::cout << "got " << ret << " bytes of normal data '" << buffer << "'" << std::endl; 45 | 46 | memset(buffer, '\0', BUF_SIZE); 47 | ret = recv(client_sockfd, buffer, BUF_SIZE - 1, MSG_OOB); 48 | std::cout << "got " << ret << " bytes of odd data '" << buffer << "'" << std::endl; 49 | 50 | memset(buffer, '\0', BUF_SIZE); 51 | ret = recv(client_sockfd, buffer, BUF_SIZE - 1, 0); 52 | std::cout << "got " << ret << " bytes of normal data '" << buffer << "'" << std::endl; 53 | 54 | close(client_sockfd); 55 | 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /第5章 Linux网络编程基础API/实战 2:使用 MSG_OOB 选项发送带外数据/test_oob_send.cpp: -------------------------------------------------------------------------------- 1 | #include "TCPClientSocket.h" 2 | #include 3 | #include 4 | 5 | // 客户端发送TCP报文 6 | int main(int argc, char *argv[]){ 7 | if (argc <= 2){ 8 | std::cout << "usage: " << basename(argv[0]) << " ip_address port_number" << std::endl; 9 | return 1; 10 | } 11 | 12 | const char* ip = argv[1]; // ip地址 13 | int port = atoi(argv[2]); // 端口号 14 | 15 | TCPClientSocket client(ip, port); 16 | 17 | client.connectToServer(); // 连接到服务器端 18 | 19 | const char* oob_data = "abc"; 20 | const char* normal_data = "123"; 21 | 22 | int sockfd = client.getSocketFd(); // 获取文件描述符 23 | 24 | send(sockfd, normal_data, strlen(normal_data), 0); 25 | sleep(1); // 加入延时,确保带外数据发送 26 | send(sockfd, oob_data, strlen(oob_data), MSG_OOB); 27 | sleep(1); // 再次加入延时,确保服务器有时间处理带外数据 28 | send(sockfd, normal_data, strlen(normal_data), 0); 29 | 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /第6章 高级IO函数/实战3:实现一个简单的CGI服务器/cgi_server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhu619/Linux-high-performance-server-programming-Notebook/62ede31d2f82c18b81a9c52df7ce3d7ed2a78f87/第6章 高级IO函数/实战3:实现一个简单的CGI服务器/cgi_server -------------------------------------------------------------------------------- /第6章 高级IO函数/实战3:实现一个简单的CGI服务器/cgi_server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | int main(int argc, char *argv[]) { 13 | if (argc <= 2) { 14 | printf("usage: %s ip_address port_number\n", basename(argv[0])); 15 | return 1; 16 | } 17 | const char *ip = argv[1]; 18 | int port = atoi(argv[2]); 19 | 20 | struct sockaddr_in address; 21 | bzero(&address, sizeof(address)); 22 | address.sin_family = AF_INET; 23 | inet_pton(AF_INET, ip, &address.sin_addr); 24 | address.sin_port = htons(port); 25 | 26 | // 创建套接字 27 | int sock = socket(PF_INET, SOCK_STREAM, 0); 28 | assert(sock >= 0); 29 | 30 | // 绑定套接字 31 | int ret = bind(sock, (struct sockaddr *)&address, sizeof(address)); 32 | assert(ret != -1); 33 | 34 | // 监听套接字,最大等待连接队列的长度为5 35 | ret = listen(sock, 5); 36 | assert(ret != -1); 37 | 38 | struct sockaddr_in client; 39 | socklen_t client_addrlength = sizeof(client); 40 | 41 | // 接受客户端连接 42 | int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength); 43 | if (connfd < 0) { 44 | printf("errno is: %d\n", errno); 45 | } else { 46 | // 先关闭标准输出文件描述符STDOUT_FILENO,其值为1 47 | close(STDOUT_FILENO); 48 | // 复制socket文件描述符connfd,由于dup函数总是返回系统中最小的可用文件描述符 49 | // 因此dup参数实际返回的是1,即之前关闭的标准输出文件描述符的值 50 | // 这样服务器输出到标准输出的内容会直接发送到与客户连接对应的socket上 51 | dup(connfd); 52 | printf("abcd\n"); 53 | close(connfd); 54 | } 55 | 56 | close(sock); 57 | return 0; 58 | } -------------------------------------------------------------------------------- /第6章 高级IO函数/实战3:实现一个简单的CGI服务器/client: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhu619/Linux-high-performance-server-programming-Notebook/62ede31d2f82c18b81a9c52df7ce3d7ed2a78f87/第6章 高级IO函数/实战3:实现一个简单的CGI服务器/client -------------------------------------------------------------------------------- /第6章 高级IO函数/实战3:实现一个简单的CGI服务器/client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /* TCP客户端实现 */ 8 | int main(int argc, char *argv[]) { 9 | 10 | // 1.创建套接字: AF_INET 表示使用 IPv4 地址,SOCK_STREAM 表示使用 TCP 连接。 11 | int fd = socket(AF_INET, SOCK_STREAM, 0); 12 | if(fd == -1) { 13 | perror("socket"); 14 | exit(-1); 15 | } 16 | 17 | // 2.连接服务器端: 设定服务器的 IP 地址和端口号(此处为 "192.168.177.146" 和 9999)。 18 | struct sockaddr_in serveraddr; 19 | serveraddr.sin_family = AF_INET; 20 | inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr.s_addr); 21 | serveraddr.sin_port = htons(8080); 22 | int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); 23 | 24 | if(ret == -1) { 25 | perror("connect"); 26 | exit(-1); 27 | } 28 | 29 | 30 | // 3.通信 31 | char recvBuf[1024] = {0}; 32 | while(1) { 33 | 34 | char * data = "hello,i am client"; 35 | // 通过 write 函数发送消息 "hello,i am client" 到服务器。 36 | write(fd, data , strlen(data)); 37 | 38 | sleep(1); 39 | 40 | int len = read(fd, recvBuf, sizeof(recvBuf)); 41 | if(len == -1) { 42 | perror("read"); 43 | exit(-1); 44 | } else if(len > 0) { 45 | printf("recv server data : %s\n", recvBuf); 46 | } else if(len == 0) { 47 | // 如果 read 返回 0,表示服务器关闭了连接,客户端程序将结束循环并关闭连接。 48 | printf("server closed..."); 49 | break; 50 | } 51 | 52 | } 53 | 54 | // 关闭连接 55 | close(fd); 56 | 57 | return 0; 58 | } 59 | 60 | -------------------------------------------------------------------------------- /第7章 Linux服务器程序规范/实战 4:测试 UID 和 EUID 的区别/test_uid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhu619/Linux-high-performance-server-programming-Notebook/62ede31d2f82c18b81a9c52df7ce3d7ed2a78f87/第7章 Linux服务器程序规范/实战 4:测试 UID 和 EUID 的区别/test_uid -------------------------------------------------------------------------------- /第7章 Linux服务器程序规范/实战 4:测试 UID 和 EUID 的区别/test_uid.cpp: -------------------------------------------------------------------------------- 1 | #include // (Unix Standard Definitions)头文件提供对POSIX操作系统API的访问,主要用于提供对POSIX操作系统API的函数原型、符号常量等。 2 | #include // (Standard Input Output Header)头文件提供了进行输入和输出操作的函数。 3 | 4 | int main(){ 5 | uid_t uid = getuid(); 6 | uid_t euid = geteuid(); 7 | 8 | printf("userid :%d , euid: %d \n", uid, euid); 9 | return 0; 10 | } -------------------------------------------------------------------------------- /第7章 Linux服务器程序规范/实战 5:使用 setsid() 创建一个新的会话和进程组/create_sid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhu619/Linux-high-performance-server-programming-Notebook/62ede31d2f82c18b81a9c52df7ce3d7ed2a78f87/第7章 Linux服务器程序规范/实战 5:使用 setsid() 创建一个新的会话和进程组/create_sid -------------------------------------------------------------------------------- /第7章 Linux服务器程序规范/实战 5:使用 setsid() 创建一个新的会话和进程组/create_sid.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | pid_t pid; 7 | 8 | // 创建一个子进程 9 | pid = fork(); 10 | printf("Child Process ID: %d\n", pid); 11 | 12 | if (pid < 0) { 13 | perror("fork failed"); 14 | exit(EXIT_FAILURE); 15 | } else if (pid > 0) { 16 | // 父进程退出,使子进程成为孤儿进程 17 | exit(EXIT_SUCCESS); 18 | } 19 | 20 | // 子进程开始执行,创建新的会话 21 | pid_t sid = setsid(); 22 | if (sid < 0) { 23 | perror("setsid failed"); 24 | exit(EXIT_FAILURE); 25 | } 26 | 27 | // 此时,进程已经成为新的会话和进程组的首领 28 | printf("New session ID: %d\n", sid); 29 | 30 | // 继续执行其他代码... 31 | 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /第9章 IO 复用/实战 6:select 调用同时接收普通数据和带外数据/TCPClientSocket.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include // 用于抛出异常 8 | 9 | class TCPClientSocket { 10 | private: 11 | int sockfd; 12 | struct sockaddr_in server_addr; 13 | 14 | public: 15 | TCPClientSocket(const char* server_ip, int server_port) { 16 | sockfd = socket(PF_INET, SOCK_STREAM, 0); 17 | if (sockfd < 0) { 18 | throw std::runtime_error("Failed to create socket"); 19 | } 20 | 21 | memset(&server_addr, 0, sizeof(server_addr)); 22 | server_addr.sin_family = AF_INET; 23 | server_addr.sin_port = htons(server_port); 24 | if (inet_aton(server_ip, &server_addr.sin_addr) == 0) { 25 | throw std::runtime_error("Invalid server IP address"); 26 | } 27 | } 28 | 29 | ~TCPClientSocket() { 30 | close(sockfd); 31 | } 32 | 33 | // 连接到服务器 34 | void connectToServer() { 35 | if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { 36 | throw std::runtime_error("Failed to connect to server"); 37 | } 38 | } 39 | 40 | // 发送数据(示例方法,需要具体实现) 41 | // void sendData(const char* data, size_t length); 42 | 43 | // 接收数据(示例方法,需要具体实现) 44 | // ssize_t receiveData(char* buffer, size_t buffer_size); 45 | 46 | // 获取socket文件描述符 47 | int getSocketFd() const { 48 | return sockfd; 49 | } 50 | }; -------------------------------------------------------------------------------- /第9章 IO 复用/实战 6:select 调用同时接收普通数据和带外数据/TCPServerSocket.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // 创建IPv4 服务器端 socket 9 | class TCPServerSocket{ 10 | 11 | private: 12 | struct sockaddr_in address; 13 | int sockfd; 14 | 15 | public: 16 | TCPServerSocket(const char *ip, int port){ 17 | sockfd = socket( PF_INET, SOCK_STREAM, 0 ); 18 | assert(sockfd >= 0); 19 | memset(&address, 0, sizeof(address)); 20 | address.sin_family = AF_INET; 21 | address.sin_port = htons(port); 22 | assert(inet_aton(ip, &address.sin_addr) == 1); 23 | } 24 | ~TCPServerSocket() { 25 | close(sockfd); 26 | } 27 | int bindSocket(); 28 | int listenSocket(int backlog); 29 | int acceptConnection(struct sockaddr_in* client, socklen_t* client_addrlength); 30 | int getSocketFd() const { return sockfd; } // 获取socket文件描述符 31 | }; 32 | 33 | // 命名socket 34 | int TCPServerSocket::bindSocket(){ 35 | return bind(sockfd, (struct sockaddr*)&address, sizeof(address)); 36 | } 37 | 38 | // 监听socket 39 | int TCPServerSocket::listenSocket(int backlog){ 40 | return listen(sockfd, backlog); 41 | } 42 | 43 | // 接受连接 44 | int TCPServerSocket::acceptConnection(struct sockaddr_in* client, socklen_t* client_addrlength){ 45 | // 注意:这里应该是传递client的指针的地址给accept 46 | int newsockfd = accept(sockfd, (struct sockaddr*)client, client_addrlength); 47 | if (newsockfd < 0) { 48 | // 可以选择抛出异常或返回错误码 49 | return -1; // 或者 throw std::runtime_error("Failed to accept connection"); 50 | } 51 | return newsockfd; // 返回新的socket文件描述符 52 | } 53 | 54 | // 注意:在实际使用中,您可能还需要检查bind和listen的返回值以确保它们成功执行。 55 | // 此外,对于accept函数,通常我们会检查它的返回值以确认连接是否被成功接受。 56 | // 如果需要处理多个客户端连接,您可能需要将accept调用放在一个循环中,或者使用多线程/多进程来处理每个连接。 57 | -------------------------------------------------------------------------------- /第9章 IO 复用/实战 6:select 调用同时接收普通数据和带外数据/client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /* TCP客户端实现 */ 8 | int main() { 9 | 10 | // 1.创建套接字: AF_INET 表示使用 IPv4 地址,SOCK_STREAM 表示使用 TCP 连接。 11 | int fd = socket(AF_INET, SOCK_STREAM, 0); 12 | if(fd == -1) { 13 | perror("socket"); 14 | exit(-1); 15 | } 16 | 17 | // 2.连接服务器端: 设定服务器的 IP 地址和端口号(此处为 "192.168.177.146" 和 9999)。 18 | struct sockaddr_in serveraddr; 19 | serveraddr.sin_family = AF_INET; 20 | inet_pton(AF_INET, "10.1.1.161", &serveraddr.sin_addr.s_addr); 21 | serveraddr.sin_port = htons(9999); 22 | int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); 23 | 24 | if(ret == -1) { 25 | perror("connect"); 26 | exit(-1); 27 | } 28 | 29 | 30 | // 3.通信 31 | char recvBuf[1024] = {0}; 32 | while(1) { 33 | 34 | char * data = "hello,i am client"; 35 | // 通过 write 函数发送消息 "hello,i am client" 到服务器。 36 | write(fd, data , strlen(data)); 37 | 38 | sleep(1); 39 | 40 | int len = read(fd, recvBuf, sizeof(recvBuf)); 41 | if(len == -1) { 42 | perror("read"); 43 | exit(-1); 44 | } else if(len > 0) { 45 | printf("recv server data : %s\n", recvBuf); 46 | } else if(len == 0) { 47 | // 如果 read 返回 0,表示服务器关闭了连接,客户端程序将结束循环并关闭连接。 48 | printf("server closed..."); 49 | break; 50 | } 51 | 52 | } 53 | 54 | // 关闭连接 55 | close(fd); 56 | 57 | return 0; 58 | } 59 | 60 | -------------------------------------------------------------------------------- /第9章 IO 复用/实战 6:select 调用同时接收普通数据和带外数据/select.cpp: -------------------------------------------------------------------------------- 1 | #include "TCPServerSocket.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define BUFF_SIZE 1024 9 | 10 | int main(int argc, char* argv[]){ 11 | 12 | if(argc <= 2){ 13 | printf("Usage: %s ip_address port_number.\n", basename(argv[0])); 14 | } 15 | 16 | const char* ip = argv[1]; // ip地址 17 | int port = atoi(argv[2]); // 端口号 18 | 19 | TCPServerSocket server(ip, port); 20 | 21 | // 绑定socket到指定的地址和端口 22 | if (server.bindSocket() < 0) { 23 | throw std::runtime_error("Failed to bind socket"); 24 | } 25 | 26 | // 开始监听连接,backlog参数指定了可以挂起(等待被accept)的连接数 27 | if (server.listenSocket(5) < 0) { 28 | throw std::runtime_error("Failed to listen on socket"); 29 | } 30 | 31 | // 等待并接受一个客户端连接 32 | struct sockaddr_in client_addr; 33 | socklen_t client_addrlen = sizeof(client_addr); 34 | int client_sockfd = server.acceptConnection(&client_addr, &client_addrlen); 35 | 36 | if (client_sockfd < 0) { 37 | throw std::runtime_error("Failed to accept connection"); 38 | } 39 | 40 | char buf[BUFF_SIZE]; 41 | fd_set read_fds; 42 | fd_set exception_fds; 43 | FD_ZERO(&read_fds); /* 清除 fd_set 的所有位 */ 44 | FD_ZERO(&exception_fds); 45 | 46 | while(1) 47 | { 48 | memset(buf, '\0', BUFF_SIZE); 49 | /* 每次调用select之前都要重新设置文件描述符,因为事件发生之后,文件描述符集合会被内核修改 */ 50 | FD_SET(client_sockfd, &read_fds); 51 | FD_SET(client_sockfd, &exception_fds); 52 | 53 | int ret = select(client_sockfd + 1, &read_fds, NULL, &exception_fds, NULL); 54 | if(ret < 0) { 55 | printf("Selection failure.\n"); 56 | break; 57 | } 58 | /* 处理可读事件,对于多个文件描述符,需要循环询问是否可读 */ 59 | if (FD_ISSET(client_sockfd, &read_fds)) { 60 | ret = recv(client_sockfd, buf, BUFF_SIZE - 1, 0); 61 | if(ret <= 0) { 62 | if (ret == 0) { printf("Client disconnected.\n"); } 63 | else { printf("Receive error.\n"); } 64 | break; 65 | } 66 | printf("Read: get %d bytes of normal data: %s\n", ret, buf); 67 | } 68 | /* 处理异常事件, 采用带MSG_OOB标志的recv函数读取带外数据 */ 69 | else if (FD_ISSET(client_sockfd, &exception_fds)){ 70 | ret = recv(client_sockfd, buf, BUFF_SIZE - 1, MSG_OOB); 71 | if(ret <= 0) { 72 | if (ret == 0) { printf("Client disconnected.\n"); } 73 | else { printf("Receive error.\n"); } 74 | break; 75 | } 76 | printf("Exception: get %d bytes of oob data: %s\n", ret, buf); 77 | } 78 | } 79 | close(client_sockfd); 80 | return 0; 81 | } --------------------------------------------------------------------------------