Linux网络编程 之 IO多路复用poll(九)
1. poll介紹
系統調用的本質一樣,poll() 的機制與 select() 類似,與 select() 在本質上沒有多大差別,管理多個描述符也是進行輪詢,根據描述符的狀態進行處理,但是 poll() 沒有最大文件描述符數量的限制(但是數量過大后性能也是會下降)。
核心函數:
#include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout);fds:指向一個結構體數組的第0個元素的指針,每個數組元素都是一個struct pollfd結構,用于指定測試某個給定的fd的條件
nfds:用來指定第一個參數數組元素個數
timeout: 指定等待的毫秒數,無論 I/O 是否準備好,poll() 都會返回.
- -1:永遠等待,直到事件發生
- 0 :立即返回
- 大于0:等待指定數目的毫秒數
fd:每一個 pollfd 結構體指定了一個被監視的文件描述符,可以傳遞多個結構體,指示 poll() 監視多個文件描述符。
events:指定監測fd的事件(輸入、輸出、錯誤)
revents:revents 域是文件描述符的操作結果事件,內核在調用返回時設置這個域。events 域中請求的任何事件都可能在 revents 域中返回。
poll函數的返回值
成功時,poll() 返回結構體中 revents 域不為 0 的文件描述符個數;如果在超時前沒有任何事件發生,poll()返回 0;
失敗時,poll() 返回 -1,并設置 errno 為下列值之一:
- EBADF:一個或多個結構體中指定的文件描述符無效。
- EFAULT:fds 指針指向的地址超出進程的地址空間。
- EINTR:請求的事件之前產生一個信號,調用可以重新發起。
- EINVAL:nfds 參數超出 PLIMIT_NOFILE 值。
- ENOMEM:可用內存不足,無法完成請求。
2. select和poll的異同
select/poll的缺點在于:
select和poll的不同點
select()的fd_set是一個位掩碼(bit mask),因此fd_set有固定的長度。
使用者在調用poll()時需要自定義pollfd結構體數組并且需要指定數組的大小,所以呢這里原理上講就是沒有限制的。
3. poll的具體實例
服務端
#include <unistd.h> #include <sys/types.h> #include <fcntl.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <sys/wait.h> #include <poll.h>#include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h>#include <vector> #include <iostream>//動態數組 typedef std::vector<struct pollfd> PollFdList;int main(void) {signal(SIGPIPE, SIG_IGN);//TIME_WAIT狀態 忽略pipe信號,避免僵尸進程signal(SIGCHLD, SIG_IGN);//int listenfd;//創建套接字if ((listenfd = socket(PF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP)) < 0){//創建socket套接字ERR_EXIT("socket");}//填充地址struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(servaddr));//先清零servaddr.sin_family = AF_INET;//協議族servaddr.sin_port = htons(5188);//端口號servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//本地的IPint on = 1;//setsockopt用來設置套接字的參數if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0){//設置地址的重新利用ERR_EXIT("setsockopt");}if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){//綁定地址和端口ERR_EXIT("bind");}if (listen(listenfd, SOMAXCONN) < 0){//監聽端口ERR_EXIT("listen");}//pollstruct pollfd pfd;pfd.fd = listenfd;pfd.events = POLLIN;//關注pollin事件,表明有事件可讀PollFdList pollfds;//創建一個動態數組(向量)pollfds.push_back(pfd);//把文件描述符添加到數組里面int nready;struct sockaddr_in peeraddr;socklen_t peerlen;int connfd;while (1){ //動態數組首地址pollfds.data()C++11//參數:結構體指針,所監聽文件描述符的個數,超時時間nready = poll(&*pollfds.begin(), pollfds.size(), -1);//負數表示無限等待,直到發生事件才返回if (nready == -1){//出錯if (errno == EINTR)continue;ERR_EXIT("poll");}if (nready == 0) //無事件發生continue;if (pollfds[0].revents & POLLIN)//監聽的pollin事件到來{peerlen = sizeof(peeraddr);//accept4為一個新的函數,可以添加選項,非阻塞//接受監聽connfd = accept4(listenfd, (struct sockaddr*)&peeraddr,&peerlen, SOCK_NONBLOCK | SOCK_CLOEXEC);if (connfd == -1)ERR_EXIT("accept4");//把時間加入監聽的事件中pfd.fd = connfd;pfd.events = POLLIN;pfd.revents = 0;//目前還沒有任何事件返回,置為零pollfds.push_back(pfd);--nready;// 連接成功,打印IP和端口信息std::cout<<"ip="<<inet_ntoa(peeraddr.sin_addr)<<" port="<<ntohs(peeraddr.sin_port)<<std::endl;if (nready == 0)//事件都處理完了continue;}//遍歷查看哪些已連接套接字產生事件(迭代器)for (PollFdList::iterator it=pollfds.begin()+1;it != pollfds.end() && nready >0; ++it){if (it->revents & POLLIN)//如果是pollin事件{--nready;connfd = it->fd;char buf[1024] = {0};int ret = read(connfd, buf, 1024);//讀取消息內容if (ret == -1)//出錯ERR_EXIT("read");if (ret == 0)//{std::cout<<"client close"<<std::endl;it = pollfds.erase(it);//這里就會自動定位了--it;//循環有++,所以這里要先--close(connfd);continue;}std::cout<<buf;//打印收到的消息內容write(connfd, buf, strlen(buf)); }}}return 0; }客戶端
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h>#include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h>#include <iostream>#define ERR_EXIT(m) \do \{ \perror(m); \exit(EXIT_FAILURE); \} while(0)int main(void) {int sock;//創建套接字if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)ERR_EXIT("socket");//填充服務器地址和端口struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(5188);servaddr.sin_addr.s_addr = inet_addr("這個填寫你的IP");//鏈接到服務器if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)ERR_EXIT("connect");struct sockaddr_in localaddr;socklen_t addrlen = sizeof(localaddr);if (getsockname(sock, (struct sockaddr*)&localaddr, &addrlen) < 0)ERR_EXIT("getsockname");//輸出IP和端口信息std::cout<<"ip="<<inet_ntoa(localaddr.sin_addr)<<" port="<<ntohs(localaddr.sin_port)<<std::endl;char sendbuf[1024] = {0};char recvbuf[1024] ={0};while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL){//發送和接受write(sock, sendbuf, strlen(sendbuf));read(sock, recvbuf, sizeof(recvbuf));fputs(recvbuf, stdout);//清空緩沖區memset(sendbuf, 0, sizeof(sendbuf));memset(recvbuf, 0, sizeof(recvbuf));}close(sock);return 0; }總結
以上是生活随笔為你收集整理的Linux网络编程 之 IO多路复用poll(九)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux网络编程 之 IO多路复用se
- 下一篇: Linux网络编程 之 IO复用epol