select和epoll
来源:互联网 发布:中文域名在线转码 编辑:程序博客网 时间:2024/05/05 14:21
如何解决服务器多并发问题?
1、每个进程处理一个客户端
2、每个线程处理一个客户端
3、io复用模型
首先我们想要了解select和epoll的区别,首先应该了解下什么是io复用模型概念。
io复用模型概念?
只需要一个进程就够了。之所以能够同时处理多个客户端的请求,原因是可以查询哪个客户端准备好了,对于准备好的客户端(例如客户端已经发了信息过来,本服务器用read读取数据的时候不会阻塞;另外,客户端已经关闭了连接,那么本服务read的时候,返回0,也不会阻塞),则和它进行通信,而未准备好的,就暂时先不理会。
select函数:
select可以实现这个轮询功能。
select函数原型:
int select(intnfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, structtimeval *timeout);
nfds:最大的那个fd整数加1。
fd_set:fd的集合。三个集合,分别是读、写、异常的集合,当置为空时表示用不到。
可以将关心的fd放到对应的集合里去,然后交给内核去轮询。
fd集合的操作:
void FD_CLR(int fd,fd_set *set);//将集合里对应的fd清掉
vid FD_SET(int fd,fd_set *set);//将fd加到集合里
int FD_IS SET(int fd,fd_set *set);//是否准备好了,好了就返回非零值
void FD_ZERO(fd_set*set);//集合全部清空
timeout:代表超时时间,是struct timeval类型的结构体,参数有:long tv_sec(秒) 和long tv_usec(微秒),为空表示永不超时。
select代码实现:
//server.c#include <stdio.h>#include <sys/socket.h>#include <stdlib.h>#include <sys/stat.h>#include <errno.h>#include <string.h>#include <fcntl.h>#include <netdb.h>#include <unistd.h>#include <sys/file.h>#include <sys/mman.h>#include <sys/wait.h>#include <mqueue.h> //消息队列#include <signal.h>#include <semaphore.h>#include <sys/socket.h> //套接字接口#include <arpa/inet.h> //网络地址的转换#include <time.h>#include <pthread.h>int main(void){ int listenfd=socket(AF_INET,SOCK_STREAM,0); struct sockaddr_in hostaddr; hostaddr.sin_family=AF_INET; hostaddr.sin_addr.s_addr=INADDR_ANY; hostaddr.sin_port=htons(3017); if(bind(listenfd,(struct sockaddr *)&hostaddr,sizeof(hostaddr))<0) { perror("bind"); } if(listen(listenfd,10)<0) { perror("listen"); } int fd1=accept(listenfd,NULL,NULL); int fd2=accept(listenfd,NULL,NULL); //创建一个fd集合 fd_set rdfdset; char buffer[1024]; while(1) { //记得要清空这个集合 FD_ZERO(&rdfdset); //将感兴趣的fd加进去 FD_SET(fd1,&rdfdset); FD_SET(fd2,&rdfdset); //算出来哪个fd最大 int maxfd=fd1>fd2?fd1:fd2; //将轮询工作委托给内核,让它找出哪个fd准备好了. select(maxfd+1,&rdfdset,NULL,NULL,NULL); //如果fd1准备好了 if(FD_ISSET(fd1,&rdfdset)) { bzero(buffer,sizeof(buffer)); read(fd1,buffer,sizeof(buffer)); write(fd2,buffer,strlen(buffer)); } //如果fd2准备好了 if(FD_ISSET(fd2,&rdfdset)) { bzero(buffer,sizeof(buffer)); read(fd2,buffer,sizeof(buffer)); write(fd1,buffer,strlen(buffer)); } }}
//client.c#include <stdio.h>#include <sys/socket.h>#include <stdlib.h>#include <sys/stat.h>#include <errno.h>#include <string.h>#include <fcntl.h>#include <netdb.h>#include <unistd.h>#include <sys/file.h>#include <sys/mman.h>#include <sys/wait.h>#include <mqueue.h> //消息队列#include <signal.h>#include <semaphore.h>#include <sys/socket.h> //套接字接口#include <arpa/inet.h> //网络地址的转换#include <time.h>#include <pthread.h>#include "header.h"int sockfd;//从键盘读数据void *readfromkeyboard(void *arg){ while(1) {/*4, 读取信息*/ char buffer[1024]={0}; printf("\n>>"); //从键盘读取 fgets(buffer,sizeof(buffer),stdin); //发给服务器 if(write(sockfd,buffer,sizeof(buffer))<0) { perror("write"); } }}//从服务器读数据void *readfromserver(void *arg){ char buffer[1024]={0}; while(1) { bzero(buffer,sizeof(buffer)); //从服务读回来 if(read(sockfd,buffer,sizeof(buffer))<0) { perror("read"); } //将信息输出到屏幕上 write(STDOUT_FILENO,buffer,strlen(buffer)); }}//argv[1]是ip地址,argv[2]是端口int main(int argc, char *argv[]){ if(argc<3) { printf("usage:filename ipaddress port"); return -1; } /*1, 创建套接字*/ sockfd=socket(AF_INET,SOCK_STREAM,0); /*2, 设置网络地址*/ struct sockaddr_in sockin; sockin.sin_family=AF_INET; inet_pton(AF_INET,argv[1],&sockin.sin_addr); sockin.sin_port=htons(atoi(argv[2])); /*3, 连接服务器 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); */ if(connect(sockfd,(struct sockaddr *)&sockin, sizeof(sockin))<0) { perror("connet"); exit(EXIT_FAILURE); } pthread_t thread1,thread2; pthread_create(&thread1,NULL,readfromkeyboard,NULL); pthread_create(&thread2,NULL,readfromserver,NULL);while(1){ }pthread_join(thread1,NULL);pthread_join(thread2,NULL);close(sockfd);}
epoll函数:
现今用的多的是epoll。
和select相比优点在于:
select是对加进去的所有fd进行轮询,返回之后也要对整个fd进行一次轮询,才能找到准备好的fd。
epoll采用事件触发的方式,当某个fd准备好后会触发事件,这样减少了内核的轮询。同时,epoll返回的是那些准备好的fd,避免程序员进行全部的轮询
另外,select的fd数上限一般是1024.但是epoll没有上限。
epoll函数原型:
1、int epoll_create(int size);
创建一个epoll接口,返回一个epoll描述符,后面要用到。
2、int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
用来从epoll接口中添加fd或删除、修改fd。
(1)epfd是接口描述符,
(2)op是添加、修改、删除三者,fd是要操作的对象,event是监视的事件。结构如下:
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
3、int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
(1)events是输出型的参数,返回的是准备好的描述符,
(2)maxevents是事件数量的上限
(3)timeout是超时,以毫秒为单位,-1代表用不超时,0代表不等待立即返回。
epoll的水平触发和边沿触发?
水平触发:epoll的事件默认情况下是lt水平触发。例如,只要客户端的数据仍然未读完,那么事件就会一直发生。告诉服务器,请将数据读出来。
边沿触发:加上了events那里加了EPOLLET这个选项后,变成边沿触发。也就是数据可读,则只触发一次事件,服务器必须一直读,直到把数据读完。
epoll代码实现://server.c#include <stdio.h>#include <sys/socket.h>#include <stdlib.h>#include <sys/stat.h>#include <errno.h>#include <string.h>#include <fcntl.h>#include <netdb.h>#include <unistd.h>#include <sys/file.h>#include <sys/mman.h>#include <sys/wait.h>#include <mqueue.h> //消息队列#include <signal.h>#include <semaphore.h>#include <sys/socket.h> //套接字接口#include <arpa/inet.h> //网络地址的转换#include <time.h>#include <pthread.h>#include "header.h"#include "sys/epoll.h"int main(void){ int listenfd=socket(AF_INET,SOCK_STREAM,0); struct sockaddr_in hostaddr; hostaddr.sin_family=AF_INET; hostaddr.sin_addr.s_addr=INADDR_ANY; hostaddr.sin_port=htons(3017); if(bind(listenfd,(struct sockaddr *)&hostaddr,sizeof(hostaddr))<0) { perror("bind"); } if(listen(listenfd,10)<0) { perror("listen"); } int fd_array[4]; int i; for(i=0;i<4;i++) fd_array[i]=accept(listenfd,NULL,NULL); int epollfd=epoll_create(10000); //创建事件结构体,用来输入到函数中的 struct epoll_event ev; //创建事件结构体数组,用来从函数中带出来数据的 struct epoll_event ev_array[10000]; for(i=0;i<4;i++) { //可读事件 ev.events=EPOLLIN|EPOLLET; //事件对应的fd ev.data.fd=fd_array[i]; //将fd加入epoll中 epoll_ctl(epollfd,EPOLL_CTL_ADD,fd_array[i],&ev); } char buffer[1024]; while(1) { //查询有多少个fd准备好了 int nr=epoll_wait(epollfd,ev_array,1000,-1); for(i=0;i<nr;i++) { int fd=ev_array[i].data.fd; bzero(buffer,sizeof(buffer)); read(fd,buffer,sizeof(buffer)); write(fd,buffer,strlen(buffer)); } }}
//client.c#include <stdio.h>#include <sys/socket.h>#include <stdlib.h>#include <sys/stat.h>#include <errno.h>#include <string.h>#include <fcntl.h>#include <netdb.h>#include <unistd.h>#include <sys/file.h>#include <sys/mman.h>#include <sys/wait.h>#include <mqueue.h> //消息队列#include <signal.h>#include <semaphore.h>#include <sys/socket.h> //套接字接口#include <arpa/inet.h> //网络地址的转换#include <time.h>#include <pthread.h>#include "header.h"int sockfd;//从键盘读数据void *readfromkeyboard(void *arg){ while(1) {/*4, 读取信息*/ char buffer[1024]={0}; printf("\n>>"); //从键盘读取 fgets(buffer,sizeof(buffer),stdin); //发给服务器 if(write(sockfd,buffer,sizeof(buffer))<0) { perror("write"); } }}//从服务器读数据void *readfromserver(void *arg){ char buffer[1024]={0}; while(1) { bzero(buffer,sizeof(buffer)); //从服务读回来 if(read(sockfd,buffer,sizeof(buffer))<0) { perror("read"); } //将信息输出到屏幕上 write(STDOUT_FILENO,buffer,strlen(buffer)); }}//argv[1]是ip地址,argv[2]是端口int main(int argc, char *argv[]){ if(argc<3) { printf("usage:filename ipaddress port"); return -1; } /*1, 创建套接字*/ sockfd=socket(AF_INET,SOCK_STREAM,0); /*2, 设置网络地址*/ struct sockaddr_in sockin; sockin.sin_family=AF_INET; inet_pton(AF_INET,argv[1],&sockin.sin_addr); sockin.sin_port=htons(atoi(argv[2])); /*3, 连接服务器 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); */ if(connect(sockfd,(struct sockaddr *)&sockin, sizeof(sockin))<0) { perror("connet"); exit(EXIT_FAILURE); } pthread_t thread1,thread2; pthread_create(&thread1,NULL,readfromkeyboard,NULL); pthread_create(&thread2,NULL,readfromserver,NULL);while(1){ }pthread_join(thread1,NULL);pthread_join(thread2,NULL);close(sockfd);}
- select 和 epoll比较
- select 和 epoll
- [ZZ]select 和 epoll
- select、poll和epoll
- select、poll和epoll
- epoll和select区别
- select 和 epoll
- select 和epoll
- select 和 epoll区别
- select、poll和epoll
- epoll 和 select 浅析
- select 和 epoll
- epoll和select区别
- select、poll和epoll
- select、poll和epoll
- select 和 epoll
- select 和 epoll区别
- epoll和select区别
- 【BZOJ 4318】[概率DP]OSU!
- java建立一个类时为什么设置private static final long serialVersionUID??
- 理解进程,线程概念
- 《ACM程序设计》书中题目―K
- rem单位的使用总结
- select和epoll
- zkw线段树,区间修改,最值查询(差分)
- JavaScript for Kids 学习笔记5. 分支和循环
- 21分钟 MySQL 入门教程
- 快速求斐波那契数列第n项值 poj3070 Fibonacci
- [POJ] 2485 Highways
- daima
- java 创建对象的方式
- 干货系列,html基础日记!