Linux高并發服務器開發(九)Tcp狀態轉移和IO多路復用

文章目錄

  • 0 包裹函數
  • 1 多進程服務器
    • 流程
    • 代碼
  • 2 多線程服務器
  • 3 TCP狀態轉移
    • 半關閉
    • 心跳包
  • 4 端口復用
  • 5 IO多路復用技術
    • 高并發服務器
  • 6 select
    • 代碼
    • 總結
  • 7 POLL
    • API
    • 代碼
    • poll相對select的優缺點
  • 8 epoll(重點)
    • API
    • 監聽管道
    • 代碼
    • EPOLL 高并發服務器
  • 9 Epoll的兩種工作方式
    • 邊緣觸發代碼


0 包裹函數

用于創建socket,綁定端口ip和監聽時,添加了錯誤時報錯的包裹函數

warp.h

#ifndef __WRAP_H_
#define __WRAP_H_
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
int tcp4bind(short port,const char *IP);
#endif

wrap.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>void perr_exit(const char *s)
{perror(s);exit(-1);
}int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{int n;again:if ((n = accept(fd, sa, salenptr)) < 0) {if ((errno == ECONNABORTED) || (errno == EINTR))goto again;elseperr_exit("accept error");}return n;
}int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{int n;if ((n = bind(fd, sa, salen)) < 0)perr_exit("bind error");return n;
}int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{int n;if ((n = connect(fd, sa, salen)) < 0)perr_exit("connect error");return n;
}int Listen(int fd, int backlog)
{int n;if ((n = listen(fd, backlog)) < 0)perr_exit("listen error");return n;
}int Socket(int family, int type, int protocol)
{int n;if ((n = socket(family, type, protocol)) < 0)perr_exit("socket error");return n;
}ssize_t Read(int fd, void *ptr, size_t nbytes)
{ssize_t n;again:if ( (n = read(fd, ptr, nbytes)) == -1) {if (errno == EINTR)goto again;elsereturn -1;}return n;
}ssize_t Write(int fd, const void *ptr, size_t nbytes)
{ssize_t n;again:if ( (n = write(fd, ptr, nbytes)) == -1) {if (errno == EINTR)goto again;elsereturn -1;}return n;
}int Close(int fd)
{int n;if ((n = close(fd)) == -1)perr_exit("close error");return n;
}/*參三: 應該讀取的字節數*/
ssize_t Readn(int fd, void *vptr, size_t n)
{size_t  nleft;              //usigned int 剩余未讀取的字節數ssize_t nread;              //int 實際讀到的字節數char   *ptr;ptr = vptr;nleft = n;while (nleft > 0) {if ((nread = read(fd, ptr, nleft)) < 0) {if (errno == EINTR)nread = 0;elsereturn -1;} else if (nread == 0)break;nleft -= nread;ptr += nread;}return n - nleft;
}ssize_t Writen(int fd, const void *vptr, size_t n)
{size_t nleft;ssize_t nwritten;const char *ptr;ptr = vptr;nleft = n;while (nleft > 0) {if ( (nwritten = write(fd, ptr, nleft)) <= 0) {if (nwritten < 0 && errno == EINTR)nwritten = 0;elsereturn -1;}nleft -= nwritten;ptr += nwritten;}return n;
}static ssize_t my_read(int fd, char *ptr)
{static int read_cnt;static char *read_ptr;static char read_buf[100];if (read_cnt <= 0) {
again:if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {if (errno == EINTR)goto again;return -1;} else if (read_cnt == 0)return 0;read_ptr = read_buf;}read_cnt--;*ptr = *read_ptr++;return 1;
}ssize_t Readline(int fd, void *vptr, size_t maxlen)
{ssize_t n, rc;char    c, *ptr;ptr = vptr;for (n = 1; n < maxlen; n++) {if ( (rc = my_read(fd, &c)) == 1) {*ptr++ = c;if (c  == '\n')break;} else if (rc == 0) {*ptr = 0;return n - 1;} elsereturn -1;}*ptr  = 0;return n;
}int tcp4bind(short port,const char *IP)
{struct sockaddr_in serv_addr;int lfd = Socket(AF_INET,SOCK_STREAM,0);bzero(&serv_addr,sizeof(serv_addr));if(IP == NULL){//如果這樣使用 0.0.0.0,任意ip將可以連接serv_addr.sin_addr.s_addr = INADDR_ANY;}else{if(inet_pton(AF_INET,IP,&serv_addr.sin_addr.s_addr) <= 0){perror(IP);//轉換失敗exit(1);}}serv_addr.sin_family = AF_INET;serv_addr.sin_port   = htons(port);Bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));return lfd;
}

1 多進程服務器

流程

因為read 和 write都是阻塞的,所以如果多個客戶端進行連接時,會阻塞。
在這里插入圖片描述
理念就是連接給一個專用的進程,然后這個進程來分配其他進程進行讀寫的操作

流程
創建套接字
綁定
監聽

while(1)
{
提取連接
fork創建子進程
子進程中 關閉lfd 服務客戶端(連接)
父進程關閉 cfd(讀寫),回收子進程資源

}

代碼

#include<sys/socket.h>
#include<stdio.h>
#include<arpa/inet.h>
#include <unistd.h>
#include "wrap.h"
#include<stdlib.h>
#include <signal.h>
#include<sys/wait.h>
#include<sys/types.h>void *free_process(int signum)
{pid_t pid;while(1){pid = waitpid(-1,NULL,WNOHANG);if(pid <= 0)    // 沒有要回收的子進程{break;}else{printf("child pid =%d\n",pid);}}}int main()
{// 阻塞信號集,在子進程創建之前// 在創建子進程之后添加信號sigset_t set;sigemptyset(&set);sigaddset(&set, SIGCHLD);sigprocmask(SIG_BLOCK, &set, NULL);// 創建套接字,綁定 鏈接socketint lfd = tcp4bind(8000,NULL);// 監聽Listen(lfd, 128);// 提取// 回射struct sockaddr_in cliaddr;socklen_t len = sizeof(cliaddr);while(1){// 讀取socketint cfd = Accept(lfd, (struct sockaddr*)&cliaddr,&len);char ip[16] = "";printf("new client ip= %s port = %d\n",inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16),ntohs(cliaddr.sin_port));// fork 創建子進程pid_t pid;pid = fork();if(pid<0){perror("");exit(0);}else if(pid == 0) // 子進程{// 關閉lfdclose(lfd);while(1){char buf[1024];int n = read(cfd,buf,sizeof(buf));if(n < 0){perror("");close(cfd);exit(0);}else if(n == 0) // 對方關閉{printf("client close\n");close(cfd);exit(0);}else{printf("%s", buf);write(cfd,buf,n);// exit(0);}}}else{close(cfd); // 回收// 注冊信號回調struct sigaction act;act.sa_flags = 0;act.sa_handler = free_process;sigemptyset(&act.sa_mask);sigaction(SIGCHLD,&act,NULL);sigprocmask(SIG_UNBLOCK, &set, NULL);}}// 回收return 0;
}

2 多線程服務器

#include<sys/socket.h>
#include<stdio.h>
#include<arpa/inet.h>
#include <unistd.h>
#include "wrap.h"
#include<stdlib.h>
#include <signal.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<pthread.h>typedef struct c_info
{int cfd;struct sockaddr_in cliaddr;}CINFO;int main(int argc, char *argv[])
{if(argc < 2){printf("argc < 2???\n ./a.out 8000\n");}// 初始化線程屬性pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);short port = atoi(argv[1]);int lfd = tcp4bind(port, NULL);Listen(lfd, 128);struct sockaddr_in cliaddr;socklen_t len = sizeof(cliaddr);CINFO *info;while(1){int cfd = Accept(lfd,(struct sockaddr*)&cliaddr,&len);char ip[16] = "";printf("new client ip = %s port = %d\n", inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16),ntohs(cliaddr.sin_port));pthread_t pthid;info = malloc(sizeof(CINFO));info->cfd = cfd;info->cliaddr = cliaddr;pthread_create(&pthid,NULL,client_fun,&info);}return 0;
}void * client_fun(void *arg)
{CINFO *info = (CINFO *)arg;char ip[16] = "";printf("new client ip = %s port = %d\n", inet_ntop(AF_INET,&(info->cliaddr.sin_addr.s_addr),ip,16),ntohs(info->cliaddr.sin_port));while(1){char buf[1024] = "";int count = 0;count = read(info->cfd, buf, sizeof(buf));if(count < 0){perror("");break;}else if(count == 0){printf("client close");break;}else{   printf("%s", buf);write(info->cfd, buf, count);}}close(info->cfd);free(info);}

3 TCP狀態轉移

在這里插入圖片描述
TIME_WAIT -> CLOSE 2MML

半關閉

處于FIN_WAIT2時,處于半關閉狀態,此時只能讀數據不能收數據

手動半關閉
在這里插入圖片描述

心跳包

每隔一段時間服務器向客戶端發送一個包,客戶端需要在一定時間內返回一個規定好的包,用于測試連接是否還存在,如果對方沒有回復,則斷開連接

在這里插入圖片描述

4 端口復用

端口重新啟用
使用 setsockopt設置端口重新使用
放在綁定之前
在這里插入圖片描述

5 IO多路復用技術

高并發服務器

1.阻塞等待
一個進程 服務一個客戶端
消耗資源

2.非阻塞忙輪詢
重復查看 進程是否有需求,是否有新連接

3.多路io
通過監聽多個文件描述符,監聽文件描述符是否還在讀寫
內核有三種方式
select:windows使用 select select跨平臺
poll: 少用
epoll: linux下使用

內核監聽多個文件描述符的屬性(讀寫緩沖區)變化,如果某個文件描述符的讀緩沖區變化了,這個時候就是可以讀了,將這個事件告知應用層

在這里插入圖片描述在這里插入圖片描述

6 select

使用select監聽文件描述符
在這里插入圖片描述
注意:變化的文件描述符,會存放在監聽的集合中,未變化的文件描述符會被刪除

代碼

#include<sys/socket.h>
#include<stdio.h>
#include<arpa/inet.h>
#include <unistd.h>
#include<sys/time.h>
#include<sys/types.h>
#include<sys/select.h>
#include "wrap.h"#define PORT 8888
int main(int argc, char *argv[])
{// 創建套接字,綁定int lfd = tcp4bind(PORT,NULL);// 監聽Listen(lfd, 128);int maxfd = lfd;    // 最大的文件描述符fd_set oldset,rset;// 清空集合FD_ZERO(&oldset);FD_ZERO(&rset);// 將lfd加入到oldset集合中FD_SET(lfd, &oldset);// whilewhile(1){rset = oldset;  // 將oldset賦值給需要監聽的集合rsetint n = select(maxfd + 1,&rset,NULL,NULL,NULL);if(n<0){perror("");break;}else if(n==0){continue;}else    // n>0  監聽到了文件描述符{// lfd變化, 則進行提取if(FD_ISSET(lfd,&rset)){struct sockaddr_in cliaddr;socklen_t len = sizeof(cliaddr);char ip[16] = "";// 提取新的連接int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, & len);printf("new client ip = %s, port = %d\n",inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr,ip,16),ntohs(cliaddr.sin_port));// 將cfd添加到oldset集合中,下次進行監聽FD_SET(cfd,&oldset);// 更新maxfdif(cfd > maxfd)maxfd = cfd;// 如果只有lfd變化,continueif(--n == 0)continue;}// cfd  遍歷cfd,看lfd之后的文件描述符是否在rset,如果在則cfd變化for(int i = lfd+1;i<=maxfd;i++){// 如果i文件描述符在rset集合中if(FD_ISSET(i,&rset)){char buf[1024] = "";int ret = Read(i,buf,sizeof(buf));if(ret < 0) //出錯,將cfd關閉,從oldset刪除cfd{perror("");close(i);FD_CLR(i,&oldset);}else if(ret == 0){printf("client close\n");close(i);FD_CLR(i,&oldset);}else{printf("%s\n", buf);Write(i,buf,ret);}}}}}// select監聽return 0;
}

總結

優缺點
優點:跨平臺
缺點:文件描述符1024的限制 由于FD_SETSIZE的限制
只是返回變化的文件描述符的個數,具體哪個變化需要遍歷
每次都需要將需要監聽的文件描述集合由應用層拷貝到內核

7 POLL

在這里插入圖片描述

API

在這里插入圖片描述

代碼

//IO多路復用技術poll函數的使用 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <poll.h>
#include "wrap.h"int main()
{int i;int n;int lfd;int cfd;int ret;int nready;int maxfd;char buf[1024];socklen_t len;int sockfd;fd_set tmpfds, rdfds;struct sockaddr_in svraddr, cliaddr;//創建socketlfd = Socket(AF_INET, SOCK_STREAM, 0);//允許端口復用int opt = 1;setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));//綁定bindsvraddr.sin_family = AF_INET;svraddr.sin_addr.s_addr = htonl(INADDR_ANY);svraddr.sin_port = htons(8888);ret = Bind(lfd, (struct sockaddr *)&svraddr, sizeof(struct sockaddr_in));//監聽listenret = Listen(lfd, 128);struct pollfd client[1024];for(i=0; i<1024; i++){client[i].fd = -1;}		//將監聽文件描述符委托給內核監控----監控讀事件client[0].fd = lfd;client[0].events = POLLIN;maxfd = 0; //maxfd表示內核監控的范圍while(1){nready = poll(client, maxfd+1, -1);if(nready<0){perror("poll error");exit(1);}//有客戶端連接請求if(client[0].fd==lfd && (client[0].revents & POLLIN)){cfd = Accept(lfd, NULL, NULL);//尋找client數組中的可用位置for(i=1; i<1024; i++){if(client[i].fd==-1){client[i].fd = cfd;client[i].events = POLLIN;break;}}//若沒有可用位置, 則關閉連接if(i==1024){Close(cfd);continue;}if(maxfd<i){maxfd = i;}if(--nready==0){continue;}}//下面是有數據到來的情況for(i=1; i<=maxfd; i++){//若fd為-1, 表示連接已經關閉或者沒有連接if(client[i].fd==-1)	{continue;}sockfd = client[i].fd;memset(buf, 0x00, sizeof(buf));n = Read(sockfd, buf, sizeof(buf));if(n<=0){printf("read error or client closed,n==[%d]\n", n);Close(sockfd);client[i].fd = -1; //fd為-1,表示不再讓內核監控}else{printf("read over,n==[%d],buf==[%s]\n", n, buf);write(sockfd, buf, n);}if(--nready==0){break;}}}Close(lfd);return 0;
}

poll相對select的優缺點

優點:相對于select沒有最大1024文件描述符限制
請求和返回是分離

缺點:
每次都需要將需要監聽的文件描述符從應用層拷貝到內核
每次都需要將數組中的元素遍歷一遍才知道哪個變化
大量并發、少量活躍率低

8 epoll(重點)

1.創建紅黑樹
2.將監聽的文件描述符上樹
3.監聽

特點:
沒有文件描述符1024的限制
以后每次監聽都不需要在此將需要監聽的文件描述符拷貝在內核
返回的是已經變化的文件描述符,不需要遍歷樹

工作原理:
在這里插入圖片描述

在這里插入圖片描述

API

1.創建紅黑樹
在這里插入圖片描述

2.上樹 下樹 修改節點

在這里插入圖片描述
在這里插入圖片描述
3. 監聽

在這里插入圖片描述

監聽管道

在這里插入圖片描述

代碼

#include<sys/socket.h>
#include<stdio.h>
#include<arpa/inet.h>
#include <unistd.h>
#include<sys/time.h>
#include<sys/types.h>
#include<sys/select.h>
#include "wrap.h"
#include<sys/epoll.h>int main()
{int fd[2];pipe(fd);pid_t pid;pid = fork();if(pid < 0)perror("");else if(pid == 0){close(fd[0]);char buf[5];char ch = 'a';while(1){sleep(3);memset(buf,ch,sizeof(buf));write(fd[1],buf,5);}}else{close(fd[1]);// 創建樹int epfd = epoll_create(1);struct epoll_event ev, evs[1];ev.data.fd = fd[0];ev.events = EPOLLIN;epoll_ctl(epfd,EPOLL_CTL_ADD,fd[0],&ev);while(1){int n = epoll_wait(epfd, &evs[1],-1,-1);if(n == 1){   char buf[128] = "";int ret = read(fd[0],buf,sizeof(buf));if(ret <= 0){close(fd[0]);epoll_ctl(epfd,EPOLL_CTL_DEL,fd[0],&ev);break;}else{printf("%s\n",buf);}}}}return 0;}

EPOLL 高并發服務器

#include<sys/socket.h>
#include<stdio.h>
#include<arpa/inet.h>
#include <unistd.h>
#include<sys/time.h>
#include<sys/types.h>
#include<sys/select.h>
#include "wrap.h"
#include<sys/epoll.h>#define PORT 8000
int main()
{// 創建套接字int lfd = tcp4bind(PORT,NULL);// 監聽Listen(lfd,128);// 創建樹int epfd = epoll_create(1);// 將lfd上樹struct epoll_event ev,evs[1024];ev.data.fd = lfd;ev.events = EPOLLIN;epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);// while循環監聽while(1){int nready = epoll_wait(epfd,evs,-1,-1);if (nready < 0){perror("");break;}else if(nready == 0){continue;}else   // nread > 0 文件描述符有變化{for(int i =0;i<nready;i++){// 判斷lfd變換,并且是讀事件變換if(evs->data.fd == lfd && evs[i].events & EPOLLIN){struct sockaddr_in cliaddr;char ip[16] = "";socklen_t len = sizeof(cliaddr);int cfd = Accept(lfd,(struct sockaddr *)&cliaddr, &len);printf("new client ip = %s port =%d\n",inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16),ntohs(cliaddr.sin_port));// 將cfd上樹ev.data.fd = cfd;ev.events = EPOLLIN;epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);}else if(evs[i].events & EPOLLIN) // cfd 變換,而且是讀事件變換{char buf[1024] = "";int n = read(evs[i].data.fd,buf,sizeof(buf));if(n < 0)   // 出錯{perror("");epoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,&evs[i]);}else if(n == 0) // 客戶端關閉,下樹{printf("client close]\n");close(evs[i].data.fd); // 關閉cfdepoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,&evs[i]); }else    // 服務端進行處理{printf("%s\n",buf);write(evs[i].data.fd,buf,n);}}}}}return 0;
}

9 Epoll的兩種工作方式

在這里插入圖片描述

  1. 監聽讀緩存區的變化
    水平觸發
    只要緩存區有數據,就會觸發epoll_wait
    邊緣觸發
    數據來一次,epoll_wait只觸發一次

2.監聽寫緩存區的變化
水平觸發:只要可以寫,就會觸發
邊沿觸發:數據從有到無就會觸發

邊緣觸發
在這里插入圖片描述
在這里插入圖片描述
觸發一次的時候只讀4位,但發送了10位,所以雖然只讀一次,但是讀不完

設置為一次讀完
設置cfd為非阻塞

在這里插入圖片描述
在這里插入圖片描述

因為設置水平觸發,只要緩存區有數據epoll_wait就會被觸發,epoll_wait 是一個系統調用,盡量少調用邊緣觸發,邊緣觸發數據來一次只觸發一次,這個時候要求一次性將數據讀完,所以while循環讀,堵到最后read默認帶阻塞,不能讓read阻塞,因為不能再去監聽,設置cfd為非阻塞,read堵到最后一次返回值為-1,判斷errno的值為eagain,則代表數據讀干凈、

工作中 邊緣觸發 + 非阻塞 = 高速模式

邊緣觸發代碼

#include<sys/socket.h>
#include<stdio.h>
#include<arpa/inet.h>
#include <unistd.h>
#include<sys/time.h>
#include<sys/types.h>
#include<sys/select.h>
#include "wrap.h"
#include<sys/epoll.h>
#include <fcntl.h>#define PORT 8000
int main()
{// 創建套接字int lfd = tcp4bind(PORT,NULL);// 監聽Listen(lfd,128);// 創建樹int epfd = epoll_create(1);// 將lfd上樹struct epoll_event ev,evs[1024];ev.data.fd = lfd;ev.events = EPOLLIN;epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);// while循環監聽while(1){int nready = epoll_wait(epfd,evs,-1,-1);printf("epoll wait");if (nready < 0){perror("");break;}else if(nready == 0){continue;}else   // nread > 0 文件描述符有變化{for(int i =0;i<nready;i++){// 判斷lfd變換,并且是讀事件變換if(evs->data.fd == lfd && evs[i].events & EPOLLIN){struct sockaddr_in cliaddr;char ip[16] = "";socklen_t len = sizeof(cliaddr);int cfd = Accept(lfd,(struct sockaddr *)&cliaddr, &len);printf("new client ip = %s port =%d\n",inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16),ntohs(cliaddr.sin_port));// 獲得cfd的標志位int flag = fcntl(cfd, F_GETFL); // 設置為非阻塞flag |= O_NONBLOCK;fcntl(cfd,F_SETFL,flag);// 將cfd上樹ev.data.fd = cfd;ev.events = EPOLLIN | EPOLLET;epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);}else if(evs[i].events & EPOLLIN) // cfd 變換,而且是讀事件變換{while(1){char buf[4] = "";// 如果讀一個緩沖區,緩沖區域沒有數據,如果是阻塞,就阻塞等待// 是非阻塞,返回值等于 -1,并且會將errorno值設置為EAGAIN   int n = read(evs[i].data.fd,buf,sizeof(buf));if(n < 0)   // 出錯{   // 如果緩沖區讀干凈了,這個時候應該跳出while循環,繼續監聽if(errno == EAGAIN){break;}// 普通錯誤perror("");close(evs[i].data.fd); // 關閉cfdepoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,&evs[i]);break;}else if(n == 0) // 客戶端關閉,下樹{printf("client close]\n");close(evs[i].data.fd); // 關閉cfdepoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,&evs[i]); break;}else    // 服務端進行處理{// printf("%s\n",buf);write(STDOUT_FILENO,buf,4);write(evs[i].data.fd,buf,n);}}}}}}return 0;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/39441.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/39441.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/39441.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Iot解決方案開發的體系結構模式和技術

前言 Foreword 計算機技術起源于20世紀40年代&#xff0c;最初專注于數學問題的基本原理&#xff1b;到了60年代和70年代&#xff0c;它以符號系統為中心&#xff0c;該領域首先開始面臨復雜性問題&#xff1b;到80年代&#xff0c;隨著個人計算的興起和人機交互的問題&#x…

【進階篇】Java 項目中對使用遞歸的理解分享

前言 筆者在最近的項目開發中&#xff0c;遇到了兩個父子關系緊密相關的場景&#xff1a;評論樹結構、部門樹結構。具體的需求如&#xff1a;找出某條評論下的所有子評論id集合&#xff0c;找出某個部門下所有的子部門id集合。 在之前的項目開發經驗中&#xff0c;遞歸使用得是…

centos7安裝python3.10

文章目錄 1. 安裝依賴項2. 下載Python 3.10源碼3. 解壓源碼并進入目錄4. 配置安裝選項5. 編譯并安裝Python6. 驗證安裝7.創建軟連接8. 安裝pip39. 換源 1. 安裝依賴項 sudo yum groupinstall -y "Development Tools" sudo yum install -y openssl-devel bzip2-devel…

Eureka的自擴展之道:服務自動擴展的秘訣

&#x1f31f; Eureka的自擴展之道&#xff1a;服務自動擴展的秘訣 在微服務架構中&#xff0c;服務的自動擴展是實現高可用性和彈性伸縮的關鍵。Eureka作為Netflix開源的服務發現框架&#xff0c;提供了一套機制來支持服務的自動擴展。本文將詳細介紹Eureka如何實現服務的自動…

【LeetCode】十、二分查找法:尋找峰值 + 二維矩陣的搜索

文章目錄 1、二分查找法 Binary Search2、leetcode704&#xff1a;二分查找3、leetcode35&#xff1a;搜索插入位置4、leetcode162&#xff1a;尋找峰值5、leetcode74&#xff1a;搜索二維矩陣 1、二分查找法 Binary Search 找一個數&#xff0c;有序的情況下&#xff0c;直接…

第4章:Electron主窗口與子窗口管理

4.1 創建主窗口 主窗口是 Electron 應用啟動后顯示的第一個窗口&#xff0c;通常用來承載應用的主界面。我們使用 BrowserWindow 類來創建主窗口。 4.1.1 創建主窗口的基礎代碼 // 引入 Electron 模塊和 Node.js 的 path 模塊 const { app, BrowserWindow } require(electr…

【動態規劃 前綴和】2478. 完美分割的方案數

本文涉及知識點 劃分型dp 動態規劃匯總 C算法&#xff1a;前綴和、前綴乘積、前綴異或的原理、源碼及測試用例 包括課程視頻 LeetCode 2478. 完美分割的方案數 給你一個字符串 s &#xff0c;每個字符是數字 ‘1’ 到 ‘9’ &#xff0c;再給你兩個整數 k 和 minLength 。 如…

【C++ Primer Plus學習記錄】指針和const

可以用兩種不同的方式將const關鍵字用于指針。第一種方法是讓指針指向一個常量對象&#xff0c;這樣就可以防止使用該指針來修改所指向的值&#xff0c;第二種方法是將指針本身聲明為常量&#xff0c;這樣可以防止改變指針指向的位置。 首先&#xff0c;聲明一個指向常量的指針…

前后端防重復提交(續)

前文介紹過前后端防重復提交的基本場景&#xff0c;簡單的情況是只發起一個異步請求&#xff0c;如果有多個異步請求怎么操作呢&#xff1f;這個要分情況看下。 如果是后端服務器的接口支持一次傳遞多個申請&#xff0c;那么可以將任務放進數組中&#xff0c;發往后端。這是最好…

074、Python 關于實例方法、靜態方法和類方法

在Python中&#xff0c;類可以定義三種類型的方法&#xff1a;實例方法、靜態方法和類方法。每種方法都有其特定的用途和調用方式。 實例方法&#xff08;Instance Methods&#xff09; 定義&#xff1a;實例方法是綁定到類實例上的方法。它們必須有一個名為self的隱式第一個參…

golang 1.22特性之for loop

背景 go1.22版本 for loop每輪循環都生成新的變量. 原諒: https://tip.golang.org/doc/go1.22 Previously, the variables declared by a “for” loop were created once and updated by each iteration. In Go 1.22, each iteration of the loop creates new variables, to …

【C++11】自己封裝RAII類,有哪些坑點?帶你了解移動語義的真相

文章目錄 一、持有資源的類定義移動構造函數的要點1.普通內置類型與std::move2.常見的容器與std::move3.結構體&#xff1a;4.智能指針與std::move 參考 一、持有資源的類定義移動構造函數的要點 1.普通內置類型與std::move 在C中&#xff0c;std::move 主要用于對象的移動語…

Wireshark - tshark支持iptables提供數據包

tshark現在的數據包獲取方式有兩種&#xff0c;分別是讀文件、網口監聽&#xff08;af-packet原始套接字&#xff09;。兩種方式在包獲取上&#xff0c;都是通過讀文件的形式&#xff1b;存在文件io操作&#xff0c;在專門處理大流量的情境下&#xff0c; 我們復用wireshark去做…

Windows編程上

Windows編程[上] 一、Windows API1.控制臺大小設置1.1 GetStdHandle1.2 SetConsoleWindowInfo1.3 SetConsoleScreenBufferSize1.4 SetConsoleTitle1.5 封裝為Innks 2.控制臺字體設置以及光標調整2.1 GetConsoleCursorInfo2.2 SetConsoleCursorPosition2.3 GetCurrentConsoleFon…

python如何輸出list

直接輸出list_a中的元素三種方法&#xff1a; list_a [1,2,3,313,1] 第一種 for i in range(len(list_a)):print(list_a[i]) 1 2 3 313 1 第二種 for i in list_a:print(i) 1 2 3 313 1 第三種&#xff0c;使用enumerate輸出list_a方法&#xff1a; for i&#xff0c;j in enum…

Redis的使用(二)redis的命令總結

1.概述 這一小節&#xff0c;我們主要來研究一下redis的五大類型的基本使用&#xff0c;數據類型如下&#xff1a; redis我們接下來看一看這八種類型的基本使用。我們可以在redis的官網查詢這些命令:Commands | Docs,同時我們也可以用help 數據類型查看命令的幫助文檔。 2. 常…

數據結構 - C/C++ - 串

字符處理 C 特性 C語言中字符串存儲在字符數組中&#xff0c;以空字符\0結束。 字符串常量&#xff0c;const char* str "Hello"&#xff0c;存儲在只讀的數據段中。 布局 字符串在內存中是字符連續存儲的集合&#xff0c;最后一個字符為空字符(ASCII值為0)&…

opencascade AIS_InteractiveContext源碼學習7 debug visualization

AIS_InteractiveContext 前言 交互上下文&#xff08;Interactive Context&#xff09;允許您在一個或多個視圖器中管理交互對象的圖形行為和選擇。類方法使這一操作非常透明。需要記住的是&#xff0c;對于已經被交互上下文識別的交互對象&#xff0c;必須使用上下文方法進行…

【問題已解決】Vue管理后臺,點擊登錄按鈕,會發起兩次網絡請求(竟然是vscode Compile Hero編譯插件導致的)

問題 VueElement UI 做的管理后臺&#xff0c;點擊登錄按鈕&#xff0c;發現 接口會連續掉兩次&#xff0c;發起兩次網絡請求&#xff0c;但其他接口都是正常調用的&#xff0c;沒有這個問題&#xff0c;并且登錄按鈕也加了loading&#xff0c;防止重復點擊&#xff0c;于是開…

搜索引擎常用語法

引號 (" "): 用雙引號將詞組括起來&#xff0c;搜索引擎將返回包含完全相同短語的結果。 示例&#xff1a;"人工智能發展趨勢" 減號 (-): 在關鍵詞前加上減號可以排除包含特定詞語的結果。 示例&#xff1a;人工智能 -機器學習&#xff08;排除包含 “機器…