4. 實例三
基于網絡C/S非阻塞模型的epoll ET觸發模式
實現過程中注意兩點:
當服務端接收到客戶端新的連接(cfd),需要設置客戶端連接問價描述符(cfd)為非阻塞模式,因為下面需要循環讀取服務端緩沖區中的數據,而如果不設置 cfd 為非阻塞模式,則當讀完緩沖區的數據 recv 再次讀取會阻塞住,則整個程序會被阻塞;
通過errno == EAGAIN來判斷緩沖區中的數據讀取完成。
nonblock_et_epoll.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 int main(int argc, const char* argv[]) 14 { 15 if(argc < 2) 16 { 17 printf("eg: ./a.out port\n"); 18 exit(1); 19 } 20 struct sockaddr_in serv_addr; 21 socklen_t serv_len = sizeof(serv_addr); 22 int port = atoi(argv[1]); 23 24 // 創建套接字 25 int lfd = socket(AF_INET, SOCK_STREAM, 0); 26 // 初始化服務器 sockaddr_in 27 memset(&serv_addr, 0, serv_len); 28 serv_addr.sin_family = AF_INET; // 地址族 29 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 監聽本機所有的IP 30 serv_addr.sin_port = htons(port); // 設置端口 31 // 綁定IP和端口 32 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 33 34 // 設置同時監聽的最大個數 35 listen(lfd, 36); 36 printf("Start accept ......\n"); 37 38 struct sockaddr_in client_addr; 39 socklen_t cli_len = sizeof(client_addr); 40 41 // 創建epoll樹根節點 42 int epfd = epoll_create(2000); 43 // 初始化epoll樹 44 struct epoll_event ev; 45 46 // 設置邊沿觸發 47 ev.events = EPOLLIN; 48 ev.data.fd = lfd; 49 epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev); 50 51 struct epoll_event all[2000]; 52 while(1) 53 { 54 // 使用epoll通知內核fd 文件IO檢測 55 int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1); 56 printf("================== epoll_wait =============\n"); 57 58 // 遍歷all數組中的前ret個元素 59 for(int i=0; i 0 ) 104 { 105 // 數據打印到終端 106 write(STDOUT_FILENO, buf, len); 107 // 發送給客戶端 108 send(fd, buf, len, 0); 109 } 110 if(len == 0) 111 { 112 printf("客戶端斷開了連接\n"); 113 ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); 114 if(ret == -1) 115 { 116 perror("epoll_ctl - del error"); 117 exit(1); 118 } 119 close(fd); 120 } 121 else if(len == -1) 122 { 123 if(errno == EAGAIN) 124 { 125 printf("緩沖區數據已經讀完\n"); 126 } 127 else 128 { 129 printf("recv error----\n"); 130 exit(1); 131 } 132 } 133 } 134 } 135 } 136 137 close(lfd); 138 return 0; 139 }
tcp_client.c
1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 10 // tcp client 11 int main(int argc, const char* argv[]) 12 { 13 if(argc < 2) 14 { 15 printf("eg: ./a.out port\n"); 16 exit(1); 17 } 18 // 創建套接字 19 int fd = socket(AF_INET, SOCK_STREAM, 0); 20 if(fd == -1) 21 { 22 perror("socket error"); 23 exit(1); 24 } 25 int port = atoi(argv[1]); 26 // 連接服務器 27 struct sockaddr_in serv_addr; 28 memset(&serv_addr, 0, sizeof(serv_addr)); 29 serv_addr.sin_family = AF_INET; 30 serv_addr.sin_port = htons(port); 31 inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr); 32 int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 33 if(ret == -1) 34 { 35 perror("connect error"); 36 exit(1); 37 } 38 39 // 通信 40 while(1) 41 { 42 // 寫數據 43 // 接收鍵盤輸入 44 char buf[512]; 45 fgets(buf, sizeof(buf), stdin); 46 // 發送給服務器 47 write(fd, buf, strlen(buf)+1); 48 49 // 接收服務器端的數據 50 int len = read(fd, buf, sizeof(buf)); 51 printf("read buf = %s, len = %d\n", buf, len); 52 } 53 return 0; 54 }
執行結果:
server端:
[root@centos epoll]# ./nonblock_et_epoll 8888 Start accept ...... ================== epoll_wait ============= New Client IP: 127.0.0.1, Port: 47634 ================== epoll_wait ============= 000001111122222 緩沖區數據已經讀完 ================== epoll_wait ============= hello world 緩沖區數據已經讀完
client端:
[root@centos epoll]# ./client 8888 000001111122222 read buf = 000001111122222 , len = 17 hello world read buf = hello world , len = 13

執行結果分析:可以看出設置為epoll et非阻塞模式,當客戶端發送數據不管有多少個字節,server端會全部從緩沖區讀取并發送給客戶端(包括客戶端發送的回車('\n'))。
5. 示例四
基于網絡C/S模型的epoll LT觸發模式
lt_epoll.c
1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 11 12 int main(int argc, const char* argv[]) 13 { 14 if(argc < 2) 15 { 16 printf("eg: ./a.out port\n"); 17 exit(1); 18 } 19 struct sockaddr_in serv_addr; 20 socklen_t serv_len = sizeof(serv_addr); 21 int port = atoi(argv[1]); 22 23 // 創建套接字 24 int lfd = socket(AF_INET, SOCK_STREAM, 0); 25 // 初始化服務器 sockaddr_in 26 memset(&serv_addr, 0, serv_len); 27 serv_addr.sin_family = AF_INET; // 地址族 28 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 監聽本機所有的IP 29 serv_addr.sin_port = htons(port); // 設置端口 30 // 綁定IP和端口 31 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 32 33 // 設置同時監聽的最大個數 34 listen(lfd, 36); 35 printf("Start accept ......\n"); 36 37 struct sockaddr_in client_addr; 38 socklen_t cli_len = sizeof(client_addr); 39 40 // 創建epoll樹根節點 41 int epfd = epoll_create(2000); 42 // 初始化epoll樹 43 struct epoll_event ev; 44 ev.events = EPOLLIN; 45 ev.data.fd = lfd; 46 epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev); 47 48 struct epoll_event all[2000]; 49 while(1) 50 { 51 // 使用epoll通知內核fd 文件IO檢測 52 int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1); 53 printf("================== epoll_wait =============\n"); 54 55 // 遍歷all數組中的前ret個元素 56 for(int i=0; itcp_client.c
1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 10 // tcp client 11 int main(int argc, const char* argv[]) 12 { 13 if(argc < 2) 14 { 15 printf("eg: ./a.out port\n"); 16 exit(1); 17 } 18 // 創建套接字 19 int fd = socket(AF_INET, SOCK_STREAM, 0); 20 if(fd == -1) 21 { 22 perror("socket error"); 23 exit(1); 24 } 25 int port = atoi(argv[1]); 26 // 連接服務器 27 struct sockaddr_in serv_addr; 28 memset(&serv_addr, 0, sizeof(serv_addr)); 29 serv_addr.sin_family = AF_INET; 30 serv_addr.sin_port = htons(port); 31 inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr); 32 int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 33 if(ret == -1) 34 { 35 perror("connect error"); 36 exit(1); 37 } 38 39 // 通信 40 while(1) 41 { 42 // 寫數據 43 // 接收鍵盤輸入 44 char buf[512]; 45 fgets(buf, sizeof(buf), stdin); 46 // 發送給服務器 47 write(fd, buf, strlen(buf)+1); 48 49 // 接收服務器端的數據 50 int len = read(fd, buf, sizeof(buf)); 51 printf("read buf = %s, len = %d\n", buf, len); 52 } 53 return 0; 54 }
執行結果:
server端:
[root@centos epoll]# ./lt_epoll 8888 Start accept ...... ================== epoll_wait ============= New Client IP: 127.0.0.1, Port: 47636 ================== epoll_wait ============= 00000================== epoll_wait ============= 11111================== epoll_wait ============= 22222================== epoll_wait =============
client端:
[root@centos epoll]# ./client 8888 000001111122222 read buf = 000001111122222 , len = 17
執行結果分析:可以看出,當客戶端發送數據(000001111122222)到server端(接收數據緩沖區內),但是由于server端一次只接受5個字節(00000),因此在接受完5個字節之后,將接收的5個字節數據保存到發送緩沖區。然后程序回到epoll_wait處,此時檢測到接收緩沖區還有未接收完的數據程序沒有在epoll_wait處阻塞等待。而是繼續從上一次緩沖區中讀取剩余的數據(11111)及(22222),讀取完成之后將所有數據發送給客戶端。
文件描述符突破1024限制:
select - 突破不了, 需要編譯內核
poll和epoll可以突破1024限制
解決辦法:
查看受計算機硬件限制的文件描述符上限
通過配置文件修改上限值
五、線程池并發服務器
1)預先創建阻塞于accept多線程,使用互斥鎖上鎖保護accept
2)預先創建多線程,由主線程調用accept
六、UDP服務器
傳輸層主要應用的協議模型有兩種,一種是TCP協議,另外一種則是UDP協議。TCP協議在網絡通信中占主導地位,絕大多數的網絡通信借助TCP協議完成數據傳輸。但UDP也是網絡通信中不可
或缺的重要通信手段。
相較于TCP而言,UDP通信的形式更像是發短信。不需要在數據傳輸之前建立、維護連接。只專心獲取數據就好。省去了三次握手的過程,通信速度可以大大提高,但與之伴隨的通信的穩定性和
正確率便得不到保證。因此,我們稱UDP為“無連接的不可靠報文傳遞”。
那么與我們熟知的TCP相比,UDP有哪些優點和不足呢?由于無需創建連接,所以UDP開銷較小,數據傳輸速度快,實時性較強。多用于對實時性要求較高的通信場合,如視頻會議、電話會議
等。但隨之也伴隨著數據傳輸不可靠,傳輸數據的正確率、傳輸順序和流量都得不到控制和保證。所以,通常情況下,使用UDP協議進行數據傳輸,為保證數據的正確性,我們需要在應用層添加輔
助校驗協議來彌補UDP的不足,以達到數據可靠傳輸的目的。
與TCP類似的,UDP也有可能出現緩沖區被填滿后,再接收數據時丟包的現象。由于它沒有TCP滑動窗口的機制,通常采用如下兩種方法解決:
1)服務器應用層設計流量控制,控制發送數據速度。
2)借助setsockopt函數改變接收緩沖區大小。如:
#include int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); int n = 220x1024 setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));
注意:udp的數據是不安全的, 容易丟包。出現丟包, 不會出現丟部分數據,要丟只會丟全部數據。
TCP和UDP的使用場景:
tcp使用場景:
1)對數據安全性要求高的時候
a. 登錄數據的傳輸
c. 文件傳輸
2)http協議
傳輸層協議 - tcp
udp使用場景:
1)效率高 - 實時性要求比較高
a. 視頻聊天
b. 通話
2)有實力的大公司
a. 使用upd
b. 在應用層自定義協議, 做數據校驗
七、C/S模型-UDP
由于UDP不需要維護連接,程序邏輯簡單了很多,但是UDP協議是不可靠的,保證通訊可靠性的機制需要在應用層實現。
編譯運行server,在兩個終端里各開一個client與server交互,看看server是否具有并發服務的能力。用Ctrl+C關閉server,然后再運行server,看此時client還能否和server聯系上。和前面TCP程序
的運行結果相比較,體會無連接的含義。
udp_server.c
1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 9 int main(int argc, const char* argv[]) 10 { 11 // 創建套接字 12 int fd = socket(AF_INET, SOCK_DGRAM, 0); 13 if(fd == -1) 14 { 15 perror("socket error"); 16 exit(1); 17 } 18 19 // fd綁定本地的IP和端口 20 struct sockaddr_in serv; 21 memset(&serv, 0, sizeof(serv)); 22 serv.sin_family = AF_INET; 23 serv.sin_port = htons(8765); 24 serv.sin_addr.s_addr = htonl(INADDR_ANY); 25 int ret = bind(fd, (struct sockaddr*)&serv, sizeof(serv)); 26 if(ret == -1) 27 { 28 perror("bind error"); 29 exit(1); 30 } 31 32 struct sockaddr_in client; 33 socklen_t cli_len = sizeof(client); 34 // 通信 35 char buf[1024] = {0}; 36 while(1) 37 { 38 int recvlen = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*)&client, &cli_len); 39 if(recvlen == -1) 40 { 41 perror("recvform error"); 42 exit(1); 43 } 44 45 printf("recv buf: %s\n", buf); 46 char ip[64] = {0}; 47 printf("New Client IP: %s, Port: %d\n", 48 inet_ntop(AF_INET, &client.sin_addr.s_addr, ip, sizeof(ip)), 49 ntohs(client.sin_port)); 50 51 // 給客戶端發送數據 52 sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&client, sizeof(client)); 53 } 54 55 close(fd); 56 57 return 0; 58 }
udp_client.c
1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 9 int main(int argc, const char* argv[]) 10 { 11 // create socket 12 int fd = socket(AF_INET, SOCK_DGRAM, 0); 13 if(fd == -1) 14 { 15 perror("socket error"); 16 exit(1); 17 } 18 19 // 初始化服務器的IP和端口 20 struct sockaddr_in serv; 21 memset(&serv, 0, sizeof(serv)); 22 serv.sin_family = AF_INET; 23 serv.sin_port = htons(8765); 24 inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr); 25 26 // 通信 27 while(1) 28 { 29 char buf[1024] = {0}; 30 fgets(buf, sizeof(buf), stdin); 31 // 數據的發送 - server - IP port 32 sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&serv, sizeof(serv)); 33 34 // 等待服務器發送數據過來 35 recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL); 36 printf("recv buf: %s\n", buf); 37 } 38 39 close(fd); 40 41 return 0; 42 }
【Linux C編程】第十八章 高并發服務器(五)
Linux TCP/IP
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。