三.IO模型
1.概念
在UNIX/Linux下主要有4种I/O 模型:
-
阻塞I/O:
最常用、最简单、效率最低
-
非阻塞I/O:
可防止进程阻塞在I/O操作上,需要轮询
-
I/O 多路复用:
允许同时对多个I/O进行控制
-
信号驱动I/O:
一种异步通信模型
2.非阻塞IO
多次访问套接字缓冲区中有无数据,有数据就打印,没数据就返回
1)fcntl()
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
作用:控制文件描述符
参数:
fd --- 文件描述符
cmd --- 命令
F_GETFL:获取文件状态标志值
F_SETFL:设置文件状态标志值
arg --- 值
F_GETFL:忽略
F_SETFL:想要设置的文件状态标志值
返回值:
F_GETFL:获取文件状态标志值
F_SETFL:0
失败:-1,并设置error
例:
int flags;
flags = fcntl(connfd,F_GETFL,0);//获取文件状态标志值
flags |= O_NONBLOCK;//添加非阻塞标志
fcntl(connfd,F_SETFL,flags);//设置文件状态标志值
注意:当设置非阻塞后,如果没有数据回收也会立即-1返回,有数据也能正常接收
/*===============================================
* 文件名称:server.c
* 创 建 者:memories
* 创建日期:2023年05月18日
* 描 述:
================================================*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
//1.创建套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
perror("socket");
return -1;
}
printf("socket-----------------\n");
//2.绑定本机地址和端口
//用IPv4结构体
struct sockaddr_in srvaddr;
memset(&srvaddr,0,sizeof(srvaddr));
srvaddr.sin_family = AF_INET;//指定地址族为IPv4的地址
srvaddr.sin_port = htons(5420);//端口号
srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);//将点分式的字符串转化32位的网络字节序
if(0 > bind(sockfd,(struct sockaddr*)&srvaddr,sizeof(srvaddr)))
{
perror("bind");
return -1;
}
printf("bind-----------------\n");
//3.设置监听套接字
if(0>listen(sockfd,5))//必须写大于1的整数
{
perror("listen");
return -1;
}
printf("listen-----------------\n");
//4.接受客户端的连接,并生成通信套接字
int connfd = accept(sockfd,NULL,NULL);
if(connfd < 0)
{
perror("accept");
return -1;
}
printf("accept-----------------\n");
int flags = fcntl(connfd,F_GETFL,0);
if(flags < 0)
{
perror("fcntl");
return -1;
}
flags |= O_NONBLOCK;
fcntl(connfd,F_SETFL,flags);
//5.与客户端通信
int ret;
char buf[1024];
while(1)
{
sleep(1);
memset(buf,0,sizeof(buf));
ret = read(connfd,buf,sizeof(buf));
if(ret < 0)
{
perror("read");
continue;
}
else if(ret == 0)
{
printf("write close\n");
break;
}
printf("recv:%s\n",buf);
if(0 > write(connfd,buf,ret))
{
perror("write");
return -1;
}
}
//6.关闭套接字
close(sockfd);
close(connfd);
return 0;
}
hqyj@ubuntu:~/5.22$ vi server.c
hqyj@ubuntu:~/5.22$ ./a.out
socket-----------------
bind-----------------
listen-----------------
accept-----------------
read: Resource temporarily unavailable
read: Resource temporarily unavailable
read: Resource temporarily unavailable
read: Resource temporarily unavailable
read: Resource temporarily unavailable
read: Resource temporarily unavailable
read: Resource temporarily unavailable
read: Resource temporarily unavailable
read: Resource temporarily unavailable
recv:kjakd
3. IO多路复用
IO多路复用的步骤:
1. 创建一张文件描述符的表(集合),把想要监测的文件描述符加入表中
2. 监测是否有文件描述符产生事件
3.判断是谁产生了事件
4.处理事件
1)select()
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
作用:监测集合中是否有事件产生
参数:
nfds --- 想要监测的最大文件描述符+1
readfds --- 读事件集合
writefds --- 写事件集合
exceptfds --- 其他事件集合
timeout --- 超时设置,NULL表示阻塞
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
返回值:
<0:出错
=0:超时设置时没有事件产生
>0:有事件产生
void FD_CLR(int fd, fd_set *set); --- 把fd从集合中取出
int FD_ISSET(int fd, fd_set *set);---判断fd是否还在集合中
void FD_SET(int fd, fd_set *set);---把fd添加到set集合中
void FD_ZERO(fd_set *set); --- 清除整个集合
/*===============================================
* 文件名称:server.c
* 创 建 者:
* 创建日期:2023年05月18日
* 描 述:
================================================*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
int main(int argc, char *argv[])
{
/*1. 创建套接字*/
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket");
return -1;
}
printf("socket..............\n");
/*2. 绑定本机地址和端口*/
struct sockaddr_in srvaddr;
memset(&srvaddr, 0, sizeof(srvaddr));
srvaddr.sin_family = AF_INET; //地址族
srvaddr.sin_port = htons(6666); //端口号
srvaddr.sin_addr.s_addr = htonl(INADDR_ANY); //IP地址
if (0 > bind(sockfd, (struct sockaddr*)&srvaddr, sizeof(srvaddr)))
{
perror("bind");
return -1;
}
printf("bind................\n");
/*3. 设置监听套接字*/
if (0 > listen(sockfd, 5))
{
perror("listen");
return -1;
}
printf("listen.................\n");
/*创建一张文件描述符表(集合), 把想要监测的文件描述符加入表中*/
fd_set fds, rfds;
FD_ZERO(&fds);
FD_SET(0, &fds); //把键盘加入集合中
FD_SET(sockfd, &fds); //把监听套接字加入集合中
int maxfd = sockfd; //最大的文件描述符
int ret, retval, i;
char buf[1024];
while (1)
{
rfds = fds;
/*监测是否有文件描述符产生事件*/
retval = select(maxfd+1, &rfds, NULL, NULL, NULL);
if (retval < 0)
{
perror("select");
break;
}
/*判断是谁产生了事件*/
for (i = 0; i < maxfd+1; i++)
{
if (FD_ISSET(i, &rfds))
{
if (0 == i) //键盘产生事件
{
fgets(buf, sizeof(buf), stdin);
printf("Keyboard: %s", buf);
}
else if (sockfd == i) //监听套接字产生事件
{
/*4. 接受客户端的连接,并生成通信套接字*/
int connfd = accept(sockfd, NULL, NULL);
if (connfd < 0)
{
perror("accept");
return -1;
}
printf("accept success!\n");
//把通信套接字加入集合中
FD_SET(connfd, &fds);
maxfd = (maxfd > connfd) ? maxfd : connfd;
}
else //通信套接字
{
memset(buf, 0, sizeof(buf));
ret = recv(i, buf, sizeof(buf), 0);
if (ret < 0)
{
perror("recv");
}
else if (0 == ret)
{
printf("client close!\n");
FD_CLR(i, &fds); //把通信套接字去掉
close(i);
}
else
{
printf("recv: %s\n", buf); //把读取的数据显示在屏幕上
}
}
}
}
}
/*6. 关闭套接字*/
close(sockfd);
return 0;
}
2)poll()
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
POLLIN:有数据可读
POLLOUT:有数据可写
作用:监测文件描述符集合中是否有事件产生
参数:
fds --- 监测文件描述符集合:用结构体数组表示
nfds --- 想要监测的文件描述符的个数
timeout --- 超时设置,以毫秒为单位
-1:阻塞
返回值:
<0:出错
=0:超时设置时没有事件产生
>0:有事件产生
/*===============================================
* 文件名称:server.c
* 创 建 者:
* 创建日期:2023年05月18日
* 描 述:
================================================*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <poll.h>
int main(int argc, char *argv[])
{
/*1. 创建套接字*/
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket");
return -1;
}
printf("socket..............\n");
/*2. 绑定本机地址和端口*/
struct sockaddr_in srvaddr;
memset(&srvaddr, 0, sizeof(srvaddr));
srvaddr.sin_family = AF_INET; //地址族
srvaddr.sin_port = htons(6666); //端口号
srvaddr.sin_addr.s_addr = htonl(INADDR_ANY); //IP地址
if (0 > bind(sockfd, (struct sockaddr*)&srvaddr, sizeof(srvaddr)))
{
perror("bind");
return -1;
}
printf("bind................\n");
/*3. 设置监听套接字*/
if (0 > listen(sockfd, 5))
{
perror("listen");
return -1;
}
printf("listen.................\n");
/*创建一张文件描述符表(集合), 把想要监测的文件描述符加入表中*/
struct pollfd fds[1024]; //文件描述符集合
fds[0].fd = 0; //把键盘添加到集合中
fds[0].events = POLLIN;
fds[1].fd = sockfd; //把监听套接字添加到集合中
fds[1].events = POLLIN;
int nfds = 2; //想要监测的文件描述符个数
int ret, retval, i, j;
char buf[1024];
while (1)
{
/*监测是否有文件描述符产生事件*/
retval = poll(fds, nfds, -1);
if (retval < 0)
{
perror("poll");
break;
}
/*判断是谁产生了事件*/
for (i = 0; i < nfds; i++) //i --- fds的下标
{
if (fds[i].revents & POLLIN) //判断是否是i下标对应的文件描述符产生了事件
{
if (0 == fds[i].fd) //键盘产生事件
{
fgets(buf, sizeof(buf), stdin);
printf("Keyboard: %s", buf);
}
else if (sockfd == fds[i].fd) //监听套接字产生事件
{
/*4. 接受客户端的连接,并生成通信套接字*/
int connfd = accept(sockfd, NULL, NULL);
if (connfd < 0)
{
perror("accept");
return -1;
}
printf("accept success!\n");
//把通信套接字加入集合中
for (j = 0; j < nfds; j++)
if (-1 == fds[j].fd)
break;
fds[j].fd = connfd;
fds[j].events = POLLIN;
if (j == nfds)
nfds++;
}
else //通信套接字
{
memset(buf, 0, sizeof(buf));
ret = recv(fds[i].fd, buf, sizeof(buf), 0);
if (ret < 0)
{
perror("recv");
}
else if (0 == ret)
{
printf("client close!\n");
close(fds[i].fd);
fds[i].fd = -1; //把通信套接字去掉
}
else
{
printf("recv: %s\n", buf); //把读取的数据显示在屏幕上
}
}
}
}
}
/*6. 关闭套接字*/
close(sockfd);
return 0;
}