【1】阻塞式IO模型
最常见的、最简单、效率最低。
读操作中的read、recv、recvfrom
写操作中的write、send、sendto
其他操作:accept、connect
【2】非阻塞式IO模型
功能:可防止进程阻塞在IO操作上,但需要轮询、浪费CPU时间
1)open() :操作时可以指定为非阻塞得状态(O_NONBLOCK)
2)int fcntl(int fd,int cmd,。。。/*arg可选项*/);
功能:对文件的属性进行操作
@cmd :F_GETFL 获取文件标志位信息
F_SETFL 设置文件的标志信息
返回值: 成功 0,或者 获取的值
出错 -1;
(注意:进行位操作的时候千万记得读、改、写,防止改变其他已有的状态)
流程:
int flags = 0;
flags = fcntl(fd,F_GETFL);读
flags &= ~O_NONBLOCK;改、设置非阻塞
fcntl(fd,F_SETFL,flags);写
【3】多路IO复用(重点)对于TCP才有意义
概念:先构造一张有关描述符的表,然后调用一个函数,当这些文件描述符中的一个或多个已准备好进行IO操作时,函数就返回告诉进程哪个已准备就绪了。
1)int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set exceptfds,struct timeval *time);
功能:监听多个文件描述符,阻塞等待有一个或者多个文件描述符,准备就绪。
当监听到了之后,内核会将没有准备就绪的文件描述符,从集合中清掉了。所以下次又得重新将文件描述符添加进来;
@nfds :最大文件描述符数,加1
@readfds :读文件描述符集合
@writefds :写文件描述符集合
@exceptfds :其他异常的文件描述符集合,通常为NULL
@timeout :超时时间(不加超时就为NULL)
返回值:
当timeout 为NULL时,成功返回准备好的文件描述符,出错返回-1;
当timeout不为NULL时,成功返回 准备好的文件描述符或者时间到达没有资源准备就绪就返回0,出错返回-1;
流程:
1】创建TCP网络套接字 socket();
2】填充服务器信息到网络结构体当中
3】绑定网络套接字 bind();
4】设置监听模式 listen();
5】
/*清空readfds,tempfds FD_ZERO();先把sockfd加进readfds,把sockfd 赋值给maxfd FD_SET();完成初始化*/
FD_ZERO(&readfds);
FD_SET(sockfd, &readfds);
maxfd = sockfd;
while(1)
{
tempfds = readfds; //设置一个临时的变量来保存已有的fd,因为select每次都会把集合剔除,所以要重新加入
if(select(maxfd+1,&tempfds, NULL, NULL,NULL) < 0){
err_log("fail to select");
}
for(i = 0; i < maxfd+1; i++){
if(FD_ISSET(i, &tempfds)){ //查看有哪个fd的状态是准备就绪的
if(i == sockfd){
if((acceptfd = accept(sockfd, (struct sockaddr*)&clientaddr, &addrlen)) < 0){
err_log("fail to accept");
}
maxfd = maxfd > acceptfd ? maxfd:acceptfd;//更新maxfd的值
FD_SET(acceptfd, &readfds); //添加进readfds中
}
else{
if(recv(i, buf, N, 0) < 0){
err_log("fail to recv");
}
printf("server:%s\n", buf);
if(strncmp(buf, "quit", 4) == 0){
FD_CLR(i, &readfds); //退出时要清除readfds中的该fd
close(i);
continue;
}
strcpy(buf, "Message from server.");
if(send(i, buf, N, 0) < 0){
err_log("fail to send");
}
}
}
}
}
2)还可以用select+线程(思想就是select一直监听sockfd,然后接受一个创建一个线程去处理):
FD_ZERO(&readfds);
FD_ZERO(&tempfds);
maxfd = sockfd;
while(1){
FD_SET(sockfd, &readfds);
if(select(maxfd+1,&readfds, NULL, NULL,NULL) < 0){
err_log("fail to select");
}
if((acceptfd = accept(sockfd, (struct sockaddr*)&clientaddr, &addrlen)) < 0){
err_log("fail to accept");
}
if(pthread_create(&tid,NULL,recv_data,&acceptfd) < 0){
err_log("fail to create pthread");
}
if(pthread_detach(tid) != 0){
err_log("Fail to detach the pthread");
}
}
3)epoll套接字
1】epoll_create函数
函数声明:int epoll_create(int size)
该函数生成一个epoll专用的文件描述符,其中的参数是指定生成描述符的最大范围。
2】epoll_ctl函数
函数声明:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
该函数用于控制某个文件描述符上的事件,可以注册事件,修改事件,删除事件。
参数:
@epfd: 由 epoll_create 生成的epoll专用的文件描述符;
@op: 要进行的操作,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修改、 EPOLL_CTL_DEL 删除;
@fd: 关联的文件描述符;
@event: 指向epoll_event的指针;
返回值:
如果调用成功则返回0,不成功则返回-1。
3】epoll_wait函数
函数声明:int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
该函数用于轮询I/O事件的发生。
参数:
@epfd: 由epoll_create 生成的epoll专用的文件描述符;
@epoll_event: 用于回传代处理事件的数组;
@maxevents: 每次能处理的事件数;
@timeout: 等待I/O事件发生的超时值;
/**
* size 表示你所关心的最大文件描述符的个数,只与内存有关
* epollfd 非负的文件描述符,专用文件描述符,epoll实例的句柄
*/
if((epollfd = epoll_create(10)) < 0)
{
err_log("fail to epoll_create");
}
//定义一个epoll事件,包括读,要监听的文件描述符
ev.events = EPOLLIN;
ev.data.fd = sockfd;
//控制专用文件描述符,将sockfd 和 ev 添加到专用文件描述符上,完成注册
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &ev) == -1) {
err_log("fail to epoll_ctl");
}
for (;;) {
pthread_mutex_lock(&mutex);
// 等待专用文件描述符epollfd 上的事件的发生
// events 待处理事件的数组,保存的是已经准备好的要处理的事件。
// MAX_EVENTS 最大处理的事件数
// nfds 返回发生的事件数
if((nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1)) < 0)
{
pthread_mutex_unlock(&mutex);
err_log("fail to epoll_wait");
}
pthread_mutex_unlock(&mutex);
for (n = 0; n < nfds; ++n) {
// 查看是那个事件发生了
if(events[n].data.fd == sockfd) {
if((acceptfd = accept(sockfd,(struct sockaddr *) &serveraddr, &addrlen)) < 0){
err_log("fail to accept");
}
// set_non_blocking(acceptfd);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = acceptfd;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, acceptfd,&ev) == -1) {
err_log("fail to epoll_ctl");
}
} else {
// 开启一个新的线程,专门用来处理对应客户端的请求
pthread_t tid;
if(pthread_create(&tid, NULL, pthread_client,&(events[n].data.fd)) < 0){
err_log("fail to pthread_create");
}
}
}
}