1. 實現示意圖linux
2. 使用多進程併發服務器時要考慮如下幾點:編程
3. 使用多進程的方式, 解決服務器處理多鏈接的問題:centos
(1)共享數組
(2)父進程 的角色是什麼?緩存
等待接受客戶端鏈接 -- accept安全
有連接:服務器
(3)子進程的角色是什麼?網絡
1)通訊多線程
2)關掉監聽的文件描述符併發
(4)建立的進程的個數有限制嗎?
(5)子進程資源回收
1)wait/waitpid
2)使用信號回收
signal
sigaction - 推薦
代碼實現:
1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <errno.h> 5 #include <sys/socket.h> 6 7 void perr_exit(const char *s) 8 { 9 perror(s); 10 exit(-1); 11 } 12 13 int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr) 14 { 15 int n; 16 17 again: 18 if ((n = accept(fd, sa, salenptr)) < 0) { 19 if ((errno == ECONNABORTED) || (errno == EINTR)) 20 goto again; 21 else 22 perr_exit("accept error"); 23 } 24 return n; 25 } 26 27 int Bind(int fd, const struct sockaddr *sa, socklen_t salen) 28 { 29 int n; 30 31 if ((n = bind(fd, sa, salen)) < 0) 32 perr_exit("bind error"); 33 34 return n; 35 } 36 37 int Connect(int fd, const struct sockaddr *sa, socklen_t salen) 38 { 39 int n; 40 41 if ((n = connect(fd, sa, salen)) < 0) 42 perr_exit("connect error"); 43 44 return n; 45 } 46 47 int Listen(int fd, int backlog) 48 { 49 int n; 50 51 if ((n = listen(fd, backlog)) < 0) 52 perr_exit("listen error"); 53 54 return n; 55 } 56 57 int Socket(int family, int type, int protocol) 58 { 59 int n; 60 61 if ((n = socket(family, type, protocol)) < 0) 62 perr_exit("socket error"); 63 64 return n; 65 } 66 67 ssize_t Read(int fd, void *ptr, size_t nbytes) 68 { 69 ssize_t n; 70 71 again: 72 if ( (n = read(fd, ptr, nbytes)) == -1) { 73 if (errno == EINTR) 74 goto again; 75 else 76 return -1; 77 } 78 return n; 79 } 80 81 ssize_t Write(int fd, const void *ptr, size_t nbytes) 82 { 83 ssize_t n; 84 85 again: 86 if ( (n = write(fd, ptr, nbytes)) == -1) { 87 if (errno == EINTR) 88 goto again; 89 else 90 return -1; 91 } 92 return n; 93 } 94 95 int Close(int fd) 96 { 97 int n; 98 if ((n = close(fd)) == -1) 99 perr_exit("close error"); 100 101 return n; 102 } 103 104 /*參三: 應該讀取的字節數*/ 105 ssize_t Readn(int fd, void *vptr, size_t n) 106 { 107 size_t nleft; //usigned int 剩餘未讀取的字節數 108 ssize_t nread; //int 實際讀到的字節數 109 char *ptr; 110 111 ptr = vptr; 112 nleft = n; 113 114 while (nleft > 0) { 115 if ((nread = read(fd, ptr, nleft)) < 0) { 116 if (errno == EINTR) 117 nread = 0; 118 else 119 return -1; 120 } else if (nread == 0) 121 break; 122 123 nleft -= nread; 124 ptr += nread; 125 } 126 return n - nleft; 127 } 128 129 ssize_t Writen(int fd, const void *vptr, size_t n) 130 { 131 size_t nleft; 132 ssize_t nwritten; 133 const char *ptr; 134 135 ptr = vptr; 136 nleft = n; 137 while (nleft > 0) { 138 if ( (nwritten = write(fd, ptr, nleft)) <= 0) { 139 if (nwritten < 0 && errno == EINTR) 140 nwritten = 0; 141 else 142 return -1; 143 } 144 145 nleft -= nwritten; 146 ptr += nwritten; 147 } 148 return n; 149 } 150 151 static ssize_t my_read(int fd, char *ptr) 152 { 153 static int read_cnt; 154 static char *read_ptr; 155 static char read_buf[100]; 156 157 if (read_cnt <= 0) { 158 again: 159 if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) { 160 if (errno == EINTR) 161 goto again; 162 return -1; 163 } else if (read_cnt == 0) 164 return 0; 165 read_ptr = read_buf; 166 } 167 read_cnt--; 168 *ptr = *read_ptr++; 169 170 return 1; 171 } 172 173 ssize_t Readline(int fd, void *vptr, size_t maxlen) 174 { 175 ssize_t n, rc; 176 char c, *ptr; 177 178 ptr = vptr; 179 for (n = 1; n < maxlen; n++) { 180 if ( (rc = my_read(fd, &c)) == 1) { 181 *ptr++ = c; 182 if (c == '\n') 183 break; 184 } else if (rc == 0) { 185 *ptr = 0; 186 return n - 1; 187 } else 188 return -1; 189 } 190 *ptr = 0; 191 192 return n; 193 }
1 #ifndef __WRAP_H_ 2 #define __WRAP_H_ 3 4 void perr_exit(const char *s); 5 int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr); 6 int Bind(int fd, const struct sockaddr *sa, socklen_t salen); 7 int Connect(int fd, const struct sockaddr *sa, socklen_t salen); 8 int Listen(int fd, int backlog); 9 int Socket(int family, int type, int protocol); 10 ssize_t Read(int fd, void *ptr, size_t nbytes); 11 ssize_t Write(int fd, const void *ptr, size_t nbytes); 12 int Close(int fd); 13 ssize_t Readn(int fd, void *vptr, size_t n); 14 ssize_t Writen(int fd, const void *vptr, size_t n); 15 ssize_t my_read(int fd, char *ptr); 16 ssize_t Readline(int fd, void *vptr, size_t maxlen); 17 18 #endif
1 #include <stdio.h> 2 #include <string.h> 3 #include <netinet/in.h> 4 #include <arpa/inet.h> 5 #include <signal.h> 6 #include <sys/wait.h> 7 #include <ctype.h> 8 #include <unistd.h> 9 10 #include "wrap.h" 11 12 #define MAXLINE 8192 13 #define SERV_PORT 8000 14 15 void do_sigchild(int num) 16 { 17 while (waitpid(0, NULL, WNOHANG) > 0); 18 } 19 20 int main(void) 21 { 22 struct sockaddr_in servaddr, cliaddr; 23 socklen_t cliaddr_len; 24 int listenfd, connfd; 25 char buf[MAXLINE]; 26 char str[INET_ADDRSTRLEN]; 27 int i, n; 28 pid_t pid; 29 30 //臨時屏蔽sigchld信號 31 sigset_t myset; 32 sigemptyset(&myset); 33 sigaddset(&myset, SIGCHLD); 34 // 自定義信號集 -》 內核阻塞信號集 35 sigprocmask(SIG_BLOCK, &myset, NULL); 36 37 38 listenfd = Socket(AF_INET, SOCK_STREAM, 0); 39 40 int opt = 1; 41 // 設置端口複用 42 setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 43 44 bzero(&servaddr, sizeof(servaddr)); 45 servaddr.sin_family = AF_INET; 46 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 47 servaddr.sin_port = htons(SERV_PORT); 48 49 Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 50 51 Listen(listenfd, 20); 52 53 printf("Accepting connections ...\n"); 54 while (1) 55 { 56 cliaddr_len = sizeof(cliaddr); 57 connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); 58 59 // 有新的鏈接則建立一個進程 60 pid = fork(); 61 if (pid == 0) 62 { 63 Close(listenfd); 64 while (1) 65 { 66 n = Read(connfd, buf, MAXLINE); 67 if (n == 0) 68 { 69 printf("the other side has been closed.\n"); 70 break; 71 } 72 printf("received from %s at PORT %d\n", 73 inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), 74 ntohs(cliaddr.sin_port)); 75 76 for (i = 0; i < n; i++) 77 buf[i] = toupper(buf[i]); 78 79 Write(STDOUT_FILENO, buf, n); 80 Write(connfd, buf, n); 81 } 82 Close(connfd); 83 return 0; 84 } 85 else if (pid > 0) 86 { 87 struct sigaction act; 88 act.sa_flags = 0; 89 act.sa_handler = do_sigchild; 90 sigemptyset(&act.sa_mask); 91 sigaction(SIGCHLD, &act, NULL); 92 // 解除對sigchld信號的屏蔽 93 sigprocmask(SIG_UNBLOCK, &myset, NULL); 94 95 Close(connfd); 96 } 97 else 98 { 99 perr_exit("fork"); 100 } 101 } 102 return 0; 103 }
1 /* client.c */ 2 #include <stdio.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <netinet/in.h> 6 #include <arpa/inet.h> 7 8 #include "wrap.h" 9 10 #define MAXLINE 8192 11 #define SERV_PORT 8000 12 13 int main(int argc, char *argv[]) 14 { 15 struct sockaddr_in servaddr; 16 char buf[MAXLINE]; 17 int sockfd, n; 18 19 sockfd = Socket(AF_INET, SOCK_STREAM, 0); 20 21 bzero(&servaddr, sizeof(servaddr)); 22 servaddr.sin_family = AF_INET; 23 inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); 24 servaddr.sin_port = htons(SERV_PORT); 25 26 Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 27 28 while (fgets(buf, MAXLINE, stdin) != NULL) 29 { 30 Write(sockfd, buf, strlen(buf)); 31 n = Read(sockfd, buf, MAXLINE); 32 if (n == 0) 33 { 34 printf("the other side has been closed.\n"); 35 break; 36 } 37 else 38 Write(STDOUT_FILENO, buf, n); 39 } 40 41 Close(sockfd); 42 43 return 0; 44 }
1 src = $(wildcard *.c) 2 obj = $(patsubst %.c, %.o, $(src)) 3 4 all: server client 5 6 server: server.o wrap.o 7 gcc server.o wrap.o -o server -Wall 8 client: client.o wrap.o 9 gcc client.o wrap.o -o client -Wall 10 11 %.o:%.c 12 gcc -c $< -Wall 13 14 .PHONY: clean all 15 clean: 16 -rm -rf server client $(obj)
1. 實現示意圖
2. 使用線程模型開發服務器時需考慮如下問題:
3. 線程共享:
代碼實現:
1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <errno.h> 5 #include <sys/socket.h> 6 7 void perr_exit(const char *s) 8 { 9 perror(s); 10 exit(-1); 11 } 12 13 int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr) 14 { 15 int n; 16 17 again: 18 if ((n = accept(fd, sa, salenptr)) < 0) { 19 if ((errno == ECONNABORTED) || (errno == EINTR)) 20 goto again; 21 else 22 perr_exit("accept error"); 23 } 24 return n; 25 } 26 27 int Bind(int fd, const struct sockaddr *sa, socklen_t salen) 28 { 29 int n; 30 31 if ((n = bind(fd, sa, salen)) < 0) 32 perr_exit("bind error"); 33 34 return n; 35 } 36 37 int Connect(int fd, const struct sockaddr *sa, socklen_t salen) 38 { 39 int n; 40 41 if ((n = connect(fd, sa, salen)) < 0) 42 perr_exit("connect error"); 43 44 return n; 45 } 46 47 int Listen(int fd, int backlog) 48 { 49 int n; 50 51 if ((n = listen(fd, backlog)) < 0) 52 perr_exit("listen error"); 53 54 return n; 55 } 56 57 int Socket(int family, int type, int protocol) 58 { 59 int n; 60 61 if ((n = socket(family, type, protocol)) < 0) 62 perr_exit("socket error"); 63 64 return n; 65 } 66 67 ssize_t Read(int fd, void *ptr, size_t nbytes) 68 { 69 ssize_t n; 70 71 again: 72 if ( (n = read(fd, ptr, nbytes)) == -1) { 73 if (errno == EINTR) 74 goto again; 75 else 76 return -1; 77 } 78 return n; 79 } 80 81 ssize_t Write(int fd, const void *ptr, size_t nbytes) 82 { 83 ssize_t n; 84 85 again: 86 if ( (n = write(fd, ptr, nbytes)) == -1) { 87 if (errno == EINTR) 88 goto again; 89 else 90 return -1; 91 } 92 return n; 93 } 94 95 int Close(int fd) 96 { 97 int n; 98 if ((n = close(fd)) == -1) 99 perr_exit("close error"); 100 101 return n; 102 } 103 104 /*參三: 應該讀取的字節數*/ 105 ssize_t Readn(int fd, void *vptr, size_t n) 106 { 107 size_t nleft; //usigned int 剩餘未讀取的字節數 108 ssize_t nread; //int 實際讀到的字節數 109 char *ptr; 110 111 ptr = vptr; 112 nleft = n; 113 114 while (nleft > 0) { 115 if ((nread = read(fd, ptr, nleft)) < 0) { 116 if (errno == EINTR) 117 nread = 0; 118 else 119 return -1; 120 } else if (nread == 0) 121 break; 122 123 nleft -= nread; 124 ptr += nread; 125 } 126 return n - nleft; 127 } 128 129 ssize_t Writen(int fd, const void *vptr, size_t n) 130 { 131 size_t nleft; 132 ssize_t nwritten; 133 const char *ptr; 134 135 ptr = vptr; 136 nleft = n; 137 while (nleft > 0) { 138 if ( (nwritten = write(fd, ptr, nleft)) <= 0) { 139 if (nwritten < 0 && errno == EINTR) 140 nwritten = 0; 141 else 142 return -1; 143 } 144 145 nleft -= nwritten; 146 ptr += nwritten; 147 } 148 return n; 149 } 150 151 static ssize_t my_read(int fd, char *ptr) 152 { 153 static int read_cnt; 154 static char *read_ptr; 155 static char read_buf[100]; 156 157 if (read_cnt <= 0) { 158 again: 159 if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) { 160 if (errno == EINTR) 161 goto again; 162 return -1; 163 } else if (read_cnt == 0) 164 return 0; 165 read_ptr = read_buf; 166 } 167 read_cnt--; 168 *ptr = *read_ptr++; 169 170 return 1; 171 } 172 173 ssize_t Readline(int fd, void *vptr, size_t maxlen) 174 { 175 ssize_t n, rc; 176 char c, *ptr; 177 178 ptr = vptr; 179 for (n = 1; n < maxlen; n++) { 180 if ( (rc = my_read(fd, &c)) == 1) { 181 *ptr++ = c; 182 if (c == '\n') 183 break; 184 } else if (rc == 0) { 185 *ptr = 0; 186 return n - 1; 187 } else 188 return -1; 189 } 190 *ptr = 0; 191 192 return n; 193 }
1 #ifndef __WRAP_H_ 2 #define __WRAP_H_ 3 4 void perr_exit(const char *s); 5 int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr); 6 int Bind(int fd, const struct sockaddr *sa, socklen_t salen); 7 int Connect(int fd, const struct sockaddr *sa, socklen_t salen); 8 int Listen(int fd, int backlog); 9 int Socket(int family, int type, int protocol); 10 ssize_t Read(int fd, void *ptr, size_t nbytes); 11 ssize_t Write(int fd, const void *ptr, size_t nbytes); 12 int Close(int fd); 13 ssize_t Readn(int fd, void *vptr, size_t n); 14 ssize_t Writen(int fd, const void *vptr, size_t n); 15 ssize_t my_read(int fd, char *ptr); 16 ssize_t Readline(int fd, void *vptr, size_t maxlen); 17 18 #endif
1 #include <stdio.h> 2 #include <string.h> 3 #include <arpa/inet.h> 4 #include <pthread.h> 5 #include <ctype.h> 6 #include <unistd.h> 7 #include <fcntl.h> 8 9 #include "wrap.h" 10 11 #define MAXLINE 8192 12 #define SERV_PORT 8000 13 14 struct s_info 15 { //定義一個結構體, 將地址結構跟cfd捆綁 16 struct sockaddr_in cliaddr; 17 int connfd; 18 }; 19 20 void *do_work(void *arg) 21 { 22 int n,i; 23 struct s_info *ts = (struct s_info*)arg; 24 char buf[MAXLINE]; 25 char str[INET_ADDRSTRLEN]; //#define INET_ADDRSTRLEN 16 可用"[+d"查看 26 27 while (1) 28 { 29 n = Read(ts->connfd, buf, MAXLINE); //讀客戶端 30 if (n == 0) 31 { 32 printf("the client %d closed...\n", ts->connfd); 33 break; //跳出循環,關閉cfd 34 } 35 printf("received from %s at PORT %d\n", 36 inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)), 37 ntohs((*ts).cliaddr.sin_port)); //打印客戶端信息(IP/PORT) 38 39 for (i = 0; i < n; i++) 40 { 41 buf[i] = toupper(buf[i]); //小寫-->大寫 42 } 43 44 Write(STDOUT_FILENO, buf, n); //寫出至屏幕 45 Write(ts->connfd, buf, n); //回寫給客戶端 46 } 47 Close(ts->connfd); 48 49 return NULL; 50 } 51 52 int main(void) 53 { 54 struct sockaddr_in servaddr, cliaddr; 55 socklen_t cliaddr_len; 56 int listenfd, connfd; 57 pthread_t tid; 58 struct s_info ts[256]; //根據最大線程數建立結構體數組. 59 int i = 0; 60 61 listenfd = Socket(AF_INET, SOCK_STREAM, 0); //建立一個socket, 獲得lfd 62 63 bzero(&servaddr, sizeof(servaddr)); //地址結構清零 64 servaddr.sin_family = AF_INET; 65 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //指定本地任意IP 66 servaddr.sin_port = htons(SERV_PORT); //指定端口號 8000 67 68 Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //綁定 69 70 Listen(listenfd, 128); //設置同一時刻連接服務器上限數 71 72 printf("Accepting client connect ...\n"); 73 74 while (1) 75 { 76 cliaddr_len = sizeof(cliaddr); 77 connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); //阻塞監聽客戶端連接請求 78 ts[i].cliaddr = cliaddr; 79 ts[i].connfd = connfd; 80 81 pthread_create(&tid, NULL, do_work, (void*)&ts[i]); 82 pthread_detach(tid); //子線程分離,防止僵線程產生. 83 i++; 84 if(i == 256) 85 { 86 break; 87 } 88 } 89 90 return 0; 91 }
1 /* client.c */ 2 #include <stdio.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <netinet/in.h> 6 #include <arpa/inet.h> 7 #include "wrap.h" 8 9 #define MAXLINE 80 10 #define SERV_PORT 8000 11 12 int main(int argc, char *argv[]) 13 { 14 struct sockaddr_in servaddr; 15 char buf[MAXLINE]; 16 int sockfd, n; 17 18 sockfd = Socket(AF_INET, SOCK_STREAM, 0); 19 20 bzero(&servaddr, sizeof(servaddr)); 21 servaddr.sin_family = AF_INET; 22 inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr.s_addr); 23 servaddr.sin_port = htons(SERV_PORT); 24 25 Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 26 27 while (fgets(buf, MAXLINE, stdin) != NULL) 28 { 29 Write(sockfd, buf, strlen(buf)); 30 n = Read(sockfd, buf, MAXLINE); 31 if (n == 0) 32 printf("the other side has been closed.\n"); 33 else 34 Write(STDOUT_FILENO, buf, n); 35 } 36 37 Close(sockfd); 38 39 return 0; 40 }
1 src = $(wildcard *.c) 2 obj = $(patsubst %.c, %.o, $(src)) 3 4 all: server client 5 6 server: server.o wrap.o 7 gcc server.o wrap.o -o server -Wall -lpthread 8 client: client.o wrap.o 9 gcc client.o wrap.o -o client -Wall -lpthread 10 11 %.o:%.c 12 gcc -c $< -Wall 13 14 .PHONY: clean all 15 clean: 16 -rm -rf server client $(obj)
多路IO轉接服務器也叫作多任務IO服務器。該類服務器實現的主旨思想是,再也不由應用程序本身監視客戶端鏈接,取而代之由內核替應用程序監視文件。
1)先構造一張有關文件描述符的列表, 將要監聽的文件描述符添加到該表中
2)而後調用一個函數,監聽該表中的文件描述符,直到這些描述符表中的一個進行I/O操做時,該函數才返回。
3)在返回時,它告訴進程有多少(哪些)描述符要進行I/O操做。
IO操做方式:
(1)阻塞等待
(2)非阻塞, 忙輪詢
一個任務:
多個任務:
解決方案:使用IO多路轉接技術 select/poll/epoll
第一種: select/poll
注意:select 代收員比較懶, 她只會告訴你有幾個快遞到了,可是哪一個快遞,你須要挨個遍歷一遍。
第二種: epoll
注意:epoll代收快遞員很勤快, 她不只會告訴你有幾個快遞到了, 還會告訴你是哪一個快遞公司的快遞。
主要使用的方法有三種:select/poll/epoll
(1)首先分析select的工做原理?
結合下面select函數的介紹及下面的僞代碼用select實現一個server端有助於上面select工做流程的理解:
1 int main() 2 { 3 int lfd = socket(); 4 bind(); 5 listen(); 6 7 // 建立一文件描述符表 8 fd_st reads, temp; 9 // 初始化 10 fd_zero(&reads); 11 // 監聽的lfd加入到讀集合 12 fd_set(lfd, &reads); 13 int maxfd = lfd; 14 15 while(1) 16 { 17 // 委託檢測 18 temp = reads; 19 int ret = select(maxfd+1, &temp, NULL, NULL, NULL); 20 21 // 是否是監聽的 22 if(fd_isset(lfd, &temp)) 23 { 24 // 接受新鏈接 25 int cfd = accept(); 26 // cfd加入讀集合 27 fd_set(cfd, &reads); 28 // 更新maxfd 29 maxfd=maxfd<cfd ? cfd:maxfd; 30 } 31 // 客戶端發送數據 32 for(int i=lfd+1; i<=maxfd; ++i) 33 { 34 if(fd_isset(i, &temp) 35 { 36 int len = read(); 37 if(len == 0) 38 { 39 // cfd 從讀集合中del 40 fd_clr(i, &reads); 41 } 42 write(); 43 } 44 } 45 } 46 }
(2)使用select函的優缺點:
跨平臺
a. 每次調用select,都須要把fd集合從用戶態拷貝到內核態,這個開銷在fd不少時會很大;
b. 同時每次調用select都須要在內核遍歷傳遞進來的全部fd,這個開銷在fd不少時也很大;
c. select支持的文件描述符數量過小了,默認是1024。
爲何是1024?
首先,看下內核中對fd_set的定義: typedef struct { unsigned long fds_bits[__FDSET_LONGS]; } __kernel_fd_set; typedef __kernel_fd_set fd_set; 其中有關的常量定義爲: #undef __NFDBITS #define __NFDBITS (8 * sizeof(unsigned long)) #undef __FD_SETSIZE #define __FD_SETSIZE 1024 #undef __FDSET_LONGS #define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS) 即__NFDBITS爲8*4=32,__FD_SETSIZE爲1024,那麼,__FDSET_LONGS爲1024/32=32,所以,fd_set其實是32個無符號長整形,也就是1024位
(2)select函數及示例
#include <sys/select.h> /* According to earlier standards */ #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); nfds: 監控的文件描述符集裏最大文件描述符加1,由於此參數會告訴內核檢測前多少個文件描述符的狀態 readfds: 監控有讀數據到達文件描述符集合,傳入傳出參數 writefds: 監控寫數據到達文件描述符集合,傳入傳出參數 exceptfds: 監控異常發生達文件描述符集合,如帶外數據到達異常,傳入傳出參數 timeout: 定時阻塞監控時間,3種狀況 1.NULL,永遠等下去 2.設置timeval,等待固定時間 3.設置timeval裏時間均爲0,檢查描述字後當即返回,輪詢 struct timeval { long tv_sec; /* seconds */ long tv_usec; /* microseconds */ }; void FD_CLR(int fd, fd_set *set); //把文件描述符集合裏fd清0 int FD_ISSET(int fd, fd_set *set); //測試文件描述符集合裏fd是否置1 void FD_SET(int fd, fd_set *set); //把文件描述符集合裏fd位置1 void FD_ZERO(fd_set *set); //把文件描述符集合裏全部位清0
select示例:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <string.h> 6 #include <sys/socket.h> 7 #include <arpa/inet.h> 8 #include <ctype.h> 9 10 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 struct sockaddr_in serv_addr; 19 socklen_t serv_len = sizeof(serv_addr); 20 int port = atoi(argv[1]); 21 22 // 建立套接字 23 int lfd = socket(AF_INET, SOCK_STREAM, 0); 24 // 初始化服務器 sockaddr_in 25 memset(&serv_addr, 0, serv_len); 26 serv_addr.sin_family = AF_INET; // 地址族 27 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 監聽本機全部的IP 28 serv_addr.sin_port = htons(port); // 設置端口 29 // 綁定IP和端口 30 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 31 32 // 設置同時監聽的最大個數 33 listen(lfd, 36); 34 printf("Start accept ......\n"); 35 36 struct sockaddr_in client_addr; 37 socklen_t cli_len = sizeof(client_addr); 38 39 // 最大的文件描述符 40 int maxfd = lfd; 41 // 文件描述符讀集合 42 fd_set reads, temp; 43 // init 44 FD_ZERO(&reads); 45 FD_SET(lfd, &reads); 46 47 while(1) 48 { 49 // 委託內核作IO檢測 50 temp = reads; 51 int ret = select(maxfd+1, &temp, NULL, NULL, NULL); 52 if(ret == -1) 53 { 54 perror("select error"); 55 exit(1); 56 } 57 // 客戶端發起了新的鏈接 58 if(FD_ISSET(lfd, &temp)) 59 { 60 // 接受鏈接請求 - accept不阻塞 61 int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len); 62 if(cfd == -1) 63 { 64 perror("accept error"); 65 exit(1); 66 } 67 char ip[64]; 68 printf("new client IP: %s, Port: %d\n", 69 inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)), 70 ntohs(client_addr.sin_port)); 71 // 將cfd加入到待檢測的讀集合中 - 下一次就能夠檢測到了 72 FD_SET(cfd, &reads); 73 // 更新最大的文件描述符 74 maxfd = maxfd < cfd ? cfd : maxfd; 75 } 76 // 已經鏈接的客戶端有數據到達 77 for(int i=lfd+1; i<=maxfd; ++i) 78 { 79 if(FD_ISSET(i, &temp)) 80 { 81 char buf[1024] = {0}; 82 int len = recv(i, buf, sizeof(buf), 0); 83 if(len == -1) 84 { 85 perror("recv error"); 86 exit(1); 87 } 88 else if(len == 0) 89 { 90 printf("客戶端已經斷開了鏈接\n"); 91 close(i); 92 // 從讀集合中刪除 93 FD_CLR(i, &reads); 94 } 95 else 96 { 97 printf("recv buf: %s\n", buf); 98 send(i, buf, strlen(buf)+1, 0); 99 } 100 } 101 } 102 } 103 104 close(lfd); 105 return 0; 106 }
select示例2:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <string.h> 6 #include <sys/socket.h> 7 #include <arpa/inet.h> 8 #include <ctype.h> 9 #include <sys/select.h> 10 11 #define SERV_PORT 8989 12 13 int main(int argc, const char* argv[]) 14 { 15 int lfd, cfd; 16 struct sockaddr_in serv_addr, clien_addr; 17 int serv_len, clien_len; 18 19 // 建立套接字 20 lfd = socket(AF_INET, SOCK_STREAM, 0); 21 // 初始化服務器 sockaddr_in 22 memset(&serv_addr, 0, sizeof(serv_addr)); 23 serv_addr.sin_family = AF_INET; // 地址族 24 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 監聽本機全部的IP 25 serv_addr.sin_port = htons(SERV_PORT); // 設置端口 26 serv_len = sizeof(serv_addr); 27 // 綁定IP和端口 28 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 29 30 // 設置同時監聽的最大個數 31 listen(lfd, 36); 32 printf("Start accept ......\n"); 33 34 int ret; 35 int maxfd = lfd; 36 // reads 實時更新,temps 內核檢測 37 fd_set reads, temps; 38 39 FD_ZERO(&reads); 40 FD_SET(lfd, &reads); 41 42 while(1) 43 { 44 temps = reads; 45 ret = select(maxfd+1, &temps, NULL, NULL, NULL); 46 if(ret == -1) 47 { 48 perror("select error"); 49 exit(1); 50 } 51 52 53 // 判斷是否有新鏈接 54 if(FD_ISSET(lfd, &temps)) 55 { 56 // 接受鏈接請求 57 clien_len = sizeof(clien_len); 58 int cfd = accept(lfd, (struct sockaddr*)&clien_addr, &clien_len); 59 60 // 文件描述符放入檢測集合 61 FD_SET(cfd, &reads); 62 // 更新最大文件描述符 63 maxfd = maxfd < cfd ? cfd : maxfd; 64 } 65 66 // 遍歷檢測的文件描述符是否有讀操做 67 for(int i=lfd+1; i<=maxfd; ++i) 68 { 69 if(FD_ISSET(i, &temps)) 70 { 71 // 讀數據 72 char buf[1024] = {0}; 73 int len = read(i, buf, sizeof(buf)); 74 if(len == -1) 75 { 76 perror("read error"); 77 exit(1); 78 } 79 else if(len == 0) 80 { 81 // 對方關閉了鏈接 82 FD_CLR(i, &reads); 83 close(i); 84 if(maxfd == i) 85 { 86 maxfd--; 87 } 88 } 89 else 90 { 91 printf("read buf = %s\n", buf); 92 for(int j=0; j<len; ++j) 93 { 94 buf[j] = toupper(buf[j]); 95 } 96 printf("--buf toupper: %s\n", buf); 97 write(i, buf, strlen(buf)+1); 98 } 99 } 100 } 101 } 102 103 close(lfd); 104 return 0; 105 }
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <string.h> 6 #include <sys/socket.h> 7 #include <arpa/inet.h> 8 #include <ctype.h> 9 #include <sys/select.h> 10 11 #define SERV_PORT 8989 12 13 int main(int argc, const char* argv[]) 14 { 15 int lfd, cfd; 16 struct sockaddr_in serv_addr, clien_addr; 17 int serv_len, clien_len; 18 19 // 建立套接字 20 lfd = socket(AF_INET, SOCK_STREAM, 0); 21 // 初始化服務器 sockaddr_in 22 memset(&serv_addr, 0, sizeof(serv_addr)); 23 serv_addr.sin_family = AF_INET; // 地址族 24 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 監聽本機全部的IP 25 serv_addr.sin_port = htons(SERV_PORT); // 設置端口 26 serv_len = sizeof(serv_addr); 27 // 綁定IP和端口 28 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 29 30 // 設置同時監聽的最大個數 31 listen(lfd, 36); 32 printf("Start accept ......\n"); 33 34 int ret; 35 int maxfd = lfd; 36 // reads 實時更新,temps 內核檢測 37 fd_set reads, temps; 38 39 /*===============================================================*/ 40 // 記錄要檢測的文件描述符的數組 41 int allfd[FD_SETSIZE]; // 1024 42 // 記錄數組中最後一個元素的下標 43 int last_index = 0; 44 // 初始化數組 45 for(int i=0; i<FD_SETSIZE; ++i) 46 { 47 allfd[i] = -1; // 無效文件描述符值 48 } 49 allfd[0] = lfd; // 監聽的文件描述符添加到數組中 50 /*===============================================================*/ 51 52 // 初始化監聽的讀集合 53 FD_ZERO(&reads); 54 FD_SET(lfd, &reads); 55 56 while(1) 57 { 58 // 每次都須要更新,不然select不會從新檢測 59 temps = reads; 60 ret = select(maxfd+1, &temps, NULL, NULL, NULL); 61 if(ret == -1) 62 { 63 perror("select error"); 64 exit(1); 65 } 66 67 int i = 0; 68 char bufip[64]; 69 // 判斷是否有新鏈接 70 if(FD_ISSET(lfd, &temps)) 71 { 72 // 接受鏈接請求 73 clien_len = sizeof(clien_len); 74 int cfd = accept(lfd, (struct sockaddr*)&clien_addr, &clien_len); 75 printf("client ip: %s, port: %d\n", 76 inet_ntop(AF_INET, &clien_addr.sin_addr.s_addr, bufip, sizeof(bufip)), 77 ntohs(clien_addr.sin_port)); 78 79 // 文件描述符放入檢測集合 80 FD_SET(cfd, &reads); 81 // 更新最大文件描述符 82 maxfd = maxfd < cfd ? cfd : maxfd; 83 // cfd添加到檢測數組中 84 for(i=0; i<FD_SETSIZE; ++i) 85 { 86 if(allfd[i] == -1) 87 { 88 allfd[i] = cfd; 89 break; 90 } 91 } 92 // 更新數組最後一個有效值下標 93 last_index = last_index < i ? i : last_index; 94 } 95 96 // 遍歷檢測的文件描述符是否有讀操做 97 for(i=lfd+1; i<=maxfd; ++i) 98 { 99 if(FD_ISSET(i, &temps)) 100 { 101 // 讀數據 102 char buf[1024] = {0}; 103 int len = read(i, buf, sizeof(buf)); 104 if(len == -1) 105 { 106 perror("read error"); 107 exit(1); 108 } 109 else if(len == 0) 110 { 111 // 對方關閉了鏈接 112 FD_CLR(i, &reads); 113 close(i); 114 if(maxfd == i) 115 { 116 maxfd--; 117 } 118 allfd[i] = -1; 119 printf("對方已經關閉了鏈接。。。。。。\n"); 120 } 121 else 122 { 123 printf("read buf = %s\n", buf); 124 for(int j=0; j<len; ++j) 125 { 126 buf[j] = toupper(buf[j]); 127 } 128 printf("--buf toupper: %s\n", buf); 129 write(i, buf, strlen(buf)+1); 130 } 131 } 132 } 133 } 134 135 close(lfd); 136 return 0; 137 }
補充 pselect:
pselect原型以下。此模型應用較少,可參考select模型自行編寫C/S:
#include <sys/select.h> int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask); struct timespec { long tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ }; 用sigmask替代當前進程的阻塞信號集,調用返回後還原原有阻塞信號集
#include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout); struct pollfd { int fd; /* 文件描述符 */ short events; /* 監控的事件 */ short revents; /* 監控事件中知足條件返回的事件 */ }; POLLIN 普通或帶外優先數據可讀,即POLLRDNORM | POLLRDBAND POLLRDNORM 數據可讀 POLLRDBAND 優先級帶數據可讀 POLLPRI 高優先級可讀數據 POLLOUT 普通或帶外數據可寫 POLLWRNORM 數據可寫 POLLWRBAND 優先級帶數據可寫 POLLERR 發生錯誤 POLLHUP 發生掛起 POLLNVAL 描述字不是一個打開的文件
fds 數組地址 nfds 監控數組中有多少文件描述符須要被監控,數組的最大長度, 數組中最後一個使用的元素下標+1,內核會輪詢檢測fd數組的每一個文件描述符 timeout 毫秒級等待 -1:阻塞等,#define INFTIM -1 Linux中沒有定義此宏 0:當即返回,不阻塞進程 >0:等待指定毫秒數,如當前系統時間精度不夠毫秒,向上取值
返回值: IO發送變化的文件描述符的個數
若是再也不監控某個文件描述符時,能夠把pollfd中,fd設置爲-1,poll再也不監控此pollfd,下次返回時,把revents設置爲0。
示例(使用poll實現的server):
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <string.h> 6 #include <sys/socket.h> 7 #include <arpa/inet.h> 8 #include <ctype.h> 9 #include <poll.h> 10 11 #define SERV_PORT 8989 12 13 int main(int argc, const char* argv[]) 14 { 15 int lfd, cfd; 16 struct sockaddr_in serv_addr, clien_addr; 17 int serv_len, clien_len; 18 19 // 建立套接字 20 lfd = socket(AF_INET, SOCK_STREAM, 0); 21 // 初始化服務器 sockaddr_in 22 memset(&serv_addr, 0, sizeof(serv_addr)); 23 serv_addr.sin_family = AF_INET; // 地址族 24 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 監聽本機全部的IP 25 serv_addr.sin_port = htons(SERV_PORT); // 設置端口 26 serv_len = sizeof(serv_addr); 27 // 綁定IP和端口 28 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 29 30 // 設置同時監聽的最大個數 31 listen(lfd, 36); 32 printf("Start accept ......\n"); 33 34 // poll結構體 35 struct pollfd allfd[1024]; 36 int max_index = 0; 37 // init 38 for(int i=0; i<1024; ++i) 39 { 40 allfd[i].fd = -1; 41 } 42 allfd[0].fd = lfd; 43 allfd[0].events = POLLIN; 44 45 while(1) 46 { 47 int i = 0; 48 int ret = poll(allfd, max_index+1, -1); 49 if(ret == -1) 50 { 51 perror("poll error"); 52 exit(1); 53 } 54 55 // 判斷是否有鏈接請求 56 if(allfd[0].revents & POLLIN) 57 { 58 clien_len = sizeof(clien_addr); 59 // 接受鏈接請求 60 int cfd = accept(lfd, (struct sockaddr*)&clien_addr, &clien_len); 61 printf("============\n"); 62 63 // cfd添加到poll數組 64 for(i=0; i<1024; ++i) 65 { 66 if(allfd[i].fd == -1) 67 { 68 allfd[i].fd = cfd; 69 break; 70 } 71 } 72 // 更新最後一個元素的下標 73 max_index = max_index < i ? i : max_index; 74 } 75 76 // 遍歷數組 77 for(i=1; i<=max_index; ++i) 78 { 79 int fd = allfd[i].fd; 80 if(fd == -1) 81 { 82 continue; 83 } 84 if(allfd[i].revents & POLLIN) 85 { 86 // 接受數據 87 char buf[1024] = {0}; 88 int len = recv(fd, buf, sizeof(buf), 0); 89 if(len == -1) 90 { 91 perror("recv error"); 92 exit(1); 93 } 94 else if(len == 0) 95 { 96 allfd[i].fd = -1; 97 close(fd); 98 printf("客戶端已經主動斷開鏈接。。。\n"); 99 } 100 else 101 { 102 printf("recv buf = %s\n", buf); 103 for(int k=0; k<len; ++k) 104 { 105 buf[k] = toupper(buf[k]); 106 } 107 printf("buf toupper: %s\n", buf); 108 send(fd, buf, strlen(buf)+1, 0); 109 } 110 111 } 112 113 } 114 } 115 116 close(lfd); 117 return 0; 118 }
poll與select的比較:
補充 ppoll:
GNU定義了ppoll(非POSIX標準),能夠支持設置信號屏蔽字,可參考poll模型自行實現C/S。
#define _GNU_SOURCE /* See feature_test_macros(7) */ #include <poll.h> int ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout_ts, const sigset_t *sigmask);
epoll是Linux下多路複用IO接口select/poll的加強版本,它能顯著提升程序在大量併發鏈接中只有少許活躍的狀況下的系統CPU利用率,由於它會複用文件描述符集合來傳遞結果而不用迫使開發者
每次等待事件以前都必須從新準備要被偵聽的文件描述符集合(用戶態和內核態共享同一片文件描述符表內存),另外一點緣由就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那
些被內核IO事件異步喚醒而加入Ready隊列的描述符集合就好了。
目前epell是linux大規模併發網絡程序中的熱門首選模型。
epoll除了提供select/poll那種IO事件的電平觸發(Level Triggered)外,還提供了邊沿觸發(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態,減小epoll_wait/epoll_pwait的調用,提
高應用程序效率。
可使用cat命令查看一個進程能夠打開的socket描述符上限。
cat /proc/sys/fs/file-max
若有須要,能夠經過修改配置文件的方式修改該上限值。
sudo vi /etc/security/limits.conf 在文件尾部寫入如下配置,soft軟限制,hard硬限制。 * soft nofile 65536 * hard nofile 100000
1)建立一個epoll句柄,參數size用來告訴內核監聽的文件描述符的個數,跟內存大小有關。
#include <sys/epoll.h> int epoll_create(int size) size:監聽數目, epoll上能關注的最大描述符數
2)控制某個epoll監控的文件描述符上的事件:註冊、修改、刪除。
#include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) epfd: 爲epoll_creat的句柄 op: 表示動做,用3個宏來表示: EPOLL_CTL_ADD (註冊新的fd到epfd), EPOLL_CTL_MOD (修改已經註冊的fd的監聽事件), EPOLL_CTL_DEL (從epfd刪除一個fd); event: 告訴內核須要監聽的事件 struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; EPOLLIN : 表示對應的文件描述符能夠讀(包括對端SOCKET正常關閉) EPOLLOUT: 表示對應的文件描述符能夠寫 EPOLLPRI: 表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來) EPOLLERR: 表示對應的文件描述符發生錯誤 EPOLLHUP: 表示對應的文件描述符被掛斷; EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)而言的 EPOLLONESHOT:只監聽一次事件,當監聽完此次事件以後,若是還須要繼續監聽這個socket的話,須要再次把這個socket加入到EPOLL隊列裏
3)等待所監控文件描述符上有事件的產生,相似於select()調用。
#include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) events: 用來存內核獲得事件的集合,用於回傳待處理事件的數組 maxevents: 告以內核這個events有多大,這個maxevents的值不能大於建立epoll_create()時的size timeout: 是超時時間 -1: 阻塞 0: 當即返回,非阻塞 >0: 指定毫秒 返回值: 成功返回有多少文件描述符就緒,時間到時返回0,出錯返回-1
epoll工做原理:
經過下面的僞代碼有助於上面的理解:
1 int main() 2 { 3 // 建立監聽的套接字 4 int lfd = socket(); 5 // 綁定 6 bind(); 7 // 監聽 8 listen(); 9 10 // epoll樹根節點 11 int epfd = epoll_create(3000); 12 // 存儲發送變化的fd對應信息 13 struct epoll_event all[3000]; 14 // init 15 // 監聽的lfd掛到epoll樹上 16 struct epoll_event ev; 17 // 在ev中init lfd信息 18 ev.events = EPOLLIN ; 19 ev.data.fd = lfd; 20 epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev); 21 while(1) 22 { 23 // 委託內核檢測事件 24 int ret = epoll_wait(epfd, all, 3000, -1); 25 // 根據ret遍歷all數組 26 for(int i=0; i<ret; ++i) 27 { 28 int fd = all[i].data.fd; 29 // 有新的鏈接 30 if( fd == lfd) 31 { 32 // 接收鏈接請求 - accept不阻塞 33 int cfd = accept(); 34 // cfd上樹 35 ev.events = EPOLLIN; 36 ev.data.fd = cfd; 37 epoll_ctl(epfd, epoll_ctl_add, cfd, &ev); 38 } 39 // 已經鏈接的客戶端有數據發送過來 40 else 41 { 42 // 只處理客戶端發來的數據 43 if(!all[i].events & EPOLLIN) 44 { 45 continue; 46 } 47 // 讀數據 48 int len = recv(); 49 if(len == 0) 50 { 51 close(fd); 52 // 檢測的fd從樹上刪除 53 epoll_ctl(epfd, epoll_ctl_del, fd, NULL); 54 } 55 // 寫數據 56 send(); 57 } 58 } 59 } 60 }
示例:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <string.h> 6 #include <sys/socket.h> 7 #include <arpa/inet.h> 8 #include <ctype.h> 9 #include <sys/epoll.h> 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檢測 sizeof(all)/sizeof(all[0]) --> sizeof(struct epoll_event) 52 int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1); 53 54 // 遍歷all數組中的前ret個元素 55 for(int i=0; i<ret; ++i) 56 { 57 int fd = all[i].data.fd; 58 // 判斷是否有新鏈接 59 if(fd == lfd) 60 { 61 // 接受鏈接請求 62 int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len); 63 if(cfd == -1) 64 { 65 perror("accept error"); 66 exit(1); 67 } 68 // 將新獲得的cfd掛到樹上 69 struct epoll_event temp; 70 temp.events = EPOLLIN; 71 temp.data.fd = cfd; 72 epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &temp); 73 74 // 打印客戶端信息 75 char ip[64] = {0}; 76 printf("New Client IP: %s, Port: %d\n", 77 inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)), 78 ntohs(client_addr.sin_port)); 79 80 } 81 else 82 { 83 // 處理已經鏈接的客戶端發送過來的數據 84 if(!all[i].events & EPOLLIN) 85 { 86 continue; 87 } 88 89 // 讀數據 90 char buf[1024] = {0}; 91 int len = recv(fd, buf, sizeof(buf), 0); 92 if(len == -1) 93 { 94 perror("recv error"); 95 exit(1); 96 } 97 else if(len == 0) 98 { 99 printf("client disconnected ....\n"); 100 // fd從epoll樹上刪除 101 ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); 102 if(ret == -1) 103 { 104 perror("epoll_ctl - del error"); 105 exit(1); 106 } 107 close(fd); 108 } 109 else 110 { 111 printf(" recv buf: %s\n", buf); 112 write(fd, buf, len); 113 } 114 } 115 } 116 } 117 118 close(lfd); 119 return 0; 120 }
1 /* client.c */ 2 #include <stdio.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <netinet/in.h> 6 #include "wrap.h" 7 8 #define MAXLINE 80 9 #define SERV_PORT 6666 10 11 int main(int argc, char *argv[]) 12 { 13 struct sockaddr_in servaddr; 14 char buf[MAXLINE]; 15 int sockfd, n; 16 17 sockfd = Socket(AF_INET, SOCK_STREAM, 0); 18 19 bzero(&servaddr, sizeof(servaddr)); 20 servaddr.sin_family = AF_INET; 21 inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); 22 servaddr.sin_port = htons(SERV_PORT); 23 24 Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 25 26 while (fgets(buf, MAXLINE, stdin) != NULL) { 27 Write(sockfd, buf, strlen(buf)); 28 n = Read(sockfd, buf, MAXLINE); 29 if (n == 0) 30 printf("the other side has been closed.\n"); 31 else 32 Write(STDOUT_FILENO, buf, n); 33 } 34 35 Close(sockfd); 36 return 0; 37 }
注意:epoll_wait 調用次數越多, 系統的開銷越大
EPOLL事件有兩種模型:
Edge Triggered (ET) 邊緣觸發只有數據到來才觸發,無論緩存區中是否還有數據。
Level Triggered (LT) 水平觸發只要有數據都會觸發。
思考以下步驟:
1)假定咱們已經把一個用來從管道中讀取數據的文件描述符(RFD)添加到epoll描述符。
2)管道的另外一端寫入了2KB的數據
3)調用epoll_wait,而且它會返回RFD,說明它已經準備好讀取操做
4)讀取1KB的數據
5)調用epoll_wait……
在這個過程當中,有兩種工做模式:
(1)ET模式
ET模式即Edge Triggered工做模式。
若是咱們在第1步將RFD添加到epoll描述符的時候使用了EPOLLET標誌,那麼在第5步調用epoll_wait以後將有可能會掛起,由於剩餘的數據還存在於文件的輸入緩衝區內,並且數據發出端還在
等待一個針對已經發出數據的反饋信息。只有在監視的文件句柄上發生了某個事件的時候 ET 工做模式纔會彙報事件。所以在第5步的時候,調用者可能會放棄等待仍在存在於文件輸入緩衝區內的剩
餘數據。epoll工做在ET模式的時候,必須使用非阻塞套接口,以免因爲一個文件句柄的阻塞讀/阻塞寫操做把處理多個文件描述符的任務餓死。最好如下面的方式調用ET模式的epoll接口,在後面
會介紹避免可能的缺陷。
讀到的數據長度小於請求的數據長度時,就能夠肯定此時緩衝中已沒有數據了,也就能夠認爲此事讀事件已處理完成。
(2)LT模式
LT模式即Level Triggered工做模式。
與ET模式不一樣的是,以LT方式調用epoll接口的時候,它就至關於一個速度比較快的poll,不管後面的數據是否被使用。
LT(level triggered):LT是缺省的工做方式,而且同時支持block和no-block socket。在這種作法中,內核告訴你一個文件描述符是否就緒了,而後你能夠對這個就緒的fd進行IO操做。若是你不做任
何操做,內核仍是會繼續通知你的,因此,這種模式編程出錯誤可能性要小一點。傳統的select/poll都是這種模型的表明。
ET(edge-triggered):ET是高速工做方式,只支持no-block socket。在這種模式下,當描述符從未就緒變爲就緒時,內核經過epoll告訴你。而後它會假設你知道文件描述符已經就緒,而且不會再
爲那個文件描述符發送更多的就緒通知。請注意,若是一直不對這個fd做IO操做(從而致使它再次變成未就緒),內核不會發送更多的通知(only once)。
基於管道epoll ET觸發模式
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <sys/epoll.h> 4 #include <errno.h> 5 #include <unistd.h> 6 7 #define MAXLINE 10 8 9 int main(int argc, char *argv[]) 10 { 11 int efd, i; 12 int pfd[2]; 13 pid_t pid; 14 char buf[MAXLINE], ch = 'a'; 15 16 pipe(pfd); 17 pid = fork(); 18 if (pid == 0) { 19 close(pfd[0]); 20 while (1) { 21 for (i = 0; i < MAXLINE/2; i++) 22 buf[i] = ch; 23 buf[i-1] = '\n'; 24 ch++; 25 26 for (; i < MAXLINE; i++) 27 buf[i] = ch; 28 buf[i-1] = '\n'; 29 ch++; 30 31 write(pfd[1], buf, sizeof(buf)); 32 sleep(2); 33 } 34 close(pfd[1]); 35 } else if (pid > 0) { 36 struct epoll_event event; 37 struct epoll_event resevent[10]; 38 int res, len; 39 close(pfd[1]); 40 41 efd = epoll_create(10); 42 /* event.events = EPOLLIN; */ 43 event.events = EPOLLIN | EPOLLET; /* ET 邊沿觸發 ,默認是水平觸發 */ 44 event.data.fd = pfd[0]; 45 epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event); 46 47 while (1) { 48 res = epoll_wait(efd, resevent, 10, -1); 49 printf("res %d\n", res); 50 if (resevent[0].data.fd == pfd[0]) { 51 len = read(pfd[0], buf, MAXLINE/2); 52 write(STDOUT_FILENO, buf, len); 53 } 54 } 55 close(pfd[0]); 56 close(efd); 57 } else { 58 perror("fork"); 59 exit(-1); 60 } 61 return 0; 62 }
基於網絡C/S模型的epoll ET觸發模式
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <string.h> 6 #include <sys/socket.h> 7 #include <arpa/inet.h> 8 #include <ctype.h> 9 #include <sys/epoll.h> 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 45 // 設置邊沿觸發 46 ev.events = EPOLLIN | EPOLLET; 47 ev.data.fd = lfd; 48 epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev); 49 50 struct epoll_event all[2000]; 51 while(1) 52 { 53 // 使用epoll通知內核fd 文件IO檢測 54 int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1); 55 printf("================== epoll_wait =============\n"); 56 57 // 遍歷all數組中的前ret個元素 58 for(int i=0; i<ret; ++i) 59 { 60 int fd = all[i].data.fd; 61 // 判斷是否有新鏈接 62 if(fd == lfd) 63 { 64 // 接受鏈接請求 65 int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len); 66 if(cfd == -1) 67 { 68 perror("accept error"); 69 exit(1); 70 } 71 // 將新獲得的cfd掛到樹上 72 struct epoll_event temp; 73 // 設置邊沿觸發 74 temp.events = EPOLLIN | EPOLLET; 75 temp.data.fd = cfd; 76 epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &temp); 77 78 // 打印客戶端信息 79 char ip[64] = {0}; 80 printf("New Client IP: %s, Port: %d\n", 81 inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)), 82 ntohs(client_addr.sin_port)); 83 84 } 85 else 86 { 87 // 處理已經鏈接的客戶端發送過來的數據 88 if(!all[i].events & EPOLLIN) 89 { 90 continue; 91 } 92 93 // 讀數據 94 char buf[5] = {0}; 95 int len = recv(fd, buf, sizeof(buf), 0); 96 if(len == -1) 97 { 98 perror("recv error"); 99 exit(1); 100 } 101 else if(len == 0) 102 { 103 printf("client disconnected ....\n"); 104 // fd從epoll樹上刪除 105 ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); 106 if(ret == -1) 107 { 108 perror("epoll_ctl - del error"); 109 exit(1); 110 } 111 close(fd); 112 } 113 else 114 { 115 // printf(" recv buf: %s\n", buf); 116 write(STDOUT_FILENO, buf, len); 117 write(fd, buf, len); 118 } 119 } 120 } 121 } 122 123 close(lfd); 124 return 0; 125 }
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 8 #include <fcntl.h> 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 }
執行結果:
client端:
[root@centos epoll]# ./client 6666 000001111122222 read buf = 000001111122222 , len = 5 read buf = 11111, len = 5 read buf = 22222, len = 5
server端:
[root@centos epoll]# ./et_epoll 6666 Start accept ...... ================== epoll_wait ============= New Client IP: 127.0.0.1, Port: 54080 ================== epoll_wait ============= 00000================== epoll_wait ============= 11111================== epoll_wait ============= 22222
執行結果分析:能夠看出,當客戶端發送數據(000001111122222)到server端(接收數據緩衝區內),可是因爲server端一次只接受5個字節(00000),所以在接受完5個字節以後,將接收的5個字節數據發回給客戶端,程序又會在epoll_wait處阻塞等待。當有新數據再次發送過來,則會將上一次緩衝區中剩餘的數據(11111)讀取併發送給客戶端,如此最後將(22222)發送給客戶端。
基於網絡C/S非阻塞模型的epoll ET觸發模式
實現過程當中注意兩點:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <string.h> 6 #include <sys/socket.h> 7 #include <arpa/inet.h> 8 #include <ctype.h> 9 #include <sys/epoll.h> 10 #include <fcntl.h> 11 #include <errno.h> 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<ret; ++i) 60 { 61 int fd = all[i].data.fd; 62 // 判斷是否有新鏈接 63 if(fd == lfd) 64 { 65 // 接受鏈接請求 66 int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len); 67 if(cfd == -1) 68 { 69 perror("accept error"); 70 exit(1); 71 } 72 // 設置文件cfd爲非阻塞模式 73 int flag = fcntl(cfd, F_GETFL); 74 flag |= O_NONBLOCK; 75 fcntl(cfd, F_SETFL, flag); 76 77 // 將新獲得的cfd掛到樹上 78 struct epoll_event temp; 79 // 設置邊沿觸發 80 temp.events = EPOLLIN | EPOLLET; 81 temp.data.fd = cfd; 82 epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &temp); 83 84 // 打印客戶端信息 85 char ip[64] = {0}; 86 printf("New Client IP: %s, Port: %d\n", 87 inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)), 88 ntohs(client_addr.sin_port)); 89 90 } 91 else 92 { 93 // 處理已經鏈接的客戶端發送過來的數據 94 if(!all[i].events & EPOLLIN) 95 { 96 continue; 97 } 98 99 // 讀數據 100 char buf[5] = {0}; 101 int len; 102 // 循環讀數據 103 while( (len = recv(fd, buf, sizeof(buf), 0)) > 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 }
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 8 #include <fcntl.h> 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'))。
基於網絡C/S模型的epoll LT觸發模式
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <string.h> 6 #include <sys/socket.h> 7 #include <arpa/inet.h> 8 #include <ctype.h> 9 #include <sys/epoll.h> 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; i<ret; ++i) 57 { 58 int fd = all[i].data.fd; 59 // 判斷是否有新鏈接 60 if(fd == lfd) 61 { 62 // 接受鏈接請求 63 int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len); 64 if(cfd == -1) 65 { 66 perror("accept error"); 67 exit(1); 68 } 69 // 將新獲得的cfd掛到樹上 70 struct epoll_event temp; 71 temp.events = EPOLLIN; 72 temp.data.fd = cfd; 73 epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &temp); 74 75 // 打印客戶端信息 76 char ip[64] = {0}; 77 printf("New Client IP: %s, Port: %d\n", 78 inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)), 79 ntohs(client_addr.sin_port)); 80 81 } 82 else 83 { 84 // 處理已經鏈接的客戶端發送過來的數據 85 if(!all[i].events & EPOLLIN) 86 { 87 continue; 88 } 89 90 // 讀數據 91 char buf[5] = {0}; 92 int len = recv(fd, buf, sizeof(buf), 0); 93 if(len == -1) 94 { 95 perror("recv error"); 96 exit(1); 97 } 98 else if(len == 0) 99 { 100 printf("client disconnected ....\n"); 101 // fd從epoll樹上刪除 102 ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); 103 if(ret == -1) 104 { 105 perror("epoll_ctl - del error"); 106 exit(1); 107 } 108 close(fd); 109 110 } 111 else 112 { 113 // printf(" recv buf: %s\n", buf); 114 write(STDOUT_FILENO, buf, len); 115 write(fd, buf, len); 116 } 117 } 118 } 119 } 120 121 close(lfd); 122 return 0; 123 }
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 8 #include <fcntl.h> 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限制:
解決辦法:
1)預先建立阻塞於accept多線程,使用互斥鎖上鎖保護accept
2)預先建立多線程,由主線程調用accept
傳輸層主要應用的協議模型有兩種,一種是TCP協議,另一種則是UDP協議。TCP協議在網絡通訊中占主導地位,絕大多數的網絡通訊藉助TCP協議完成數據傳輸。但UDP也是網絡通訊中不可
或缺的重要通訊手段。
相較於TCP而言,UDP通訊的形式更像是發短信。不須要在數據傳輸以前創建、維護鏈接。只專心獲取數據就好。省去了三次握手的過程,通訊速度能夠大大提升,但與之伴隨的通訊的穩定性和
正確率便得不到保證。所以,咱們稱UDP爲「無鏈接的不可靠報文傳遞」。
那麼與咱們熟知的TCP相比,UDP有哪些優勢和不足呢?因爲無需建立鏈接,因此UDP開銷較小,數據傳輸速度快,實時性較強。多用於對實時性要求較高的通訊場合,如視頻會議、電話會議
等。但隨之也伴隨着數據傳輸不可靠,傳輸數據的正確率、傳輸順序和流量都得不到控制和保證。因此,一般狀況下,使用UDP協議進行數據傳輸,爲保證數據的正確性,咱們須要在應用層添加輔
助校驗協議來彌補UDP的不足,以達到數據可靠傳輸的目的。
與TCP相似的,UDP也有可能出現緩衝區被填滿後,再接收數據時丟包的現象。因爲它沒有TCP滑動窗口的機制,一般採用以下兩種方法解決:
1)服務器應用層設計流量控制,控制發送數據速度。
2)藉助setsockopt函數改變接收緩衝區大小。如:
#include <sys/socket.h> 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. 在應用層自定義協議, 作數據校驗
UDP處理模型
因爲UDP不須要維護鏈接,程序邏輯簡單了不少,可是UDP協議是不可靠的,保證通信可靠性的機制須要在應用層實現。
編譯運行server,在兩個終端裏各開一個client與server交互,看看server是否具備併發服務的能力。用Ctrl+C關閉server,而後再運行server,看此時client還可否和server聯繫上。和前面TCP程序
的運行結果相比較,體會無鏈接的含義。
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 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 }
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 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 }
廣播結構圖:
注意:廣播只適用於局域網
代碼實現:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 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 // 綁定server的iP和端口 20 struct sockaddr_in serv; 21 memset(&serv, 0, sizeof(serv)); 22 serv.sin_family = AF_INET; 23 serv.sin_port = htons(8787); // server端口 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 // 初始化客戶端地址信息 33 struct sockaddr_in client; 34 memset(&client, 0, sizeof(client)); 35 client.sin_family = AF_INET; 36 client.sin_port = htons(6767); // 客戶端要綁定的端口 37 // 使用廣播地址給客戶端發數據 38 inet_pton(AF_INET, "192.168.30.255", &client.sin_addr.s_addr); 39 40 // 給服務器開放廣播權限 41 int flag = 1; 42 setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag)); 43 44 // 通訊 45 while(1) 46 { 47 // 一直給客戶端發數據 48 static int num = 0; 49 char buf[1024] = {0}; 50 sprintf(buf, "hello, udp == %d\n", num++); 51 int ret = sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&client, sizeof(client)); 52 if(ret == -1) 53 { 54 perror("sendto error"); 55 break; 56 } 57 58 printf("server == send buf: %s\n", buf); 59 60 sleep(1); 61 } 62 63 close(fd); 64 65 return 0; 66 }
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 8 9 int main(int argc, const char* argv[]) 10 { 11 int fd = socket(AF_INET, SOCK_DGRAM, 0); 12 if(fd == -1) 13 { 14 perror("socket error"); 15 exit(1); 16 } 17 18 // 綁定iP和端口 19 struct sockaddr_in client; 20 memset(&client, 0, sizeof(client)); 21 client.sin_family = AF_INET; 22 client.sin_port = htons(6767); 23 inet_pton(AF_INET, "0.0.0.0", &client.sin_addr.s_addr); 24 int ret = bind(fd, (struct sockaddr*)&client, sizeof(client)); 25 if(ret == -1) 26 { 27 perror("bind error"); 28 exit(1); 29 } 30 31 // 接收數據 32 while(1) 33 { 34 char buf[1024] = {0}; 35 int len = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL); 36 if(len == -1) 37 { 38 perror("recvfrom error"); 39 break; 40 } 41 42 printf("client == recv buf: %s\n", buf); 43 } 44 45 close(fd); 46 47 return 0; 48 }
使用範圍:
組播結構圖:
結構體:
struct ip_mreqn { // 組播組的IP地址. struct in_addr imr_multiaddr; // 本地某一網絡設備接口的IP地址。 struct in_addr imr_interface; int imr_ifindex; // 網卡編號 }; struct in_addr { in_addr_t s_addr; };
組播組能夠是永久的也能夠是臨時的。組播組地址中,有一部分由官方分配的,稱爲永久組播組。永久組播組保持不變的是它的ip地址,組中的成員構成能夠發生變化。永久組播組中成員的數量
均可以是任意的,甚至能夠爲零。那些沒有保留下來供永久組播組使用的ip組播地址,能夠被臨時組播組利用。
224.0.0.0~224.0.0.255 爲預留的組播地址(永久組地址),地址224.0.0.0保留不作分配,其它地址供路由協議使用; 224.0.1.0~224.0.1.255 是公用組播地址,能夠用於Internet;欲使用需申請。 224.0.2.0~238.255.255.255 爲用戶可用的組播地址(臨時組地址),全網範圍內有效; 239.0.0.0~239.255.255.255 爲本地管理組播地址,僅在特定的本地範圍內有效。
可以使用ip ad命令查看網卡編號,如:
[root@centos ~]# ip ad 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 00:0c:29:60:ad:b3 brd ff:ff:ff:ff:ff:ff inet 192.168.30.137/24 brd 192.168.30.255 scope global noprefixroute dynamic ens33 valid_lft 1642sec preferred_lft 1642sec inet6 fe80::7341:e2f1:498c:8501/64 scope link noprefixroute valid_lft forever preferred_lft forever
if_nametoindex 命令能夠根據網卡名,獲取網卡序號。
代碼實現:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 8 #include <net/if.h> 9 10 int main(int argc, const char* argv[]) 11 { 12 // 建立套接字 13 int fd = socket(AF_INET, SOCK_DGRAM, 0); 14 if(fd == -1) 15 { 16 perror("socket error"); 17 exit(1); 18 } 19 20 // 綁定server的iP和端口 21 struct sockaddr_in serv; 22 memset(&serv, 0, sizeof(serv)); 23 serv.sin_family = AF_INET; 24 serv.sin_port = htons(8787); // server端口 25 serv.sin_addr.s_addr = htonl(INADDR_ANY); 26 int ret = bind(fd, (struct sockaddr*)&serv, sizeof(serv)); 27 if(ret == -1) 28 { 29 perror("bind error"); 30 exit(1); 31 } 32 33 // 初始化客戶端地址信息 34 struct sockaddr_in client; 35 memset(&client, 0, sizeof(client)); 36 client.sin_family = AF_INET; 37 client.sin_port = htons(6666); // 客戶端要綁定的端口 38 // 使用組播地址給客戶端發數據 39 inet_pton(AF_INET, "239.0.0.10", &client.sin_addr.s_addr); 40 41 // 給服務器開放組播權限 42 struct ip_mreqn flag; 43 // init flag 44 inet_pton(AF_INET, "239.0.0.10", &flag.imr_multiaddr.s_addr); // 組播地址 45 inet_pton(AF_INET, "0.0.0.0", &flag.imr_address.s_addr); // 本地IP 46 flag.imr_ifindex = if_nametoindex("ens33"); 47 setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &flag, sizeof(flag)); 48 49 // 通訊 50 while(1) 51 { 52 // 一直給客戶端發數據 53 static int num = 0; 54 char buf[1024] = {0}; 55 sprintf(buf, "hello, udp == %d\n", num++); 56 int ret = sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&client, sizeof(client)); 57 if(ret == -1) 58 { 59 perror("sendto error"); 60 break; 61 } 62 63 printf("server == send buf: %s\n", buf); 64 65 sleep(1); 66 } 67 68 close(fd); 69 70 return 0; 71 }
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 8 #include <net/if.h> 9 10 int main(int argc, const char* argv[]) 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 // 綁定iP和端口 20 struct sockaddr_in client; 21 memset(&client, 0, sizeof(client)); 22 client.sin_family = AF_INET; 23 client.sin_port = htons(6666); // ........ 24 inet_pton(AF_INET, "0.0.0.0", &client.sin_addr.s_addr); 25 int ret = bind(fd, (struct sockaddr*)&client, sizeof(client)); 26 if(ret == -1) 27 { 28 perror("bind error"); 29 exit(1); 30 } 31 32 // 加入到組播地址 33 struct ip_mreqn fl; 34 inet_pton(AF_INET, "239.0.0.10", &fl.imr_multiaddr.s_addr); 35 inet_pton(AF_INET, "0.0.0.0", &fl.imr_address.s_addr); 36 fl.imr_ifindex = if_nametoindex("ens33"); 37 setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &fl, sizeof(fl)); 38 39 // 接收數據 40 while(1) 41 { 42 char buf[1024] = {0}; 43 int len = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL); 44 if(len == -1) 45 { 46 perror("recvfrom error"); 47 break; 48 } 49 50 printf("client == recv buf: %s\n", buf); 51 } 52 53 close(fd); 54 55 return 0; 56 }
經過管道與經過本地套接字實現進程間通訊:
socket API本來是爲網絡通信設計的,但後來在socket的框架上發展出一種IPC機制,就是UNIX Domain Socket。雖然網絡socket也可用於同一臺主機的進程間通信(經過loopback地址
127.0.0.1),可是UNIX Domain Socket用於IPC更有效率:不須要通過網絡協議棧,不須要打包拆包、計算校驗和、維護序號和應答等,只是將應用層數據從一個進程拷貝到另外一個進程。這是因
爲,IPC機制本質上是可靠的通信,而網絡協議是爲不可靠的通信設計的。UNIX Domain Socket也提供面向流和麪向數據包兩種API接口,相似於TCP和UDP,可是面向消息的UNIX Domain Socket
也是可靠的,消息既不會丟失也不會順序錯亂。
UNIX Domain Socket是全雙工的,API接口語義豐富,相比其它IPC機制有明顯的優越性,目前已成爲使用最普遍的IPC機制,好比X Window服務器和GUI程序之間就是經過UNIXDomain Socket
通信的。
使用UNIX Domain Socket的過程和網絡socket十分類似,也要先調用socket()建立一個socket文件描述符,address family指定爲AF_UNIX,type能夠選擇SOCK_DGRAM或SOCK_STREAM,
protocol參數仍然指定爲0便可。
UNIX Domain Socket與網絡socket編程最明顯的不一樣在於地址格式不一樣,用結構體sockaddr_un表示,網絡編程的socket地址是IP地址加端口號,而UNIX Domain Socket的地址是一個socket類型
的文件在文件系統中的路徑,這個socket文件由bind()調用建立,若是調用bind()時該文件已存在,則bind()錯誤返回。
對比網絡套接字地址結構和本地套接字地址結構:
struct sockaddr_in { __kernel_sa_family_t sin_family; /* Address family */ 地址結構類型 __be16 sin_port; /* Port number */ 端口號 struct in_addr sin_addr; /* Internet address */ IP地址 }; struct sockaddr_un { __kernel_sa_family_t sun_family; /* AF_UNIX */ 地址結構類型 char sun_path[UNIX_PATH_MAX]; /* pathname */ socket文件名(含路徑) };
如下程序將UNIX Domain socket綁定到一個地址。
size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path); #define offsetof(type, member) ((int)&((type *)0)->MEMBER)
代碼實現:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 8 #include <sys/un.h> 9 10 int main(int argc, const char* argv[]) 11 { 12 int lfd = socket(AF_LOCAL, SOCK_STREAM, 0); 13 if(lfd == -1) 14 { 15 perror("socket error"); 16 exit(1); 17 } 18 19 // 若是套接字文件存在, 刪除套接字文件 20 unlink("server.sock"); 21 22 // 綁定 23 struct sockaddr_un serv; 24 serv.sun_family = AF_LOCAL; 25 strcpy(serv.sun_path, "server.sock"); 26 int ret = bind(lfd, (struct sockaddr*)&serv, sizeof(serv)); 27 if(ret == -1) 28 { 29 perror("bind error"); 30 exit(1); 31 } 32 33 // 監聽 34 ret = listen(lfd, 36); 35 if(ret == -1) 36 { 37 perror("listen error"); 38 exit(1); 39 } 40 41 // 等待接收鏈接請求 42 struct sockaddr_un client; 43 socklen_t len = sizeof(client); 44 int cfd = accept(lfd, (struct sockaddr*)&client, &len); 45 if(cfd == -1) 46 { 47 perror("accept error"); 48 exit(1); 49 } 50 printf("======client bind file: %s\n", client.sun_path); 51 52 // 通訊 53 while(1) 54 { 55 char buf[1024] = {0}; 56 int recvlen = recv(cfd, buf, sizeof(buf), 0); 57 if(recvlen == -1) 58 { 59 perror("recv error"); 60 exit(1); 61 } 62 else if(recvlen == 0) 63 { 64 printf("clietn disconnect ....\n"); 65 close(cfd); 66 break; 67 } 68 else 69 { 70 printf("recv buf: %s\n", buf); 71 send(cfd, buf, recvlen, 0); 72 } 73 } 74 close(cfd); 75 close(lfd); 76 77 return 0; 78 }
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 8 #include <sys/un.h> 9 10 int main(int argc, const char* argv[]) 11 { 12 int fd = socket(AF_LOCAL, SOCK_STREAM, 0); 13 if(fd == -1) 14 { 15 perror("socket error"); 16 exit(1); 17 } 18 19 unlink("client.sock"); 20 21 // ================================ 22 // 給客戶端綁定一個套接字文件 23 struct sockaddr_un client; 24 client.sun_family = AF_LOCAL; 25 strcpy(client.sun_path, "client.sock"); 26 int ret = bind(fd, (struct sockaddr*)&client, sizeof(client)); 27 if(ret == -1) 28 { 29 perror("bind error"); 30 exit(1); 31 } 32 33 // 初始化server信息 34 struct sockaddr_un serv; 35 serv.sun_family = AF_LOCAL; 36 strcpy(serv.sun_path, "server.sock"); 37 38 // 鏈接服務器 39 connect(fd, (struct sockaddr*)&serv, sizeof(serv)); 40 41 // 通訊 42 while(1) 43 { 44 char buf[1024] = {0}; 45 fgets(buf, sizeof(buf), stdin); 46 send(fd, buf, strlen(buf)+1, 0); 47 48 // 接收數據 49 recv(fd, buf, sizeof(buf), 0); 50 printf("recv buf: %s\n", buf); 51 } 52 53 close(fd); 54 55 return 0; 56 } 57
gethostbyname根據給定的主機名,獲取主機信息。
過期,僅用於IPv4,且線程不安全。
1 #include <stdio.h> 2 #include <netdb.h> 3 #include <arpa/inet.h> 4 5 extern int h_errno; 6 7 int main(int argc, char *argv[]) 8 { 9 struct hostent *host; 10 char str[128]; 11 host = gethostbyname(argv[1]); 12 printf("%s\n", host->h_name); 13 14 while (*(host->h_aliases) != NULL) 15 printf("%s\n", *host->h_aliases++); 16 17 switch (host->h_addrtype) { 18 case AF_INET: 19 while (*(host->h_addr_list) != NULL) 20 printf("%s\n", inet_ntop(AF_INET, (*host->h_addr_list++), str, sizeof(str))); 21 break; 22 default: 23 printf("unknown address type\n"); 24 break; 25 } 26 return 0; 27 }
gethostbyaddr函數。
此函數只能獲取域名解析服務器的url和/etc/hosts裏登記的IP對應的域名。
1 #include <stdio.h> 2 #include <netdb.h> 3 #include <arpa/inet.h> 4 5 extern int h_errno; 6 7 int main(int argc, char *argv[]) 8 { 9 struct hostent *host; 10 char str[128]; 11 struct in_addr addr; 12 13 inet_pton(AF_INET, argv[1], &addr); 14 host = gethostbyaddr((char *)&addr, 4, AF_INET); 15 printf("%s\n", host->h_name); 16 17 while (*(host->h_aliases) != NULL) 18 printf("%s\n", *host->h_aliases++); 19 switch (host->h_addrtype) { 20 case AF_INET: 21 while (*(host->h_addr_list) != NULL) 22 printf("%s\n", inet_ntop(AF_INET, (*host->h_addr_list++), str, sizeof(str))); 23 break; 24 default: 25 printf("unknown address type\n"); 26 break; 27 } 28 return 0; 29 }
getservbyname
getservbyport
根據服務程序名字或端口號獲取信息。使用頻率不高。
getaddrinfo
getnameinfo
freeaddrinfo
可同時處理IPv4和IPv6,線程安全的。
getsockname
根據accpet返回的sockfd,獲得臨時端口號
getpeername
根據accpet返回的sockfd,獲得遠端連接的端口號,在exec後能夠獲取客戶端信息。