学习笔记,小白可以相互学习,大佬看到能告诉咱理解不对的地方就好了。
I/O模型
在UNIX/Linux下主要有4种i/o模型:
1.阻塞i/o:最常用,最简单,效率最低。
大部分程序使用的就是阻塞i/o模式,缺省下,套接字建立后所处于的模式就是它。
2.非阻塞i/o:可以防止进程在i/o操作上,需要轮询。
用程序不停地来检查i/o操作是否就绪,这个是非常浪费cpu资源的操作。
可以使用fcntl()设置一个套接字的标志为O_NONBLOCK来实现非阻塞
int fcntl(int fd,int cmd,long arg)
int flag; flag = fcntl(sockfd,F_GETFL,0);//第二个参数是获得属性命令
flag |= O_NONBLOCK; //设置非阻塞的属性
fcntl = (sockfd,F_GETFL,flag); //再把非阻塞的属性赋给sockfd
3.i/o多路复用:允许同时对多个i/o进行控制
4.信号驱动i/o:一种异步通信模型
I/O多路复用
应用程序中同时处理多路输入输出流,
若采用阻塞模式,将得不到预期的目的;
若采用非阻塞模式,对多个输入进行轮询,但又太浪费cpu时间。
若设置多个进程分别处理一条数据路,将产生进程间同步与通信问题,使程序变得复杂。
最好的方法是使用i/o多路复用。
i/o多路复用的基本思想是:
1. 将所有要处理的文件描述符存储到一张表当中;
2. 检测表当中有没有已经就绪的文件描述符,如果有返回就绪的文件描述符;
3. 轮询所有就绪的文件描述符
while(1) {
if(listenfd)
说明是新的客户端发起了连接请求;
if(connfd)
说明是已经连接的客户端发送了数据请求;
if(普通文件)
的到普通文件的数据;
if(0)
标准输入
}
根据处理细节的不同,分为三种类型:
select()机制:
1. 根据描述符处理事件不同,创建不同的表,例如将所有要读的文件描述符添加到一张读的表中
2. 检测表当中是否有就额绪的文件描述符,如果有返回有的状态,同时返回就绪的文件描述符
3. 轮询 :a找到那些文件描述符就绪;b处理数据
函数原型:int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
功能:检测是否有文件描述符处于就绪状态,有返回。没有就一直阻塞。
参数:nfds: 最大文件描述符+1
readfds:读的文件描述符表
writefds:
写的文件描述符表
exceptfds:
错误处理文件描述符表
timeout:
超时时间;不设置用NULL,表示一直阻塞
返回:成功返回准备就绪的文件描述符的个数;失败返回-1;超时返回0;
宏函数:
void FD_CLR(int fd, fd_set *set); //将fd从集合set当中清除;
int FD_ISSET(int fd, fd_set *set);
//判断fd是否在集合set当中;
void FD_SET(int fd, fd_set *set); //将fd添加到集合set当中;
void FD_ZERO(fd_set *set); //清空集合set
int main()
{
int ret;
int nfds;
char buf[256];
struct timeval time;
/*创建集合*/
fd_set readfds;
FD_ZERO(&readfds);
/*添加所要处理的文件描述付*/
FD_SET(0,&readfds);
nfds = 1;
memset(buf,0,sizeof(buf));
/**如果不设置,表示select阻塞读集合,直到有文件描述府就绪,select函数返回*/
/*设置之后,select函数阻塞3s,如果达到超时时间,改为非阻塞模式,不管有没有文件描述府,都返回*/
while(1)
{
time.tv_sec = 3;
time.tv_usec = 0;
fd_set rfds = readfds;
/*检测继续的文件描述府*/
ret = select(nfds,&rfds,NULL,NULL,&time);
if(-1 == ret)
{
perror("select");
return -1;
}
else if(0 ==ret)
{
printf("time out....\n");
continue;
}
int fd;
for(fd = 0;fd < nfds; fd++)
{
if (FD_ISSET(fd,&rfds))
{
ret = read(0,buf,sizeof(buf));
if(-1 == ret)
{
perror("read");
return -1;
}
printf("buf: %s\n",buf);
}
}
memset(buf,0,sizeof(buf));
}
return 0;
}
poll()机制:
1. 创建一个集合;添加(文件描述符及其所要处理的事件)
2. 检测集合当中是否有事件发生(是否有文件描述符准备就绪);如果有返回有的状态,同时返回就绪的文件描述符及事件;
3. 轮询 :a. 找所发生的事件;b. 处理数据
函数原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout);
函数功能:检测是否有文件描述符和事件处于就绪状态,有返回。没有一直阻塞。
参数:
fds:事件和文件描述符的集合;
struct pollfd {
int fd; /* 文件描述符*/
short events; /* 请求的事件 */
short revents; /*返回事件 */
};
nfds: 最大文件描述符+1;
timeout:
超时时间;ms级
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <poll.h>
int client(int connfd)
{
int ret;
char buf[256];
memset(buf, 0, sizeof(buf));
ret = read(connfd, buf, sizeof(buf));
if (ret == -1) {
perror("server->read");
return -1;
} else if (ret == 0) {
close(connfd);
return -1;
}
printf("buf : %s\n", buf);
ret = write(connfd, buf, sizeof(buf));
if (ret == -1) {
perror("server->write");
return -1;
}
return 0;
}
int main(int argc, char *argv[])
{
int listenfd;
int ret;
socklen_t addrlen;
int connfd;
pid_t pid;
char buf[256];
struct sockaddr_in srvaddr;
struct sockaddr_in cltaddr;
/* 1. 创建服务器(创建一socket套接字);socket */
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd == -1) {
perror("server->socket");
return -1;
}
printf("create listenfd = %d success\n", listenfd);
/* 2. 设置服务器的IP地址和端口号(将socket和服务器的IP地址和端口号进行绑定);bind */
memset(&srvaddr, 0, sizeof(struct sockaddr_in));
srvaddr.sin_family = AF_INET;
srvaddr.sin_port = htons(9999);
srvaddr.sin_addr.s_addr = inet_addr("192.168.2.100");
ret = bind(listenfd, (const struct sockaddr *)&srvaddr, sizeof(srvaddr));
printf("port : %d\n", ntohs(srvaddr.sin_port));
if (ret == -1) {
perror("server->bind");
return -1;
}
printf("bind success !\n");
/* 3. 启动监听(启动服务器); listen */
ret = listen(listenfd, 1024);
if (ret == -1) {
perror("server->listen");
return -1;
}
printf("listen success !\n");
/* 创建集合,并且将集合当中的每一个数组元素的fd成员赋值为-1;*/
int nfds;
int fd;
int i;
int j;
struct pollfd fds[1024];
for (i = 0; i < 1024; i++) {
fds[i].fd = -1;
}
/* 添加所需要处理的事件和文件描述符 */
fds[0].fd = listenfd;
fds[0].events = POLLIN;
nfds = listenfd+1;
while(1) {
/* 检测集合当中是否有继续的文件描述符 */
ret = poll(fds, nfds, 5000);
if (ret == -1) {
perror("poll");
return -1;
} else if (ret == 0) {
printf("timeout\n");
continue;
}
/* 轮循 */
for (i = 0; i < nfds; i++) {
/* 判断是什么事件 */
if (POLLIN == fds[i].revents) {
/* 判断,寻找就绪的文件描述符 */
if (fds[i].fd != -1) {
fd = fds[i].fd;
/* 如果是监听套接字listenfd,则建立连接 */
if (fd == listenfd) {
memset(&cltaddr, 0, sizeof(cltaddr));
addrlen = sizeof(socklen_t);
connfd = accept(listenfd, (struct sockaddr *)&cltaddr, &addrlen);
if (connfd == -1) {
perror("accept");
return -1;
}
printf("connfd = %d\n", connfd);
for (j = 0; j < 1024; j++) {
if (fds[j].fd != -1) {
continue;
}
fds[j].fd = connfd;
fds[j].events = POLLIN;
#if 0
if (nfds <= connfd) {
nfds = connfd + 1;
}
#endif
nfds = nfds <= connfd ? connfd+1 : nfds;
break;
}
} else {
ret = client(fd);
if (ret == -1) {
fds[i].fd = -1;
}
}
}
}
}
}
close(listenfd);
return 0;
}
epoll机制:
1. 创建一个集合,添加(文件描述符及其所要处理的事件);
2. 检测集合当中是否有事件发生(是否有文件描述符准备就绪);如果有返回有的状态,同时返回就绪的文件描述符及事件的集合;
3. 轮询 :a. 找所发生的事件;b. 处理数据;
int epoll_create(int size);
功能:创建一个epoll实例
参数: size:epoll实例所能处理的文件描述符的最大个数。而不是容纳的文件描述符的个数。
返回值:成功,返回一个非负的文件描述符。错误,返回- 1,并设置errno
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:设置epoll实例(新增、修改、删除);
参数:epfd:epoll实例
op:执行的动作:
EPOLL_CTL_ADD (新增)
EPOLL_CTL_MOD (修改)
EPOLL_CTL_DEL (删除)
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的事件 */
epoll_data_t data; /* 用户数据变量 */
};
The events member is a bit set composed using the following available event types:
事件的成员是一个点集组成使用下面提供的事件类型:
EPOLLIN
The associated file is available for read(2) operations.相关文件可供阅读(2)的操作。
The associated file is available for write(2) operations.相关文件可供阅读(2)的操作。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
功能:等待检测事件;
参数:
epfd:epoll实例;
events:事件集合(存储的是,准备就绪的文件描述符和对应事件集合);
maxevents:表示最大值。
timeout:超时时间;ms级;
返回值:
成功返回准备就绪的文件描述符的个数;失败返回-1;超时返回0;
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
int client(int connfd)
{
int ret;
char buf[256];
memset(buf, 0, sizeof(buf));
ret = read(connfd, buf, sizeof(buf));
if (ret == -1) {
perror("server->read");
return -1;
} else if (ret == 0) {
return -1;
}
printf("buf : %s\n", buf);
ret = write(connfd, buf, sizeof(buf));
if (ret == -1) {
perror("server->write");
return -1;
}
return 0;
}
int main(int argc, char *argv[])
{
int listenfd;
int ret;
socklen_t addrlen;
int connfd;
pid_t pid;
char buf[256];
struct sockaddr_in srvaddr;
struct sockaddr_in cltaddr;
/* 1. 创建服务器(创建一socket套接字);socket */
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd == -1) {
perror("server->socket");
return -1;
}
printf("create listenfd = %d success\n", listenfd);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
/* 2. 设置服务器的IP地址和端口号(将socket和服务器的IP地址和端口号进行绑定);bind */
memset(&srvaddr, 0, sizeof(struct sockaddr_in));
srvaddr.sin_family = AF_INET;
srvaddr.sin_port = htons(9999);
srvaddr.sin_addr.s_addr = inet_addr("192.168.2.100");
ret = bind(listenfd, (const struct sockaddr *)&srvaddr, sizeof(srvaddr));
printf("port : %d\n", ntohs(srvaddr.sin_port));
if (ret == -1) {
perror("server->bind");
return -1;
}
printf("bind success !\n");
/* 3. 启动监听(启动服务器); listen */
ret = listen(listenfd, 1024);
if (ret == -1) {
perror("server->listen");
return -1;
}
printf("listen success !\n");
/* 创建集合;*/
int epfd;
int i;
int fd;
epfd = epoll_create(1024);
if (epfd == -1) {
perror("epoll_create");
return -1;
}
printf("epfd = %d\n", epfd);
/* 添加所需要处理的事件和文件描述符 */
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = listenfd;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &event);
if (ret == -1) {
perror("epoll_ctl->EPOLL_CTL_ADD");
return -1;
}
struct epoll_event events[1024];
while(1) {
/* 检测集合当中是否有继续的文件描述符 */
ret = epoll_wait(epfd, events, 1024, 5000);
if (ret == -1) {
perror("poll");
return -1;
} else if (ret == 0) {
printf("timeout\n");
continue;
}
/* 轮循 */
for (i = 0; i < ret; i++) {
/* 判断是什么事件 */
if (EPOLLIN == events[i].events) {
fd = events[i].data.fd;
/* 如果是监听套接字listenfd,则建立连接 */
if (fd == listenfd) {
memset(&cltaddr, 0, sizeof(cltaddr));
addrlen = sizeof(socklen_t);
connfd = accept(listenfd, (struct sockaddr *)&cltaddr, &addrlen);
if (connfd == -1) {
perror("accept");
return -1;
}
printf("connfd = %d\n", connfd);
event.events = EPOLLIN;
event.data.fd = connfd;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &event);
if (ret == -1) {
perror("epoll_ctl->EPOLL_CTL_ADD");
return -1;
}
} else {
ret = client(fd);
if (ret == -1) {
ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &event);
if (ret == -1) {
perror("epoll_ctl->EPOLL_CTL_DEL");
return -1;
}
close(fd);
}
}
}
}
}
close(epfd);
close(listenfd);
return 0;
}