├── 1. 客户端自动发送固定信息 ├── client01 ├── client01.c ├── server01 └── server01.c ├── 10. 代码清单-IO复用 ├── CODELIST9-1.c ├── CODELIST9-3(ET).c ├── CODELIST9-3(LT).c └── CODELIST9-5.c ├── 11. 代码清单9-6和9-7 聊天室程序 ├── CHAT_CLIENT.c └── CHAT_SERVER.c ├── 12. 代码清单11-2和11-3及11-4 链表定时器, 处理非活动连接 ├── lst_timer.cpp ├── main.cpp └── util_lsmg.h ├── 13. 猜数字游戏客户端以及服务器实现桌位加入 ├── client.cpp └── server.cpp ├── 14. 进程同步练习 └── main.cpp ├── 15. 进程间通信 ├── main.cpp ├── read_msg.cpp └── write_msg.cpp ├── 16.线程同步 └── locker.cpp ├── 17.简单http服务器 修改版 支持大文件发送 ├── httpconnection.cpp ├── httpconnection.h ├── locker.h ├── main.cpp ├── threadpool.cpp └── threadpool.h ├── 2. 伪双工TCP ├── client.c └── server.c ├── 3. 伪双工UDP ├── client.c └── server.c ├── 4. 代码清单5-12 访问daytime服务 └── server.c ├── 5. 代码清单6-3 用sendfile函数传输文件 └── server.c ├── 6. 代码清单6-4 用splice函数实现的回射服务器 └── server.c ├── 7. 基于TCP的贪吃蛇服务器 ├── server.c └── snake.c ├── 8. 服务器模型-CS模型 └── server.c ├── 9. 代码清单8-3 HTTP请求的读取和分析 └── server.c └── README.md /1. 客户端自动发送固定信息/client01: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiganFish/Notes-HighPerformanceLinuxServerProgramming/d1449f5974905d5464f8bee644504413da78208f/1. 客户端自动发送固定信息/client01 -------------------------------------------------------------------------------- /1. 客户端自动发送固定信息/client01.c: -------------------------------------------------------------------------------- 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 | 14 | assert(argc > 2); 15 | 16 | const char* ip = argv[1]; 17 | 18 | int port = atoi(argv[2]); 19 | 20 | struct sockaddr_in server_address; 21 | memset(&server_address, 0, sizeof(server_address)); 22 | inet_pton(AF_INET, ip, &server_address.sin_addr); 23 | server_address.sin_family = AF_INET; 24 | server_address.sin_port = htons(port); 25 | 26 | int sockfd = socket(PF_INET, SOCK_STREAM, 0); 27 | assert(sockfd >= 0); 28 | 29 | if (connect(sockfd, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) { 30 | printf("connect failed\nerror: %d, msg: %s\n", errno, strerror(errno)); 31 | } else { 32 | 33 | const char* oob_data = "abc"; 34 | const char* normal_data = "123"; 35 | send(sockfd, normal_data, strlen(normal_data), 0); 36 | send(sockfd, oob_data, strlen(oob_data), MSG_OOB); 37 | send(sockfd, normal_data, strlen(normal_data), 0); 38 | } 39 | 40 | close(sockfd); 41 | return 0; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /1. 客户端自动发送固定信息/server01: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiganFish/Notes-HighPerformanceLinuxServerProgramming/d1449f5974905d5464f8bee644504413da78208f/1. 客户端自动发送固定信息/server01 -------------------------------------------------------------------------------- /1. 客户端自动发送固定信息/server01.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define BUF_SIZE 1024 12 | 13 | int main(int argc, char* argv[]) { 14 | 15 | assert(argc > 2); 16 | const char* ip = argv[1]; 17 | int port = atoi(argv[2]); 18 | 19 | struct sockaddr_in address; 20 | memset(&address, 0, sizeof(address)); 21 | inet_pton(AF_INET, ip, &address.sin_addr); 22 | address.sin_port = htons(port); 23 | address.sin_family = AF_INET; 24 | 25 | int sock = socket(PF_INET, SOCK_STREAM, 0); 26 | assert(sock != -1); 27 | 28 | int ret = bind(sock, (struct sockaddr*)&address, sizeof(address)); 29 | assert(ret != -1); 30 | 31 | ret = listen(sock, 5); 32 | assert(ret != -1); 33 | 34 | struct sockaddr_in client; 35 | socklen_t client_addrlength = sizeof(client); 36 | 37 | int connfd = accept(sock, (struct sockaddr*)&client, &client_addrlength); 38 | if(connfd < 0) { 39 | printf("error"); 40 | } else { 41 | char buffer[BUF_SIZE]; 42 | 43 | memset(buffer, '\0', BUF_SIZE); 44 | ret = recv(connfd, buffer, BUF_SIZE - 1, 0); 45 | printf("got %d bytes of normal data '%s'\n", ret, buffer); 46 | 47 | memset(buffer, '\0', BUF_SIZE); 48 | ret = recv(connfd, buffer, BUF_SIZE - 1, MSG_OOB); 49 | printf("got %d bytes of obb data '%s'\n", ret, buffer); 50 | 51 | memset(buffer, '\0', BUF_SIZE); 52 | ret = recv(connfd, buffer, BUF_SIZE - 1, 0); 53 | printf("got %d bytes of normal data '%s'\n", ret, buffer); 54 | 55 | 56 | close(connfd); 57 | } 58 | 59 | close(sock); 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /10. 代码清单-IO复用/CODELIST9-1.c: -------------------------------------------------------------------------------- 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 | 14 | if(argc <= 2) { 15 | printf("Wrong number of parameters\n"); 16 | return 1; 17 | } 18 | 19 | int port = atoi(argv[2]); 20 | char* ip = argv[1]; 21 | 22 | struct sockaddr_in address; 23 | address.sin_port = htons(port); 24 | address.sin_family = AF_INET; 25 | inet_pton(AF_INET, ip, &address.sin_addr); 26 | 27 | int listenfd = socket(PF_INET, SOCK_STREAM, 0); 28 | assert(listenfd >=0); 29 | 30 | int ret = -1; 31 | while(ret == -1) { 32 | address.sin_port = htons(++port); 33 | ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address)); 34 | printf("The port is %d\n", port); 35 | } 36 | 37 | ret = listen(listenfd, 5); 38 | assert(ret != -1); 39 | 40 | struct sockaddr_in client_address; 41 | socklen_t client_addr_length = sizeof(client_address); 42 | int connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addr_length); 43 | if(connfd <0) { 44 | printf("The error code is%d, error is%s\n", errno, strerror(errno)); 45 | close(listenfd); 46 | } 47 | 48 | char buf[1024]; 49 | fd_set read_fds, exception_fds; 50 | FD_ZERO(&read_fds); 51 | FD_ZERO(&exception_fds); 52 | 53 | while(1) { 54 | 55 | memset(buf, '\0', sizeof(buf)); 56 | FD_SET(connfd, &read_fds); 57 | FD_SET(connfd, &exception_fds); 58 | ret = select(connfd + 1, &read_fds, NULL, &exception_fds, NULL); 59 | if(ret < 0) { 60 | printf("select error\n"); 61 | break; 62 | } 63 | 64 | if(FD_ISSET(connfd, &read_fds)) { 65 | ret = recv(connfd, buf, sizeof(buf) - 1, 0); 66 | if(ret <= 0) { 67 | break; 68 | } 69 | 70 | printf("get %d bytes of normal data: %s\n", ret, buf); 71 | } 72 | 73 | if(FD_ISSET(connfd, &exception_fds)) { 74 | ret = recv(connfd, buf, sizeof(buf) - 1, 0); 75 | if(ret <= 0) { 76 | break; 77 | } 78 | 79 | printf("get %d bytes of obb data: %s\n", ret, buf); 80 | } 81 | } 82 | close(connfd); 83 | close(listenfd); 84 | return 0; 85 | 86 | } 87 | -------------------------------------------------------------------------------- /10. 代码清单-IO复用/CODELIST9-3(ET).c: -------------------------------------------------------------------------------- 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 | 15 | #define MAX_EVENT_NUMBER 1024 16 | #define BUFFER_SIZE 10 17 | 18 | int setnonblocking(int fd) { 19 | int old_option = fcntl(fd, F_GETFL); 20 | int new_option = old_option | O_NONBLOCK; 21 | fcntl(fd, F_SETFL, new_option); 22 | return old_option; 23 | } 24 | 25 | void addfd(int epollfd, int fd, bool enable_et) { 26 | epoll_event event; 27 | event.data.fd = fd; 28 | event.events = EPOLLIN; 29 | if(enable_et) { 30 | event.events |= EPOLLET; 31 | } 32 | epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); 33 | setnonblocking(fd); 34 | } 35 | 36 | void lt(epoll_event* events, int number, int epollfd, int listenfd) { 37 | char buf[BUFFER_SIZE]; 38 | for(int i = 0; i < number; i++) { 39 | int sockfd = events[i].data.fd; 40 | if(sockfd == listenfd) { 41 | struct sockaddr_in client_address; 42 | socklen_t client_addrlength = sizeof(client_address); 43 | int connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength); 44 | addfd(epollfd, connfd, false); 45 | } else if(events[i].events & EPOLLIN) { 46 | printf("event trigger once\n"); 47 | memset(buf, '\0', BUFFER_SIZE); 48 | int ret = recv(sockfd, buf, BUFFER_SIZE - 1, 0); 49 | if(ret <= 0) { 50 | close(sockfd); 51 | continue; 52 | } 53 | printf("get %dbytes of contents: %s\n", ret, buf); 54 | } else { 55 | printf("something else happened\n"); 56 | } 57 | } 58 | } 59 | 60 | void et(epoll_event* events, int number, int epollfd, int listenfd) { 61 | char buf[BUFFER_SIZE]; 62 | for(int i = 0; i < number; i++) { 63 | int sockfd = events[i].data.fd; 64 | if(sockfd == listenfd) { 65 | struct sockaddr_in client_address; 66 | socklen_t client_addrlength = sizeof(client_address); 67 | int connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength); 68 | addfd(epollfd, connfd, true); 69 | 70 | } else if(events[i].events & EPOLLIN) { 71 | 72 | printf("event trigger once\n"); 73 | while(1) { 74 | memset(buf, '\0', BUFFER_SIZE); 75 | int ret = recv(sockfd, buf, BUFFER_SIZE - 1, 0); 76 | if(ret < 0) { 77 | if((errno == EAGAIN) || (errno == EWOULDBLOCK)) { 78 | printf("read later\n"); 79 | break; 80 | } 81 | close(sockfd); 82 | break; 83 | } else if(ret == 0) { 84 | close(sockfd); 85 | } else { 86 | printf("get %dbytes of content: %s\n", ret, buf); 87 | } 88 | } 89 | } else { 90 | printf("something else happened\n"); 91 | } 92 | } 93 | } 94 | 95 | int main(int argc, char* argv[]) { 96 | if(argc <= 2) { 97 | printf("Wrong number of parameters\n"); 98 | return 1; 99 | } 100 | 101 | int port = atoi(argv[2]); 102 | char* ip = argv[1]; 103 | 104 | struct sockaddr_in address; 105 | address.sin_port = htons(port); 106 | address.sin_family = AF_INET; 107 | inet_pton(AF_INET, ip, &address.sin_addr); 108 | 109 | int listenfd = socket(PF_INET, SOCK_STREAM, 0); 110 | assert(listenfd >=0); 111 | 112 | int ret = -1; 113 | while(ret == -1) { 114 | address.sin_port = htons(++port); 115 | ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address)); 116 | printf("The port is %d\n", port); 117 | } 118 | 119 | ret = listen(listenfd, 5); 120 | assert(ret != -1); 121 | 122 | epoll_event events[MAX_EVENT_NUMBER]; 123 | int epollfd = epoll_create(5); 124 | assert(epollfd != -1); 125 | addfd(epollfd, listenfd, true); 126 | while(1) { 127 | int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1); 128 | if(ret < 0) { 129 | printf("epool failure\n"); 130 | break; 131 | } 132 | //lt(events, ret, epollfd, listenfd); 133 | et(events, ret, epollfd, listenfd); 134 | } 135 | close(listenfd); 136 | return 0; 137 | 138 | } 139 | -------------------------------------------------------------------------------- /10. 代码清单-IO复用/CODELIST9-3(LT).c: -------------------------------------------------------------------------------- 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 | 15 | #define MAX_EVENT_NUMBER 1024 16 | #define BUFFER_SIZE 10 17 | 18 | int setnonblocking(int fd) { 19 | int old_option = fcntl(fd, F_GETFL); 20 | int new_option = old_option | O_NONBLOCK; 21 | fcntl(fd, F_SETFL, new_option); 22 | return old_option; 23 | } 24 | 25 | void addfd(int epollfd, int fd, bool enable_et) { 26 | epoll_event event; 27 | event.data.fd = fd; 28 | event.events = EPOLLIN; 29 | if(enable_et) { 30 | event.events |= EPOLLET; 31 | } 32 | epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); 33 | setnonblocking(fd); 34 | } 35 | 36 | void lt(epoll_event* events, int number, int epollfd, int listenfd) { 37 | char buf[BUFFER_SIZE]; 38 | for(int i = 0; i < number; i++) { 39 | int sockfd = events[i].data.fd; 40 | if(sockfd == listenfd) { 41 | struct sockaddr_in client_address; 42 | socklen_t client_addrlength = sizeof(client_address); 43 | int connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength); 44 | addfd(epollfd, connfd, false); 45 | } else if(events[i].events & EPOLLIN) { 46 | printf("event trigger once\n"); 47 | memset(buf, '\0', BUFFER_SIZE); 48 | int ret = recv(sockfd, buf, BUFFER_SIZE - 1, 0); 49 | if(ret <= 0) { 50 | close(sockfd); 51 | continue; 52 | } 53 | printf("get %dbytes of contents: %s\n", ret, buf); 54 | } else { 55 | printf("something else happened\n"); 56 | } 57 | } 58 | } 59 | 60 | void et(epoll_event* events, int number, int epollfd, int listenfd) { 61 | char buf[BUFFER_SIZE]; 62 | for(int i = 0; i < number; i++) { 63 | int sockfd = events[i].data.fd; 64 | if(sockfd == listenfd) { 65 | struct sockaddr_in client_address; 66 | socklen_t client_addrlength = sizeof(client_address); 67 | int connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength); 68 | addfd(epollfd, connfd, true); 69 | 70 | } else if(events[i].events & EPOLLIN) { 71 | 72 | printf("event trigger once\n"); 73 | while(1) { 74 | memset(buf, '\0', BUFFER_SIZE); 75 | int ret = recv(sockfd, buf, BUFFER_SIZE - 1, 0); 76 | if(ret < 0) { 77 | if((errno == EAGAIN) || (errno == EWOULDBLOCK)) { 78 | printf("read later\n"); 79 | break; 80 | } 81 | close(sockfd); 82 | break; 83 | } else if(ret == 0) { 84 | close(sockfd); 85 | } else { 86 | printf("get %dbytes of content: %s\n", ret, buf); 87 | } 88 | } 89 | } else { 90 | printf("something else happened\n"); 91 | } 92 | } 93 | } 94 | 95 | int main(int argc, char* argv[]) { 96 | if(argc <= 2) { 97 | printf("Wrong number of parameters\n"); 98 | return 1; 99 | } 100 | 101 | int port = atoi(argv[2]); 102 | char* ip = argv[1]; 103 | 104 | struct sockaddr_in address; 105 | address.sin_port = htons(port); 106 | address.sin_family = AF_INET; 107 | inet_pton(AF_INET, ip, &address.sin_addr); 108 | 109 | int listenfd = socket(PF_INET, SOCK_STREAM, 0); 110 | assert(listenfd >=0); 111 | 112 | int ret = -1; 113 | while(ret == -1) { 114 | address.sin_port = htons(++port); 115 | ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address)); 116 | printf("The port is %d\n", port); 117 | } 118 | 119 | ret = listen(listenfd, 5); 120 | assert(ret != -1); 121 | 122 | epoll_event events[MAX_EVENT_NUMBER]; 123 | int epollfd = epoll_create(5); 124 | assert(epollfd != -1); 125 | addfd(epollfd, listenfd, true); 126 | while(1) { 127 | int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1); 128 | if(ret < 0) { 129 | printf("epool failure\n"); 130 | break; 131 | } 132 | lt(events, ret, epollfd, listenfd); 133 | //et(events, ret, epollfd, listenfd); 134 | } 135 | close(listenfd); 136 | return 0; 137 | 138 | } 139 | -------------------------------------------------------------------------------- /10. 代码清单-IO复用/CODELIST9-5.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiganFish/Notes-HighPerformanceLinuxServerProgramming/d1449f5974905d5464f8bee644504413da78208f/10. 代码清单-IO复用/CODELIST9-5.c -------------------------------------------------------------------------------- /11. 代码清单9-6和9-7 聊天室程序/CHAT_CLIENT.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define BUFFER_SIZE 64 14 | 15 | int main(int argc, char* argv[]) { 16 | 17 | if(argc <= 2) { 18 | printf("Wrong number of parameters \n"); 19 | return 1; 20 | } 21 | 22 | int port = atoi(argv[2]); 23 | char* ip = argv[1]; 24 | 25 | struct sockaddr_in address; 26 | memset(&address, 0, sizeof(address)); 27 | address.sin_family = AF_INET; 28 | address.sin_port = htons(port); 29 | inet_pton(AF_INET, ip, &address.sin_addr); 30 | 31 | int sockfd = socket(PF_INET, SOCK_STREAM, 0); 32 | assert(sockfd >= 0); 33 | 34 | if(connect(sockfd, (struct sockaddr*)&address, sizeof(address)) < 0) { 35 | printf("Connect error\n"); 36 | close(sockfd); 37 | return 1; 38 | } 39 | 40 | pollfd fds[2]; 41 | fds[0].fd = 0; 42 | fds[0].events = POLLIN; 43 | fds[0].revents = 0; 44 | fds[1].fd = sockfd; 45 | fds[1].events = POLLIN | POLLRDHUP; 46 | fds[1].revents = 0; 47 | 48 | char read_buf[BUFFER_SIZE]; 49 | int pipefd[2]; 50 | int ret = pipe(pipefd); 51 | assert(ret != -1); 52 | 53 | while (1) { 54 | 55 | ret = poll(fds, 2, -1); 56 | if(ret < 0) { 57 | printf("poll failure\n"); 58 | break; 59 | } 60 | 61 | if(fds[1].revents & POLLRDHUP) { 62 | printf("server close the connection\n"); 63 | break; 64 | } 65 | if(fds[1].revents & POLLIN) { 66 | memset(read_buf, '\0', BUFFER_SIZE); 67 | recv(fds[1].fd, read_buf, BUFFER_SIZE - 1, 0); 68 | printf("%s\n", read_buf); 69 | } 70 | 71 | if(fds[0].revents & POLLIN) { 72 | ret = splice(0, nullptr, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE); 73 | ret = splice(pipefd[0], nullptr, sockfd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE); 74 | } 75 | } 76 | 77 | close(sockfd); 78 | return 0; 79 | } 80 | -------------------------------------------------------------------------------- /11. 代码清单9-6和9-7 聊天室程序/CHAT_SERVER.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiganFish/Notes-HighPerformanceLinuxServerProgramming/d1449f5974905d5464f8bee644504413da78208f/11. 代码清单9-6和9-7 聊天室程序/CHAT_SERVER.c -------------------------------------------------------------------------------- /12. 代码清单11-2和11-3及11-4 链表定时器, 处理非活动连接/lst_timer.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by lsmg on 2/1/20. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define BUFFER_SIZE 1024 11 | 12 | class util_timer; 13 | 14 | // 用户数据结构 客户端的socket地址 socket文件描述符 读缓存 定时器 15 | struct client_data 16 | { 17 | sockaddr_in address; 18 | int sockfd; 19 | char buf[BUFFER_SIZE]; 20 | util_timer* timer; 21 | }; 22 | 23 | class util_timer 24 | { 25 | public: 26 | util_timer():prev(nullptr), next(nullptr){}; 27 | public: 28 | util_timer *prev; // 前驱 29 | util_timer *next; // 后继 30 | 31 | time_t expire; // 超时时间 32 | client_data *user_data; // 回调函数处理的客户端数据 33 | void (*cb_func)(client_data*); // 回调函数 34 | }; 35 | 36 | // 定时器链表 升续 双向 带有头尾节点 37 | class sort_timer_lst 38 | { 39 | public: 40 | sort_timer_lst():head(nullptr), tail(nullptr){}; 41 | // 析构时删除其中所有的定时器 42 | ~sort_timer_lst() 43 | { 44 | util_timer *temp = head; 45 | while(nullptr != temp) 46 | { 47 | delete head; 48 | head = temp->next; 49 | temp = head; 50 | } 51 | } 52 | // 将目标定时器添加到链表中 53 | void add_timer(util_timer *timer) 54 | { 55 | if (nullptr == timer) 56 | { 57 | return; 58 | } 59 | if (nullptr == head) 60 | { 61 | head = timer; 62 | tail = timer; 63 | return; 64 | } 65 | // 超时时间小于链表中最小时间 直接插入头部 66 | if (timer->expire < head->expire) 67 | { 68 | head = timer; 69 | timer->next = tail; 70 | tail->prev = timer; 71 | return; 72 | } 73 | // 需要插入到链表中 或链表尾部 74 | add_timer(timer, head); 75 | } 76 | 77 | // 增加timer定时器expire 78 | void adjust_timer(util_timer *timer) 79 | { 80 | if (nullptr == timer) 81 | { 82 | return; 83 | } 84 | util_timer *temp = timer->next; 85 | if (timer == tail || (timer->expire < temp->expire)) 86 | { 87 | return; 88 | } 89 | // 如果调整的定时器 是head 则需要取出这个定时器 90 | if (timer == head) 91 | { 92 | head = head->next; 93 | head->prev = nullptr; 94 | timer->next = nullptr; 95 | add_timer(timer, head); 96 | } 97 | else 98 | { 99 | // 不是head 在中间部分 100 | timer->prev->next = timer->next; 101 | timer->next->prev = timer->prev; 102 | add_timer(timer, timer->next); 103 | } 104 | } 105 | 106 | void del_timer(util_timer *timer) 107 | { 108 | if (nullptr == timer) 109 | { 110 | return; 111 | } 112 | if ((timer == head) && (timer == tail)) 113 | { 114 | delete timer; 115 | head = nullptr; 116 | tail = nullptr; 117 | return; 118 | } 119 | if (timer == head) 120 | { 121 | head = head->next; 122 | head->prev = nullptr; 123 | delete timer; 124 | return; 125 | } 126 | if (timer == tail) 127 | { 128 | tail = tail->prev; 129 | tail->next = nullptr; 130 | delete timer; 131 | return; 132 | } 133 | timer->prev->next = timer->next; 134 | timer->next->prev = timer->prev; 135 | delete timer; 136 | } 137 | 138 | void tick() 139 | { 140 | if (nullptr == head) 141 | { 142 | return; 143 | } 144 | printf("timer tick\n"); 145 | time_t cur = time(NULL); 146 | util_timer *temp = head; 147 | while (nullptr != head) 148 | { 149 | if (cur < temp->expire) 150 | { 151 | break; 152 | } 153 | temp->cb_func(temp->user_data); 154 | head = temp->next; 155 | if (nullptr != head) 156 | { 157 | head->prev = nullptr; 158 | } 159 | delete temp; 160 | temp = head; 161 | } 162 | } 163 | private: 164 | util_timer *head; 165 | util_timer *tail; 166 | 167 | private: 168 | void add_timer(util_timer *timer, util_timer *lst_head) 169 | { 170 | util_timer *temp = lst_head; 171 | // 更新temp节点为timer->expire < temp->expire 172 | while (timer->expire > temp->expire) 173 | { 174 | if (temp->next != nullptr) 175 | { 176 | temp = temp->next; 177 | } 178 | else 179 | { 180 | // 如果temp == tail 并且expire依然大 则说明应该插入尾部 181 | tail->next = timer; 182 | timer->prev = tail; 183 | tail = timer; 184 | return; 185 | } 186 | } 187 | 188 | timer->next = temp; 189 | timer->prev = temp->prev; 190 | temp->prev->next = timer; 191 | temp->prev = timer; 192 | } 193 | }; -------------------------------------------------------------------------------- /12. 代码清单11-2和11-3及11-4 链表定时器, 处理非活动连接/main.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 "lst_timer.cpp" 14 | #include "util_lsmg.h" 15 | 16 | static int pipefd[2]; 17 | static sort_timer_lst timer_lst; 18 | static int epollfd = 0; 19 | 20 | #define MAX_EVENT_NUMBER 1024 21 | #define FD_LIMIT 65535 22 | #define TIMESLOT 5 23 | 24 | void setnonblock(int fd) 25 | { 26 | int old_option = fcntl(fd, F_GETFL); 27 | int new_option = old_option | O_NONBLOCK; 28 | fcntl(fd, F_SETFL, new_option); 29 | } 30 | 31 | void sig_handler(int sig) 32 | { 33 | int save_errno = errno; 34 | int msg = sig; 35 | send(pipefd[1], (char*)&msg, 1, 0); 36 | errno = save_errno; 37 | } 38 | 39 | void addsig(int sig) 40 | { 41 | struct sigaction sa{}; 42 | sa.sa_flags |= SA_RESTART; 43 | sa.sa_handler = sig_handler; 44 | sigfillset(&sa.sa_mask); 45 | exit_if(sigaction(sig, &sa, nullptr) == -1, "add sig error"); 46 | } 47 | 48 | void addfd(int fd) 49 | { 50 | epoll_event ev{}; 51 | ev.data.fd = fd; 52 | ev.events = EPOLLIN | EPOLLET; 53 | epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev); 54 | setnonblock(fd); 55 | } 56 | 57 | void cb_func(client_data* user_data) 58 | { 59 | epoll_ctl(epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0); 60 | close(user_data->sockfd); 61 | printf("close fd %d\n", user_data->sockfd); 62 | } 63 | 64 | void timer_handler() 65 | { 66 | timer_lst.tick(); 67 | alarm(TIMESLOT); 68 | } 69 | int main(int argc, char* argv[]) 70 | { 71 | exit_if(argc <= 2, "wrong number of parameters") 72 | 73 | char* ip = argv[1]; 74 | int port = atoi(argv[2]); 75 | 76 | struct sockaddr_in address{}; 77 | address.sin_family = AF_INET; 78 | address.sin_port = htons(port); 79 | address.sin_addr.s_addr = inet_addr(ip); 80 | 81 | int listenfd = socket(PF_INET, SOCK_STREAM, 0); 82 | exit_if(listenfd == -1, "socket error") 83 | 84 | int ret = bind(listenfd, (sockaddr*)&address, sizeof(address)); 85 | exit_if(ret == -1, "bind error"); 86 | 87 | ret = listen(listenfd, 5); 88 | exit_if(ret == -1, "listen errro"); 89 | 90 | addsig(SIGALRM); 91 | addsig(SIGTERM); 92 | 93 | client_data *users = new client_data[FD_LIMIT]; 94 | 95 | epoll_event events[MAX_EVENT_NUMBER]; 96 | int epfd = epoll_create(5); 97 | epollfd = epfd; 98 | addfd(listenfd); 99 | 100 | ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd); 101 | setnonblock(pipefd[1]); 102 | addfd(pipefd[0]); 103 | 104 | bool stop_server = false; 105 | bool timeout = false; 106 | alarm(TIMESLOT); 107 | 108 | while(!stop_server) 109 | { 110 | int number = epoll_wait(epfd, events, MAX_EVENT_NUMBER, -1); 111 | exit_if((number < 0) && (errno != EINTR), "epoll_wait error"); 112 | for (int i = 0; i < number; ++i) 113 | { 114 | int sockfd = events[i].data.fd; 115 | if (sockfd == listenfd) 116 | { 117 | struct sockaddr_in client_address{}; 118 | socklen_t client_addresslen = sizeof(client_address); 119 | 120 | int newfd = accept(listenfd, (sockaddr*)&client_address, &client_addresslen); 121 | 122 | addfd(newfd); 123 | 124 | util_timer *timer = new util_timer; 125 | timer->user_data = &users[newfd]; 126 | timer->cb_func = cb_func; 127 | time_t now = time(NULL); 128 | timer->expire = now + TIMESLOT * 3; 129 | 130 | users[newfd].address = client_address; 131 | users[newfd].sockfd = newfd; 132 | users[newfd].timer = timer; 133 | timer_lst.add_timer(timer); 134 | } 135 | else if ((sockfd == pipefd[0]) && (events[i].events & EPOLLIN)) 136 | { 137 | int sig; 138 | char signals[1024]; 139 | ret = recv(sockfd, signals, sizeof(signals), 0); 140 | if (ret == -1) 141 | { 142 | continue; 143 | } 144 | else if (ret == 0) 145 | { 146 | continue; 147 | } 148 | else 149 | { 150 | for (int j = 0; j < ret; ++j) 151 | { 152 | switch(signals[j]) 153 | { 154 | case SIGALRM: 155 | { 156 | timeout = true; 157 | break; 158 | } 159 | case SIGTERM: 160 | { 161 | stop_server = true; 162 | } 163 | } 164 | } 165 | } 166 | } 167 | else if (events[i].events & EPOLLIN) 168 | { 169 | memset(users[sockfd].buf, '\0', BUFFER_SIZE); 170 | ret = recv(sockfd, users[sockfd].buf, BUFFER_SIZE - 1, 0); 171 | printf("get %d bytes of client data %s from %d\n", ret, 172 | users[sockfd].buf, sockfd); 173 | 174 | util_timer *timer = users[sockfd].timer; 175 | if (ret < 0) 176 | { 177 | if (errno != EAGAIN) 178 | { 179 | cb_func(&users[sockfd]); 180 | if (timer) 181 | { 182 | timer_lst.del_timer(timer); 183 | } 184 | } 185 | } 186 | else if (ret == 0) 187 | { 188 | cb_func(&users[sockfd]); 189 | if (nullptr != timer) 190 | { 191 | timer_lst.del_timer(timer); 192 | } 193 | } 194 | else 195 | { 196 | if (nullptr !=timer) 197 | { 198 | time_t cur = time(NULL); 199 | timer->expire = cur + 3 * TIMESLOT; 200 | printf("adjust timr once\n"); 201 | timer_lst.adjust_timer(timer); 202 | } 203 | } 204 | } 205 | } 206 | if (timeout) 207 | { 208 | timer_handler(); 209 | timeout = false; 210 | } 211 | } 212 | close(listenfd); 213 | close(pipefd[1]); 214 | close(pipefd[0]); 215 | delete []users; 216 | return 0; 217 | } -------------------------------------------------------------------------------- /12. 代码清单11-2和11-3及11-4 链表定时器, 处理非活动连接/util_lsmg.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by lsmg on 2/2/20. 3 | // 4 | 5 | #ifndef UNTITLED_UTIL_LSMG_H 6 | #define UNTITLED_UTIL_LSMG_H 7 | 8 | #define exit_if(r, ...) \ 9 | { \ 10 | if (r) \ 11 | { \ 12 | printf(__VA_ARGS__); \ 13 | printf("\nerrno no: %d, error msg is %s", errno, strerror(errno)); \ 14 | exit(1); \ 15 | } \ 16 | } \ 17 | 18 | #endif //UNTITLED_UTIL_LSMG_H 19 | -------------------------------------------------------------------------------- /13. 猜数字游戏客户端以及服务器实现桌位加入/client.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 | 15 | #define BUFFER_SIZE 1024 16 | 17 | #define B_GETDESK 1 18 | #define B_ENTER 2 19 | #define B_GAMESTATUS 3 20 | #define B_GAME 4 21 | #define B_GAME_RANGE 5 22 | #define B_GAME_RESULT 6 23 | 24 | void ProcessMsgCenter(const char* msg); 25 | int now_game_status = 1; 26 | bool can_guess_num = true; 27 | 28 | int main(int argc, char* argv[]) { 29 | 30 | 31 | if (argc <= 2) 32 | { 33 | printf("Wrong number of parameters"); 34 | return 1; 35 | } 36 | 37 | char* ip = argv[1]; 38 | int port = atoi(argv[2]); 39 | 40 | struct sockaddr_in host_address{}; 41 | host_address.sin_port = htons(port); 42 | host_address.sin_family = AF_INET; 43 | inet_pton(AF_INET, ip, &host_address.sin_addr); 44 | 45 | int host_sock = socket(PF_INET, SOCK_STREAM, 0); 46 | assert(host_sock >= 0); 47 | 48 | int ret = connect(host_sock, (struct sockaddr*)&host_address, sizeof(host_address)); 49 | assert(ret != -1); 50 | 51 | pollfd pds[2]; 52 | pds[0].fd = 0; 53 | pds[0].events = POLLIN; 54 | pds[0].revents = 0; 55 | pds[1].fd = host_sock; 56 | pds[1].events = POLLIN | POLLRDHUP; 57 | pds[1].revents = 0; 58 | 59 | assert(ret != -1); 60 | 61 | char buff[BUFFER_SIZE]; 62 | 63 | printf("%s", "输入任意内容回车获取房间信息\n"); 64 | 65 | while (1) 66 | { 67 | 68 | ret = poll(pds, 2, -1); 69 | if (ret <= 0) 70 | { 71 | printf("Poll error\n"); 72 | break; 73 | } 74 | 75 | if (pds[0].revents & POLLIN) 76 | { 77 | memset(buff, '\0', BUFFER_SIZE); 78 | int recv_length = read(pds[0].fd, buff, BUFFER_SIZE - 1); 79 | buff[strlen(buff) - 1] = '\0'; 80 | 81 | if (buff[0] == '&') 82 | { 83 | now_game_status++; 84 | continue; 85 | } 86 | 87 | if (now_game_status == 4 && !can_guess_num) 88 | { 89 | 90 | printf("现在没有轮到你, 请等待\n"); 91 | continue; 92 | } 93 | if (now_game_status == 4) 94 | { 95 | can_guess_num = false; 96 | } 97 | std::string the_msg = "{" + std::to_string(now_game_status) + "@" + buff + "}"; 98 | send(host_sock, the_msg.c_str(), the_msg.length(), 0); 99 | } 100 | 101 | if (pds[0].revents & POLLRDHUP) 102 | { 103 | const char* msg = "lose connection"; 104 | printf("%s", msg); 105 | } 106 | 107 | if (pds[1].revents & POLLIN) 108 | { 109 | memset(buff, '\0', BUFFER_SIZE); 110 | read(pds[1].fd, buff, BUFFER_SIZE - 1); 111 | 112 | 113 | ProcessMsgCenter(buff); 114 | } 115 | 116 | } 117 | 118 | return 0; 119 | } 120 | 121 | void PlayGame() { 122 | 123 | } 124 | 125 | void EnterRoom(int msg_bool, int msg_length, char* true_msg) { 126 | if(msg_bool == 1) { 127 | printf("已有%d-游戏开始所需人数%d\n", true_msg[0] - 48, true_msg[2] - 48); 128 | now_game_status = 3; 129 | } 130 | else 131 | { 132 | printf("%s\n","进入房间失败, 请进入其它房间\n"); 133 | } 134 | 135 | } 136 | 137 | void MsgProcess(int msg_type, int msg_type_bool, int msg_length, char* true_msg) { 138 | switch (msg_type) 139 | { 140 | case B_GETDESK: 141 | printf("请选择房间加入\n%s\n", true_msg); 142 | now_game_status = 2; 143 | break; 144 | case B_ENTER: 145 | EnterRoom(msg_type_bool, msg_length, true_msg); 146 | break; 147 | case B_GAMESTATUS: 148 | printf("游戏开始\n"); 149 | now_game_status = 4; 150 | break; 151 | case B_GAME: 152 | printf("%s\n",true_msg); 153 | can_guess_num = msg_type_bool; 154 | break; 155 | case B_GAME_RANGE: 156 | printf("当前范围%s\n",true_msg); 157 | 158 | break; 159 | case B_GAME_RESULT: 160 | printf("玩家%s胜利\n输入任意内容回车获取房间信息\n",true_msg); 161 | now_game_status = 1; 162 | break; 163 | } 164 | } 165 | 166 | void GetInfoFromMsg(const char* msg) { 167 | 168 | int msg_type = msg[0] - 48; 169 | int msg_type_bool = msg[1] - 48; 170 | char msg_length_char[3] = {}; 171 | int msg_length_char_sub = 0; 172 | bool is_first_flag = true; 173 | int msg_length = strlen(msg); 174 | for(int i = 0; i < msg_length; ++i) 175 | { 176 | if(is_first_flag &&msg[i] == '@') 177 | { 178 | msg_length_char[msg_length_char_sub] = msg[i + 1]; 179 | msg_length_char_sub++; 180 | if(msg[i + 2] == '@') 181 | { 182 | break; 183 | } 184 | 185 | is_first_flag = false; 186 | continue; 187 | } 188 | if(!is_first_flag && msg[i] == '@') 189 | { 190 | msg_length_char[msg_length_char_sub] = msg[i - 1]; 191 | break; 192 | } 193 | } 194 | 195 | int msg_true_length = atoi(msg_length_char); 196 | char true_msg[1024] = {}; 197 | strcpy(true_msg, msg + msg_length - msg_true_length); 198 | MsgProcess(msg_type, msg_type_bool, msg_true_length, true_msg); 199 | } 200 | 201 | void ProcessMsgCenter(const char* msg) 202 | { 203 | bool left_bracket_falg = false; 204 | bool right_bracket_falg = false; 205 | 206 | static char recv_buffer [1024]; 207 | static char recv_buffer_last[1024]; 208 | 209 | std::string the_msg = msg; 210 | 211 | int the_msg_length = (int)the_msg.length(); 212 | 213 | int begin_sub = 0; 214 | for (int i = 0; i < the_msg_length; ++i) 215 | { 216 | 217 | if (the_msg[i] == '{') 218 | { 219 | left_bracket_falg = true; 220 | begin_sub = i + 1; 221 | memset(recv_buffer_last, '\0', 1024); 222 | } 223 | if (the_msg[i] == '}') 224 | { 225 | if (left_bracket_falg) 226 | { 227 | memset(recv_buffer, '\0', 1024); 228 | strncpy(recv_buffer, msg + begin_sub, i - begin_sub); 229 | 230 | if (i + 1 < the_msg_length) 231 | { 232 | strncpy(recv_buffer_last, msg + i + 1, the_msg_length - i); 233 | GetInfoFromMsg(recv_buffer); 234 | ProcessMsgCenter(recv_buffer_last); 235 | } 236 | else 237 | { 238 | GetInfoFromMsg(recv_buffer); 239 | fflush(stdout); 240 | } 241 | 242 | } 243 | } 244 | 245 | } 246 | } -------------------------------------------------------------------------------- /13. 猜数字游戏客户端以及服务器实现桌位加入/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 | #include 14 | 15 | #define BUFFER_SIZE 1024 16 | #define MAX_DESK 3 17 | #define DESK_MAXUSER 2 18 | #define FD_LIMIT 65535 19 | int MAX_NUM = 100; 20 | int MIN_NUM = 0; 21 | 22 | #define B_GETDESK 1 23 | #define B_ENTER 2 24 | #define B_GAMESTATUS 3 25 | #define B_GAME 4 26 | #define B_GAME_RANGE 5 27 | #define B_GAME_RESULT 6 28 | 29 | int MsgProcessCenter(int msg_fd, const char* msg); 30 | void SendDeskMsg(int socket); 31 | void AddToDesk(int msg_fd, const char* desk_msg); 32 | void PlayGame(int msg_fd, int desk_sub, const char* number); 33 | void ResetGameDesk(const int desk_sub); 34 | 35 | struct SDESK { 36 | int online_user_num = 0; 37 | int online_user[DESK_MAXUSER]; 38 | bool is_playing = false; 39 | 40 | int max_num; 41 | int min_num; 42 | int ans_num; 43 | }; 44 | 45 | struct SCLIENT_DATA { 46 | sockaddr_in sock_address; 47 | int desk_sub; 48 | char buf[BUFFER_SIZE]; 49 | char* write_buf; 50 | }; 51 | 52 | SCLIENT_DATA* users = new SCLIENT_DATA[FD_LIMIT]; 53 | SDESK* desks = new SDESK[MAX_DESK]; 54 | 55 | void SendMsgCenter(const int sockfd, int desk_sub, int send_method, const int type, const bool result, const char* msg) 56 | { 57 | char msgBuffer[1024] = {}; 58 | 59 | if(nullptr == msg) 60 | { 61 | sprintf(msgBuffer, "{%d%d@1@1}", type, result?1:0); 62 | } 63 | else 64 | { 65 | sprintf(msgBuffer, "{%d%d@%zu@%s}", type, result?1:0, strlen(msg), msg); 66 | } 67 | // 1 all 2 except owner 3 owner 68 | if(send_method == 3) 69 | { 70 | send(sockfd, msgBuffer, strlen(msgBuffer), 0); 71 | return; 72 | } 73 | 74 | for(int sock : desks[desk_sub].online_user) 75 | { 76 | 77 | if(send_method == 2 && sock != sockfd) { 78 | send(sock, msgBuffer, strlen(msgBuffer), 0); 79 | } 80 | else if (send_method == 1) 81 | { 82 | send(sock, msgBuffer, strlen(msgBuffer), 0); 83 | } 84 | } 85 | 86 | } 87 | 88 | void RemoveLeftClient(pollfd* fds, int& user_counter, int left_sock) { 89 | int desk_sub; 90 | if ((desk_sub = users[left_sock].desk_sub) != -1) { 91 | int (*desk_users) = desks[desk_sub].online_user; 92 | for (int i = 0; i < DESK_MAXUSER; ++i) { 93 | if (desk_users[i] == left_sock) { 94 | std::string msg = "player " + std::to_string(left_sock) + " left, game over;"; 95 | SendMsgCenter(left_sock, desk_sub, 2, B_GAME_RESULT, true, msg.c_str()); 96 | } 97 | } 98 | ResetGameDesk(desk_sub); 99 | } 100 | 101 | int i = 0; 102 | while (fds[i].fd != left_sock) { 103 | i++; 104 | } 105 | close(fds[i].fd); 106 | fds[i].fd = fds[user_counter].fd; 107 | fds[user_counter].fd = -1; 108 | user_counter--; 109 | 110 | } 111 | 112 | int main(int argc, char* argv[]) { 113 | 114 | if (argc <= 2) 115 | { 116 | printf("Wrong number of parameters"); 117 | return 1; 118 | } 119 | 120 | char* ip = argv[1]; 121 | int port = atoi(argv[2]); 122 | 123 | struct sockaddr_in address{}; 124 | address.sin_port = htons(port); 125 | address.sin_family = AF_INET; 126 | inet_pton(AF_INET, ip, &address.sin_addr); 127 | 128 | int listenfd = socket(PF_INET, SOCK_STREAM, 0); 129 | assert(listenfd >= 0); 130 | 131 | int ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address)); 132 | assert(ret != -1); 133 | 134 | ret = listen(listenfd, 5); 135 | assert(ret != -1); 136 | 137 | int MAX_USER = MAX_DESK * DESK_MAXUSER; 138 | int user_counter = 0; 139 | 140 | pollfd fds[MAX_USER + 1]; 141 | for (int i = 1; i <= MAX_USER; i++) 142 | { 143 | fds[i].fd = -1; 144 | fds[i].events = 0; 145 | } 146 | fds[0].fd = listenfd; 147 | fds[0].events = POLLIN | POLLERR; 148 | fds[0].revents = 0; 149 | 150 | while (1) 151 | { 152 | 153 | ret = poll(fds, MAX_USER + 1, -1); 154 | if (ret <= 0) 155 | { 156 | printf("poll error\n"); 157 | break; 158 | } 159 | 160 | for (int i = 0; i < MAX_USER + 1; i++) 161 | { 162 | 163 | pollfd the_fds = fds[i]; 164 | 165 | if ((the_fds.fd == listenfd) && (the_fds.revents & POLLIN)) 166 | { 167 | if (user_counter == MAX_USER) 168 | { 169 | continue; 170 | } 171 | user_counter++; 172 | 173 | struct sockaddr_in client_address{}; 174 | socklen_t client_addrlength = sizeof(client_address); 175 | int client_sock = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength); 176 | 177 | users[client_sock].sock_address = client_address; 178 | users[client_sock].desk_sub = -1; 179 | fds[user_counter].fd = client_sock; 180 | fds[user_counter].events = POLLIN | POLLRDHUP | POLLERR; 181 | continue; 182 | } 183 | 184 | if (the_fds.revents & POLLERR) 185 | { 186 | // TODO remove loser 187 | printf("client %d left-POLLERR\n", the_fds.fd); 188 | continue; 189 | } 190 | 191 | if (the_fds.revents & POLLRDHUP) 192 | { 193 | // TODO remove loser 194 | 195 | printf("client %d left-POLLRDHUP\n", the_fds.fd); 196 | RemoveLeftClient(fds, user_counter, the_fds.fd); 197 | continue; 198 | } 199 | 200 | if (the_fds.revents & POLLIN) 201 | { 202 | 203 | char recvbuf[BUFFER_SIZE] = {}; 204 | recv(the_fds.fd, recvbuf, BUFFER_SIZE - 1, 0); 205 | 206 | printf("%d-%s\n", the_fds.fd, recvbuf); 207 | 208 | int processResult = MsgProcessCenter(the_fds.fd, recvbuf); 209 | if (processResult == -1) 210 | { 211 | printf("Wrong msg %s\n", recvbuf); 212 | } 213 | } 214 | } 215 | } 216 | delete[] users; 217 | delete[] desks; 218 | close(listenfd); 219 | return 0; 220 | } 221 | 222 | void MsgProcess(const int msg_fd, const int msgType, const char* trueMsg) 223 | { 224 | switch (msgType) 225 | { 226 | case 1: 227 | SendDeskMsg(msg_fd); 228 | break; 229 | case 2: 230 | 231 | case 3: 232 | AddToDesk(msg_fd, trueMsg); 233 | break; 234 | case 4: 235 | PlayGame(msg_fd, users[msg_fd].desk_sub, trueMsg); 236 | default: 237 | break; 238 | } 239 | } 240 | 241 | int MsgProcessCenter(const int msg_fd, const char* msg) 242 | { 243 | char trueMsg[BUFFER_SIZE] = {}; 244 | int length = strlen(msg); 245 | if (msg[0] == '{' && msg[length-1] == '}') 246 | { 247 | 248 | strncpy(trueMsg, msg + 3, length - 4); 249 | 250 | MsgProcess(msg_fd, msg[1] - 48, trueMsg); 251 | return 0; 252 | } 253 | else 254 | { 255 | return -1; 256 | } 257 | } 258 | 259 | void SendDeskMsg(const int socket) 260 | { 261 | std::string deskInfo; 262 | for (int i =0; i < MAX_DESK; i++) 263 | { 264 | deskInfo += std::to_string(i) + " SDESK - " + std::to_string(DESK_MAXUSER - desks[i].online_user_num) + " persion available\n"; 265 | } 266 | 267 | SendMsgCenter(socket, 0, 3, B_GETDESK, true, deskInfo.c_str()); 268 | } 269 | 270 | void AddToDesk(const int msg_fd, const char* desk_msg) 271 | { 272 | 273 | int deskSub = desk_msg[0] - 48; 274 | int deskOnlineer = desks[deskSub].online_user_num; 275 | 276 | 277 | for (int onlinesocket : desks[msg_fd].online_user) 278 | { 279 | if (onlinesocket == msg_fd) 280 | { 281 | SendMsgCenter(msg_fd, 0, 3, B_ENTER, false, nullptr); 282 | return; 283 | } 284 | } 285 | 286 | if (deskOnlineer == DESK_MAXUSER) 287 | { 288 | 289 | SendMsgCenter(msg_fd, 0,3,B_ENTER, false, nullptr); 290 | } 291 | users[msg_fd].desk_sub = deskSub; 292 | desks[deskSub].online_user[deskOnlineer] = msg_fd; 293 | desks[deskSub].online_user_num++; 294 | deskOnlineer++; 295 | 296 | if (deskOnlineer == DESK_MAXUSER) 297 | { 298 | SendMsgCenter(msg_fd, users[msg_fd].desk_sub, 1, B_GAMESTATUS, true, nullptr); 299 | } 300 | else 301 | { 302 | std::string result4 = std::to_string(deskOnlineer) + "-" + std::to_string(DESK_MAXUSER); 303 | SendMsgCenter(msg_fd, users[msg_fd].desk_sub, 3, B_ENTER, true, result4.c_str()); 304 | } 305 | } 306 | 307 | void IntiGame(const int desk_sub) 308 | { 309 | desks[desk_sub].max_num = MAX_NUM; 310 | desks[desk_sub].min_num = MIN_NUM; 311 | 312 | std::srand((unsigned int)time(nullptr)); 313 | int a = MAX_NUM - MIN_NUM; 314 | desks[desk_sub].ans_num = std::rand() % a; 315 | desks[desk_sub].is_playing = true; 316 | } 317 | 318 | void ResetGameDesk(const int desk_sub) 319 | { 320 | users[desks[desk_sub].online_user[0]].desk_sub = -1; 321 | users[desks[desk_sub].online_user[1]].desk_sub = -1; 322 | desks[desk_sub].online_user[0] = -1; 323 | desks[desk_sub].online_user[1] = -1; 324 | desks[desk_sub].max_num = MAX_NUM; 325 | desks[desk_sub].min_num = MIN_NUM; 326 | desks[desk_sub].online_user_num = 0; 327 | } 328 | 329 | void PlayGame(const int msg_fd, const int desk_sub, const char* number) 330 | { 331 | 332 | if (!desks[desk_sub].is_playing) 333 | { 334 | IntiGame(desk_sub); 335 | } 336 | 337 | int num = atoi(number); 338 | 339 | 340 | if ((num < desks[desk_sub].min_num) || (num > desks[desk_sub].max_num)) 341 | { 342 | SendMsgCenter(msg_fd, desk_sub, 3, B_GAME, true, nullptr); 343 | 344 | return; 345 | } 346 | std::string result_status ="player-" + std::to_string(msg_fd) + " guess-" + number; 347 | SendMsgCenter(msg_fd, desk_sub, 2, B_GAME, true, result_status.c_str()); 348 | 349 | if (num < desks[desk_sub].ans_num) 350 | { 351 | desks[desk_sub].min_num = num; 352 | } 353 | else if (num > desks[desk_sub].ans_num) 354 | { 355 | desks[desk_sub].max_num = num; 356 | } 357 | else if (num == desks[desk_sub].ans_num) 358 | { 359 | char result_winer[1] = {static_cast(48 + msg_fd)}; 360 | SendMsgCenter(msg_fd, desk_sub, 1, B_GAME_RESULT, true, result_winer); 361 | ResetGameDesk(desk_sub); 362 | return; 363 | } 364 | 365 | std::string result1 = std::to_string(desks[desk_sub].min_num) + "-" + std::to_string(desks[desk_sub].max_num); 366 | SendMsgCenter(msg_fd, desk_sub, 1, B_GAME_RANGE, true, result1.c_str()); 367 | } -------------------------------------------------------------------------------- /14. 进程同步练习/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | union semun 8 | { 9 | int val; /* Value for SETVAL */ 10 | struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ 11 | unsigned short *array; /* Array for GETALL, SETALL */ 12 | struct seminfo *__buf; /* Buffer for IPC_INFO(Linux-specific) */ 13 | }; 14 | 15 | bool P(int sem_id) 16 | { 17 | struct sembuf sem_b; 18 | sem_b.sem_num = 0; 19 | sem_b.sem_op = -1; // P 20 | sem_b.sem_flg = SEM_UNDO; 21 | return semop(sem_id, &sem_b, 1) != -1; 22 | } 23 | 24 | int V(int sem_id) 25 | { 26 | struct sembuf sem_b; 27 | sem_b.sem_num = 0; 28 | sem_b.sem_op = 1; // V 29 | sem_b.sem_flg = SEM_UNDO; 30 | return semop(sem_id, &sem_b, 1) != -1; 31 | } 32 | 33 | int main(int argc, char *argv[]) 34 | { 35 | char message = 'x'; 36 | 37 | int sem_id = semget((key_t) 1234, 1, 0666 | IPC_CREAT); 38 | 39 | // 初次调用程序初始化信号量 40 | if (argc > 1) 41 | { 42 | // 初始化信号量 43 | union semun sem_union; 44 | sem_union.val = 1; 45 | 46 | if (semctl(sem_id, 0, SETVAL, sem_union) == -1) 47 | { 48 | exit(0); 49 | } 50 | message = argv[1][0]; 51 | sleep(2); 52 | } 53 | 54 | for (int i = 0; i < 10; ++i) 55 | { 56 | if (!P(sem_id)) 57 | { 58 | exit(EXIT_FAILURE); 59 | } 60 | // 打印自己的特有message 61 | printf("%c", message); 62 | fflush(stdout); 63 | 64 | sleep(rand() % 3); 65 | 66 | printf("%c", message); 67 | fflush(stdout); 68 | 69 | if (!V(sem_id)) 70 | { 71 | exit(EXIT_FAILURE); 72 | } 73 | } 74 | 75 | sleep(2); 76 | printf("\n%d - finished\n", getpid()); 77 | 78 | // 初次调用完删除信号量 79 | if (argc > 1) 80 | { 81 | sleep(3); 82 | // 删除信号量 83 | union semun sem_union{}; 84 | if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1) 85 | { 86 | exit(EXIT_FAILURE); 87 | } 88 | } 89 | exit(EXIT_SUCCESS); 90 | } -------------------------------------------------------------------------------- /15. 进程间通信/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "log.h" 10 | 11 | #define USER_LIMIT 5 12 | #define BUFFER_SIZE 1024 13 | #define FD_LIMIT 65535 14 | #define MAX_EVENT_NUMBER 1024 15 | #define PROCESS_LIMIT 65535 16 | 17 | struct ClientData 18 | { 19 | sockaddr_in address; 20 | int connfd; 21 | pid_t pid; 22 | int pipefd[2]; 23 | }; 24 | 25 | ClientData *users = nullptr; 26 | int *pid_to_user_sub = nullptr; 27 | 28 | static const char *shm_name = "/my_shm"; 29 | char *share_mem = nullptr; 30 | int listenfd; 31 | int epollfd; 32 | int sig_pipe[2]; 33 | int shmfd; 34 | 35 | int user_count = 0; 36 | 37 | bool stop_child = false; 38 | void ChildTermHandler(int sig) 39 | { 40 | stop_child = true; 41 | }; 42 | 43 | void SetNonblock(int fd) 44 | { 45 | int old_option = fcntl(fd, F_GETFL); 46 | fcntl(fd, old_option | O_NONBLOCK); 47 | } 48 | 49 | void Addfd(int epfd, int fd) 50 | { 51 | epoll_event ev{}; 52 | ev.data.fd = fd; 53 | ev.events = EPOLLIN | EPOLLET; 54 | 55 | epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev); 56 | SetNonblock(fd); 57 | } 58 | 59 | void SignalHandler(int sig) 60 | { 61 | int save_errno = errno; 62 | int msg = sig; 63 | send(sig_pipe[1], (char*)&msg, 1, 0); 64 | errno = save_errno; 65 | } 66 | 67 | void AddSignal(int sig, void (*handler)(int), bool restat = true) 68 | { 69 | struct sigaction sa{}; 70 | sa.sa_handler = handler; 71 | if (restat) 72 | { 73 | sa.sa_flags |= SA_RESTART; 74 | } 75 | sigfillset(&sa.sa_mask); 76 | int ret = sigaction(sig, &sa, nullptr); 77 | ERROR_IF(ret == -1, "add sig"); 78 | } 79 | 80 | int RunChild(int idx, ClientData *the_users, char *the_share_mem) 81 | { 82 | epoll_event events[MAX_EVENT_NUMBER]{}; 83 | int child_epollfd = epoll_create(5); 84 | ERROR_IF(child_epollfd == -1, "child epoll_create"); 85 | 86 | int connfd = the_users[idx].connfd; 87 | Addfd(child_epollfd, connfd); 88 | 89 | int pipefd = the_users[idx].pipefd[1]; 90 | Addfd(child_epollfd, pipefd); 91 | 92 | AddSignal(SIGTERM, ChildTermHandler, false); 93 | 94 | while (!stop_child) 95 | { 96 | int number = epoll_wait(child_epollfd, events, MAX_EVENT_NUMBER, -1); 97 | if ((number < 0) && (errno != EINTR)) 98 | { 99 | printf("epoll failure"); 100 | break; 101 | } 102 | 103 | for (int i = 0; i < number; ++i) 104 | { 105 | int sockfd = events[i].data.fd; 106 | if ((sockfd == connfd) && (events[i].events & EPOLLIN)) 107 | { 108 | memset(the_share_mem + idx * BUFFER_SIZE, '\0', BUFFER_SIZE); 109 | int ret = recv(connfd, the_share_mem + idx * BUFFER_SIZE, BUFFER_SIZE - 1, 0); 110 | if (ret < 0) 111 | { 112 | if (errno != EAGAIN) 113 | { 114 | stop_child = true; 115 | } 116 | } 117 | else if (ret == 0) 118 | { 119 | stop_child = true; 120 | } 121 | else 122 | { 123 | send(pipefd, (char*)&idx, sizeof(idx), 0); 124 | } 125 | } 126 | else if ((sockfd == pipefd) && (events[i].events & EPOLLIN)) 127 | { 128 | int client = 0; 129 | int ret = recv(sockfd, (char*)&client, sizeof(client), 0); 130 | if (ret < 0) 131 | { 132 | if (errno != EAGAIN) 133 | { 134 | stop_child = true; 135 | } 136 | } 137 | else if (ret == 0) 138 | { 139 | stop_child = true; 140 | } 141 | else 142 | { 143 | send(connfd, the_share_mem + client * BUFFER_SIZE, BUFFER_SIZE, 0); 144 | } 145 | } 146 | } 147 | } 148 | close(connfd); 149 | close(pipefd); 150 | close(child_epollfd); 151 | return 0; 152 | } 153 | 154 | int main() 155 | { 156 | Net::Log::SetLogger(Net::OUT_CONSOLE, Net::LOG_LEVEL_INFO); 157 | 158 | const int port = 8001; 159 | 160 | sockaddr_in addr{}; 161 | addr.sin_port = htons(port); 162 | addr.sin_family = AF_INET; 163 | 164 | listenfd = socket(PF_INET, SOCK_STREAM, 0); 165 | int ret = bind(listenfd, (sockaddr*)&addr, sizeof(addr)); 166 | ERROR_IF(ret == -1, "bind"); 167 | 168 | ret = listen(listenfd, USER_LIMIT); 169 | 170 | users = new ClientData[USER_LIMIT + 1]{}; 171 | pid_to_user_sub = new int[PROCESS_LIMIT]; 172 | for (int i = 0; i < PROCESS_LIMIT; ++i) 173 | { 174 | pid_to_user_sub[i] = -1; 175 | } 176 | 177 | epoll_event events[MAX_EVENT_NUMBER]; 178 | epollfd = epoll_create(5); 179 | Addfd(epollfd, listenfd); 180 | 181 | AddSignal(SIGCHLD, SignalHandler); 182 | AddSignal(SIGTERM, SignalHandler); 183 | AddSignal(SIGINT, SignalHandler); 184 | AddSignal(SIGPIPE, SignalHandler); 185 | 186 | shmfd = shm_open(shm_name, O_CREAT | O_RDWR, 0666); 187 | ERROR_IF(shmfd == -1, "shm open"); 188 | 189 | ret = ftruncate(shmfd, USER_LIMIT * BUFFER_SIZE); 190 | ERROR_IF(ret == -1, "ftruncate"); 191 | 192 | share_mem = (char*)mmap(nullptr, USER_LIMIT * BUFFER_SIZE, 193 | PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0); 194 | ERROR_IF(share_mem == MAP_FAILED, "share_mem"); 195 | close(shmfd); 196 | 197 | bool stop_server = false; 198 | bool terminate = false; 199 | 200 | while (!stop_server) 201 | { 202 | int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1); 203 | if ((number < 0) && (errno != EINTR)) 204 | { 205 | LOG_WARN("epoll_wait failed"); 206 | break; 207 | } 208 | 209 | for (int i = 0; i < number; ++i) 210 | { 211 | int sockfd = events[i].data.fd; 212 | 213 | if (sockfd == listenfd) 214 | { 215 | sockaddr_in client_addr{}; 216 | socklen_t client_addrlength = sizeof(client_addr); 217 | int connfd = accept(listenfd, (sockaddr*)&client_addr, &client_addrlength); 218 | 219 | if (connfd < 0) 220 | { 221 | LOG_WARN("accept failed"); 222 | continue; 223 | } 224 | 225 | if (user_count >= USER_LIMIT) 226 | { 227 | const char *info = "too many users\n"; 228 | LOG_WARN("%s", info); 229 | send(connfd, info, strlen(info), 0); 230 | close(connfd); 231 | continue; 232 | } 233 | 234 | users[user_count].address = client_addr; 235 | users[user_count].connfd = connfd; 236 | ret = socketpair(PF_UNIX, SOCK_STREAM, 0, users[user_count].pipefd); 237 | ERROR_IF(ret == -1, "socket pair"); 238 | pid_t pid = fork(); 239 | if (pid < 0) 240 | { 241 | close(connfd); 242 | continue; 243 | } 244 | else if (pid == 0) 245 | { 246 | close(epollfd); 247 | close(listenfd); 248 | close(users[user_count].pipefd[0]); 249 | close(sig_pipe[0]); 250 | close(sig_pipe[1]); 251 | 252 | RunChild(user_count, users, share_mem); 253 | munmap((void*)share_mem, USER_LIMIT * BUFFER_SIZE); 254 | exit(0); 255 | } 256 | else 257 | { 258 | close(connfd); 259 | close(users[user_count].pipefd[1]); 260 | Addfd(epollfd, users[user_count].pipefd[0]); 261 | users[user_count].pid = pid; 262 | pid_to_user_sub[pid] = user_count; 263 | user_count++; 264 | } 265 | } 266 | else if ((sockfd == sig_pipe[0]) && (events[i].events & EPOLLIN)) 267 | { 268 | int sig; 269 | char signals[1024]{}; 270 | ret = recv(sockfd, signals, sizeof(signals), 0); 271 | if (ret <= 0) 272 | { 273 | continue; 274 | } 275 | else 276 | { 277 | for (int j = 0; j < ret; ++j) 278 | { 279 | switch (signals[i]) 280 | { 281 | case SIGCHLD: 282 | { 283 | pid_t pid{}; 284 | int stat; 285 | while ((pid == waitpid(-1, &stat, WNOHANG)) > 0) 286 | { 287 | int del_user_sub = pid_to_user_sub[pid]; 288 | pid_to_user_sub[pid] = -1; 289 | if ((del_user_sub < 0) || (del_user_sub > USER_LIMIT)) 290 | { 291 | continue; 292 | } 293 | epoll_ctl(epollfd, EPOLL_CTL_DEL, users[del_user_sub].pipefd[0], 0); 294 | close(users[del_user_sub].pipefd[0]); 295 | 296 | users[del_user_sub] = users[--user_count]; 297 | // 现在del_user_sub 是有效的 从尾部移动过来的 298 | pid_to_user_sub[users[del_user_sub].pid] = del_user_sub; 299 | } 300 | if (terminate && user_count == 0) 301 | { 302 | stop_server = true; 303 | } 304 | break; 305 | } 306 | case SIGTERM: 307 | case SIGINT: 308 | { 309 | LOG_WARN("kill al child now\n"); 310 | if (user_count == 0) 311 | { 312 | stop_server = true; 313 | break; 314 | } 315 | for (int i1 = 0; i1 < user_count; ++i1) 316 | { 317 | int pid = users[i].pid; 318 | kill(pid, SIGTERM); 319 | } 320 | terminate = true; 321 | break; 322 | } 323 | default: 324 | break; 325 | } 326 | } 327 | } 328 | } 329 | else if (events[i].events & EPOLLIN) 330 | { 331 | int child = 0; 332 | ret = recv(sockfd, (char*)&child, sizeof(child), 0); 333 | LOG_INFO("read data from child accross pipe\n"); 334 | if (ret <= 0) 335 | { 336 | continue; 337 | } 338 | else 339 | { 340 | for (int j = 0; j < user_count; ++j) 341 | { 342 | if (users[j].pipefd[0] != sockfd) 343 | { 344 | LOG_INFO("send data to child accross pie"); 345 | send(users[j].pipefd[0], (char*)&child, sizeof(child), 0); 346 | } 347 | } 348 | } 349 | } 350 | } 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /15. 进程间通信/read_msg.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct msg_buf 6 | { 7 | long mtype; 8 | char mtext[256]; 9 | }; 10 | 11 | int main() 12 | { 13 | int msqid = msgget(123, 0666 |IPC_CREAT); 14 | if (msqid != -1) 15 | { 16 | msg_buf msg_b{}; 17 | 18 | if (msgrcv(msqid, &msg_b, sizeof(msg_b.mtext), 0, IPC_NOWAIT) != -1) 19 | { 20 | printf("read success: %s\n", msg_b.mtext); 21 | 22 | msgctl(msqid, IPC_RMID, 0); 23 | } 24 | } 25 | 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /15. 进程间通信/write_msg.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct msg_buf 6 | { 7 | long mtype; 8 | char mtext[256]; 9 | }; 10 | 11 | int main() 12 | { 13 | int msqid = msgget(123, 0666 |IPC_CREAT); 14 | if (msqid != -1) 15 | { 16 | msg_buf msg_b{}; 17 | msg_b.mtype = 1; 18 | strcpy(msg_b.mtext, "I'm send process\n"); 19 | 20 | if (msgsnd(msqid, &msg_b, sizeof(msg_b.mtext), 0) == 0) 21 | { 22 | printf("send success\n"); 23 | } 24 | else 25 | { 26 | printf("send failure\n"); 27 | } 28 | } 29 | 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /16.线程同步/locker.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by lsmg on 3/23/20. 3 | // 4 | 5 | #ifndef PTHREAD_LOCKER_H 6 | #define PTHREAD_LOCKER_H 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | class Sem 13 | { 14 | Sem() 15 | { 16 | if (sem_init(&sem_, 0, 0) != 0) 17 | { 18 | throw std::exception(); 19 | } 20 | } 21 | ~Sem() 22 | { 23 | sem_destroy(&sem_); 24 | } 25 | bool Wait() 26 | { 27 | return sem_wait(&sem_) == 0; 28 | } 29 | bool Post() 30 | { 31 | return sem_post(&sem_) == 0; 32 | } 33 | private: 34 | sem_t sem_; 35 | }; 36 | 37 | class Mutex 38 | { 39 | Mutex() 40 | { 41 | if (pthread_mutex_init(&mutex_, nullptr) != 0) 42 | { 43 | throw std::exception(); 44 | } 45 | 46 | } 47 | ~Mutex() 48 | { 49 | pthread_mutex_destroy(&mutex_); 50 | } 51 | bool Lock() 52 | { 53 | return pthread_mutex_lock(&mutex_) == 0; 54 | } 55 | bool Unlock() 56 | { 57 | return pthread_mutex_unlock(&mutex_) == 0; 58 | } 59 | 60 | private: 61 | pthread_mutex_t mutex_; 62 | }; 63 | 64 | class Cond 65 | { 66 | public: 67 | Cond() 68 | { 69 | if (pthread_mutex_init(&mutex_, nullptr) != 0) 70 | { 71 | throw std::exception(); 72 | } 73 | if (pthread_cond_init(&cond_, nullptr) != 0) 74 | { 75 | // 76 | pthread_mutex_destroy(&mutex_); 77 | throw std::exception(); 78 | } 79 | } 80 | ~Cond() 81 | { 82 | pthread_mutex_destroy(&mutex_); 83 | pthread_cond_destroy(&cond_); 84 | }; 85 | bool Wait() 86 | { 87 | int ret = 0; 88 | pthread_mutex_lock(&mutex_); 89 | ret = pthread_cond_wait(&cond_, &mutex_); 90 | pthread_mutex_unlock(&mutex_); 91 | return ret == 0; 92 | } 93 | bool Signal() 94 | { 95 | return pthread_cond_signal(&cond_) == 0; 96 | } 97 | private: 98 | pthread_cond_t cond_; 99 | pthread_mutex_t mutex_; 100 | }; 101 | 102 | #endif //PTHREAD_LOCKER_H 103 | -------------------------------------------------------------------------------- /17.简单http服务器 修改版 支持大文件发送/httpconnection.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by lsmg on 3/24/20. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "httpconnection.h" 16 | 17 | const char * const OK_200_TITILE = "OK"; 18 | const char * const ERROR_400_TITILE = "Bad Request"; 19 | const char * const ERROR_400_FORM = "Your request has bad syntax or is inherently impossible to satisfy.\n"; 20 | const char * const ERROR_403_TITLE = "Forbidden"; 21 | const char * const ERROR_403_FORM = "You don't have the permission to get file from this server.\n"; 22 | const char * const ERROR_404_TITLE = "Not Fount"; 23 | const char * const ERROR_404_FORM = "The requested file was not found on this server.\n"; 24 | const char * const ERROR_500_TITLE = "Internal Error"; 25 | const char * const ERROR_500_FORM = "There was an unusual problem serving the requested file.\n"; 26 | 27 | // website roor dir 28 | const char * const DOC_ROOT = "/home/lsmg/web"; 29 | 30 | int SetNonblocking(int fd) 31 | { 32 | int old_option = fcntl(fd, F_GETFL); 33 | int new_option = old_option | O_NONBLOCK; 34 | fcntl(fd, F_SETFL, new_option); 35 | return old_option; 36 | } 37 | 38 | void Addfd(int epollfd, int fd, bool one_shot) 39 | { 40 | struct epoll_event ev{}; 41 | ev.data.fd = fd; 42 | ev.events = EPOLLIN | EPOLLET | EPOLLRDHUP; 43 | if (one_shot) 44 | { 45 | // 单个fd只触发一次epollwait返回 之后需要重新注册 46 | ev.events |= EPOLLONESHOT; 47 | } 48 | epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev); 49 | SetNonblocking(fd); 50 | } 51 | 52 | void Removefd(int epollfd, int fd) 53 | { 54 | epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, nullptr); 55 | close(fd); 56 | } 57 | 58 | void Modfd(int epollfd, int fd, int event) 59 | { 60 | struct epoll_event ev{}; 61 | ev.events = event | EPOLLET | EPOLLONESHOT | EPOLLRDHUP; 62 | ev.data.fd = fd; 63 | epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev); 64 | } 65 | 66 | int HttpConnection::user_count_ = 0; 67 | int HttpConnection::epollfd_ = -1; 68 | 69 | HttpConnection::HttpConnection(): 70 | write_buff_(DEFAULT_WRITE_BUFFER_SIZE) 71 | { 72 | 73 | } 74 | 75 | HttpConnection::~HttpConnection() 76 | { 77 | 78 | } 79 | 80 | void HttpConnection::Init(int sockfd, const sockaddr_in &addr) 81 | { 82 | sockfd_ = sockfd; 83 | address_ = addr; 84 | 85 | Addfd(epollfd_, sockfd_, true); 86 | user_count_++; 87 | Init(); 88 | } 89 | 90 | void HttpConnection::CloseConn(bool read_close) 91 | { 92 | if (read_close && (sockfd_ != -1)) 93 | { 94 | printf("fd: %d close connection\n", sockfd_); 95 | Removefd(epollfd_, sockfd_); 96 | sockfd_ = -1; 97 | user_count_--; 98 | } 99 | } 100 | 101 | void HttpConnection::Process() 102 | { 103 | HttpCode read_ret = ProcessRead(); 104 | if (read_ret == kNoRequest) 105 | { 106 | Modfd(epollfd_, sockfd_, EPOLLIN); 107 | return; 108 | } 109 | bool write_ret = ProcessWrite(read_ret); 110 | if (!write_ret) 111 | { 112 | CloseConn(); 113 | } 114 | Modfd(epollfd_, sockfd_, EPOLLOUT); 115 | } 116 | 117 | bool HttpConnection::Read() 118 | { 119 | if (read_idx_ >= READ_BUFFER_SIZE) 120 | { 121 | return false; 122 | } 123 | int bytes_read = 0; 124 | while (true) 125 | { 126 | bytes_read = recv(sockfd_, read_buff_ + read_idx_, READ_BUFFER_SIZE - read_idx_, 0); 127 | 128 | if (bytes_read == -1) 129 | { 130 | if (errno == EAGAIN || errno == EWOULDBLOCK) 131 | { 132 | break; 133 | } 134 | return false; 135 | } 136 | else if (bytes_read == 0) 137 | { 138 | return false; 139 | } 140 | read_idx_ += bytes_read; 141 | } 142 | return true; 143 | } 144 | 145 | bool HttpConnection::Write() 146 | { 147 | int temp = 0; 148 | if (write_sum_ - write_idx_ == 0) 149 | { 150 | Modfd(epollfd_, sockfd_, EPOLLIN); 151 | Init(); 152 | return true; 153 | } 154 | while (true) 155 | { 156 | temp = send(sockfd_, &*write_buff_.begin() + write_idx_, write_sum_ - write_idx_, 0); 157 | if (temp <= -1) 158 | { 159 | if (errno == EAGAIN) 160 | { 161 | Modfd(epollfd_, sockfd_, EPOLLOUT); 162 | return true; 163 | } 164 | } 165 | write_idx_ += temp; 166 | 167 | if (write_idx_ == write_sum_) 168 | { 169 | if (linger_) 170 | { 171 | Init(); 172 | Modfd(epollfd_, sockfd_, EPOLLIN); 173 | return true; 174 | } 175 | else 176 | { 177 | Modfd(epollfd_, sockfd_, EPOLLIN); 178 | return false; 179 | } 180 | } 181 | } 182 | } 183 | 184 | void HttpConnection::Init() 185 | { 186 | check_state_ = kCheckStateRequestLine; 187 | linger_ = false; 188 | method_ = kGet; 189 | url_ = nullptr; 190 | version_ = nullptr; 191 | host_ = nullptr; 192 | content_length_ = 0; 193 | 194 | start_line_ = 0; 195 | checked_idx_ = 0; 196 | read_idx_ = 0; 197 | write_idx_ = 0; 198 | write_sum_ = 0; 199 | memset(read_buff_, '\0', READ_BUFFER_SIZE); 200 | memset(full_file_, '\0', READ_BUFFER_SIZE); 201 | } 202 | 203 | HttpConnection::HttpCode HttpConnection::ProcessRead() 204 | { 205 | LineStatus line_status = kLineOk; 206 | HttpCode ret = kNoRequest; 207 | char *text = nullptr; 208 | while (((check_state_ == kCheckStateContent) && (line_status == kLineOk)) || 209 | ((line_status = ParseLine()) == kLineOk)) 210 | { 211 | text = GetLine(); 212 | start_line_ = checked_idx_; 213 | switch (check_state_) 214 | { 215 | case kCheckStateRequestLine: 216 | { 217 | ret = ParseRequestLine(text); 218 | if (ret == kBadRequest) 219 | { 220 | return kBadRequest; 221 | } 222 | break; 223 | } 224 | case kCheckStateHeader: 225 | { 226 | ret = ParseHeaders(text); 227 | if (ret == kBadRequest) 228 | { 229 | return kBadRequest; 230 | } 231 | if (ret == kGetRequest) 232 | { 233 | return DoRequest(); 234 | } 235 | break; 236 | } 237 | case kCheckStateContent: 238 | { 239 | ret = ParseContent(text); 240 | if (ret == kGetRequest) 241 | { 242 | return DoRequest(); 243 | } 244 | line_status = kLineOpen; 245 | break; 246 | } 247 | default: 248 | { 249 | return kInternalError; 250 | } 251 | } 252 | } 253 | return kNoRequest; 254 | } 255 | 256 | bool HttpConnection::ProcessWrite(HttpConnection::HttpCode ret) 257 | { 258 | switch (ret) 259 | { 260 | case kBadRequest: 261 | { 262 | AddStatusLine(400, ERROR_400_TITILE); 263 | AddHeaders(strlen(ERROR_400_FORM)); 264 | if (!AddContent(ERROR_400_FORM)) 265 | { 266 | return false; 267 | } 268 | break; 269 | } 270 | case kNoResource: 271 | { 272 | AddStatusLine(404, ERROR_404_TITLE); 273 | AddHeaders(strlen(ERROR_404_FORM)); 274 | if (!AddContent(ERROR_404_FORM)) 275 | { 276 | return false; 277 | } 278 | break; 279 | } 280 | case kForbiddenRequest: 281 | { 282 | AddStatusLine(403, ERROR_403_TITLE); 283 | AddHeaders(strlen(ERROR_403_FORM)); 284 | if (!AddContent(ERROR_403_FORM)) 285 | { 286 | return false; 287 | } 288 | break; 289 | } 290 | case kFileRequest: 291 | { 292 | AddStatusLine(200, OK_200_TITILE); 293 | if (file_stat_.st_size != 0) 294 | { 295 | AddHeaders(file_stat_.st_size); 296 | int filesize = file_stat_.st_size; 297 | 298 | write_buff_.resize(DEFAULT_WRITE_BUFFER_SIZE + file_stat_.st_size); 299 | std::copy(file_address_, file_address_ + filesize, &*write_buff_.begin() + write_sum_); 300 | write_sum_ += filesize; 301 | Unmap(); 302 | return true; 303 | } 304 | else 305 | { 306 | const char * ok_string = ""; 307 | AddHeaders(strlen(ok_string)); 308 | if (!AddContent(ok_string)) 309 | { 310 | return false; 311 | } 312 | } 313 | } 314 | case kInternalError: 315 | { 316 | AddStatusLine(500, ERROR_500_TITLE); 317 | AddHeaders(strlen(ERROR_500_FORM)); 318 | if (!AddContent(ERROR_500_FORM)) 319 | { 320 | return false; 321 | } 322 | break; 323 | } 324 | default: 325 | { 326 | return false; 327 | } 328 | } 329 | return true; 330 | } 331 | 332 | HttpConnection::HttpCode HttpConnection::ParseRequestLine(char *text) 333 | { 334 | url_ = strpbrk(text, " \t"); 335 | if (!url_) 336 | { 337 | return kBadRequest; 338 | } 339 | // 去处 空格或者 \t 340 | *url_++ = '\0'; 341 | 342 | char *method = text; 343 | if (strcasecmp(method, "GET") == 0) 344 | { 345 | method_ = kGet; 346 | } 347 | else 348 | { 349 | return kBadRequest; 350 | } 351 | // 又去除一次空格?? 352 | url_ += strspn(url_, " \t"); 353 | version_ = strpbrk(url_, " \t"); 354 | if (!version_) 355 | { 356 | return kBadRequest; 357 | } 358 | *version_++ = '\0'; 359 | version_ += strspn(version_, " \t"); 360 | if (strcasecmp(version_, "HTTP/1.1") != 0) 361 | { 362 | if (strcasecmp(version_, "HTTP/1.0") != 0) 363 | { 364 | return kBadRequest; 365 | } 366 | linger_ = false; 367 | } 368 | if (strncasecmp(url_, "http://", 7) == 0) 369 | { 370 | url_ += 7; 371 | url_ = strchr(url_, '/'); 372 | } 373 | if (!url_ || url_[0] != '/') 374 | { 375 | return kBadRequest; 376 | } 377 | check_state_ = kCheckStateHeader; 378 | return kNoRequest; 379 | } 380 | 381 | HttpConnection::HttpCode HttpConnection::ParseHeaders(char *text) 382 | { 383 | // 读取到空行 说明头部解析完毕 384 | if (text[0] == '\0') 385 | { 386 | // 如果有消息体 则需要解析消息体 387 | if (content_length_ != 0) 388 | { 389 | check_state_ = kCheckStateContent; 390 | return kNoRequest; 391 | } 392 | return kGetRequest; 393 | } 394 | else if (strncasecmp(text, "Connection:", 11) == 0) 395 | { 396 | text += 11; 397 | text += strspn(text, " \t"); 398 | if (strcasecmp(text, "keep-alive") == 0) 399 | { 400 | linger_ = true; 401 | } 402 | } 403 | else if (strncasecmp(text, "Content-Length:", 15) == 0) 404 | { 405 | text += 15; 406 | text += strspn(text, " \t"); 407 | content_length_ = atol(text); 408 | } 409 | else if (strncasecmp(text, "Host:", 5) == 0) 410 | { 411 | text += 5; 412 | text += strspn(text, " \t"); 413 | host_ = text; 414 | } 415 | else 416 | { 417 | 418 | } 419 | return kNoRequest; 420 | } 421 | 422 | HttpConnection::HttpCode HttpConnection::ParseContent(char *text) 423 | { 424 | if (read_idx_ >= content_length_ + checked_idx_) 425 | { 426 | text[content_length_] = '\0'; 427 | return kGetRequest; 428 | } 429 | return kNoRequest; 430 | } 431 | 432 | HttpConnection::HttpCode HttpConnection::DoRequest() 433 | { 434 | strcpy(full_file_, DOC_ROOT); 435 | int len = strlen(DOC_ROOT); 436 | strncpy(full_file_ + len, url_, FILENAME_MAXLEN - len - 1); 437 | if (stat(full_file_, &file_stat_) < 0) 438 | { 439 | return kNoResource; 440 | } 441 | if (!(file_stat_.st_mode & S_IROTH)) 442 | { 443 | return kForbiddenRequest; 444 | } 445 | if (S_ISDIR(file_stat_.st_mode)) 446 | { 447 | return kBadRequest; 448 | } 449 | int fd = open(full_file_, O_RDONLY); 450 | file_address_ = static_cast(mmap(nullptr, file_stat_.st_size, PROT_READ, 451 | MAP_PRIVATE, fd, 0)); 452 | close(fd); 453 | 454 | return kFileRequest; 455 | } 456 | 457 | char *HttpConnection::GetLine() 458 | { 459 | return read_buff_ + start_line_; 460 | } 461 | 462 | HttpConnection::LineStatus HttpConnection::ParseLine() 463 | { 464 | char temp = '\0'; 465 | for (;checked_idx_ < read_idx_; ++checked_idx_) 466 | { 467 | temp = read_buff_[checked_idx_]; 468 | if (temp == '\r') 469 | { 470 | if (checked_idx_ + 1 == read_idx_) 471 | { 472 | return kLineOpen; 473 | } 474 | else if (read_buff_[checked_idx_ + 1] == '\n') 475 | { 476 | read_buff_[checked_idx_++] = '\0'; 477 | read_buff_[checked_idx_++] = '\0'; 478 | return kLineOk; 479 | } 480 | return kLineBad; 481 | } 482 | else if (temp == '\n') 483 | { 484 | if (checked_idx_ > 1 && read_buff_[checked_idx_ - 1] == '\r') 485 | { 486 | read_buff_[checked_idx_ - 1] = '\0'; 487 | read_buff_[checked_idx_++] = '\0'; 488 | return kLineOk; 489 | } 490 | return kLineBad; 491 | } 492 | } 493 | return kLineOpen; 494 | } 495 | 496 | void HttpConnection::Unmap() 497 | { 498 | if (file_address_) 499 | { 500 | munmap(file_address_, file_stat_.st_size); 501 | file_address_ = nullptr; 502 | } 503 | } 504 | 505 | bool HttpConnection::AddResponse(const char *format, ...) 506 | { 507 | if (write_sum_ >= DEFAULT_WRITE_BUFFER_SIZE) 508 | { 509 | return false; 510 | } 511 | va_list arg_list; 512 | va_start(arg_list, format); 513 | int len = vsnprintf(&*write_buff_.begin() + write_sum_, DEFAULT_WRITE_BUFFER_SIZE - 1 - write_sum_, 514 | format, arg_list); 515 | if (len >= (DEFAULT_WRITE_BUFFER_SIZE - 1 - write_sum_)) 516 | { 517 | return false; 518 | } 519 | write_sum_ += len; 520 | va_end(arg_list); 521 | return true; 522 | } 523 | 524 | bool HttpConnection::AddContent(const char *content) 525 | { 526 | return AddResponse("%s", content); 527 | } 528 | 529 | bool HttpConnection::AddStatusLine(int status, const char *title) 530 | { 531 | return AddResponse("%s %d %s\r\n", "HTTP/1.1", status, title); 532 | } 533 | 534 | bool HttpConnection::AddHeaders(int content_length) 535 | { 536 | AddContentLength(content_length); 537 | AddLinger(); 538 | AddBlankLine(); 539 | } 540 | 541 | bool HttpConnection::AddContentLength(int content_length) 542 | { 543 | return AddResponse("Content-Length: %d\r\n", content_length); 544 | } 545 | 546 | bool HttpConnection::AddLinger() { 547 | return AddResponse("Connection: %s\r\n", linger_ ? "keep-alive" : "close"); 548 | } 549 | 550 | bool HttpConnection::AddBlankLine() 551 | { 552 | return AddResponse("%s", "\r\n"); 553 | } 554 | -------------------------------------------------------------------------------- /17.简单http服务器 修改版 支持大文件发送/httpconnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by lsmg on 3/24/20. 3 | // 4 | 5 | #ifndef PTHREAD_HTTPCONNECTION_H 6 | #define PTHREAD_HTTPCONNECTION_H 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | class HttpConnection 13 | { 14 | public: 15 | const static int FILENAME_MAXLEN = 200; 16 | const static int READ_BUFFER_SIZE = 2048; 17 | const static int DEFAULT_WRITE_BUFFER_SIZE = 1024; 18 | 19 | enum Method {kGet = 0, kPost, kPut, kDelete}; 20 | 21 | enum CheckState {kCheckStateRequestLine = 0, kCheckStateHeader, kCheckStateContent}; 22 | 23 | enum HttpCode {kNoRequest, kGetRequest, kBadRequest, kNoResource, kForbiddenRequest, 24 | kFileRequest, kInternalError, kClosedConnection}; 25 | 26 | enum LineStatus {kLineOk = 0, kLineBad, kLineOpen}; 27 | 28 | public: 29 | HttpConnection(); 30 | ~HttpConnection(); 31 | 32 | public: 33 | /** 34 | * 初始化接受新的连接 35 | * @param sockfd 36 | * @param addr 37 | */ 38 | void Init(int sockfd, const sockaddr_in &addr); 39 | 40 | /** 41 | * 关闭连接 42 | * @param read_close 43 | */ 44 | void CloseConn(bool read_close = true); 45 | 46 | /** 47 | * 处理请求 48 | */ 49 | void Process(); 50 | 51 | /** 52 | * 非阻塞读操作 53 | * @return 54 | */ 55 | bool Read(); 56 | 57 | /** 58 | * 非阻塞写操作 59 | * @return 60 | */ 61 | bool Write(); 62 | 63 | private: 64 | /** 65 | * 初始化连接 66 | */ 67 | void Init(); 68 | 69 | /** 70 | * 解析Http请求 71 | * @return 72 | */ 73 | HttpCode ProcessRead(); 74 | 75 | /** 76 | * 填充HTTP应答 77 | * @param ret 78 | * @return 79 | */ 80 | bool ProcessWrite(HttpCode ret); 81 | 82 | // 用于解析Http请求 83 | HttpCode ParseRequestLine(char *text); 84 | HttpCode ParseHeaders(char *text); 85 | HttpCode ParseContent(char *text); 86 | HttpCode DoRequest(); 87 | char* GetLine(); 88 | LineStatus ParseLine(); 89 | 90 | // 用于填充Http应答 91 | void Unmap(); 92 | bool AddResponse(const char *format, ...); 93 | bool AddContent(const char *content); 94 | bool AddStatusLine(int status, const char *title); 95 | bool AddHeaders(int content_length); 96 | bool AddContentLength(int content_length); 97 | bool AddLinger(); 98 | bool AddBlankLine(); 99 | 100 | public: 101 | static int epollfd_; 102 | 103 | // 统计用户数量 104 | static int user_count_; 105 | private: 106 | // 该Http连接的socket和对方的socket地址 107 | int sockfd_; 108 | sockaddr_in address_; 109 | 110 | char read_buff_[READ_BUFFER_SIZE]; 111 | int read_idx_; 112 | int checked_idx_; 113 | int start_line_; 114 | 115 | std::vector write_buff_; 116 | int write_idx_; 117 | int write_sum_; 118 | 119 | // 状态机当前状态 120 | CheckState check_state_; 121 | Method method_; 122 | 123 | // 请求文件的完整路径名 124 | char full_file_[FILENAME_MAXLEN]; 125 | 126 | // request中的文件名 127 | char *url_; 128 | char *version_; 129 | char *host_; 130 | int content_length_; 131 | bool linger_; 132 | 133 | // 请求的目标文件被mmap到内存中的起始位置 134 | char *file_address_; 135 | struct stat file_stat_; 136 | }; 137 | 138 | #endif //PTHREAD_HTTPCONNECTION_H 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /17.简单http服务器 修改版 支持大文件发送/locker.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by lsmg on 3/23/20. 3 | // 4 | 5 | #ifndef PTHREAD_LOCKER_H 6 | #define PTHREAD_LOCKER_H 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | class Sem 13 | { 14 | public: 15 | Sem() 16 | { 17 | if (sem_init(&sem_, 0, 0) != 0) 18 | { 19 | throw std::exception(); 20 | } 21 | } 22 | ~Sem() 23 | { 24 | sem_destroy(&sem_); 25 | } 26 | bool Wait() 27 | { 28 | return sem_wait(&sem_) == 0; 29 | } 30 | bool Post() 31 | { 32 | return sem_post(&sem_) == 0; 33 | } 34 | private: 35 | sem_t sem_; 36 | }; 37 | 38 | class Mutex 39 | { 40 | public: 41 | Mutex() 42 | { 43 | if (pthread_mutex_init(&mutex_, nullptr) != 0) 44 | { 45 | throw std::exception(); 46 | } 47 | 48 | } 49 | ~Mutex() 50 | { 51 | pthread_mutex_destroy(&mutex_); 52 | } 53 | bool Lock() 54 | { 55 | return pthread_mutex_lock(&mutex_) == 0; 56 | } 57 | bool Unlock() 58 | { 59 | return pthread_mutex_unlock(&mutex_) == 0; 60 | } 61 | 62 | private: 63 | pthread_mutex_t mutex_; 64 | }; 65 | 66 | class Cond 67 | { 68 | public: 69 | Cond() 70 | { 71 | if (pthread_mutex_init(&mutex_, nullptr) != 0) 72 | { 73 | throw std::exception(); 74 | } 75 | if (pthread_cond_init(&cond_, nullptr) != 0) 76 | { 77 | // 78 | pthread_mutex_destroy(&mutex_); 79 | throw std::exception(); 80 | } 81 | } 82 | ~Cond() 83 | { 84 | pthread_mutex_destroy(&mutex_); 85 | pthread_cond_destroy(&cond_); 86 | }; 87 | bool Wait() 88 | { 89 | int ret = 0; 90 | pthread_mutex_lock(&mutex_); 91 | ret = pthread_cond_wait(&cond_, &mutex_); 92 | pthread_mutex_unlock(&mutex_); 93 | return ret == 0; 94 | } 95 | bool Signal() 96 | { 97 | return pthread_cond_signal(&cond_) == 0; 98 | } 99 | private: 100 | pthread_cond_t cond_; 101 | pthread_mutex_t mutex_; 102 | }; 103 | 104 | #endif //PTHREAD_LOCKER_H 105 | -------------------------------------------------------------------------------- /17.简单http服务器 修改版 支持大文件发送/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "locker.h" 11 | #include "httpconnection.h" 12 | #include "threadpool.h" 13 | 14 | 15 | 16 | constexpr int MAX_FD = 65535; 17 | constexpr int MAX_EVENT_NUMBER = 10000; 18 | 19 | extern int Addfd(int epollfd, int fd, bool one_shot); 20 | extern int Removefd(int epollfd, int fd); 21 | 22 | void AddSig(int sig, void(handler)(int), bool restart = true) 23 | { 24 | struct sigaction sa{}; 25 | sa.sa_handler = handler; 26 | if (restart) 27 | { 28 | sa.sa_flags |= SA_RESTART; 29 | } 30 | sigfillset(&sa.sa_mask); 31 | assert(sigaction(sig, &sa, nullptr) != -1); 32 | } 33 | 34 | void show_error(int connfd, const char *info) 35 | { 36 | printf("%s", info); 37 | send(connfd, info, strlen(info), 0); 38 | close(connfd); 39 | } 40 | 41 | int main(int argc, char *argv[]) 42 | { 43 | if (argc < 2) 44 | { 45 | printf("port need\n"); 46 | return 1; 47 | } 48 | int port = atoi(argv[1]); 49 | 50 | AddSig(SIGPIPE, SIG_IGN); 51 | 52 | ThreadPool *pool = nullptr; 53 | try 54 | { 55 | pool = new ThreadPool; 56 | } 57 | catch (...) 58 | { 59 | return 1; 60 | } 61 | 62 | HttpConnection *users = new HttpConnection[MAX_FD]; 63 | assert(users); 64 | int user_count = 0; 65 | 66 | int listenfd = socket(PF_INET, SOCK_STREAM, 0); 67 | assert(listenfd >= 0); 68 | 69 | struct linger temp{1, 0}; 70 | setsockopt(listenfd, SOL_SOCKET, SO_LINGER, &temp, sizeof(temp)); 71 | 72 | int ret = 0; 73 | struct sockaddr_in address{}; 74 | address.sin_port = htons(port); 75 | address.sin_family = AF_INET; 76 | 77 | ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address)); 78 | assert(ret >= 0); 79 | 80 | ret = listen(listenfd, 5); 81 | 82 | struct epoll_event events[MAX_EVENT_NUMBER]; 83 | int epollfd = epoll_create(5); 84 | assert(epollfd != -1); 85 | Addfd(epollfd, listenfd, false); 86 | HttpConnection::epollfd_ = epollfd; 87 | 88 | while (true) 89 | { 90 | int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1); 91 | if ((number < 0) && (errno != EINTR)) 92 | { 93 | printf("epoll failure\n"); 94 | break; 95 | } 96 | for (int i = 0; i < number; ++i) 97 | { 98 | int sockfd = events[i].data.fd; 99 | int what = events[i].events; 100 | if (sockfd == listenfd) 101 | { 102 | struct sockaddr_in client_address{}; 103 | socklen_t client_addresslen = sizeof(client_address); 104 | int connfd = accept(listenfd, (sockaddr*)&client_address, &client_addresslen); 105 | if (connfd < 0) 106 | { 107 | printf("errno is: %d\n", errno); 108 | continue; 109 | } 110 | if (HttpConnection::user_count_ >= MAX_FD) 111 | { 112 | show_error(connfd, "Internal server busy"); 113 | continue; 114 | } 115 | users[connfd].Init(connfd, client_address); 116 | } 117 | else if (what & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) 118 | { 119 | users[sockfd].CloseConn(); 120 | } 121 | else if (what & EPOLLIN) 122 | { 123 | if (users[sockfd].Read()) 124 | { 125 | pool->Append(users + sockfd); 126 | } 127 | else 128 | { 129 | users[sockfd].CloseConn(); 130 | } 131 | } 132 | else if (what & EPOLLOUT) 133 | { 134 | if (!users[sockfd].Write()) 135 | { 136 | users[sockfd].CloseConn(); 137 | } 138 | } 139 | } 140 | } 141 | close(epollfd); 142 | close(listenfd); 143 | delete [] users; 144 | delete pool; 145 | return 0; 146 | } 147 | -------------------------------------------------------------------------------- /17.简单http服务器 修改版 支持大文件发送/threadpool.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by lsmg on 3/24/20. 3 | // 4 | 5 | #include "threadpool.h" 6 | #include "httpconnection.h" 7 | 8 | template class ThreadPool; 9 | 10 | template 11 | ThreadPool::ThreadPool(int threadnum, int maxtasks): 12 | thread_number_(threadnum), max_tasks_(maxtasks), stop_(false) 13 | { 14 | if (thread_number_ <= 0 || max_tasks_ <= 0) 15 | { 16 | throw std::exception(); 17 | } 18 | threads_ = new pthread_t[thread_number_]; 19 | if (!threads_) 20 | { 21 | throw std::exception(); 22 | } 23 | 24 | for (int i = 0; i < thread_number_; ++i) 25 | { 26 | if (pthread_create(&threads_[i], nullptr, Work, static_cast(this)) != 0) 27 | { 28 | delete [] threads_; 29 | throw std::exception(); 30 | } 31 | if (pthread_detach(threads_[i]) != 0) 32 | { 33 | delete [] threads_; 34 | throw std::exception(); 35 | } 36 | } 37 | } 38 | 39 | template 40 | ThreadPool::~ThreadPool() 41 | { 42 | delete []threads_; 43 | stop_ = true; 44 | } 45 | 46 | template 47 | void *ThreadPool::Work(void *arg) 48 | { 49 | ThreadPool *pool = static_cast(arg); 50 | pool->Run(); 51 | return pool; 52 | } 53 | 54 | template 55 | void ThreadPool::Run() 56 | { 57 | while (!stop_) 58 | { 59 | queuestat_.Wait(); 60 | 61 | queue_mutex_.Lock(); 62 | if (work_queue_.empty()) 63 | { 64 | queue_mutex_.Unlock(); 65 | continue; 66 | } 67 | T *request = work_queue_.front(); 68 | work_queue_.pop(); 69 | queue_mutex_.Unlock(); 70 | 71 | if (!request) 72 | { 73 | continue; 74 | } 75 | request->Process(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /17.简单http服务器 修改版 支持大文件发送/threadpool.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by lsmg on 3/24/20. 3 | // 4 | 5 | #ifndef PTHREAD_THREADPOOL_H 6 | #define PTHREAD_THREADPOOL_H 7 | 8 | #include 9 | #include "locker.h" 10 | 11 | template 12 | class ThreadPool 13 | { 14 | public: 15 | explicit ThreadPool(int threadnum = 8, int maxtasks = 10000); 16 | ~ThreadPool(); 17 | 18 | void Append(T* work) {work_queue_.push(work); queuestat_.Post();} 19 | private: 20 | static void* Work(void *arg); 21 | 22 | void Run(); 23 | private: 24 | // 线程数量 25 | int thread_number_; 26 | // 最大任务数量 27 | int max_tasks_; 28 | // 线程池数组, 大小为thread_number 29 | pthread_t *threads_; 30 | // 工作队列 31 | std::queue work_queue_; 32 | // 保护工作队列的锁 33 | Mutex queue_mutex_; 34 | // 是否有新任务要处理 35 | Sem queuestat_; 36 | // 是否结束线程 37 | bool stop_; 38 | }; 39 | 40 | #endif //PTHREAD_THREADPOOL_H 41 | -------------------------------------------------------------------------------- /2. 伪双工TCP/client.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiganFish/Notes-HighPerformanceLinuxServerProgramming/d1449f5974905d5464f8bee644504413da78208f/2. 伪双工TCP/client.c -------------------------------------------------------------------------------- /2. 伪双工TCP/server.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiganFish/Notes-HighPerformanceLinuxServerProgramming/d1449f5974905d5464f8bee644504413da78208f/2. 伪双工TCP/server.c -------------------------------------------------------------------------------- /3. 伪双工UDP/client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define BUFF_SIZE 1024 14 | 15 | int main(int argc, char* argv[]) { 16 | 17 | if(argc <= 2) { 18 | printf("error, argue is %d\n", argc); 19 | return 1; 20 | } 21 | 22 | char* ip = argv[1]; 23 | int port = atoi(argv[2]); 24 | 25 | struct sockaddr_in server_address; 26 | memset(&server_address, 0, sizeof(server_address)); 27 | inet_pton(AF_INET, ip, &server_address.sin_addr); 28 | server_address.sin_family = AF_INET; 29 | server_address.sin_port = htons(port); 30 | 31 | int sockfd = socket(PF_INET, SOCK_DGRAM, 0); 32 | assert(sockfd >= 0); 33 | 34 | int conn = connect(sockfd, (struct sockaddr*)&server_address, sizeof(server_address)); 35 | printf("error code is %d, error is %s\n", errno, strerror(errno)); 36 | assert(conn != -1); 37 | 38 | char send_buffer[BUFF_SIZE]; 39 | memset(send_buffer, '\0', BUFF_SIZE); 40 | 41 | char recv_buffer[BUFF_SIZE]; 42 | 43 | socklen_t serveraddress_len = sizeof(server_address); 44 | 45 | while(1) { 46 | 47 | memset(recv_buffer, '\0', BUFF_SIZE); 48 | recvfrom(sockfd, recv_buffer, BUFF_SIZE - 1, MSG_DONTWAIT, (struct sockaddr*)&server_address, &serveraddress_len); 49 | if(recv_buffer[0] != '\0') { 50 | printf("msg: %s\n",recv_buffer); 51 | } 52 | 53 | sleep(1); 54 | strcpy(send_buffer, "abv"); 55 | sendto(sockfd, send_buffer, BUFF_SIZE - 1, 0,(struct sockaddr*)&server_address, sizeof(server_address)); 56 | memset(send_buffer, '\0', BUFF_SIZE); 57 | } 58 | close(sockfd); 59 | return 0; 60 | } 61 | 62 | -------------------------------------------------------------------------------- /3. 伪双工UDP/server.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiganFish/Notes-HighPerformanceLinuxServerProgramming/d1449f5974905d5464f8bee644504413da78208f/3. 伪双工UDP/server.c -------------------------------------------------------------------------------- /4. 代码清单5-12 访问daytime服务/server.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiganFish/Notes-HighPerformanceLinuxServerProgramming/d1449f5974905d5464f8bee644504413da78208f/4. 代码清单5-12 访问daytime服务/server.c -------------------------------------------------------------------------------- /5. 代码清单6-3 用sendfile函数传输文件/server.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiganFish/Notes-HighPerformanceLinuxServerProgramming/d1449f5974905d5464f8bee644504413da78208f/5. 代码清单6-3 用sendfile函数传输文件/server.c -------------------------------------------------------------------------------- /6. 代码清单6-4 用splice函数实现的回射服务器/server.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiganFish/Notes-HighPerformanceLinuxServerProgramming/d1449f5974905d5464f8bee644504413da78208f/6. 代码清单6-4 用splice函数实现的回射服务器/server.c -------------------------------------------------------------------------------- /7. 基于TCP的贪吃蛇服务器/server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define BUFFER_SIZE 1024 13 | 14 | int main(int argc, char* argv[]) { 15 | 16 | if(argc <= 2) { 17 | printf("Wrong number of parameters!\n"); 18 | return 1; 19 | } 20 | 21 | char* ip = argv[1]; 22 | int port = atoi(argv[2]); 23 | 24 | // construct socket address 25 | struct sockaddr_in address; 26 | memset(&address, 0, sizeof(address)); 27 | address.sin_family = AF_INET; 28 | address.sin_port = htons(port); 29 | inet_pton(AF_INET, ip,&address.sin_addr); 30 | 31 | int sock = socket(PF_INET, SOCK_STREAM, 0); 32 | assert(sock >= 0); 33 | 34 | int ret = bind(sock, (struct sockaddr*)&address, sizeof(address)); 35 | assert(ret != -1); 36 | 37 | ret = listen(sock, 5); 38 | assert(ret != -1); 39 | 40 | struct sockaddr_in client; 41 | socklen_t client_length = sizeof(client); 42 | 43 | while(1) { 44 | 45 | int conn = accept(sock, (struct sockaddr*)&client, &client_length); 46 | 47 | char ip_client[INET_ADDRSTRLEN]; 48 | inet_ntop(AF_INET, &client.sin_addr, ip_client, INET_ADDRSTRLEN); 49 | int port_client; 50 | port_client = ntohs(client.sin_port); 51 | printf("A new client connect ip-%s, port-%d\n", ip_client, port_client); 52 | 53 | char buffer[BUFFER_SIZE]; 54 | while(1) { 55 | memset(buffer, '\0', BUFFER_SIZE); 56 | recv(conn, buffer, BUFFER_SIZE - 1, 0); 57 | if(buffer[0] == '\0') { 58 | printf("client is dead!\n"); 59 | break; 60 | } 61 | printf("recv %s",buffer); 62 | } 63 | } 64 | close(sock); 65 | return 0; 66 | } 67 | -------------------------------------------------------------------------------- /7. 基于TCP的贪吃蛇服务器/snake.c: -------------------------------------------------------------------------------- 1 | /********************************************************************************* 2 | * Copyright: (C) 2017 zoulei 3 | * All rights reserved. 4 | * 5 | * Filename: snake.c 6 | * Description: This file 7 | * 8 | * Version: 1.0.0(2017年09月09日) 9 | * Author: zoulei 10 | * ChangeLog: 1, Release initial version on "2017年09月09日 17时05分19秒" 11 | * 12 | ********************************************************************************/ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #define NUM 60 28 | 29 | struct direct //用来表示方向的 30 | { 31 | int cx; 32 | int cy; 33 | }; 34 | typedef struct node //链表的结点 35 | { 36 | int cx; 37 | int cy; 38 | struct node *back; 39 | struct node *next; 40 | }node; 41 | 42 | void initGame(); //初始化游戏 43 | int setTicker(int); //设置计时器 44 | void show(); //显示整个画面 45 | void showInformation(); //显示游戏信息(前两行) 46 | void showSnake(); //显示蛇的身体 47 | void getOrder(); //从键盘中获取命令 48 | void over(int i); //完成游戏结束后的提示信息 49 | 50 | void creatLink(); //(带头尾结点)双向链表以及它的操作 51 | void insertNode(int x, int y); 52 | void deleteNode(); 53 | void deleteLink(); 54 | 55 | int ch; //输入的命令 56 | int hour, minute, second; //时分秒 57 | int length, tTime, level; //(蛇的)长度,计时器,(游戏)等级 58 | struct direct dir, food; //蛇的前进方向,食物的位置 59 | node *head, *tail; //链表的头尾结点 60 | 61 | void connectToServer(char* ip, char* port); 62 | void sendMsgToServer(char* msg); 63 | int sock; 64 | 65 | int main(int argc, char* argv[]) 66 | { 67 | 68 | if(argc <= 2) { 69 | printf("Wrong number of parameters\n"); 70 | return 1; 71 | } 72 | 73 | connectToServer(argv[1], argv[2]); 74 | 75 | initscr(); 76 | initGame(); 77 | signal(SIGALRM, show); 78 | getOrder(); 79 | endwin(); 80 | return 0; 81 | } 82 | 83 | void initGame() 84 | { 85 | cbreak(); //把终端的CBREAK模式打开 86 | noecho(); //关闭回显 87 | curs_set(0); //把光标置为不可见 88 | keypad(stdscr, true); //使用用户终端的键盘上的小键盘 89 | srand(time(0)); //设置随机数种子 90 | //初始化各项数据 91 | hour = minute = second = tTime = 0; 92 | length = 1; 93 | dir.cx = 1; 94 | dir.cy = 0; 95 | ch = 'A'; 96 | food.cx = rand() % COLS; 97 | food.cy = rand() % (LINES-2) + 2; 98 | creatLink(); 99 | setTicker(20); 100 | } 101 | 102 | //设置计时器(这个函数是书本上的例子,有改动) 103 | int setTicker(int n_msecs) 104 | { 105 | struct itimerval new_timeset; 106 | long n_sec, n_usecs; 107 | 108 | n_sec = n_msecs / 1000 ; 109 | n_usecs = ( n_msecs % 1000 ) * 1000L ; 110 | new_timeset.it_interval.tv_sec = n_sec; 111 | new_timeset.it_interval.tv_usec = n_usecs; 112 | n_msecs = 1; 113 | n_sec = n_msecs / 1000 ; 114 | n_usecs = ( n_msecs % 1000 ) * 1000L ; 115 | new_timeset.it_value.tv_sec = n_sec ; 116 | new_timeset.it_value.tv_usec = n_usecs ; 117 | return setitimer(ITIMER_REAL, &new_timeset, NULL); 118 | } 119 | 120 | void showInformation() 121 | { 122 | tTime++; 123 | if(tTime >= 1000000) // 124 | tTime = 0; 125 | if(1 != tTime % 50) 126 | return; 127 | move(0, 3); 128 | //显示时间 129 | printw("time: %d:%d:%d %c", hour, minute, second); 130 | second++; 131 | if(second > NUM) 132 | { 133 | second = 0; 134 | minute++; 135 | } 136 | if(minute > NUM) 137 | { 138 | minute = 0; 139 | hour++; 140 | } 141 | //显示长度,等级 142 | move(1, 0); 143 | int i; 144 | for(i=0;inext->cx && 1==dir.cx) 167 | || (0==head->next->cx && -1==dir.cx) 168 | || (LINES-1==head->next->cy && 1==dir.cy) 169 | || (2==head->next->cy && -1==dir.cy)) 170 | { 171 | over(1); 172 | return; 173 | } 174 | //如果蛇头砬到自己的身体,则游戏结束 175 | if('*' == mvinch(head->next->cy+dir.cy, head->next->cx+dir.cx) ) 176 | { 177 | over(2); 178 | return; 179 | } 180 | insertNode(head->next->cx+dir.cx, head->next->cy+dir.cy); 181 | //蛇吃了一个“食物” 182 | if(head->next->cx==food.cx && head->next->cy==food.cy) 183 | { 184 | lenChange = true; 185 | length++; 186 | //恭喜你,通关了 187 | if(length >= 50) 188 | { 189 | over(3); 190 | return; 191 | } 192 | //重新设置食物的位置 193 | food.cx = rand() % COLS; 194 | food.cy = rand() % (LINES-2) + 2; 195 | } 196 | if(!lenChange) 197 | { 198 | move(tail->back->cy, tail->back->cx); 199 | printw(" "); 200 | deleteNode(); 201 | } 202 | move(head->next->cy, head->next->cx); 203 | printw("*"); 204 | } 205 | 206 | void show() 207 | { 208 | signal(SIGALRM, show); //设置中断信号 209 | showInformation(); 210 | showSnake(); 211 | refresh(); //刷新真实屏幕 212 | } 213 | 214 | void getOrder() 215 | { 216 | //建立一个死循环,来读取来自键盘的命令 217 | while(1) 218 | { 219 | ch = getch(); 220 | if(KEY_LEFT == ch) 221 | { 222 | dir.cx = -1; 223 | dir.cy = 0; 224 | } 225 | else if(KEY_UP == ch) 226 | { 227 | dir.cx = 0; 228 | dir.cy = -1; 229 | } 230 | else if(KEY_RIGHT == ch) 231 | { 232 | dir.cx = 1; 233 | dir.cy = 0; 234 | } 235 | else if(KEY_DOWN == ch) 236 | { 237 | dir.cx = 0; 238 | dir.cy = 1; 239 | } 240 | setTicker(20); 241 | } 242 | } 243 | 244 | void over(int i) 245 | { 246 | //显示结束原因 247 | move(0, 0); 248 | int j; 249 | for(j=0;j= 0); 275 | 276 | int conn = connect(sock, (struct sockaddr*)&address, sizeof(address)); 277 | printf("error is %s\n", strerror(errno)); 278 | assert(conn >= 0); 279 | } 280 | 281 | //创建一个双向链表 282 | void creatLink() 283 | { 284 | node *temp = (node *)malloc( sizeof(node) ); 285 | head = (node *)malloc( sizeof(node) ); 286 | tail = (node *)malloc( sizeof(node) ); 287 | temp->cx = 5; 288 | temp->cy = 10; 289 | head->back = tail->next = NULL; 290 | head->next = temp; 291 | temp->next = tail; 292 | tail->back = temp; 293 | temp->back = head; 294 | } 295 | 296 | //在链表的头部(非头结点)插入一个结点 297 | void insertNode(int x, int y) 298 | { 299 | char msg[1024]; 300 | memset(msg, '\0', 1024); 301 | sprintf(msg, "The X is %d, The Y is %d\n", x, y); 302 | 303 | sendMsgToServer(msg); 304 | 305 | node *temp = (node *)malloc( sizeof(node) ); 306 | temp->cx = x; 307 | temp->cy = y; 308 | temp->next = head->next; 309 | head->next = temp; 310 | temp->back = head; 311 | temp->next->back = temp; 312 | } 313 | 314 | //删除链表的(非尾结点的)最后一个结点 315 | void deleteNode() 316 | { 317 | node *temp = tail->back; 318 | node *bTemp = temp->back; 319 | bTemp->next = tail; 320 | tail->back = bTemp; 321 | temp->next = temp->back = NULL; 322 | free(temp); 323 | temp = NULL; 324 | } 325 | 326 | //删除整个链表 327 | void deleteLink() 328 | { 329 | while(head->next != tail) 330 | deleteNode(); 331 | head->next = tail->back = NULL; 332 | free(head); 333 | free(tail); 334 | } 335 | -------------------------------------------------------------------------------- /8. 服务器模型-CS模型/server.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiganFish/Notes-HighPerformanceLinuxServerProgramming/d1449f5974905d5464f8bee644504413da78208f/8. 服务器模型-CS模型/server.c -------------------------------------------------------------------------------- /9. 代码清单8-3 HTTP请求的读取和分析/server.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiganFish/Notes-HighPerformanceLinuxServerProgramming/d1449f5974905d5464f8bee644504413da78208f/9. 代码清单8-3 HTTP请求的读取和分析/server.c -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 第一章 TCP/IP协议族 2 | 3 | ## TCP/IP协议族体系结构和主要协议 4 | 协议族中协议众多, 这本书只选取了IP和TCP协议 - 对网络编程影响最直接 5 | 6 | ![](https://lsmg-img.oss-cn-beijing.aliyuncs.com/Linux%E9%AB%98%E6%80%A7%E8%83%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E8%AE%B0%E5%BD%95/%E5%9B%9B%E5%B1%82%E7%BB%93%E6%9E%84.jpg) 7 | 8 | ![](https://gss1.bdstatic.com/9vo3dSag_xI4khGkpoWK1HF6hhy/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=3a1768f4c6fc1e17e9b284632bf99d66/0dd7912397dda144d48ab350bbb7d0a20df48655.jpg) 9 | 10 | 同样七层是osi参考模型, 简化后得到四层 11 | 不同层次之间, 通过接口互相交流, 这样方便了各层次的修改 12 | 13 | **应用层** 14 | 负责处理应用程序的逻辑 15 | 16 | **表示层** 17 | 定义了数据的格式及加密 18 | 19 | **会话层** 20 | 它定义了如何开始、控制和结束一个会话,包括对多个双向消息的控制和管理,以便在只完成连续消息的一部分时可以通知应用,从而使表示层看到的数据是连续的 21 | 22 | **传输层** 23 | 为两台主机的应用提供端到端(end to end)的通信. 与网络层使用的下一跳不同, 他只关心起始和终止, 中转过程交给下层处理. 24 | 此层存在两大协议TCP协议和UDP协议 25 | TCP协议(Transmission Control Protocol 传输控制协议) 26 | - 为应用层提供`可靠的, 面向连接, 基于流的服务` 27 | - 通过`超时重传`和`数据确认`等确保数据正常送达. 28 | - TCP需要存储一些必要的状态, 连接的状态, 读写缓冲区, 诸多定时器 29 | UPD协议(User Datagram Protocol 用户数据报协议) 30 | - 为应用层提供`不可靠的, 无连接的, 基于数据报的服务` 31 | - 一般需要自己处理`数据确认`和`超时重传`的问题 32 | - 通信两者不存储状态, 每次发送都需要指定地址信息. `有自己的长度` 33 | 34 | **网络层** 35 | 实现了数据包的选路和转发. 只有数据包到不了目标地址, 就`下一跳`(hop by hop), 选择最近的. 36 | *IP协议(Internet Protocol)* 以及 *ICMP协议(Internet Control Message Protocol)* 37 | 后者协议是IP协议的补充, 用来检测网络连接 1. 差错报文, 用来回应状态 2. 查询报文(ping程序就是使用的此报文来判断信息是否送达) 38 | 39 | **数据链路层** 40 | 实现了网卡接口的网络驱动程序. 这里驱动程序方便了厂商的下层修改, 只需要向上层提供规定的接口即可. 41 | 存在两个协议 *ARP协议(Address Resolve Protocol, 地址解析协议)*. 还有*RARP(Reverse ~, 逆地址解析协议)*. 由于网络层使用IP地址寻址机器, 但是数据链路层使用物理地址(通常为MAC地址), 之间的转化涉及到ARP协议*ARP欺骗, 可能与这个有关, 目前不去学习* 42 | 43 | **封装** 44 | 上层协议发送到下层协议. 通过封装实现, 层与层之间传输的时候, 加上自己的头部信息. 45 | 被TCP封装的数据成为 `TCP报文段` 46 | - 内核部分发送成功后删除数据 47 | 48 | 被UDP封装的数据成为 `UDP数据报` 49 | - 发送后即删除 50 | 51 | 再经IP封装后成为`IP数据报` 52 | 最后经过数据链路层封装后为 `帧` 53 | 54 | 以太网最大数据帧1518字节 抛去14头部 帧尾4校验 55 | MTU: 帧的最大传输单元 一般为1500字节 56 | MSS: TCP数据包最大的数据载量 1460字节 = 1500字节 - 20Ip头-20TCP头 还有额外的40字节可选部分 57 | 58 | **ARP** 59 | ARP协议能实现任意网络层地址到任意物理地址的转换 60 | 61 | # 第二章 IP协议详解 62 | IP协议是TCP/IP协议簇的核心协议, 是socket网络编程的基础之一 63 | IP协议为上层协议提供无状态, 无连接, 不可靠的服务 64 | 65 | IP数据报最大长度是65535(2^16 - 1)字节, 但是有MTU的限制 66 | 67 | 当IP数据报的长度超过MTU 将会被分片传输. 分片可能发生在发送端, 也可能发生在中转路由器, 还可能被多次分片. 只有在最终的目标机器上, 这些分片才会被内核中的ip模块重新组装 68 | 69 | ![](https://lsmg-img.oss-cn-beijing.aliyuncs.com/Linux%E9%AB%98%E6%80%A7%E8%83%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E8%AE%B0%E5%BD%95/%E6%90%BA%E5%B8%A6ICMP%E6%8A%A5%E6%96%87%E7%9A%84IP%E6%95%B0%E6%8D%AE%E6%8A%A5%E8%A2%AB%E5%88%86%E7%89%87.png) 70 | 71 | 72 | 路由机制 73 | 74 | 75 | ![](https://lsmg-img.oss-cn-beijing.aliyuncs.com/Linux%E9%AB%98%E6%80%A7%E8%83%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E8%AE%B0%E5%BD%95/%E8%B7%AF%E7%94%B1%E6%9C%BA%E5%88%B6.png) 76 | 77 | 78 | 给定了目标IP地址后, 将会匹配路由表中的哪一项呢? 分三个步骤 79 | - 查找路由表中和数据报的目标IP地址完全匹配的主机IP地址. 如果找到就使用该路由项. 否则下一步 80 | - 查找路由表中和数据报的目标IP地址具有相同网路ID的网络IP地址 找到....... 否则下一步 81 | - 选择默认路由项, 通常意味着下一跳路由是网关 82 | 83 | 84 | # 第三章 TCP协议详解 85 | 86 | Tcp读写都是针对缓冲区来说, 所以没有固定的读写次数对应关系. 87 | 88 | UDP没有缓冲区, 必须及时接受数据否则会丢包, 或者接收缓冲区过小就会造成数据报截断 89 | 90 | ISN-初始序号值 91 | 32位序号 后续的TCP报文段中序号值 seq = ISN + 报文段首字节在整个字节流中的偏移 92 | 32位确认号 收到的TCP报文序号值+1. 这个32位确认号每次发送的是上一次的应答 93 | 94 | ACK标志: 表示确认号是否有效. 携带ACK标志的报文段称为`确认报文段` 95 | PSH标志: 提示接收端应用程序从TCP接受缓冲区中读走数据, 为后续数据腾出空间 96 | RST标志: 要求对方重新建立连接 携带......`复位报文段` 97 | SYN标志: 标志请求建立一个连接 携带......`同步报文段` 98 | FIN标志: 通知对方本端连接要关闭了, 携带..`结束报文段` 99 | 100 | 16位窗口大小: 窗口指的是接收通告窗口, 告诉对方本端的TCP 接收缓冲区还能容纳多少字节的数据 101 | 16位校验和: `可靠传输的重要保障`发送端填充, 接收端执行CRC算法校验是否损坏, 同时校验`TCP头部`和`数据部分` 102 | 103 | 104 | **TCP连接的建立和关闭** 105 | 106 | ```s 107 | # 三次握手 108 | # 客户端发送请求连接 ISN=seq + 0 = 3683340920 109 | # mss 最大数据载量1460 110 | IP 192.168.80.1.7467 > ubuntu.8000: 111 | Flags [S], seq 3683340920, win 64240, 112 | options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0 113 | 114 | # 同意客户端连接 115 | # ack = 客户端发送 seq + 1 116 | # 同时发送服务端的seq 117 | IP ubuntu.8000 > 192.168.80.1.7467: 118 | Flags [S.], seq 938535101, ack 3683340921, win 64240, 119 | options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0 120 | 121 | # 虽然这个报文段没有字节 但由于是同步报文段 需要占用一个序号值 122 | # 这里是tcpdump的处理 ack显示相对值 即 3683340921 - 3683340920 = 1 123 | IP 192.168.80.1.7467 > ubuntu.8000: 124 | Flags [.], ack 938535102, win 4106, length 0 125 | 126 | 127 | # 包含FIN标志 说明要求结束连接 也需要占用一个序号值 128 | IP 192.168.80.1.7467 > ubuntu.8000: 129 | Flags [F.], seq 1, ack 1, win 4106, length 0 130 | 131 | # 服务端确认关闭连接 132 | IP ubuntu.8000 > 192.168.80.1.7467: 133 | Flags [.], ack 2, win 502, length 0 134 | 135 | # 服务端发送关闭连接 136 | IP ubuntu.8000 > 192.168.80.1.7467: 137 | Flags [F.], seq 1, ack 2, win 4105, length 0 138 | 139 | # 客户端确认 140 | IP 192.168.80.1.7467 > ubuntu.8000: 141 | Flags [.], ack 2, win 503, length 0 142 | ``` 143 | 144 | ## 第五章Linux网络编程基础API 145 | 146 | 147 | socket基础api位于 `sys/socket.h` 头文件中 148 | socket最开始的含义是 一个IP地址和端口对. 唯一的表示了TCP通信的一段 149 | 网络信息api `netdb.h`头文件中 150 | 151 | ### 主机字节序和网络字节序 152 | 字节序分为 `大端字节序`和`小端字节序` 153 | 由于大多数PC采用小端字节序(高位存在高地址处), 所以小端字节序又称为主机字节序 154 | 155 | 为了防止不同机器字节序不同导致的错乱问题. 规定传输的时候统一为 大端字节序(网络字节序). 156 | 这样主机会根据自己的情况决定 - 是否转换接收到的数据的字节序 157 | 158 | 159 | ### API 160 | 161 | **基础连接** 162 | ![](https://lsmg-img.oss-cn-beijing.aliyuncs.com/Linux%E9%AB%98%E6%80%A7%E8%83%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E8%AE%B0%E5%BD%95/%E5%9C%B0%E5%9D%80%E7%BB%93%E6%9E%84%E4%BD%93.jpg) 163 | ![](https://lsmg-img.oss-cn-beijing.aliyuncs.com/Linux%E9%AB%98%E6%80%A7%E8%83%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E8%AE%B0%E5%BD%95/%E5%8D%8F%E8%AE%AE%E7%BB%84%E5%90%88%E5%9C%B0%E5%9D%80%E6%97%8F.jpg) 164 | ```c++ 165 | // 主机序和网络字节序转换 166 | #include 167 | unsigned long int htonl (unsigned long int hostlong); // host to network long 168 | unsigned short int htons (unsigned short int hostlong); // host to network short 169 | 170 | unsigned long int htonl (unsigned long int netlong); 171 | unsigned short int htons (unsigned short int netlong); 172 | 173 | // IP地址转换函数 174 | #include 175 | // 将点分十进制字符串的IPv4地址, 转换为网络字节序整数表示的IPv4地址. 失败返回INADDR_NONE 176 | in_addr_t inet_addr( const char* strptr); 177 | 178 | // 功能相同不过转换结果存在 inp指向的结构体中. 成功返回1 反之返回0 179 | int inet_aton( const char* cp, struct in_addr* inp); 180 | 181 | // 函数返回一个静态变量地址值, 所以多次调用会导致覆盖 182 | char* inet_ntoa(struct in_addr in); 183 | 184 | // src为 点分十进制字符串的IPv4地址 或 十六进制字符串表示的IPv6地址 存入dst的内存中 af指定地址族 185 | // 可以为 AF_INET AF_INET6 成功返回1 失败返回-1 186 | int inet_pton(int af, const char * src, void* dst); 187 | // 协议名, 需要转换的ip, 存储地址, 长度(有两个常量 INET_ADDRSTRLEN, INET6_ADDRSTRLEN) 188 | const char* inet_ntop(int af, const void* src, char* dst, socklen_t cnt); 189 | 190 | 191 | // 创建 命名 监听 socket 192 | # include 193 | # include 194 | // domain指定使用那个协议族 PF_INET PF_INET6 195 | // type指定服务类型 SOCK_STREAM (TCP协议) SOCK_DGRAM(UDP协议) 196 | // protocol设置为默认的0 197 | // 成功返回socket文件描述符(linux一切皆文件), 失败返回-1 198 | int socket(int domain, int type, int protocol); 199 | 200 | // socket为socket文件描述符 201 | // my_addr 为地址信息 202 | // addrlen为socket地址长度 203 | // 成功返回0 失败返回 -1 204 | int bind(int socket, const struct sockaddr* my_addr, socklen_t addrlen); 205 | 206 | // backlog表示队列最大的长度 207 | int listen(int socket, int backlog); 208 | // 接受连接 失败返回-1 成功时返回socket 209 | int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen) 210 | ``` 211 | 客户端 212 | ```c 213 | // 发起连接 214 | #include 215 | #include 216 | // 第三个参数为 地址指定的长度 217 | // 成功返回0 失败返回-1 218 | int connect(int sockfd, const struct sockaddr * serv_addr, socklen_t addrlen); 219 | 220 | // 关闭连接 221 | #include 222 | // 参数为保存的socket 223 | // 并非立即关闭, 将socket的引用计数-1, 当fd的引用计数为0, 才能关闭(需要查阅) 224 | int close(int fd); 225 | 226 | // 立即关闭 227 | #include 228 | // 第二个参数为可选值 229 | // SHUT_RD 关闭读, socket的接收缓冲区的数据全部丢弃 230 | // SHUT_WR 关闭写 socket的发送缓冲区全部在关闭前发送出去 231 | // SHUT_RDWR 同时关闭读和写 232 | // 成功返回0 失败为-1 设置errno 233 | int shutdown(int sockfd, int howto) 234 | ``` 235 | **基础TCP** 236 | ```c 237 | #include 238 | #include 239 | 240 | // 读取sockfd的数据 241 | // buf 指定读缓冲区的位置 242 | // len 指定读缓冲区的大小 243 | // flags 参数较多 244 | // 成功的时候返回读取到的长度, 可能小于预期长度, 需要多次读取. 读取到0 通信对方已经关闭连接, 错误返回-1 245 | ssize_t recv(int sockfd, void *buf, size_t len, int flags); 246 | // 发送 247 | ssize_t send(int sockfd, const void *buf, size_t len, int flags); 248 | ``` 249 | 250 | | 选项名 | 含义 | 可用于发送 | 可用于接收 | 251 | | ------------- | ---------------------------------------------------------------------------------------- | ---------- | ---------- | 252 | | MSG_CONFIRM | 指示链路层协议持续监听, 直到得到答复.(仅能用于SOCK_DGRAM和SOCK_RAW类型的socket) | Y | N | 253 | | MSG_DONTROUTE | 不查看路由表, 直接将数据发送给本地的局域网络的主机(代表发送者知道目标主机就在本地网络中) | Y | N | 254 | | MSG_DONTWAIT | 非阻塞 | Y | Y | 255 | | MSG_MORE | 告知内核有更多的数据要发送, 等到数据写入缓冲区完毕后,一并发送.减少短小的报文提高传输效率 | Y | N | 256 | | MSG_WAITALL | 读操作一直等待到读取到指定字节后才会返回 | N | Y | 257 | | MSG_PEEK | 看一下内缓存数据, 并不会影响数据 | N | Y | 258 | | MSG_OOB | 发送或接收紧急数据 | Y | Y | 259 | | MSG_NOSIGNAL | 向读关闭的管道或者socket连接中写入数据不会触发SIGPIPE信号 | Y | N | 260 | 261 | **基础UDP** 262 | ```c 263 | #include 264 | #include 265 | // 由于UDP不保存状态, 每次发送数据都需要 加入目标地址. 266 | // 不过recvfrom和sendto 也可以用于 面向STREAM的连接, 这样可以省略发送和接收端的socket地址 267 | ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen); 268 | ssize_t sendto(int sockfd, const void* buf, size_t len, ing flags, const struct sockaddr* dest_addr, socklen_t addrlen); 269 | 270 | ``` 271 | 272 | **通用读写函数** 273 | 274 | ```c 275 | #inclued 276 | ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags); 277 | ssize_t sendmsg(int sockfd, struct msghdr* msg, int flags); 278 | 279 | struct msghdr 280 | { 281 | /* socket address --- 指向socket地址结构变量, 对于TCP连接需要设置为NULL*/ 282 | void* msg_name; 283 | 284 | 285 | socklen_t msg_namelen; 286 | 287 | /* 分散的内存块 --- 对于 recvmsg来说数据被读取后将存放在这里的块内存中, 内存的位置和长度由 288 | * msg_iov指向的数组指定, 称为分散读(scatter read) ---对于sendmsg而言, msg_iovlen块的分散内存中 289 | * 的数据将一并发送称为集中写(gather write); 290 | */ 291 | struct iovec* msg_iov; 292 | int msg_iovlen; /* 分散内存块的数量*/ 293 | void* msg_control; /* 指向辅助数据的起始位置*/ 294 | socklen_t msg_controllen; /* 辅助数据的大小*/ 295 | int msg_flags; /* 复制函数的flags参数, 并在调用过程中更新*/ 296 | }; 297 | 298 | struct iovec 299 | { 300 | void* iov_base /* 内存起始地址*/ 301 | size_t iov_len /* 这块内存长度*/ 302 | } 303 | ``` 304 | **其他Api** 305 | ```c 306 | #include 307 | // 用于判断 sockfd是否处于带外标记, 即下一个被读取到的数据是否是带外数据, 308 | // 是的话返回1, 不是返回0 309 | // 这样就可以选择带MSG_OOB标志的recv调用来接收带外数据. 310 | int sockatmark(int sockfd); 311 | 312 | // getsockname 获取sockfd对应的本端socket地址, 存入address指定的内存中, 长度存入address_len中 成功返回0失败返回-1 313 | // getpeername 获取远端的信息, 同上 314 | int getsockname(int sockfd, struct sockaddr* address, socklen_t* address_len); 315 | int getpeername(int sockfd, struct sockaddr* address, socklen_t* address_len); 316 | 317 | /* 以下函数头文件均相同*/ 318 | 319 | // sockfd 目标socket, level执行操作协议(IPv4, IPv6, TCP) option_name 参数指定了选项的名字. 后面值和长度 320 | // 成功时返回0 失败返回-1 321 | int getsockopt(int sockfd, int level, int option_name, void* option_value, 322 | socklen_t restrict option_len); 323 | int setsockopt(int sockfd, int level, int option_name, void* option_value, 324 | socklen_t restrict option_len); 325 | ``` 326 | 327 | | SO_REUSEADDR | 重用本地地址 | sock被设置此属性后, 即使sock在被bind()后处于TIME_WAIT状态, 此时与他绑定的socket地址依然能够立即重用来绑定新的sock | 328 | | ------------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------ | 329 | | SO_RCVBUF | TCP接收缓冲区大小 | 最小值为256字节. 设置完后系统会自动加倍你所设定的值. 多出来的一倍将用用作空闲缓冲区处理拥塞 | 330 | | SO_SNDBUF | TCP发送缓冲区大小 | 最小值为2048字节 | 331 | | SO_RCVLOWAT | 接收的低水位标记 | 默认为1字节, 当TCP接收缓冲区中可读数据的总数大于其低水位标记时, IO复用系统调用将通知应用程序可以从对应的socket上读取数据 | 332 | | SO_SNDLOWAT | 发送的高水位标记 | 默认为1字节, 当TCP发送缓冲区中空闲空间大于低水位标记的时候可以写入数据 | 333 | | SO_LINGER | | | 334 | 335 | 336 | ```c 337 | struct linger 338 | { 339 | int l_onoff /* 开启非0, 关闭为0*/ 340 | int l_linger; /* 滞留时间*/ 341 | /* 342 | * 当onoff为0的时候此项不起作用, close调用默认行为关闭socket 343 | * 当onoff不为0 且linger为0, close将立即返回, TCP将丢弃发送缓冲区的残留数据, 同时发送一个复位报文段 344 | * 当onoff不为0 且linger大于0 . 当socket阻塞的时候close将会等待TCP模块发送完残留数据并得到确认后关 345 | * 闭, 如果是处于非阻塞则立即关闭 346 | */ 347 | }; 348 | ``` 349 | 350 | ![](https://lsmg-img.oss-cn-beijing.aliyuncs.com/Linux%E9%AB%98%E6%80%A7%E8%83%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E8%AE%B0%E5%BD%95/socket%E9%80%89%E9%A1%B9.jpg) 351 | **网络信息API** 352 | ```c 353 | #include 354 | // 通过主机名查找ip 355 | struct hostent* gethostbyname(const char* name); 356 | 357 | // 通过ip获取主机完整信息 358 | // type为IP地址类型 AF_INET和AF_INET6 359 | struct hostent* gethostbyaddr(const void* addr, size_t len, int type); 360 | 361 | struct hostent 362 | { 363 | char *h_name; /* Official name of host. */ 364 | char **h_aliases; /* Alias list. */ 365 | int h_addrtype; /* Host address type. */ 366 | int h_length; /* Length of address. */ 367 | char **h_addr_list; /* List of addresses from name server. */ 368 | } 369 | 370 | int main(int argc, char* argv[]) 371 | { 372 | if (argc != 2) 373 | { 374 | printf("非法输入\n"); 375 | exit(0); 376 | } 377 | char* name = argv[1]; 378 | 379 | struct hostent *hostptr{}; 380 | 381 | hostptr = gethostbyname(name); 382 | if (hostptr == nullptr) 383 | { 384 | printf("输入存在错误 或无法获取\n"); 385 | exit(0); 386 | } 387 | 388 | printf("Official name of hostptr: %s\n", hostptr->h_name); 389 | 390 | char **pptr; 391 | char inet_addr[INET_ADDRSTRLEN]; 392 | 393 | printf("Alias list:\n"); 394 | for (pptr = hostptr->h_aliases; *pptr != nullptr; ++pptr) 395 | { 396 | printf("\t%s\n", *pptr); 397 | } 398 | 399 | switch (hostptr->h_addrtype) 400 | { 401 | case AF_INET: 402 | { 403 | printf("List of addresses from name server:\n"); 404 | for (pptr = hostptr->h_addr_list; *pptr != nullptr; ++pptr) 405 | { 406 | printf("\t%s\n", 407 | inet_ntop(hostptr->h_addrtype, *pptr, inet_addr, sizeof(inet_addr))); 408 | } 409 | break; 410 | } 411 | default: 412 | { 413 | printf("unknow address type\n"); 414 | exit(0); 415 | } 416 | } 417 | return 0; 418 | } 419 | 420 | /* 421 | ./run baidu.com 422 | Official name of hostptr: baidu.com 423 | Alias list: 424 | List of addresses from name server: 425 | 39.156.69.79 426 | 220.181.38.148 427 | */ 428 | ``` 429 | 以下两个函数通过读取/etc/services文件 来获取服务信息 以下内容来自维基百科 430 | 431 | Service文件是现代操作系统在etc目录下的一个配置文件,记录网络服务名对应的端口号与协议 其用途如下 432 | - 通过TCP/IP的API函数(声明在netdb.h中)直接查到网络服务名与端口号、使用协议的对应关系。如getservbyname("serve","tcp")获取端口号;getservbyport(htons(port),“tcp”)获取端口和协议上的服务名 433 | - 如果用户在这个文件中维护所有使用的网络服务名字、端口、协议,那么可以一目了然的获悉哪些端口号用于哪个服务,哪些端口号是空闲的 434 | ```c 435 | #include 436 | // 根据名称获取某个服务的完整信息 437 | struct servent getservbyname(const char* name, const char* proto); 438 | 439 | // 根据端口号获取服务信息 440 | struct servent getservbyport(int port, const char* proto); 441 | 442 | struct servent 443 | { 444 | char* s_name; /* 服务名称*/ 445 | char ** s_aliases; /* 服务的别名列表*/ 446 | int s_port; /* 端口号*/ 447 | char* s_proto; /* 服务类型, 通常为TCP或UDP*/ 448 | } 449 | ``` 450 | ```c 451 | #include 452 | // 内部使用的gethostbyname 和 getserverbyname 453 | // hostname 用于接收主机名, 也可以用来接收字符串表示的IP地址(点分十进制, 十六进制字符串) 454 | // service 用于接收服务名, 字符串表示的十进制端口号 455 | // hints参数 对getaddrinfo的输出进行更准确的控制, 可以设置为NULL, 允许反馈各种有用的结果 456 | // result 指向一个链表, 用于存储getaddrinfo的反馈结果 457 | int getaddrinfo(const char* hostname, const char* service, const struct addrinfo* hints, struct addrinfo** result) 458 | 459 | struct addrinfo 460 | { 461 | int ai_flags; 462 | int ai_family; 463 | int ai_socktype; /* 服务类型, SOCK_STREAM或者SOCK_DGRAM*/ 464 | int ai_protocol; 465 | socklen_t ai_addrlen; 466 | char* ai_canonname; /* 主机的别名*/ 467 | struct sockaddr* ai_addr; /* 指向socket地址*/ 468 | struct addrinfo* ai_next; /* 指向下一个结构体*/ 469 | } 470 | 471 | // 需要手动的释放堆内存 472 | void freeaddrinfo(struct addrinfo* res); 473 | ``` 474 | ![](https://ftp.bmp.ovh/imgs/2019/08/7ebedb14d8eedeac.png) 475 | 476 | ```c 477 | #include 478 | // host 存储返回的主机名 479 | // serv存储返回的服务名 480 | 481 | int getnameinfo(const struct sockaddr* sockaddr, socklen_t addrlen, char* host, socklen_t hostlen, char* serv 482 | socklen_t servlen, int flags); 483 | 484 | ``` 485 | ![](https://ftp.bmp.ovh/imgs/2019/08/bc7196e9a30d5152.png) 486 | 487 | 测试 488 | 使用 489 | ```shell 490 | telnet ip port #来连接服务器的此端口 491 | netstat -nt | grep port #来查看此端口的监听 492 | ``` 493 | 494 | ## 第六章高级IO函数 495 | 496 | Linux提供的高级IO函数, 自然是特定条件下能力更强, 不然要他干啥, 特定条件自然限制了他的使用频率 497 | *文件描述符* 498 | 文件描述符在是一个非负整数。是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。 499 | STDOUT_FILENO(值为1)- 值为1的文件描述符为标准输出, 关闭STDOUT_FILENO后用dup即可返回最小可用值(目前为, 1) 这样输出就重定向到了调用dup的参数指向的文件 500 | 501 | ### 创建文件描述符 - pipe dup dup2 splice select 502 | **pipe函数** 503 | 这个函数可用于创建一个管道, 实现进程间的通信. 504 | 505 | ```c 506 | // 函数定义 507 | // 参数文件描述符数组 fd[0] 读出 fd[1]写入 单向管道 508 | // 成功返回0, 并将一对打开的文件描述符填入其参数指向的数组 509 | // 失败返回-1 errno 510 | #include 511 | int pipe(int fd[2]); 512 | ``` 513 | ```c 514 | // 双向管道 515 | // 第一个参数为 协议PF_UNIX(书上是AF_UNIX)感觉这里指明协议使用PF更好一些 516 | #include 517 | #include 518 | int socketpair(int domain, int type, int protocol, int fd[2]); 519 | ``` 520 | 学习了后面的内容了解到了进程间通信, 回来补上一个例子 521 | ```c 522 | int main() 523 | { 524 | int fds[2]; 525 | socketpair(PF_UNIX, SOCK_STREAM, 0, fds); 526 | int pid = fork(); 527 | if (pid == 0) 528 | { 529 | close(fds[0]); 530 | char a[] = "123"; 531 | send(fds[1], a, strlen(a), 0); 532 | } 533 | else if (pid > 0) 534 | { 535 | close(fds[1]); 536 | char b[20] {}; 537 | recv(fds[0], b, 20, 0); 538 | printf("%s", b); 539 | } 540 | } 541 | ``` 542 | **dup和dup2函数** 543 | 复制一个现有的文件描述符 544 | ```c 545 | #include 546 | // 返回的文件描述符总是取系统当前可用的最小整数值 547 | int dup(int oldfd); 548 | // 可以用newfd来制定新的文件描述符, 如果newfd已经被打开则先关闭 549 | // 如果newfd==oldfd 则不关闭newfd直接返回 550 | int dup2(int oldfd, int newfd); 551 | ``` 552 | dup函数创建一个新的文件描述符, 新的文件描述符和原有的file_descriptor共同指向相同的目标. 553 | 回来补上例子, 这个例子由于关掉了`STDOUT_FILENO`dup最小的即为`STDOUT_FILENO`所以 554 | 标准输出都到了这个文件之中 555 | ```c 556 | int main() 557 | { 558 | int filefd = open("/home/lsmg/1.txt", O_WRONLY); 559 | close(STDOUT_FILENO); 560 | dup(filefd); 561 | printf("123\n"); 562 | exit(0); 563 | } 564 | ``` 565 | 566 | ### 读写数据 - readv writev mmap munmap 567 | **readv/writev** 568 | ```c 569 | #include 570 | // count 为 vector的长度, 即为有多少块内存 571 | // 成功时返回写入\读取的长度 失败返回-1 572 | ssize_t readv(int fd, const struct iovec* vector, int count); 573 | ssize_t writev(int fd, const struct iovec* vector, int count); 574 | 575 | struct iovec { 576 | void* iov_base /* 内存起始地址*/ 577 | size_t iov_len /* 这块内存长度*/ 578 | } 579 | ``` 580 | 回来补上一个使用例子, 这个例子将一个int的内存表示写入到了文件之中 581 | 使用hexdump查看这个文件`0000000 86a0 0001`可以看到`186a0`即为100000 582 | ```c 583 | // 2020年1月7日16:52:11 584 | int main() 585 | { 586 | int file = open("/home/lsmg/1.txt", O_WRONLY); 587 | int temp = 100000; 588 | iovec temp_iovec{}; 589 | temp_iovec.iov_base = &temp; 590 | temp_iovec.iov_len = sizeof(temp); 591 | writev(file, &temp_iovec, 1); 592 | } 593 | ``` 594 | sendfile函数 595 | ```c 596 | #include 597 | // offset为指定输入流从哪里开始读, 如果为NULL 则从开头读取 598 | ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count); 599 | 600 | O_RDONLY只读模式 601 | O_WRONLY只写模式 602 | O_RDWR读写模式 603 | int open(file_name, flag); 604 | ``` 605 | stat结构体, 可用fstat生成, **简直就是文件的身份证** 606 | ```c 607 | #include 608 | struct stat 609 | { 610 | dev_t st_dev; /* ID of device containing file -文件所在设备的ID*/ 611 | ino_t st_ino; /* inode number -inode节点号*/ 612 | mode_t st_mode; /* protection -保护模式?*/ 613 | nlink_t st_nlink; /* number of hard links -链向此文件的连接数(硬连接)*/ 614 | uid_t st_uid; /* user ID of owner -user id*/ 615 | gid_t st_gid; /* group ID of owner - group id*/ 616 | dev_t st_rdev; /* device ID (if special file) -设备号,针对设备文件*/ 617 | off_t st_size; /* total size, in bytes -文件大小,字节为单位*/ 618 | blksize_t st_blksize; /* blocksize for filesystem I/O -系统块的大小*/ 619 | blkcnt_t st_blocks; /* number of blocks allocated -文件所占块数*/ 620 | time_t st_atime; /* time of last access -最近存取时间*/ 621 | time_t st_mtime; /* time of last modification -最近修改时间*/ 622 | time_t st_ctime; /* time of last status change - */ 623 | }; 624 | ``` 625 | **身份证**生成函数 626 | ```c 627 | // 第一个参数需要调用open生成文件描述符 628 | // 下面其他两个为文件全路径 629 | int fstat(int filedes, struct stat *buf); 630 | 631 | // 当路径指向为符号链接的时候, lstat为符号链接的信息. stat为符号链接指向文件信息 632 | int stat(const char *path, struct stat *buf); 633 | int lstat(const char *path, struct stat *buf); 634 | 635 | /* 636 | * ln -s source dist 建立软连接, 类似快捷方式, 也叫符号链接 637 | * ln source dist 建立硬链接, 同一个文件使用多个不同的别名, 指向同一个文件数据块, 只要硬链接不被完全 638 | * 删除就可以正常访问 639 | * 文件数据块 - 文件的真正数据是一个文件数据块, 打开的`文件`指向这个数据块, 就是说 640 | * `文件`本身就类似快捷方式, 指向文件存在的区域. 641 | */ 642 | ``` 643 | **mmap和munmap函数** 644 | 645 | `mmap`创建一块进程通讯共享的内存(可以将文件映射入其中), `munmap`释放这块内存 646 | ```c 647 | #include 648 | 649 | // start 内存起始位置, 如果为NULL则系统分配一个地址 length为长度 650 | // port参数 PROT_READ(可读) PROT_WRITE(可写) PROT_EXEC(可执行), PROT_NONE(不可访问) 651 | // flag参数 内存被修改后的行为 652 | // - MAP_SHARED 进程间共享内存, 对内存的修改反映到映射文件中 653 | // - MAP_PRIVATE 为调用进程私有, 对该内存段的修改不会反映到文件中 654 | // - MAP_ANONUMOUS 不是从文件映射而来, 内容被初始化为0, 最后两个参数被忽略 655 | // 成功返回区域指针, 失败返回 -1 656 | void* mmap(void* start, size_t length, int port, int flags, int fd, off_t offset); 657 | // 成功返回0 失败返回-1 658 | int munmap(void* start, size_t length); 659 | ``` 660 | **splice函数** 661 | 用于在两个文件名描述符之间移动数据, 0拷贝操作 662 | ```c 663 | #include 664 | // fd_in 为文件描述符, 如果为管道文件描述符则 off_in必须为NULL, 否则为读取开始偏移位置 665 | // len为指定移动的数据长度, flags参数控制数据如何移动. 666 | // - SPLICE_F_NONBLOCK 非阻塞splice操作, 但会受文件描述符自身的阻塞 667 | // - SPLICE_F_MORE 给内核一个提示, 后续的splice调用将读取更多的数据??????? 668 | ssize_t splice(int fd_in, loff_t* off_in, int fd_out, loff_t* off_out, size_t len, unsigned int flags); 669 | 670 | // 使用splice函数 实现echo服务器 671 | int main(int argc, char* argv[]) 672 | { 673 | if (argc <= 2) 674 | { 675 | printf("the parmerters is wrong\n"); 676 | exit(errno); 677 | } 678 | char *ip = argv[1]; 679 | 680 | int port = atoi(argv[2]); 681 | printf("the port is %d the ip is %s\n", port, ip); 682 | 683 | int sockfd = socket(PF_INET, SOCK_STREAM, 0); 684 | assert(sockfd >= 0); 685 | 686 | struct sockaddr_in address{}; 687 | address.sin_family = AF_INET; 688 | address.sin_port = htons(port); 689 | inet_pton(AF_INET, ip, &address.sin_addr); 690 | 691 | int ret = bind(sockfd, (sockaddr*)&address, sizeof(address)); 692 | assert(ret != -1); 693 | 694 | ret = listen(sockfd, 5); 695 | 696 | int clientfd{}; 697 | sockaddr_in client_address{}; 698 | socklen_t client_addrlen = sizeof(client_address); 699 | 700 | clientfd = accept(sockfd, (sockaddr*)&client_address, &client_addrlen); 701 | if (clientfd < 0) 702 | { 703 | printf("accept error\n"); 704 | } 705 | else 706 | { 707 | printf("a new connection from %s:%d success\n", inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port)); 708 | int fds[2]; 709 | pipe(fds); 710 | ret = splice(clientfd, nullptr, fds[1], nullptr, 32768, SPLICE_F_MORE); 711 | assert(ret != -1); 712 | 713 | ret = splice(fds[0], nullptr, clientfd, nullptr, 32768, SPLICE_F_MORE); 714 | assert(ret != -1); 715 | 716 | close(clientfd); 717 | } 718 | close(sockfd); 719 | exit(0); 720 | } 721 | ``` 722 | 723 | **select 函数** 724 | select函数在第二个参数列表 可读的时候返回 725 | 或者是等到了规定的时间返回 726 | 727 | 返回之后 第二个参数指向fdset的集合 被修改为可读的fd列表 728 | 这就需要每次返回后都更新 fdset集合 729 | 730 | 返回后 此函数的返回值为可读的fd数量, 遍历fdset集合 同时使用FD_ISSET判断fdset[i] 是否在其中 731 | 然后判断此fd是否为listenfd 如果是则接受新的连接 如果不是说明是已经接受的其他fd 判断是有数据可读 732 | 还是此连接断开 733 | 734 | ```c 735 | #include 736 | // maxfdp 最大数 FD_SETSIZE 737 | // struct fd_set 一个集合,可以存储多个文件描述符 738 | // - FD_ZERO(&fd_set) 清空 -FD_SET(fd, &fd_set) 放入fd FD_CLR(fd, &fd_set)从其中清除fd 739 | // - FD_ISSET(fd, &fd_set) 判断是否在其中 740 | // readfds 需要监视的文件描述符读变化, 其中的文件描述符可读的时候返回 741 | // writefds 需要监视的文件描述符写变化, 其中的文件描述符可写的时候返回 742 | // errorfds 错误 743 | // timeout 传入NULL为阻塞, 设置为0秒0微秒则变为非阻塞函数 744 | // 返回值 负值为错误 等待超时说明文件无变化返回0 有变化返回正值 745 | int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval*timeout); 746 | 747 | #define exit_if(r, ...) \ 748 | { \ 749 | if (r) \ 750 | { \ 751 | printf(__VA_ARGS__); \ 752 | printf("errno no: %d, error msg is %s", errno, strerror(errno)); \ 753 | exit(1); \ 754 | } \ 755 | } \ 756 | 757 | int main(int argc, char* argv[]) 758 | { 759 | int keyboard_fd = open("/dev/tty", O_RDONLY | O_NONBLOCK); 760 | exit_if(keyboard_fd < 0, "open keyboard fd error\n"); 761 | fd_set readfd; 762 | char recv_buffer = 0; 763 | 764 | while (true) 765 | { 766 | FD_ZERO(&readfd); 767 | FD_SET(0, &readfd); 768 | 769 | timeval timeout {5, 0}; 770 | 771 | int ret = select(keyboard_fd + 1, &readfd, nullptr, nullptr, &timeout); 772 | exit_if(ret == -1, "select error\n"); 773 | if (ret > 0) 774 | { 775 | if (FD_ISSET(keyboard_fd, &readfd)) 776 | { 777 | recv_buffer = 0; 778 | read(keyboard_fd, &recv_buffer, 1); 779 | if ('\n' == recv_buffer) 780 | { 781 | continue; 782 | } 783 | if ('q' == recv_buffer) 784 | { 785 | break; 786 | } 787 | printf("the input is %c\n", recv_buffer); 788 | } 789 | 790 | } 791 | if (ret == 0) 792 | { 793 | printf("timeout\n"); 794 | } 795 | } 796 | } 797 | ``` 798 | ## 第七章Linux服务器程序规范 799 | 800 | - Linux程序服务器 一般以后台进程形式运行. 后台进程又称为守护进程(daemon). 他没有控制终端, 因而不会意外的接收到用户输入. 守护进程的父进程通常都是init进程(PID为1的进程) 801 | - Linux服务器程序有一套日志系统, 他至少能输出日志到文件. 日志这东西太重要了,排错对比全靠它. 802 | - Linux服务器程序一般以某个专门的非root身份运行. 比如mysqld有自己的账户mysql. 803 | - Linux服务器程序一般都有自己的配置文件, 而不是把所有配置都写死在代码里面, 方便后续的更改. 804 | - Linux服务器程序通常在启动的时候生成一个PID文件并存入/var/run 目录中, 以记录改后台进程的PID. 805 | - Linux服务器程序通常需要考虑系统资源和限制, 预测自己的承受能力 806 | 807 | ### 日志 808 | 809 | ```shell 810 | sudo service rsyslog restart // 启动守护进程 811 | ``` 812 | ```c 813 | #include 814 | // priority参数是所谓的设施值(记录日志信息来源, 默认为LOG_USER)与日志级别的按位或 815 | // - 0 LOG_EMERG /* 系统不可用*/ 816 | // - 1 LOG_ALERT /* 报警需要立即采取行动*/ 817 | // - 2 LOG_CRIT /* 非常严重的情况*/ 818 | // - 3 LOG_ERR /* 错误*/ 819 | // - 4 LOG_WARNING /* 警告*/ 820 | // - 5 LOG_NOTICE /* 通知*/ 821 | // - 6 LOG_INFO /* 信息*/ 822 | // -7 LOG_DEBUG /* 调试*/ 823 | void syslog(int priority, const char* message, .....); 824 | 825 | // ident 位于日志的时间后 通常为名字 826 | // logopt 对后续 syslog调用的行为进行配置 827 | // - 0x01 LOG_PID /* 在日志信息中包含程序PID*/ 828 | // - 0x02 LOG_CONS /* 如果信息不能记录到日志文件, 则打印到终端*/ 829 | // - 0x04 LOG_ODELAY /* 延迟打开日志功能直到第一次调用syslog*/ 830 | // - 0x08 LOG_NDELAY /* 不延迟打开日志功能*/ 831 | // facility参数可以修改syslog函数中的默认设施值 832 | void openlog(const char* ident, int logopt, int facility); 833 | 834 | // maskpri 一共八位 0000-0000 835 | // 如果将最后一个0置为1 表示 记录0级别的日志 836 | // 如果将最后两个0都置为1 表示记录0和1级别的日志 837 | // 可以通过LOG_MASK() 宏设定 比如LOG_MASK(LOG_CRIT) 表示将倒数第三个0置为1, 表示只记录LOG_CRIT 838 | // 如果直接设置setlogmask(3); 3的二进制最后两个数均为1 则记录 0和1级别的日志 839 | int setlogmask(int maskpri); 840 | 841 | // 关闭日志功能 842 | void closelog(); 843 | ``` 844 | 845 | ### 用户信息, 切换用户 846 | UID - 真实用户ID 847 | EUID - 有效用户ID - 方便资源访问 848 | GID - 真实组ID 849 | EGID - 有效组ID 850 | ```c 851 | #include 852 | #include 853 | 854 | uid_t getuid(); 855 | uid_t geteuid(); 856 | gid_t getgid(); 857 | gid_t getegid(); 858 | int setuid(uid_t uid); 859 | int seteuid(uid_t euid); 860 | int setgid(gid_t gid); 861 | int setegid(gid_t gid); 862 | ``` 863 | 864 | 可以通过 `setuid`和`setgid`切换用户 **root用户uid和gid均为0** 865 | 866 | ### 进程间关系 867 | PGID - 进程组ID(Linux下每个进程隶属于一个进程组) 868 | 869 | #include 870 | pid_t getpgid(pid_t pid); 成功时返回pid所属的pgid 失败返回-1 871 | int setpgid(pid_t pid, pid_t pgid); 872 | 873 | **会话** 874 | 一些有关联的进程组将形成一个会话 875 | 略过 876 | 877 | **查看进程关系** 878 | ps和less 879 | 880 | **资源限制** 881 | 略 882 | **改变目录** 883 | 略 884 | 885 | ## 第八章高性能服务器程序框架 886 | 887 | **服务器模型-CS模型** 888 | 889 | **优点** 890 | - 实现起来简单 891 | **缺点** 892 | - 服务器是通信的中心, 访问过大的时候会导致响应过慢 893 | 894 | 模式图 895 | ![](https://lsmg-img.oss-cn-beijing.aliyuncs.com/Linux%E9%AB%98%E6%80%A7%E8%83%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E8%AE%B0%E5%BD%95/%E5%9B%BE8-2%20TCP%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%92%8C%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B.png) 896 | 897 | 编写的demo 没有用到fork函数. 后续待完善 898 | 899 | **服务器框架 IO模型** 900 | 901 | ![](https://lsmg-img.oss-cn-beijing.aliyuncs.com/Linux%E9%AB%98%E6%80%A7%E8%83%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E8%AE%B0%E5%BD%95/%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%9F%BA%E6%9C%AC%E6%A1%86%E6%9E%B6.png) 902 | 903 | 这个模型大概能够理解, 自己也算是学了半年的Javaweb. 904 | 905 | socket在创建的时候默认是阻塞的, 不过可以通过传`SOCK_NONBLOCK`参解决 906 | 非阻塞调用都会立即返回 但可能事件没有发生(recv没有接收到信息), 没有发生和出错都会`返回-1` 所以需要通过`errno`来区分这些错误. 907 | **事件未发生** 908 | accept, send,recv errno被设置为 `EAGAIN(再来一次)`或`EWOULDBLOCK(期望阻塞)` 909 | connect 被设置为 `EINPROGRESS(正在处理中)` 910 | 911 | 需要在事件已经发生的情况下 去调用非阻塞IO, 才能提高性能 912 | 913 | 常用IO复用函数 `select` `poll` `epoll_wait` 将在第九章后面说明 914 | 信号将在第十章说明 915 | 916 | **两种高效的事件处理模式和并发模式** 917 | ![](https://lsmg-img.oss-cn-beijing.aliyuncs.com/Linux%E9%AB%98%E6%80%A7%E8%83%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E8%AE%B0%E5%BD%95/Reactor%E6%A8%A1%E5%BC%8F.png) 918 | 919 | ![](https://lsmg-img.oss-cn-beijing.aliyuncs.com/Linux%E9%AB%98%E6%80%A7%E8%83%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E8%AE%B0%E5%BD%95/Proactor%E6%A8%A1%E5%BC%8F.png) 920 | 921 | ![](https://lsmg-img.oss-cn-beijing.aliyuncs.com/Linux%E9%AB%98%E6%80%A7%E8%83%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E8%AE%B0%E5%BD%95/%E7%94%A8%E5%90%8C%E6%AD%A5IO%E6%A8%A1%E6%8B%9F%E5%87%BA%E7%9A%84Proactor%E6%A8%A1%E5%BC%8F.png) 922 | 923 | 程序分为计算密集型(CPU使用很多, IO资源使用很少)和IO密集型(反过来). 924 | 前者使用并发编程反而会降低效率, 后者则会提升效率 925 | 并发编程有多进程和多线程两种方式 926 | 927 | 并发模式 - IO单元和多个逻辑单元之间协调完成任务的方法. 928 | 服务器主要有两种并发模式 929 | - 半同步/半异步模式 930 | - 领导者/追随者模式 931 | 932 | **半同步/半异步模式** 933 | 在IO模型中, 异步和同步的区分是内核向应用程序通知的是何种IO事件(就绪事件还是完成事件), 以及由谁来完成IO读写(应用程序还是内核) 934 | 935 | 而在这里(并发模式) 936 | 同步指的是完全按照代码序列的顺序执行 - 按照同步方式运行的线程称为同步线程 937 | 异步需要系统事件(中断, 信号)来驱动 - 按照异步方式运行的线程称为异步线程 938 | ![](https://lsmg-img.oss-cn-beijing.aliyuncs.com/Linux%E9%AB%98%E6%80%A7%E8%83%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E8%AE%B0%E5%BD%95/%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%BC%8F%E4%B8%AD%E7%9A%84%E5%BC%82%E6%AD%A5%E5%92%8C%E5%90%8C%E6%AD%A5.png) 939 | 940 | 服务器(需要较好的实时性且能同时处理多个客户请求) - 一般使用同步线程和异步线程来实现,即为半同步/半异步模式 941 | 同步线程 - 处理客户逻辑, 处理请求队列中的对象 942 | 异步线程 - 处理IO事件, 接收到客户请求后将其封装成请求对象并插入请求队列 943 | 944 | 半同步/半异步模式 存在变体 `半同步/半反应堆模式` 945 | ![](https://lsmg-img.oss-cn-beijing.aliyuncs.com/Linux%E9%AB%98%E6%80%A7%E8%83%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E8%AE%B0%E5%BD%95/%E5%8D%8A%E5%90%8C%E6%AD%A5%E5%8D%8A%E5%8F%8D%E5%BA%94%E5%A0%86%E6%A8%A1%E5%BC%8F.png) 946 | 947 | 异步线程 - 主线程 - 负责监听所有socket上的事件 948 | 949 | **领导者/追随者模式** 950 | 略 951 | 952 | **高效编程方法 - 有限状态机** 953 | ```c 954 | // 状态独立的有限状态机 955 | STATE_MACHINE(Package _pack) { 956 | 957 | PackageType _type = _pack.GetType(); 958 | switch(_type) { 959 | case type_A: 960 | xxxx; 961 | break; 962 | case type_B: 963 | xxxx; 964 | break; 965 | } 966 | } 967 | 968 | // 带状态转移的有限状态机 969 | STATE_MACHINE() { 970 | State cur_State = type_A; 971 | while(cur_State != type_C) { 972 | 973 | Package _pack = getNewPackage(); 974 | switch(cur_State) { 975 | 976 | case type_A: 977 | process_package_state_A(_pack); 978 | cur_State = type_B; 979 | break; 980 | case type_B: 981 | xxxx; 982 | cur_State = type_C; 983 | break; 984 | } 985 | } 986 | } 987 | ``` 988 | 989 | 花了小一个小时 终于一个字母一个字母的抄完了那个5000多字的代码 990 | @2019年9月8日22:08:46@ 991 | 992 | ### 提高服务器性能的其他建议 池 数据复制 上下文切换和锁 993 | 994 | **池** - 用空间换取时间 995 | 进程池和线程池 996 | 997 | **数据复制** - 高性能的服务器应该尽量避免不必要的复制 998 | 999 | **上下文切换和锁** 1000 | 减少`锁`的作用区域. 不应该创建太多的工作进程, 而是使用专门的业务逻辑线程. 1001 | 1002 | # 第九章 I/O复用 1003 | 1004 | I/O复用使得程序能同时监听多个文件描述符. 1005 | - 客户端程序需要同时处理多个socket 非阻塞connect技术 1006 | - 客户端程序同时处理用户输入和网络连接 聊天室程序 1007 | - TCP服务器要同时处理监听socket和连接socket 1008 | - 同时处理TCP和UDP请求 - 回射服务器 1009 | - 同时监听多个端口, 或者处理多种服务 - xinetd服务器 1010 | 1011 | 常用手段`select`, `poll`, `epoll` 1012 | 1013 | ## select 1014 | ```c++ 1015 | #include 1016 | // nfds - 被监听的文件描述符总数 1017 | // 后面三个分别指向 可读, 可写, 异常等事件对应的文件描述符集合 1018 | // timeval select超时时间 如果传递0 则为非阻塞, 设置为NULL则为阻塞 1019 | // 成功返回就绪(可读, 可写, 异常)文件描述符的总数, 没有则返回0 失败返回-1 1020 | int select (int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout); 1021 | 1022 | //操作fd_set的宏 1023 | FD_ZERO(fd_set* fdset); 1024 | FD_SET(int fd, fd_set* fdset); 1025 | FD_CLR(int fd, fd_set* fdset); 1026 | FD_ISSET(int fd, fd_set* fdset); 1027 | // 设置 timeval 超时时间 1028 | struct timeval 1029 | { 1030 | long tv_sec; // 秒 1031 | long tv_usec; // 微秒 1032 | } 1033 | ``` 1034 | **select** 1035 | 1036 | 文件描述符就绪条件 1037 | - socket内核接收缓存区中的字节数大于或等于 其低水位标记 1038 | - socket通信的对方关闭连接, 对socket的读操作返回0 1039 | - 监听socket上有新的连接请求 1040 | - socket上有未处理的错误, 可以使用getsockopt来读取和清除错误 1041 | - socket内核的发送缓冲区的可用字节数大于或等于 其低水位标记 1042 | - socket的写操作被关闭, 对被关闭的socket执行写操作将会触发一个SIGPIPE信号 1043 | - socket使用非阻塞connect 连接成功或失败后 1044 | ## poll 1045 | **poll** 1046 | ![](https://lsmg-img.oss-cn-beijing.aliyuncs.com/Linux%E9%AB%98%E6%80%A7%E8%83%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E8%AE%B0%E5%BD%95/poll%E6%97%B6%E9%97%B4%E7%B1%BB%E5%9E%8B1.png) 1047 | ![](https://lsmg-img.oss-cn-beijing.aliyuncs.com/Linux%E9%AB%98%E6%80%A7%E8%83%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E8%AE%B0%E5%BD%95/poll%E6%97%B6%E9%97%B4%E7%B1%BB%E5%9E%8B2.png) 1048 | 1049 | ```c++ 1050 | #include 1051 | // fds 结构体类型数组 指定我们感兴趣的文件描述符上发生的可读可写和异常事件\ 1052 | // nfds 遍历结合大小 左闭右开 1053 | // timeout 单位为毫秒 -1 为阻塞 0 为立即返回 1054 | int poll(struct pollfd* fds, nfds_t nfds, int timeout); 1055 | 1056 | struct pollfd 1057 | { 1058 | int fd; 1059 | short events; //注册的事件, 告知poll监听fd上的哪些事件 1060 | short revents; // 实际发生的事件 1061 | } 1062 | ``` 1063 | ```c++ 1064 | #define exit_if(r, ...) \ 1065 | { \ 1066 | if (r) \ 1067 | { \ 1068 | printf(__VA_ARGS__); \ 1069 | printf("errno no: %d, error msg is %s", errno, strerror(errno)); \ 1070 | exit(1); \ 1071 | } \ 1072 | } \ 1073 | 1074 | struct client_info 1075 | { 1076 | char *ip_; 1077 | int port_; 1078 | }; 1079 | 1080 | int main(int argc, char* argv[]) 1081 | { 1082 | int port = 8001; 1083 | char ip[] = "127.0.0.1"; 1084 | 1085 | struct sockaddr_in address; 1086 | address.sin_port = htons(port); 1087 | address.sin_family = AF_INET; 1088 | address.sin_addr.s_addr = htons(INADDR_ANY); 1089 | 1090 | int listenfd = socket(PF_INET, SOCK_STREAM, 0); 1091 | exit_if(listenfd < 0, "socket error\n"); 1092 | 1093 | int ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address)); 1094 | exit_if(ret == -1, "bind error\n"); 1095 | 1096 | ret = listen(listenfd, 5); 1097 | exit_if(ret == -1, "listen error\n"); 1098 | 1099 | constexpr int MAX_CLIENTS = 1024; 1100 | struct pollfd polls[MAX_CLIENTS] = {}; 1101 | struct client_info clientsinfo[MAX_CLIENTS] = {}; 1102 | 1103 | polls[3].fd = listenfd; 1104 | polls[3].events = POLLIN | POLLRDHUP; 1105 | 1106 | 1107 | while (true) 1108 | { 1109 | ret = poll(polls, MAX_CLIENTS + 1, -1); 1110 | exit_if(ret == -1, "poll error\n"); 1111 | 1112 | for (int i = 3; i <= MAX_CLIENTS; ++i) 1113 | { 1114 | int fd = polls[i].fd; 1115 | 1116 | if (polls[i].revents & POLLRDHUP) 1117 | { 1118 | polls[i].events = 0; 1119 | printf("close fd-%d from %s:%d\n", fd, clientsinfo[fd].ip_, clientsinfo[fd].port_); 1120 | } 1121 | 1122 | if (polls[i].revents & POLLIN) 1123 | { 1124 | if (fd == listenfd) 1125 | { 1126 | struct sockaddr_in client_address; 1127 | socklen_t client_addresslen = sizeof(client_address); 1128 | 1129 | int clientfd = accept(listenfd, (struct sockaddr*)&client_address, 1130 | &client_addresslen); 1131 | 1132 | struct client_info *clientinfo = &clientsinfo[clientfd]; 1133 | 1134 | clientinfo->ip_ = inet_ntoa(client_address.sin_addr); 1135 | clientinfo->port_ = ntohs(client_address.sin_port); 1136 | 1137 | exit_if(clientfd < 0, "accpet error, from %s:%d\n", clientinfo->ip_, 1138 | clientinfo->port_); 1139 | printf("accept from %s:%d\n", clientinfo->ip_, clientinfo->port_); 1140 | 1141 | polls[clientfd].fd = clientfd; 1142 | polls[clientfd].events = POLLIN | POLLRDHUP; 1143 | } 1144 | else 1145 | { 1146 | char buffer[1024]; 1147 | memset(buffer, '\0', sizeof(buffer)); 1148 | 1149 | ret = read(fd, buffer, 1024); 1150 | if(ret == 0) 1151 | { 1152 | close(fd); 1153 | } 1154 | else 1155 | { 1156 | printf("recv from %s:%d:\n%s\n", clientsinfo[fd].ip_, 1157 | clientsinfo[fd].port_, buffer); 1158 | } 1159 | } 1160 | } 1161 | } 1162 | } 1163 | } 1164 | ``` 1165 | ## epoll 1166 | **epoll** 1167 | 1168 | epoll是Linux特有的I/O复用函数, 实现上与select,poll有很大的差异 1169 | - epoll使用一组函数完成任务 1170 | - epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中 1171 | - epoll无需每次调用都传入文件描述符集或事件集. 1172 | 1173 | 有特定的文件描述符创建函数, 来标识这个事件表`epoll_create()` 1174 | `epoll_ctl()` 用来操作这个内核事件表 1175 | `epoll_wait()` 为主要函数 成功返回就绪的文件描述符个数 失败返回-1 1176 | 如果`epoll_wait()`函数检测到事件,就将所有就绪的事件从内核事件表(由第一个参数, epoll_create返回的结果) 中复制到第二个参数event指向的数组中, 这个数组只用于输出`epoll_wait`检测到的就绪事件. 1177 | 1178 | *event不同于select和poll的数组参数 既用于传入用户注册的事件, 又用于输出内核检测到的就绪事件, 提高了效率* 1179 | 1180 | ```c++ 1181 | // 索引poll返回的就绪文件描述符 1182 | int ret = poll(fds, MAX_EVENT_NUMBER - 1); 1183 | // 遍历 1184 | for(int i = 0; i < MAX_EVENT_NUMBER; ++i) { 1185 | if(fds[i].revents & POLLIN) { 1186 | int sockfd = fds[i].fd; 1187 | } 1188 | } 1189 | 1190 | // 索引epoll返回的就绪文件描述符 1191 | int ret = epoll_wait(epoll_fd, events, MAX_EVENT_NUMBER, -1); 1192 | for(int i = 0; i < ret; i++) { 1193 | int sockfd = events[i].data.fd; 1194 | // sockfd 一定就绪 ????? 1195 | } 1196 | ``` 1197 | 1198 | **LT和ET模式** 1199 | LT(电平触发, 默认的工作模式) 1200 | LT模式下的epoll相当于一个效率较高的poll 1201 | epoll_wait将会一只通知一个事件知道这个事件被处理 1202 | 1203 | ET(边沿触发, epoll的高效工作模式)模式 1204 | 当向epoll内核事件表中注册一个文件描述符上的EPOLLET事件的时候, epoll将用ET模式来操作这个 1205 | 文件描述符 1206 | epoll_wait只会通知一次, 不论这个事件有没有完成 1207 | 1208 | ET模式 1209 | ``` 1210 | -> 123456789-123456789-123456789 1211 | event trigger once 1212 | get 9bytes of content: 123456789 1213 | get 9bytes of content: -12345678 1214 | get 9bytes of content: 9-1234567 1215 | get 4bytes of content: 89 1216 | read later 1217 | ``` 1218 | LT模式 1219 | ``` 1220 | -> 123456789-123456789-123456789 1221 | event trigger once 1222 | get 9bytes of contents: 123456789 1223 | event trigger once 1224 | get 9bytes of contents: -12345678 1225 | event trigger once 1226 | get 9bytes of contents: 9-1234567 1227 | event trigger once 1228 | get 4bytes of contents: 89 1229 | ``` 1230 | ET模式有任务到来就必须做完, 因为后续将不会继续通知这个事件, 所以ET是epoll的高效工作模式 1231 | LT模式只要事件没被处理就会一直通知 1232 | 1233 | ```c++ 1234 | #include 1235 | // size 参数只是给内核一个提示, 事件表需要多大 1236 | // 函数返回其他所有epoll系统调用的第一个参数, 来指定要访问的内核事件表 1237 | int epoll_create(int size); 1238 | 1239 | // epfd 为 epoll_create的返回值 1240 | // op为操作类型 1241 | // - EPOLL_CTL_ADD 向事件表中注册fd上的事件 1242 | // - EPOLL_CTL_MOD 修改fd上的注册事件 1243 | // - EPOLL_CTL_DEL 删除fd上的注册事件 1244 | // fd 为要操作的文件描述符 1245 | int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event); 1246 | 1247 | struct epoll_event 1248 | { 1249 | _uint32_t events; // epoll事件 1250 | epoll_data_t data; // 用户数据 是一个联合体 1251 | } 1252 | 1253 | typedef union epoll_data 1254 | { 1255 | void* ptr; // ptr fd 不能同时使用 1256 | int fd; 1257 | uint32_t u32; 1258 | uint64_t u64; 1259 | }epoll_data_t 1260 | 1261 | // maxevents监听事件数 必须大于0 1262 | // timeout 为-1 表示阻塞 1263 | // 成功返回就绪的文件描述符个数 失败返回-1 1264 | int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout); 1265 | ``` 1266 | 1267 | ## 三种IO复用的比较 1268 | `select`以及`poll`和`epoll` 1269 | 相同 1270 | - 都能同时监听多个文件描述符, 都将等待timeout参数指定的超时时间, 直到一个或多个文件描述符上有事件发生. 1271 | - 返回值为就绪的文件描述符数量, 返回0则表示没有事件发生 1272 | - ![](https://lsmg-img.oss-cn-beijing.aliyuncs.com/Linux%E9%AB%98%E6%80%A7%E8%83%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E8%AE%B0%E5%BD%95/%E4%B8%89%E7%A7%8DIO%E5%A4%8D%E7%94%A8%E6%AF%94%E8%BE%83.png) 1273 | 1274 | ## I/O 复用的高级应用, 非阻塞connect 1275 | 1276 | connect出错的时候会返回一个errno值 EINPROGRESS - 表示对非阻塞socket调用connect, 连接又没有立即建立的时候, 这时可以调用select和poll函数来监听这个连接失败的socket上的可写事件. 1277 | 1278 | 当函数返回的时候, 可以用getsockopt来读取错误码, 并清楚该socket上的错误. 错误码为0表示成功 1279 | 1280 | 1281 | # 第十章信号 1282 | 1283 | ## Api 1284 | 发送信号Api 1285 | ```c++ 1286 | #include 1287 | #include 1288 | 1289 | // pid > 0 发送给PID为pid标识的进程 1290 | // 0 发送给本进程组的其他进程 1291 | // -1 发送给进程以外的所有进程, 但发送者需要有对目标进程发送信号的权限 1292 | // < -1 发送给组ID为 -pid 的进程组中的所有成员 1293 | 1294 | // 出错信息 EINVAL 无效信号, EPERM 该进程没有权限给任何一个目标进程 ESRCH 目标进程(组) 不存在 1295 | int kill(pid_t pid, int sig); 1296 | ``` 1297 | 接收信号Api 1298 | ```c++ 1299 | #include 1300 | typedef void(*_sighandler_t) (int); 1301 | 1302 | #include // 此头文件中有所有的linux可用信号 1303 | // 忽略目标信号 1304 | #define SIG_DFL ((_sighandler_t) 0) 1305 | // 使用信号的默认处理方式 1306 | #define SIG_IGN ((_sighandler_t) 1) 1307 | ``` 1308 | 常用信号 1309 | ``` 1310 | SIGHUP 控制终端挂起 1311 | SIGPIPE 往读端被关闭的管道或者socket连接中写数据 1312 | SIGURG socket连接上收到紧急数据 1313 | SIGALRM 由alarm或setitimer设置的实时闹钟超时引起 1314 | SIGCHLD 子进程状态变化 1315 | ``` 1316 | 信号函数 1317 | ```c++ 1318 | // 为一个信号设置处理函数 1319 | #include 1320 | // _handler 指定sig的处理函数 1321 | _sighandler_t signal(int sig, __sighandler_t _handler) 1322 | 1323 | 1324 | int sigaction(int sig, struct sigaction* act, struct sigaction* oact) 1325 | ``` 1326 | ## 概述 1327 | 1328 | 信号是用户, 系统, 或者进程发送给目标进程的信息, 以通知目标进程某个状态的改变或者系统异常. 1329 | 产生条件 1330 | - 对于前台进程 1331 | 用户可以通过输入特殊的终端字符来给它发送信号, CTRL+C 通常为一个中断信号 `SIGINT` 1332 | - 系统异常 1333 | 浮点异常和非法内存段的访问 1334 | - 系统状态变化 1335 | 由alarm定时器到期将引起`SIGALRM`信号 1336 | - 运行kill命令或调用kill函数 1337 | 1338 | *服务器必须处理(至少忽略) 一些常见的信号, 以免异常终止* 1339 | 1340 | 中断系统调用? 1341 | 1342 | # 第十一章定时器 1343 | ## socket选项`SO_RCVTIMEO` 和 `SO_SNDTIMEO` 1344 | ![](https://lsmg-img.oss-cn-beijing.aliyuncs.com/Linux%E9%AB%98%E6%80%A7%E8%83%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E8%AE%B0%E5%BD%95/SO_RCVTIMEO%E5%92%8CSO_SNDTIMEO%E9%80%89%E9%A1%B9%E7%9A%84%E4%BD%9C%E7%94%A8.png) 1345 | 1346 | 使用示例, 通过设置对应的SO_SNDTIMEO 得到超时后的路线 1347 | ```c++ 1348 | int timeout_connect(const char* ip, const int port, const int sec) 1349 | { 1350 | struct sockaddr_in address{}; 1351 | address.sin_family = AF_INET; 1352 | address.sin_port = htons(port); 1353 | address.sin_addr.s_addr = inet_addr(ip); 1354 | 1355 | int sockfd = socket(PF_INET, SOCK_STREAM, 0); 1356 | exit_if(sockfd < 0, "socket error\n"); 1357 | 1358 | struct timeval timeout{}; 1359 | timeout.tv_sec = sec; 1360 | timeout.tv_usec = 0; 1361 | socklen_t timeout_len = sizeof(timeout); 1362 | 1363 | setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, timeout_len); 1364 | 1365 | int ret = connect(sockfd, (struct sockaddr*)&address, sizeof(address)); 1366 | if (ret == -1) 1367 | { 1368 | // 当 errno为EINPROGRESS 说明 等待了 10S后依然无法连接成功 实现了定时器 1369 | if (errno == EINPROGRESS) 1370 | { 1371 | printf("connecting timeout, process timeout logic\n"); 1372 | return -1; 1373 | } 1374 | printf("error occur when connecting to server\n"); 1375 | return -1; 1376 | } 1377 | return sockfd; 1378 | } 1379 | 1380 | int main(int argc, char* argv[]) 1381 | { 1382 | exit_if(argc <= 2, "wrong number of parameters\n") 1383 | const char* ip = argv[1]; 1384 | const int port = atoi(argv[2]); 1385 | 1386 | int sockfd = timeout_connect(ip, port, 10); 1387 | if (sockfd < 0) 1388 | { 1389 | return 1; 1390 | } 1391 | return 0; 1392 | } 1393 | ``` 1394 | 1395 | ## SIGALRM信号-基于升序链表的定时器 1396 | 由alarm和setitimer函数设定的实时闹钟一旦超时, 将会触发SIGALRM信号, 用信号处理函数处理定时任务 1397 | 相关的代码放在了github上 代码还是很多的就不放上来了[连接](https://github.com/rjd67441/Notes-HighPerformanceLinuxServerProgramming/tree/master/12.%20%E4%BB%A3%E7%A0%81%E6%B8%85%E5%8D%9511-2%E5%92%8C11-3%E5%8F%8A11-4%20%E9%93%BE%E8%A1%A8%E5%AE%9A%E6%97%B6%E5%99%A8%2C%20%E5%A4%84%E7%90%86%E9%9D%9E%E6%B4%BB%E5%8A%A8%E8%BF%9E%E6%8E%A5) 1398 | 1399 | 总结放在了 日记的博客上 链接后面再甩出来 1400 | ## IO复用系统调用的超时参数 1401 | 1402 | ## 高性能定时器 1403 | ## # 时间轮 1404 | ## # 时间堆 1405 | 1406 | # 第十二章高性能IO框架库 1407 | 另出一篇博客 1408 | 1409 | # 第十三章多进程编程 1410 | 1411 | ## exec系列系统调用 1412 | ```c++ 1413 | #include 1414 | // 声明这个是外部函数或外部变量 1415 | extern char** environ; 1416 | 1417 | // path 参数指定可执行文件的完成路径 file接收文件名,具体位置在PATH中搜寻 1418 | // arg-接受可变参数 和 argv用于向新的程序传递参数数组 1419 | // envp用于设置新程序的环境变量, 未设置则使用全局的环境变量 1420 | // exec函数是不返回的, 除非出错 1421 | // 如果未报错则源程序被新的程序完全替换 1422 | 1423 | int execl(const char* path, const char* arg, ...); 1424 | int execlp(const char* file, const char* arg, ...); 1425 | int execle(const char* path, const char* arg, ..., char* const envp[]) 1426 | int execv(const char* path, char* const argv[]); 1427 | int execvp(const char* file, char* const argv[]); 1428 | int execve(const char* path, char* const argv[], char* const envp[]); 1429 | ``` 1430 | ## fork系统调用-进程的创建 1431 | ```c++ 1432 | #include 1433 | #include 1434 | // 每次调用都返回两次, 在父进程中返回的子进程的PID, 在子进程中返回0 1435 | // 次返回值用于区分是父进程还是子进程 1436 | // 失败返回-1 1437 | pid_t fork(viod); 1438 | ``` 1439 | fork系统调用 1440 | fork() 函数复制当前的进程, 在内核进程表中创建一个新的进程表项 1441 | 新的进程表项有很多的属性和原进程相同 1442 | - 堆指针 1443 | - 栈指针 1444 | - 标志寄存器的值 1445 | - 子进程代码与父进程完全相同 1446 | - 同时复制(采用了写时复制, 父进程和子进程对数据执行了写操作才会复制)父进程的数据(堆数据, 栈数据, 静态数据) 1447 | - 创建子进程后, *父进程打开的文件描述符默认在子进程中也是打开的* `文件描述符的引用计数`, `父进程的用户根目录, 当前工作目录等变量的引用计数` 均加1 1448 | 1449 | 也存在不同的项目 1450 | - 该进程的PPID(标识父进程)被设置成原进程的PID, 1451 | - `信号位图被清除`(原进程设置的信号处理函数对新进程无效) 1452 | 1453 | 1454 | (引自维基百科-引用计数是计算机编程语言中的一种内存管理技术,是指将资源(可以是对象、内存或磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。) 1455 | 1456 | 1457 | The child process is an exact duplicate of the parent process except 1458 | for the following points: 1459 | * The child has its own unique process ID, and this PID does not 1460 | match the ID of any existing process group (setpgid(2)) or 1461 | session. 子进程拥有自己唯一的进程ID, 不与其他相同 1462 | 1463 | * The child's parent process ID is the same as the parent's process 1464 | ID. 子进程的父进程ID PPID 与父进程ID PID相同 1465 | 1466 | * The child does not inherit its parent's memory locks (mlock(2), 1467 | mlockall(2)). 子进程不继承父进程的内存锁(保证一部分内存处于内存中, 而不是sawp分区) 1468 | 1469 | * Process resource utilizations (getrusage(2)) and CPU time counters 1470 | (times(2)) are reset to zero in the child. 1471 | 进程资源使用和CPU时间计数器在子进程中重置为0 1472 | 1473 | * The child's set of pending signals is initially empty 1474 | (sigpending(2)). 信号位图被初始化为空 原信号处理函数对子进程无效 需重新设置 1475 | 1476 | * The child does not inherit semaphore adjustments from its parent 1477 | (semop(2)). 不会继承semadj 1478 | 1479 | * The child does not inherit process-associated record locks from 1480 | its parent (fcntl(2)). (On the other hand, it does inherit 1481 | fcntl(2) open file description locks and flock(2) locks from its 1482 | parent.) 1483 | 1484 | * The child does not inherit timers from its parent (setitimer(2), 1485 | alarm(2), timer_create(2)). 不会继承定时器 1486 | 1487 | * The child does not inherit outstanding asynchronous I/O operations 1488 | from its parent (aio_read(3), aio_write(3)), nor does it inherit 1489 | any asynchronous I/O contexts from its parent (see io_setup(2)). 1490 | 1491 | 1492 | 1493 | 1494 | ## 处理僵尸进程-进程的管理 1495 | ```c++ 1496 | #include 1497 | #include 1498 | // wait进程将阻塞进程, 直到该进程的某个子进程结束运行为止. 他返回结束的子进程的PID, 并将该子进程的退出状态存储于stat_loc参数指向的内存中. sys/wait.h 头文件中定义了宏来帮助解释退出信息. 1499 | pid_t wait(int* stat_loc); 1500 | 1501 | // 非阻塞, 只等待由pid指定的目标子进程(-1为阻塞) 1502 | // options函数取值WNOHANG-waitpid立即返回 1503 | // 如果目标子进程正常退出, 则返回子进程的pid 1504 | // 如果还没有结束或意外终止, 则立即返回0 1505 | // 调用失败返回-1 1506 | pid_t waitpid(pid_t pid, int* stat_loc, int options); 1507 | 1508 | WIFEXITED(stat_val); // 子进程正常结束, 返回一个非0 1509 | WEXITSTATUS(stat_val); // 如果WIFEXITED 非0, 它返回子进程的退出码 1510 | WIFSIGNALED(stat_val);// 如果子进程是因为一个未捕获的信号而终止, 返回一个非0值 1511 | WTERMSIG(stat_val);// 如果WIFSIGNALED非0 返回一个信号值 1512 | WIFSTOPPED(stat_val);// 如果子进程意外终止, 它返回一个非0值 1513 | WSTOPSIG(stat_val);// 如果WIFSTOPED非0, 它返回一个信号值 1514 | ``` 1515 | 1516 | 对于多进程程序而言, 父进程一般需要跟踪子进程的退出状态. 因此, 当子进程结束运行是, 内核不会立即释放该进程的进程表表项, 以满足父进程后续对孩子进程推出信息的查询 1517 | - 在`子进程结束运行之后, 父进程读取其退出状态前`, 我们称该子进程处于`僵尸态` 1518 | - 另外一使子进程进入僵尸态的情况 - 父进程结束或者异常终止, 而子进程继续运行. (子进程的PPID设置为1,init进程接管了子进程) `父进程结束运行之后, 子进程退出之前`, 处于`僵尸态` 1519 | 1520 | 以上两种状态都是父进程没有正确处理子进程的返回信息, 子进程都停留在僵尸态, 占据着内核资源. 1521 | 1522 | waitpid()虽然为非阻塞, 则需要在 waitpid所监视的进程结束后再调用. 1523 | SIGCHLD信号- 子进程结束后将会给父进程发送此信号 1524 | ```c++ 1525 | static void handle_child(int sig) 1526 | { 1527 | pid_t pid; 1528 | int stat; 1529 | while ((pid = waitpid(-1, &stat, WNOHANG)) > 0) 1530 | { 1531 | // 善后处理emmmm 1532 | } 1533 | } 1534 | ``` 1535 | 1536 | ## 信号量-进程的锁 1537 | *信号量原语* 1538 | 只支持两种操作, 等待(wait)和信号(signal) , 在LInux中等待和信号有特殊的含义, 所以又称为P(passeren, 传递就好像进入临界区)V(vrijgeven, 释放就好像退出临界区)操作. 1539 | 假设有信号量SV(可取任何自然数, 这本书只讨论二进制信号量), 对它的PV操作含义为 1540 | - P(SV), 如果SV的值大于0, 就将它减1, 如果sv的值为0 则挂起进程的执行 1541 | - V(SV), 如果其他进程因为等待SV而挂起, 则唤醒之, 如果没有则将SV加1 1542 | ![](https://lsmg-img.oss-cn-beijing.aliyuncs.com/Linux%E9%AB%98%E6%80%A7%E8%83%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E8%AE%B0%E5%BD%95/%E4%BD%BF%E7%94%A8%E4%BF%A1%E5%8F%B7%E9%87%8F%E4%BF%9D%E6%8A%A4%E5%85%B3%E9%94%AE%E4%BB%A3%E7%A0%81%E6%AE%B5.png) 1543 | 1544 | **总结PV使用方法** 1545 | 1546 | 使用`semget`获取到唯一的标识. 1547 | 使用`semctl`的`SETVAL`传入初始化val的`sem_un`联合体.来初始化val 1548 | 调用`semop` 传入唯一标识, `sem_op=-1`执行P(锁)操作`sem_op=1`执行V(开锁)操作 1549 | 开关锁通过当`sem_op=-1,semval=0 ` 1550 | 且未指定`IPC_NOWAIT` 1551 | 等待`semval`被`sem_op=1`改为`semval=1` 1552 | 1553 | **创建信号量** 1554 | ```c++ 1555 | // semeget 系统调用 1556 | // 创建一个全局唯一的信号量集, 或者获取一个已经存在的信号量集 1557 | // key 参数是一个键值, 用来标识一个全局唯一的信号量级,可以在不同进程中获取 1558 | // num_sems 参数指定要创建/获取的信号量集中信号量的数目. 如果是创建信号量-必须指定, 如果是获取-可以指定为0. 一般都是为1 1559 | // sem_flags指定一组标志, 来控制权限 1560 | // - 可以与IPC_CREAT 做或运算创建新的信号量集, 即使信号量集存在也不会报错 1561 | // - IPC_CREAT | IPC_EXCL来创建一组唯一信号量集 如果已经存在则会返回错误 errno = EEXIST 1562 | // 成功返回一个正整数, 是信号量集的标识符, 失败返回 -1 1563 | int semget(key_t key, int num_sems, int sem_flags); 1564 | 1565 | int sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT); 1566 | ``` 1567 | 1568 | **初始化** 1569 | ```c++ 1570 | // semctl 系统调用 1571 | // sem_id 参数是由semget返回的信号量集标识符 1572 | // sen_num指定被操作的信号量在信号集中的编号 1573 | // command指定命令, 可以追加命令所需的参数, 不过有推荐格式 1574 | // 成功返回对应command的参数, 失败返回-1 errno 1575 | int semctl(int sem_id, int sem_num, int command, ...); 1576 | 1577 | // 第四个参数 竟然需要手动声明... 1578 | union semun 1579 | { 1580 | int val; /* Value for SETVAL */ 1581 | struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ 1582 | unsigned short *array; /* Array for GETALL, SETALL */ 1583 | struct seminfo *__buf; /* Buffer for IPC_INFO 1584 | (Linux-specific) */ 1585 | }; 1586 | // 初始化信号量 1587 | union semun sem_union; 1588 | sem_union.val = 1; 1589 | // 这里可以直接第三个参数传入1(val) 1590 | if (semctl(sem_id, 0, SETVAL, sem_union) == -1) 1591 | { 1592 | exit(0); 1593 | } 1594 | 1595 | // 删除信号量 1596 | union semun sem_union{}; 1597 | if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1) 1598 | { 1599 | exit(EXIT_FAILURE); 1600 | } 1601 | ``` 1602 | --- 1603 | 与semop信号量关联的一些重要的内核变量 1604 | ```c++ 1605 | unsigned short semval; // 信号量的值 1606 | unsigned short semzcnt; // 等待信号量值变为0的进程数量 1607 | unsigned short semncnt// 等待信号量值增加的进程数量 1608 | pid_t sempid; // 最后一次执行semop操作的进程ID 1609 | ``` 1610 | 操作信号量, 实际上就是对上面的内核变量操作 1611 | 1612 | ```c++ 1613 | // sem_id 是由semget调用返回的信号量集的标识符, 用以指定被操作的,目标信号量集. 1614 | // sem_ops 参数指向一个sembuf结构体类型的数组 1615 | // num_sem_ops 说明操作数组中哪个信号量 1616 | // 成功返回0, 失败返回-1 errno. 失败的时候sem_ops[] 中的所有操作不执行 1617 | int semop(int sem_id, struct sembuf* sem_ops, size_t num_sem_ops); 1618 | 1619 | // sem_op < 0 期望获得信号量 1620 | // semval-=abs(sem_op),要求调用进程对被操作信号量集有写权限 1621 | // 如果semval的值大于等于sem_op的绝对值, 则操作成功, 调用进程立即获得信号量 1622 | 1623 | // 如果semval < abs(sem_op) 则在被指定IPC_NOWAIT的时候semop立即返回error, errno=EAGIN 1624 | // 如果没有指定 则 阻塞进程等待信号量可用, 且 semzcnt +=1, 等到下面三种情况唤醒 1625 | // 1 发生semval >= abs(sem_op), semzcnt-=1, semval-=abs(sem_op). 在SEM_UNDO设置时更新semadj 1626 | // 2 被操作的信号量所在的信号量集被进程移除, 此时semop调用失败返回, errno=EIDRM (同 sem_op = 0) 1627 | // 3 调用被系统中断, 此时semop调用失败返回, errno=EINTR, 同时将该信号量的semzcnt减1 (同 sem_op = 0) 1628 | bool P(int sem_id) 1629 | { 1630 | struct sembuf sem_b; 1631 | sem_b.sem_num = 0; // 信号量编号 第几个信号量 一般都是第0个 1632 | sem_b.sem_op = -1; // P 1633 | // IPC_NOWAIT 无论信号量操作是否成功, 都立即返回 1634 | // SEM_UNDO当进程退出的时候, 取消正在进行的semop操作 PV操作系统更新进程的semadj变量 1635 | sem_b.sem_flg = SEM_UNDO; 1636 | return semop(sem_id, &sem_b, 1) != -1; 1637 | } 1638 | 1639 | 1640 | // sem_op > 0 1641 | // semval+=sem_op , 要求调用进程对被操作的信号量集有写权限 1642 | // 如果此时设置了SEM_UNDO标志, 则系统将更新进程的semadj变量(用以跟踪进程对信号量的修改情况) 1643 | bool V(int sem_id) 1644 | { 1645 | struct sembuf sem_b; 1646 | sem_b.sem_num = 0; 1647 | sem_b.sem_op = 1; // V 1648 | sem_b.sem_flg = SEM_UNDO; 1649 | return semop(sem_id, &sem_b, 1) != -1; 1650 | } 1651 | 1652 | 1653 | // -- sem_op = 0 1654 | // -- 标着这是一个`等待0`的操作, 要求调用进程对被操作信号量集有用读权限 1655 | // -- 如果此时信号量的值是0, 则调用立即返回, 否则semop失败返回, 或者阻塞进程以等待信号量变为0 1656 | // -- 此时如果IPC_NOWAIT 标志被设置, sem_op立即返回错误 errno=EAGAIN 1657 | // -- 如果未指定此标志, 则信号量的semzcnt的值增加1, 这时进程被投入睡眠直到下列三个条件之一发生 1658 | // -- 1 信号量的值samval变为0, 此时系统将该信号量的semzcnt减1 1659 | // -- 2 被操作的信号量所在的信号量集被进程移除, 此时semop调用失败返回, errno=EIDRM 1660 | // -- 3 调用被系统中断, 此时semop调用失败返回, errno=EINTR, 同时将该信号量的semzcnt减1 1661 | 1662 | ``` 1663 | 1664 | semget成功时返回一个与之关联的内核结构体semid_ds 1665 | ```c++ 1666 | struct semid_ds 1667 | { 1668 | struct ipc_perm sem_perm; 1669 | unsigned long int sem_nsems; // 被设置为num_sems 1670 | time_t sem_otime; // 被设置为0 1671 | time_t sem_ctime; // 被设置为当前的系统时间 1672 | } 1673 | // 用来描述权限 1674 | struct ipc_perm 1675 | { 1676 | uid_t uid; // 所有者的有效用户ID, 被semget设置为调用进程的有效用户ID 1677 | gid_t gid; // 所有者的有效组ID, 被semget设置为调用进程的有效用户ID 1678 | uid_t cuid; // 创建者的有效用户ID, 被semget设置为调用进程的有效用户ID 1679 | gid_t cgid; // 创建者的有效组ID, 被semget设置为调用进程的有效用户ID 1680 | mode_t mode;// 访问权限, 背着只为sem_flags参数的最低9位. 1681 | } 1682 | ``` 1683 | 1684 | ## 共享内存-进程间通信 1685 | **最高效的IPC(进程间通信)机制** 1686 | 需要自己同步进程对其的访问, 否则会产生竞态条件 1687 | 1688 | ```c++ 1689 | // key 1690 | // 与semget相同 标识一段全局唯一的共享内存 1691 | // size 内存区域大小 单位字节 1692 | // shmflg 1693 | // IPC_CREAT 存不存在都创建新的共享内存 1694 | // IPC_CREAT | IPC_EXCL 不存在则创建 存在则报错 1695 | // SHM_HUGETLB 系统将使用"大页面"来为共享内存分配空间 1696 | // SHM_NORESERVE 不为共享内存保留swap空间, 如果物理内存不足 1697 | // -在执行写操作的时候将会触发`SIGSEGV`信号 1698 | // -成功返回唯一标识, 失败返回-1 errno 1699 | int shmget(key_t key, size_t size, int shmflg) 1700 | ``` 1701 | 1702 | ```c++ 1703 | // shm_id 1704 | // shmget返回的唯一标识 1705 | // shm_addr 1706 | // 关联到进程的哪块地址空间, 其效果还受到shmflg的可选标识SHM_RND的影响 1707 | // 如果shm_addr = NULL, 则关联地址由操作系统决定, 代码可移植性强 1708 | // 如果 shm_addr 非空,且没有`SHM_RND`标志 则关联到指定的地址处 1709 | // 如果 shm_addr 非空, 但是设置了标志 *这里还没用到, 暂时不写* 1710 | // shmflg 1711 | // SHM_RDONLY 设置后内存内容变成只读, 不设置则为读写模式 1712 | // SHM_REMAP 如果地址shmaddr已经关联到一段内存上则重新关联 1713 | // SHM_EXEC 有执行权限 1714 | // 成功返回关联到的地址, 失败返回 (void*)-1 errno 1715 | void* shmat(int shm_id, const void* shm_addr, int shmflg) 1716 | 1717 | // 将共享内存关联到进程的地址空间 调用成功之后, 修改shmid_ds的部分内容 1718 | // -shm_nattach +1 1719 | // -更新 shm_lpid 1720 | // -shm_atime设置为当前时间 1721 | ``` 1722 | 1723 | 1724 | ```c++ 1725 | // 将共享内存从进程地址空间中分离 1726 | // 成功后 1727 | // -shm_nattach -1 1728 | // -更新 shm_lpid和shm_dtime设置为当前时间 1729 | // 成功返回0 失败返回-1 errno 1730 | int shmdt(const void* shm_addr) 1731 | ``` 1732 | 1733 | ```c++ 1734 | int shm_ctl(int shm_id, int command, struct shmid_ds* buf) 1735 | ``` 1736 | ![](https://lsmg-img.oss-cn-beijing.aliyuncs.com/Linux%E9%AB%98%E6%80%A7%E8%83%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E8%AE%B0%E5%BD%95/shmctl.png) 1737 | 1738 | ---- 1739 | 1740 | shmget 同时会创建对应的`shmid_ds`结构体 1741 | ```c++ 1742 | struct shmid_ds 1743 | { 1744 | struct ipc_perm shm_per; // 权限相关 1745 | size_t shm_segsz; // 共享内存大小 单位字节 size 1746 | __time_t shm_atime; // 对这段内存最后一次调用semat的时间 0 1747 | __time_t shm_dtime; // 对这段内存最后一次调用semdt的时间 0 1748 | __time_t shm_ctime; // 对这段内存最后一次调用semctl的时间 当前时间 1749 | __pid_t shm_cpid; // 创建者PID 1750 | __pid_t lpid; // 最后一次执行shmat或shmdt的进程PID 1751 | shmatt_t shm_nattach // 关联到此共享内存空间的进程数量 1752 | } 1753 | ``` 1754 | 1755 | **共享内存的POSIX方法** 1756 | ```c++ 1757 | int shmfd = shm_open("/shm_name", O_CREAT | O_RDWR, 0666); 1758 | ERROR_IF(shmfd == -1, "shm open"); 1759 | 1760 | int ret = ftruncate(shmfd, BUFFER_SIZE); 1761 | ERROR_IF(ret == -1, "ftruncate"); 1762 | 1763 | share_mem = (char*)mmap(nullptr, BUFFER_SIZE, 1764 | PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0); 1765 | ERROR_IF(share_mem == MAP_FAILED, "share_mem"); 1766 | close(shmfd); 1767 | 1768 | // 取消关联 1769 | munmap((void*)share_mem, BUFFER_SIZE); 1770 | ``` 1771 | 1772 | ## 进程通信-管道 1773 | 1774 | 管道可以在父,子进程间传递数据, 利用的是fork调用后两个文件描述符(fd[0]和fd[1])都保持打开. 一对这样的文件描述符只能保证 1775 | 父,子进程间一个方向的数据传输, 父进程和子进程必须有一个关闭fd[0], 另一个关闭fd[1]. 1776 | 1777 | 可以用两个管道来实现双向传输数据, 也可以用`socketpair`来创建管道 1778 | 1779 | ## 消息队列 1780 | 消息队列是两个进程之间传递二进制块数据的一种简单有效的方式. 1781 | 每个数据块都有自己的类型, 接收方可以根据类型有选择的接收数据 1782 | 1783 | ```c++ 1784 | #include 1785 | // 与semget 相同, 成功返回标识符 1786 | // msgflg的设置和作用域setget相同 1787 | int msgget(key_t key, int msgflg); 1788 | ``` 1789 | 1790 | ```c++ 1791 | // msg_ptr参数指向一个准备发送的消息, 消息必须按如下定义 1792 | // msg_sz 指的是mtext的长度!!! 1793 | // msgflg通常仅支持IPC_NOWAIT 以非阻塞形式发送数据 1794 | int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg); 1795 | 默认如果消息队列已满, 则会阻塞. 如果设置了 IPC_NOTWAIT 1796 | 就立即返回 设置errno=EAGIN 1797 | 1798 | 系统自带这个结构体 不过mtext长度是1... 1799 | struct msgbuf 1800 | { 1801 | long mtype; /* 消息类型 正整数*/ 1802 | char mtext[512]; /* 消息数据*/ 1803 | } 1804 | ``` 1805 | 1806 | ```c++ 1807 | // msgtype = 0 读取消息队列第一个消息 1808 | // msgtype > 0 读取消息队列第一个类型是msgtype的消息 除非标志了MSG_EXCEPT 1809 | // msgtype < 0 读取第一个 类型值 < abs(msgtype)的消息 1810 | 1811 | // IPC_NOWAIT 如果消息队列没有消息, 则msgrcv立即返回并设置errno=ENOMSG 1812 | // MSG_EXCEPT 如果msgtype大于0, 则接收第一个非 msgtype 的数据 1813 | // MSG_NOERROR 消息部分长度超过msg_sz 则将它截断 1814 | int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg); 1815 | 处于阻塞状态 当消息队列被移除(errno=EIDRM)或者程序接受到信号(errno=EINTR) 都会中断阻塞状态 1816 | ``` 1817 | 1818 | ```c++ 1819 | int msgctl(int msqid, int command, struct msqid_ds *buf); 1820 | 1821 | IPC_STAT 复制消息队列关联的数据结构 1822 | IPC_SET 将buf中的部分成员更新到目标的内核数据 1823 | IPC_RMID 立即移除消息队列, 唤醒所有等待读消息和写消息的进程 1824 | IPC_INFO 获取系统消息队列资源配置信息 1825 | 1826 | MSG_INFO 返回已经分配的消息队列所占用资源信息 1827 | MSG_STAT msgqid不再是标识符, 而是内核消息队列的数组索引 1828 | ``` 1829 | 1830 | 1831 | ## 在进程间传递文件描述符 1832 | 1833 | ## IPC命令-查看进程间通信的全局唯一key 1834 | 1835 | # 第十四章 多线程编程 1836 | 根据运行环境和调度者身份, 线程可以分为两种 1837 | 内核线程 1838 | 运行在内核空间, 由内核来调度. 1839 | 用户线程 1840 | 运行在用空间, 由线程库来调用 1841 | 1842 | 当内核线程获得CPU的使用权的时候, 他就加载并运行一个用户线程, 所以内核线程相当于用户线程的容器. 1843 | 1844 | 线程有三种实现方式 1845 | - 完全在用户空间实现-无需内核支持 1846 | 创建和调度线程无需内核干预, 速度很快. 1847 | 不占用额外的内核资源, 对系统影响较小 1848 | 但是无法运行在多个处理器上, 因为这些用户线程是是实现在一个内核线程上的 1849 | - 完全由内核调度 1850 | 创建和调度线程的任务都交给了内核, 运行在用户空间的线程库无需管理 1851 | 优缺点正好与上一个相反 1852 | - 双层调度 1853 | 结合了前两个的优点 1854 | 不会消耗过多的内核资源,而且线程切换快, 同时它可以充分利用多处理器的优势 1855 | 1856 | ## 进程的创建和终止 1857 | ```c++ 1858 | #include 1859 | int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void* (*start_routine)(void*), void* arg); 1860 | // 成功返回0 失败返回错误码 1861 | // thread 用来唯一的标识一个新线程 1862 | // attr用来设置新县城的属性 传递NULL表示默认线程属性 1863 | // start_routine 指定新线程运行的函数 1864 | // arg指定函数的参数 1865 | ``` 1866 | ```c++ 1867 | void pthread_exit(void* retval); 1868 | 用来保证线程安全干净的退出, 线程函数最好结束时调用. 1869 | 通过`retval`参数向线程的回收者传递其退出信息 1870 | 执行后不会返回到调用者, 而且永远不会失败 1871 | 1872 | int pthread_join(pthread_t thread, void** retval) 1873 | 可以调用这个函数来回收其他线程 不过线程必须是可回收的该函数会一直阻塞知道被回收的线程结束. 1874 | 成功时返回0, 失败返回错误码 1875 | 等待其他线程结束 1876 | thread 线程标识符 1877 | retval 目标线程的退出返回信息 1878 | 1879 | 错误码如下 1880 | `EDEADLK`引起死锁, 两个线程互相针对对方调用pthread_join 或者对自身调用 1881 | `EINVAL`目标线程是不可回收的, 或是其他线程在回收目标线程 1882 | `ESRCH`目标线程不存在 1883 | 1884 | int pthread_cancel(pthread_t thread) 1885 | 异常终止一个线程, 即为取消线程 1886 | 成功返回0, 失败返回错误码 1887 | ``` 1888 | 1889 | **线程属性设置** 1890 | ```c++ 1891 | 接收到取消请求的目标线程可以决定是否允许被取消以及如何取消. 1892 | // 启动线程取消 1893 | int pthread_setcancelstart(int state, int* oldstate) 1894 | 第一个参数 1895 | PTHREAD_CANCEL_ENABLE 允许线程被取消, 默认状态 1896 | PTHREAD_CANCEL_DISABLE 不允许被取消, 如果这种线程接收到取消请求, 则会挂起请求直到 1897 | 这个线程允许被取消 1898 | 第二个参数 返回之前设定的状态 1899 | 1900 | // 设置线程取消类型 1901 | int pthread_setcanceltype(int type, int* oldtype) 1902 | 第一个参数 1903 | PTHREAD_CANCEL_ASYNCHRONOUS 线程可以随时被取消 1904 | PTHREAD_CANCEL_DEFERRED 允许目标现成推迟行动, 直到调用了下面几个所谓的取消点函数 1905 | 最好使用pthread_testcancel函数设置取消点 1906 | 设置取消类型(如何取消) 1907 | 第二个参数 1908 | 原来的取消类型 1909 | ``` 1910 | 1911 | **设置脱离线程** 1912 | ```c++ 1913 | // 初始化线程属性对象 1914 | int pthread_attr_init(pthread_attr_t *attr); 1915 | // 销毁线程属性对象, 直到再次初始化前都不能用 1916 | int pthread_attr_destory(pthread_attr_t *attr) 1917 | 1918 | // 参数取值 1919 | // -PTHREAD_CREATE_JOINABLE 线程可回收 1920 | // -PTHREAD_CREATE_DETACH 脱离与进程中其他线程的同步 成为脱离线程 1921 | int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate); 1922 | int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); 1923 | // 可以直接设置为脱离线程 1924 | int pthread_detach(pthread_t thread) 1925 | ``` 1926 | 1927 | ## 线程同步机制的使用场景 1928 | POSIX信号量-需要自己维护计数值, 用户空间有计数值 信号量自己又计数值 1929 | 两份计数值容易出错 1930 | 1931 | 互斥锁-对临界资源的独占式访问 1932 | 1933 | 条件变量-等待某个条件满足 1934 | 当某个共享数据达到某个值的时候, 唤醒等待这个共享数据的线程 1935 | 1936 | 读写锁-可以多个进程读, 读的时候不能写, 同时只能一个写 1937 | 1938 | 自旋锁-通过while循环频繁尝试获取锁, 适用于锁事件短, 需要快速切换的场景 1939 | 1940 | ## POSIX信号量 1941 | 多线程也必须考虑线程同步的问题. 1942 | 虽然`pthread_join()`可以看做简单的线程同步方式不过它无法高效的实现复杂的同步需求 1943 | 比如无法实现共享资源独占式访问, 或者在某种条件下唤醒指定的指定线程. 1944 | 1945 | ```c++ 1946 | #include 1947 | // 用于初始化一个未命名的信号量. 1948 | // pshared==0 则表示是当前进程的局部信号量, 否则信号量可以在多个进程间共享 1949 | // value指定参数的初始值 1950 | int sem_init(sem_t* sem, int pshared, unsigned int value) 1951 | 1952 | // 销毁信号量, 释放其占用的系统资源 1953 | int sem_destory(sem_t* sem) 1954 | 1955 | // 以原子操作的形式将信号量的值 -1, 如果信号量的值为0, 则sem_wait将被阻塞直到sem_wait具有非0值 1956 | int sem_wait(sem_t* sem) 1957 | 1958 | // 跟上面的函数相同不过不会阻塞. 信号量不为0则减一操作, 为0则返回-1 errno 1959 | int sem_trywait(sem_t* sem) 1960 | 1961 | // 原子操作将信号量的值 +1 1962 | int sem_post(sem_t* sem) 1963 | ``` 1964 | 初始化已经存在的信号量会导致无法预期的结果 1965 | 1966 | 销毁正被其他线程等待的信号量, 将会导致无法预期的结果 1967 | 1968 | 例子如下 1969 | ```c++ 1970 | constexpr int kNumberMax = 10; 1971 | std::vector number(kNumberMax); 1972 | 1973 | constexpr int kThreadNum = 10; 1974 | sem_t sems[kThreadNum]; 1975 | pthread_t threads[kThreadNum]; 1976 | 1977 | constexpr int kPrintTime = 1; 1978 | 1979 | void* t(void *no) 1980 | { 1981 | int start_sub = *static_cast(no); 1982 | int sub =start_sub; 1983 | int time = 0; 1984 | while(++time <= kPrintTime) 1985 | { 1986 | // 锁住本线程 释放下一个线程 1987 | sem_wait(&sems[start_sub]); 1988 | printf("%d\n", number[sub]); 1989 | sem_post(&sems[(start_sub + 1) % kThreadNum]); 1990 | // 计算下一次要打印的下标 1991 | sub = (sub + kThreadNum) % kNumberMax; 1992 | } 1993 | pthread_exit(nullptr); 1994 | } 1995 | 1996 | int main() 1997 | { 1998 | std::iota(number.begin(), number.end(), 0); 1999 | sem_init(&sems[0], 0, 1); 2000 | for (int i = 1; i < kThreadNum; ++i) 2001 | { 2002 | sem_init(&sems[i], 0, 0); 2003 | } 2004 | for (int i = 0; i < kThreadNum; ++i) 2005 | { 2006 | pthread_create(&threads[i], nullptr, t, &number[i]); 2007 | } 2008 | // 等待最后一个线程结束 2009 | pthread_join(threads[kThreadNum - 1], nullptr); 2010 | } 2011 | ``` 2012 | kThreadNum个进程依次打印`[0, kNumberMax)` 2013 | 每个进程打印kPrintTime次 2014 | 最后一个进程打印完后主线程才能结束 2015 | 2016 | ## 互斥锁 2017 | ```c++ 2018 | // 初始化互斥锁 2019 | // 第一个参数指向目标互斥锁, 第二个参数指定属性 nullptr则为默认 2020 | int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); 2021 | 2022 | // 销毁目标互斥锁 2023 | int pthread_mutex_destory(pthread_mutex_t *mutex); 2024 | 2025 | // 针对普通锁加锁 2026 | int pthread_mutex_lock(pthread_mutex_t *mutex); 2027 | 2028 | // 针对普通锁立即返回 目标未加锁则加锁 如果已经加锁则返回错误码EBUSY 2029 | int pthread_mutex_trylock(pthread_mutex_t *mutex); 2030 | 2031 | // 解锁 如果有其他线程在等待这个互斥锁, 则其中之一获得 2032 | int pthread_mutex_unlock(pthread_mutex_t *mutex); 2033 | ``` 2034 | 2035 | 销毁一个已经加锁的互斥锁 会发生不可预期的后果 2036 | 也可使使用宏`PTHREAD_MUTEX_INITIALIZER`来初始化一个互斥锁 2037 | pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 2038 | 2039 | 2040 | **互斥锁属性设置** 2041 | 2042 | ```c++ 2043 | int pthread_mutexattr_init(pthread_mutexattr_t *attr); 2044 | 2045 | int pthread_mutexattr_destory(pthread_mutexattr_t *attr); 2046 | 2047 | // PTHREAD_PROCESS_SHARED 跨进程共享 2048 | // PTHREAD_PROCESS_PRIVATE 隶属同一进程的线程 2049 | int pthread_mutexattr_getpshared(const pthread_mutexattr_t *attr, int *pshared); 2050 | int pthread_mutexattr_setpshared(const pthread_mutexattr_t *attr, int pshared); 2051 | 2052 | // PTHREAD_MUTEX_NORMAL 普通锁 默认类型 2053 | // PTHREAD_MUTEX_ERRORCHECK 检错锁 2054 | // PTHREAD_MUTEX_RECURSVE 嵌套锁 2055 | // PTHREAD_MUTEX_DEFAULT 默认锁 2056 | int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type); 2057 | int pthread_mutexattr_settype(const pthread_mutexattr_t *attr, int type); 2058 | ``` 2059 | PTHREAD_MUTEX_NORMAL 2060 | 一个线程对其加锁后, 其他请求该锁的进程会形成一个等待队列, 解锁后然后按照优先级获得. 保证资源分配公平 2061 | A线程对一个`已经加锁`的普通锁`再次加锁(也是A线程)`-同一线程在解锁前再次加锁引发死锁 2062 | 对一个已经`被其他线程加锁`的普通锁`解锁`, 或者`再次解锁已经解锁`的普通锁--解锁-不可预期后果 2063 | 2064 | PTHREAD_MUTEX_ERRORCHECK 2065 | 线程对`已经加锁`的检错锁`再次加锁`--加锁-加锁操作返回EDEADLK 2066 | 对一个已经`被其他线程加锁`的检错锁`解锁`, 或者`再次解锁已经解锁`的检错锁--解锁-返回EPERM 2067 | 2068 | PTHREAD_MUTEX_RECURSVE 2069 | 允许一个线程在释放锁前多次加锁 而不发生死锁. 2070 | 如果`其他线程`要获得这个锁, 则`当前锁拥有者`必须执行相应次数的解锁操作--加锁 2071 | 对于`已经被其他进程`加锁的嵌套锁解锁, 或者对`已经解锁`的再次解锁--解锁-返回EPERM 2072 | 2073 | PTHREAD_MUTEX_DEFAULT 2074 | 这种锁的实现可能为上面三种之一 2075 | 对已经加锁的默认锁再次加锁 2076 | 对被其他线程加锁的默认锁解锁 2077 | 再次解锁已经解锁的默认锁 2078 | 都将会发生不可预料后果 2079 | 2080 | 例子 2081 | ```c++ 2082 | pthread_mutex_t mutex; 2083 | int count = 0; 2084 | void* t(void *a) 2085 | { 2086 | pthread_mutex_lock(&mutex); 2087 | printf("%d\n", count); 2088 | count++; 2089 | pthread_mutex_unlock(&mutex); 2090 | } 2091 | int main() 2092 | { 2093 | pthread_mutex_init(&mutex, nullptr); 2094 | pthread_t thread[10]; 2095 | for (int i = 0; i < 10; ++i) 2096 | { 2097 | pthread_create(&thread[i], nullptr, t, nullptr); 2098 | } 2099 | sleep(3); 2100 | pthread_mutex_destroy(&mutex); 2101 | } 2102 | ``` 2103 | 2104 | ## 条件变量 2105 | 2106 | ```c++ 2107 | int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr *cond_attr); 2108 | 2109 | // 销毁一个正在被等待的条件变量 将会失败并返回EBUSY 2110 | int pthread_cont_destory(pthread_cond_t *cond); 2111 | 2112 | // 广播式的唤醒所有等待目标条件变量的线程 2113 | int pthread_cont_broadcast(pthread_cond_t *cond); 2114 | 2115 | // 唤醒一个等待目标条件变量的线程 2116 | int pthread_cond_signal(pthread_cond_t *cond); 2117 | 2118 | // 等待目标条件变量 2119 | int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); 2120 | ``` 2121 | 2122 | pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 2123 | 将各个字段初始化为0 2124 | 2125 | pthread_cond_wait的第二个参数, 用于保护条件变量的互斥锁 2126 | 掉用函数前必须将 mutex加锁, 否则会发生不可预料的后果. 2127 | 函数执行前将调用线程放入条件变量等待队列, 然后将mutex解锁 2128 | 2129 | 从函数调用, 到被放入等待队列的时间内, pthread_cond_signal(broadcast)不会修改条件变量的值 2130 | 也就是 pthread_cond_wait函数不会错过目标条件变量的任何变化, 2131 | 将pthread_cond_wait函数返回的时候, 互斥锁mutex将会再次锁上 2132 | 2133 | 例子 2134 | ```c++ 2135 | pthread_mutex_t mutex; 2136 | pthread_cond_t cond; 2137 | int good = 3; 2138 | int produce_count = 0; 2139 | int consume_count = 0; 2140 | 2141 | void* Producer(void *arg) 2142 | { 2143 | while(produce_count < 10) 2144 | { 2145 | pthread_mutex_lock(&mutex); 2146 | good++; 2147 | pthread_mutex_unlock(&mutex); 2148 | 2149 | produce_count++; 2150 | printf("produce a good\n"); 2151 | // 通知一个线程 2152 | pthread_cond_signal(&cond); 2153 | sleep(2); 2154 | } 2155 | pthread_exit(nullptr); 2156 | } 2157 | 2158 | void* Consumer(void *arg) 2159 | { 2160 | while (consume_count < 13) 2161 | { 2162 | // 传入前需要加锁 2163 | pthread_mutex_lock(&mutex); 2164 | if (good > 0) 2165 | { 2166 | good--; 2167 | consume_count++; 2168 | printf("consume a good, reset %d\n", good); 2169 | } 2170 | else 2171 | { 2172 | printf("good is 0\n"); 2173 | // wait pthread_cond_signal 2174 | pthread_cond_wait(&cond, &mutex); 2175 | } 2176 | pthread_mutex_unlock(&mutex); 2177 | 2178 | usleep(500 * 1000); 2179 | } 2180 | pthread_exit(nullptr); 2181 | } 2182 | 2183 | int main() 2184 | { 2185 | mutex = PTHREAD_MUTEX_INITIALIZER; 2186 | cond = PTHREAD_COND_INITIALIZER; 2187 | pthread_t producer, consumer; 2188 | 2189 | pthread_create(&consumer, nullptr, Consumer, nullptr); 2190 | pthread_create(&producer, nullptr, Producer, nullptr); 2191 | 2192 | pthread_join(consumer, nullptr); 2193 | pthread_mutex_destroy(&mutex); 2194 | pthread_cond_destroy(&cond); 2195 | } 2196 | ``` 2197 | 2198 | ## 读写锁 2199 | 2200 | ## 自旋锁 2201 | 2202 | 2203 | ## 线程同步包装类-多线程环境 2204 | ```c++ 2205 | class Sem 2206 | { 2207 | public: 2208 | Sem() 2209 | { 2210 | if (sem_init(&sem_, 0, 0) != 0) 2211 | { 2212 | throw std::exception(); 2213 | } 2214 | } 2215 | ~Sem() 2216 | { 2217 | sem_destroy(&sem_); 2218 | } 2219 | bool Wait() 2220 | { 2221 | return sem_wait(&sem_) == 0; 2222 | } 2223 | bool Post() 2224 | { 2225 | return sem_post(&sem_) == 0; 2226 | } 2227 | private: 2228 | sem_t sem_; 2229 | }; 2230 | 2231 | class Mutex 2232 | { 2233 | public: 2234 | Mutex() 2235 | { 2236 | if (pthread_mutex_init(&mutex_, nullptr) != 0) 2237 | { 2238 | throw std::exception(); 2239 | } 2240 | 2241 | } 2242 | ~Mutex() 2243 | { 2244 | pthread_mutex_destroy(&mutex_); 2245 | } 2246 | bool Lock() 2247 | { 2248 | return pthread_mutex_lock(&mutex_) == 0; 2249 | } 2250 | bool Unlock() 2251 | { 2252 | return pthread_mutex_unlock(&mutex_) == 0; 2253 | } 2254 | 2255 | private: 2256 | pthread_mutex_t mutex_; 2257 | }; 2258 | 2259 | class Cond 2260 | { 2261 | public: 2262 | Cond() 2263 | { 2264 | if (pthread_mutex_init(&mutex_, nullptr) != 0) 2265 | { 2266 | throw std::exception(); 2267 | } 2268 | if (pthread_cond_init(&cond_, nullptr) != 0) 2269 | { 2270 | // 这里我一开始没有想到.. 2271 | pthread_mutex_destroy(&mutex_); 2272 | throw std::exception(); 2273 | } 2274 | } 2275 | ~Cond() 2276 | { 2277 | pthread_mutex_destroy(&mutex_); 2278 | pthread_cond_destroy(&cond_); 2279 | }; 2280 | bool Wait() 2281 | { 2282 | int ret = 0; 2283 | pthread_mutex_lock(&mutex_); 2284 | ret = pthread_cond_wait(&cond_, &mutex_); 2285 | pthread_mutex_unlock(&mutex_); 2286 | return ret == 0; 2287 | } 2288 | bool Signal() 2289 | { 2290 | return pthread_cond_signal(&cond_) == 0; 2291 | } 2292 | private: 2293 | pthread_cond_t cond_; 2294 | pthread_mutex_t mutex_; 2295 | }; 2296 | ``` 2297 | 2298 | 线程安全或可重入函数--函数能被多个线程同时调用而不发生竞态条件 2299 | 2300 | 多线程程序某个线程调用fork函数, 新进程不会与父进程有相同数量的线程 2301 | 子进程只有一个线程-调用fork线程的完美复制 2302 | 2303 | 但是子进程会继承父进程的互斥锁(条件变量)的状态, 如果互斥锁被加锁了, 但`不是由`调用fork线程 2304 | 锁住的, 此时`子进程`再次对这个互斥锁`执行加锁`操作将会`死锁`. 2305 | 2306 | ```c++ 2307 | pthread_mutex_t mutex; 2308 | void* another(void *arg) 2309 | { 2310 | printf("in child thread, lock the mutex\n"); 2311 | pthread_mutex_lock(&mutex); 2312 | sleep(5); 2313 | // 解锁后 Prepare才能加锁 2314 | pthread_mutex_unlock(&mutex); 2315 | pthread_exit(nullptr); 2316 | } 2317 | // 这个函数在fork创建子进程前被调用 2318 | void Prepare() 2319 | { 2320 | // 但是会阻塞 直到执行another函数的线程解锁 才能够继续执行 2321 | // 这个函数执行完毕前fork不会创建子进程 2322 | pthread_mutex_lock(&mutex); 2323 | } 2324 | // fork创建线程后 返回前 会在子进程和父进程中执行这个函数 2325 | void Infork() 2326 | { 2327 | pthread_mutex_unlock(&mutex); 2328 | } 2329 | int main() 2330 | { 2331 | pthread_mutex_init(&mutex, nullptr); 2332 | pthread_t id; 2333 | pthread_create(&id, nullptr, another, nullptr); 2334 | 2335 | sleep(1); 2336 | // pthread_atfork(Prepare, Infork, Infork); 2337 | int pid = fork(); 2338 | if (pid < 0) 2339 | { 2340 | printf("emmm????\n"); 2341 | pthread_join(id, nullptr); 2342 | pthread_mutex_destroy(&mutex); 2343 | return 1; 2344 | } 2345 | else if (pid == 0) 2346 | { 2347 | printf("child process, want to get the lock\n"); 2348 | pthread_mutex_lock(&mutex); 2349 | printf("i cann't run to here, opps....\n"); 2350 | pthread_mutex_unlock(&mutex); 2351 | exit(0); 2352 | } 2353 | else 2354 | { 2355 | printf("wait start\n"); 2356 | wait(nullptr); 2357 | printf("wait over\n"); // 没有打印 因为子进程不会终止 2358 | } 2359 | pthread_join(id, nullptr); 2360 | pthread_mutex_destroy(&mutex); 2361 | return 0; 2362 | } 2363 | // $ in child thread, lock the mutex 2364 | // $ wait start 2365 | // $ child process, want to get the lock 2366 | 2367 | // $ in child thread, lock the mutex 2368 | // $ wait start 2369 | // $ child process, want to get the lock 2370 | // $ i cann't run to here, opps.... 2371 | // $ wait over 2372 | ``` 2373 | 2374 | 原版就会发生死锁, 新版(去掉注释的代码) 能够正常运行 2375 | 2376 | ```c++ 2377 | int pthread_atfork (void (*__prepare) (void), 2378 | void (*__parent) (void), 2379 | void (*__child) (void)); 2380 | ``` 2381 | 第一个句柄 在fork创建子进程前执行 2382 | 第二个句柄 在fork创建出子进程后, fork返回前在父进程中执行 2383 | 第二个句柄 在fork创建出子进程后, fork返回前在子进程中执行 2384 | 2385 | 2386 | # 第十五章 进程池和线程池 2387 | 2388 | # 线程池 和 简单HTTP服务器 2389 | 对我而言神秘已久的线程池终于揭开了面纱. 2390 | 没想到这就是线程池23333 2391 | 2392 | 线程池写完后 直接写了书上的HTTP服务器. 2393 | 2394 | 那个服务器至少我发现两个问题 2395 | - 无法发送大文件 2396 | - 部分请求无法回复 2397 | 2398 | 无法发送大文件, 是因为书中使用了writev发送数据 2399 | 期初我以为下面的判断 writev返回值 等于 -1就是为了发送大文件, 后来发现这个判断只是给期初就发送失败准备的. 2400 | 2401 | 正好前一阵子看了一个服务器的代码 2402 | https://github.com/Jigokubana/Notes-flamingo 2403 | 2404 | 2405 | 我就索性直接将发送部分修改了 2406 | ```c++ 2407 | // write_sum_ 需发送总大小 2408 | // write_idx_ 已发送大小 2409 | 2410 | int temp = 0; 2411 | if (write_sum_ - write_idx_ == 0) 2412 | { 2413 | Modfd(epollfd_, sockfd_, EPOLLIN); 2414 | Init(); 2415 | return true; 2416 | } 2417 | while (true) 2418 | { 2419 | temp = send(sockfd_, &*write_buff_.begin() + write_idx_, write_sum_ - write_idx_, 0); 2420 | if (temp <= -1) 2421 | { 2422 | if (errno == EAGAIN) 2423 | { 2424 | Modfd(epollfd_, sockfd_, EPOLLOUT); 2425 | return true; 2426 | } 2427 | } 2428 | write_idx_ += temp; 2429 | 2430 | if (write_idx_ == write_sum_) 2431 | { 2432 | // 解除绑定移到了其他地方 2433 | if (linger_) 2434 | { 2435 | Init(); 2436 | Modfd(epollfd_, sockfd_, EPOLLIN); 2437 | return true; 2438 | } 2439 | else 2440 | { 2441 | Modfd(epollfd_, sockfd_, EPOLLIN); 2442 | return false; 2443 | } 2444 | } 2445 | } 2446 | ``` 2447 | 2448 | 第二个奇葩的问题就是使用ab压测时候 有些请求无法收到回复. 2449 | 这个问题等后面在解决把, 等我知识更加丰富了再说 --------------------------------------------------------------------------------