目录
1、用select写一个回射服务器。
1-1、select参数
参数readfds、writefds 以及exceptfds都 是指向文件描述符集合的指针,所指向的数据类型是fd_ set。 这些参数按照如下方式使用。
readfds是用来检测输入是否就绪的文件描述符集合。
writefds是用来检测输出是否就绪的文件描述符集合。
exceptfds是用来检测异常情况是否发生的文件描述符集合。
所有关于文件描述符集合的操作都是通过四个宏来完成的:FD__ZERO(),FD_ SET(), FD_ CLR()以及FD_ ISSET()。
FD_ ZERO()将fdset所指向的集合初始化为空。
FD_ SET()将文件描述符fd添加到由fdset所指向的集合中。
FD_CLR()将文件描述符fd从fdset所指向的集合中移除。
如果文件描述符fd是fdset所指向的集合中的成员,FD_ ISSET() 返回true。
timeout参数:
如果结构体timeval的两个域都为0的话,此时select()不会阻塞, 它只是简单地轮询指定的文件描述符集合,看看其中是否有就绪的文件描述符并立刻返回。否则,timeout将为select()指定一个等待时间的上限值。
struct timeval {
time t tv_sec; /*Seconds*/
suseconds_t tv_usec; / *Microseconds (long int) */
};
1-2、select()返回值
返回-1表示有错误发生。可能的错误码包括EBADF 和EINTR。EBADF 表示readfds、writefds 或者exceptfds 中有一个文件描述符是非法的(例如当前并没有打开)。EINTR 表示该调用被信号处理例程中断了。
返回0表示在任何文件描述符成为就绪态之前select() 调用已经超时。在这种情况下,每个返回的文件描述符集合将被清空。
返回一个正整数表示有1个或多个文件描述符已达到就绪态。返回值表示处于就绪态的文件描述符个数。在这种情况下,每个返回的文件描述符集合都需要检查(通过FD _ISSET()),以此找出发生的I/O 事件是什么。如果同一个文件描述符在readfds、 writefds 和exceptfds 中同时被指定,且它对于多个I/O事件都处于就绪态的话,那么就会被统计多次。换句话说,select() 返回所有在3个集合中被标记为就绪态的文件描述符总数。
1-3、回射服务器代码实现
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/select.h>
#define SERPORT 8000
void myerr(char* str)
{
perror(str);
exit(1);
}
int main(int argc, char* argv[])
{
int lfd =socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in seraddr,cliaddr;
seraddr.sin_family=AF_INET;
seraddr.sin_port=htons(SERPORT);
seraddr.sin_addr.s_addr=INADDR_ANY;
int ret=bind(lfd,(struct sockaddr*)&seraddr,sizeof(seraddr));
if(ret<0)
{
myerr("bind error");
}
listen(lfd,64);
char buf[1024];
socklen_t addrlen=sizeof(cliaddr);
fd_set aset,rset;
FD_ZERO(&aset);
FD_SET(lfd,&aset);
int maxfd=lfd;
int i=0;
while(1)
{
rset=aset;
int selectret=select(maxfd+1,&rset,NULL,NULL,NULL);
if(selectret<0)
{
myerr("select error");
}
else
{
if(FD_ISSET(lfd,&rset))
{
int cfd=accept(lfd,(struct sockaddr*)&cliaddr,&addrlen);
if(cfd<0)
{
myerr("accept error");
}
char dst[64];
inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,dst,sizeof(dst));
int port=ntohs(cliaddr.sin_port);
printf("客户端建立连接成功:clientIP=%s,clientPORT=%d,clientCFD=%d\n",dst,port,cfd);
FD_SET(cfd,&aset);
if(maxfd<cfd)
{
maxfd=cfd;
}
}
for(i=lfd;i<maxfd+1;i++)
{
if(FD_ISSET(i,&rset))
{
int rr=read(i,buf,sizeof(buf));
write(1,buf,rr);
write(i,buf,rr);
}
}
}
}
return 0;
}
运行效果:
2、用poll写一个回射服务器。
2.1、poll参数
pollfd结构体数组
#include <poll.h>
//数据结构
struct pollfd {
int fd; //需要监视的文件描述符
short events; //需要内核监视的事件
short revents; //实际发生的事件
};
// API
int poll (struct pollfd fds[] ,nfds_t nfds,int timeout) ;
//Returns number of ready file descriptors, 0 on timeout, or -1 on error
timeout参数: 参数timeout决定了pol1()的阻塞行为, 具体如下。
如果timeout 等于-1,poll()会一 直阻塞直到fds数组中列出的文件描述符有一个达到就绪态(定义在对应的events字段中)或者捕获到一个信号。
如果timeout等于0,pol1()不会阻塞---- 只是执行- -次检查看看哪个文件描述符处于就绪态。
如果timeout 大于0,pol1()至多阻塞timeout 毫秒,直到fds列出的文件描述符中有一个达到就绪态,或者直到捕获到一个信号为止。同select()一样, timeout 的精度受软件时钟粒度的限制。
2.2、poll()的返回值
返回-1表示有错误发生。一种可能的错误是EINTR, 表示该调用被一个信号
处理例程中断。
返回0表示该调用在任意一个文件描述符成为就绪态之前就超时了。
返回正整数表示有1个或多个文件描述符处于就绪态了。返回值表示数组fds中拥有非零
revents字段的pollfd 结构体数量。
2.3、回射服务器代码实现
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <poll.h>
#define PORT 8000
#define IP 0 //IP端口,可改
int main(int argc, char* argv[])
{
int lfd=socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in seraddr,cliaddr;
seraddr.sin_family=AF_INET;
seraddr.sin_port=htons(PORT);
seraddr.sin_addr.s_addr=IP;
int a=bind(lfd,(struct sockaddr*)&seraddr,sizeof(seraddr));
if(a<0)
{
perror("bind error:");
return 0;
}
listen(lfd,64);
socklen_t len=sizeof(cliaddr);
//定义一个结构体数组
struct pollfd fds[1025];//定义了1025个结构体数组,他比select优势在于他的个数可以由结构体指定,而不是像select一样固定的1024个
int i=0;
for(i=0; i<1025;i++)
{
fds[i].fd=-1;//全部赋值为-1是为了后面好操作。
}
fds[0].fd=lfd;//监听lfd文件描述符
fds[0].events=POLLIN;
int maxfd=0;//在结构体数组中的目前最大下标是0.
while(1)
{
int pollnum= poll(fds, maxfd+1 , -1);//-1表示一直等待,如果没有事件发生他不会返回直接执行下面的语句。fds表示结构体数组
if(pollnum < 0)
{
perror("accpet error:");
// exit(1);
}
else//poll没有出错,监听有没有新的客户机和我连接
{
if(fds[0].revents & POLLIN)//判断lfd的POLLIN事件有没有发生,用&,判断是因为如果不仅仅是POLLIN事件发生了那么用==判断就会出错,这里只>
//没有就执行for语句的数据处理,不加该判断会导致,没有新的连接时阻塞在accept上
{
int cfd=accept(lfd,(struct sockaddr*)&cliaddr,&len);
if(cfd < 0)//表示accept出错
{
perror("accept error:");
// exit(1);
}
else
{
//accept没有出错,解析客户机的IP和端口号
int cliport=ntohs(cliaddr.sin_port);//网络端口号转本地端口号
char det[1025];
inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, det,sizeof(det));//网络IP转本地IP
printf("客户端已连接:IP = %s , 端口号 = %d\n", det , cliport);
//我们要把accept返回的文件描述符即与客户机通信的描述符重新加回到监听行列中
for(i= 1;i < 1025; i++)
{
if(fds[i].fd==-1)//前面初始化时候全部赋值为-1就是为了这里好找到空闲的
{
fds[i].fd=cfd;
fds[i].events=POLLIN;
break;
}
// if(--pollnum ==0 )//表示全部加到监听结构体里面了
// {
// break;
// }为什么不能把这个if语句写在for里面,假如同时间有n个客户机连接我。但是accept一次只能从全连接队列里面取一个出来,你写在for里面看似把所有的客户机的描述符都放进来结构体数组里面,其实结构体数组里面有多个重复的fds[].fd
}
//修改maxfd的值
if(maxfd < i)
{
maxfd = i;
}
if(--pollnum == 0)
{
continue;
}
}
}
//lfd处理完了,现在可以对客户机发送的数据进行处理了。
for( i=1 ; i < maxfd+1; i++)//通过循环的方式,一个一个的去判断POLLIN事件有没有发生
{
if(fds[i].revents & POLLIN)//判断POLLIN事件有没有发生
{
char buf[1024];
int rr=read(fds[i].fd, buf,sizeof(buf));
if(rr==0)
{
printf("客户端断开连接\n");
close(fds[i].fd);
fds[i].fd=-1;
}
if(rr<0)
{
perror("read error:");
exit(1);
}
write(1, buf, rr);
write(fds[i].fd, buf, rr);
if(--pollnum==0)//要么不写这个,要么只能写if(fds[i].revents & POLLIN)的里面
{
break;
}
}
//if(--pollnum==0)
//{
// break;
//}//为什么不能把这个if语句写外面,假如现在只有一个客户端发送了数据,但是这个客户端的描述符可能不是1。但是你每一次都执行了--pollunm就导致了都没有判断到客户端的cfd触发没有呢就break了
}
}
}
return 0;
}
运行效果:
3、用epoll写一个回射服务器
3.1、epoll API
记录了在进程中声明过的感兴趣的文件描述符列表一interest list (兴趣列表),
维护了处于I/0就绪态的文件描述符列表-ready list (就绪列表)。.ready list中的成员是interest list的子集。
epoll_create()创建epoll实例:
参数size指定了我们想要epoll实例来检查的文件描述符个数。但并不是一个上限值,目的是让内核知道应该如何为内部数据结构划分初始大小。
#include <sys/epoll.h>
int epoll_create(int size);
epoll_ctl()修改epoll的兴趣列表
#include <sys/epo1l .h>
int epoll_ctl (int epfd,int op,int fd,struct epoll_event *event) ;
参数fd指明了要修改兴趣列表中的哪一个文件描述符的设定。该参数可以是代表管道、套接字、POSIX 消息队列等。但是,这里fd不能作为普通文件或目录的文件描述符(会出现EPERM错误)
参数op用来指定需要执行的操作,它可以是如下几种值。
EPOLL_CTL_ ADD 将描述符fd添加到epoll 实例epfd中的兴趣列表中去。
EPOLL_CTL_ MOD 修改描述符fd上设定的事件,需要用到由event 所指向的结构体中的信息。
EPOLL_CTL_DEL 将文件描述符fd从epfd 的兴趣列表中移除。
参数event是指向结构体epoll_ event的指针,结构体如下:
struct epoll_ event {
uint32_t events ; /* Epoll events */
epoll_data_t data; /* User data variable */
};
//结构体 epoll_ event 中的data 字段的类型为:
typedef union epoll_ data
{
void *ptr;
int fd;
uint32_t u32 ;
uint64_t u64 ;
} epoll_ data_ t;
epoll_wait()等待事件
#include <sys/epo1l .h>
int epoll_wait (int epfd, struct epoll_event *events, int maxevents,int timeout) ;
//Returns number of ready file descriptors,0 on timeout, or -1 on error
参数events所指向的结构体数组中返回的是有关就绪态文件描述符的信息。
参数timeout 用来确定epoll_ wait()的阻塞行为,有以下几种:
如果timeout等于-1,调用将一直阻塞,直到兴趣列表中的文件描述符.上有
事件产生,或者直到捕获到一个信号为止。
如果timeout 等于0,执行一次非阻塞式的检查,看兴趣列表中的文件描述
符上产生了哪个事件。
如果timeout 大于0,调用将阻塞至多timeout 毫秒,直到文件描述符上有
事件发生,或者直到捕获到一个信号为止。
3.2、epoll实现 回射服务器代码:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <poll.h>
#include <sys/epoll.h>
#define SERPORT 8000
#define MAXEVENT 1025
void myerr(char* str)
{
perror(str);
exit(1);
}
int main(int argc, char* argv[])
{
//socket的初始化
int lfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in seraddr, cliaddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(SERPORT);
seraddr.sin_addr.s_addr = INADDR_ANY;
int ret = bind(lfd, (struct sockaddr*)&seraddr, sizeof(seraddr));
if(ret < 0)
{
myerr("bind error");
}
listen(lfd, 64);
char buf[1024];
socklen_t addrlen = sizeof(cliaddr);
//创建一个epoll实例
int epfd = epoll_create(64);
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &event);
struct epoll_event events[2048];
int i = 0;
int cfd;
while(1)
{
int epollret = epoll_wait(epfd, events, 2048, -1);
if(epollret < 0)
{
myerr("epoll_wait error");
}
else
{
for(i = 0; i < epollret; i++)
{
if(events[i].events & EPOLLIN)
{
int hfd = events[i].data.fd;
if(hfd == lfd)
{
cfd = accept(lfd, (struct sockaddr*)&cliaddr, &addrlen);
if(cfd < 0)
{
myerr("accept error");
}
char dst[64];
inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, dst, sizeof(dst));
int port = ntohs(cliaddr.sin_port);
printf("客户端建立连接成功:clientIP = %s, clientPORT = %d, clientCFD = %d\n", dst, port, cfd);
event.events = EPOLLIN;
event.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event);
}
else
{
int rr = read(hfd, buf, sizeof(buf));
if(rr < 0)
{
myerr("read error");
}
else if(rr == 0)
{
printf("客户端断连接\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, hfd, NULL);
close(hfd);
}
else
{
write(1, buf, rr);
write(hfd, buf, rr);
}
}
}
}
}
}
return 0;
}