1. 网络 I/O编程模型:
阻塞 Blocking IO:当数据未准备好时,则阻塞等待,当数据到达时将其唤醒
非阻塞 NoneBlocking IO :用户线程不时的询问数据是否准备好,线程不阻塞。
IO多路复用 IO multiplexing:
(1)select方式,进程设置为非阻塞,当数据未准备好时,由select函数阻塞相应的进程,
select可以查询多个数据情况
(2)epoll,对select的一种改进
信号方 signal driven IO*:通过信号来通知数据是否准备好
异步 asynchronous IO:异步通信方式
2.select
调用select函数后会阻塞。直到有描述符就绪(有数据可读,可写,或者有异常),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以通过遍历faset,来找到就绪的描述符。
函数说明:
#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);
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加入到集合之中
void FD_ZERO(fd_set *set);//清空整个集合
参数说明:
○ nfds: 要检测的文件描述中最大的fd+1 - 1024
○ readfds: 读集合
○ writefds: 写集合
○ exceptfds: 异常集合
○timeout
NULL: 永久阻塞
等待阻塞时间struct timeval
fd_set:文件描述符集合操作函数
FD_ZERO(&set); //清空集合
FD_SET(fd, &set); //将相应的文件描述符加入集合
FD_CLR(fd, &set); //删除某个文件描述符
FD_ISSET(fd, &set); //判断某个文件描述符是否在集合里面
select工作原理:
假设现在有客户端对应的文件描述符分别为:3 、4、100,且文件描述符4、3发送了消息。
当执行select函数后则,发送相应信息的文件描述符将会被置为1,未发送消息的文件描述符将会被置为0.
select函数的使用流程:
s = socket(AF_INET, SOCK_STREAM, 0);
.....
fd_set set;
while(1)
{
FD_ZERO(&set);//初始化集合
FD_SET(s, &set);//将文件描述符加入相应的集合
select(s+1,&set,NULL,NULL,NULL);//将set集合进行selec后,如果有可读的文件描述符,那么这个文件描述符将会加入可读集合
if(FD_ISSET(s, &set) //检查s是否存在,即s是否被设置
{
recv(s,...);
}
...........
...........
...........
}
select函数的使用例程(服务器端):
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <sys/select.h>
#define SERV_PORT 8000
int main(int argc, const char* argv[])
{
int lfd, cfd;
struct sockaddr_in serv_addr, clien_addr;
int serv_len, clien_len;
// 创建套接字
lfd = socket(AF_INET, SOCK_STREAM, 0);
// 初始化服务器 sockaddr_in
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET; // 地址族
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有的IP
serv_addr.sin_port = htons(SERV_PORT); // 设置端口
serv_len = sizeof(serv_addr);
// 绑定IP和端口
bind(lfd, (struct sockadd*)&serv_addr, serv_len);
// 设置同时监听的最大个数
listen(lfd, 36);
printf("Start accept ......\n");
int ret;
int maxfd = lfd;
// reads 实时更新,temps 内核检测
fd_set reads, temps;
FD_ZERO(&reads);
FD_SET(lfd, &reads);//将lfd加入set以便于监听是否有客户端连接
while(1)
{
temps = reads;
ret = select(maxfd+1, &temps, NULL, NULL, NULL);
if(ret == -1)
{
perror("select error");
exit(1);
}
// 判断是否有新连接
if(FD_ISSET(lfd, &temps))//即检查lfd再temps中是否被设置
{
// 接受连接请求
clien_len = sizeof(clien_len);
int cfd = accept(lfd, (struct sockaddr*)&clien_addr, &clien_len);
// 文件描述符放入检测集合
FD_SET(cfd, &reads);
// 更新最大文件描述符
maxfd = maxfd < cfd ? cfd : maxfd;
}
// 遍历检测的文件描述符是否有读操作
for(int i=lfd+1; i<=maxfd; ++i)
{
if(FD_ISSET(i, &temps))
{
// 读数据
char buf[1024] = {0};
int len = read(i, buf, sizeof(buf));
if(len == -1)
{
perror("read error");
exit(1);
}
else if(len == 0)
{
// 对方关闭了连接
printf("%d closed",i);
FD_CLR(i, &reads);
close(i);
if(maxfd == i)
{
maxfd--;
}
}
else
{
printf("read buf = %s\n", buf);
char *buf1="recived message."
write(i, buf1, strlen(buf1)+1);
}
}
}
}
close(lfd);
return 0;
}
3.poll
poll实现的功能和select差不多但其效率比select1更高,poll()接受一个指向结构’struct pollfd’列表的指针,其中包括了你想测试的文件描述符和事件。事件由一个在结构中事件域的比特掩码确定。当前的结构在调用后将被填写并在事件发生后返回。在事件的等待时间精确到毫秒 (但令人困惑的是等待时间的类型却是int,当等待时间为0时,poll()函数立即返回,-1则使poll()一直挂起直到一个指定事件发生。
与select()函数不同,调用select()函数之后,select()函数会清空它所检测的socket描述符集合,导致每次调用select()之前都必须把socket描述符重新加入到待检测的集合中;因此,select()函数适合于只检测一个socket描述符的情况,而poll()函数适合于大量socket描述符的情况;
函数原型:
#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
参数1:
一个结构体数组,其中包含相应的文件描述符和相应的事件
参数2:
nfds_t类型的参数,用于标记数组fds中的结构体元素的总数量;
参数3:
是poll函数调用阻塞的时间,单位:毫秒
结构体poolfd:
struct pollfd {
int fd; /*文件描述符*/
short events; /* 等待的需要测试事件 */
short revents; /* 实际发生了的事件,也就是返回结果 */
};
事件events:为用户需要设置的相应的文件描述符的类型,其可以取以下的值
如果想要监听相应文件描述符是否有数据可读则相应的events设置为POLLIN或者POLLRDNORM,
事件revents&POLLIN:如果相应的事件events的到满足则为非零的数,没有相应的数据准备好为0,发生错误则为-1;
poll函数的使用流程:
// 声名poll结构体
struct pollfd fd_poll[size];
将相应的文件描符,和相应events类型设置到fd_poll中
for(int i=0;i<size;i++)
{
int ret = poll(allfd, max_index+1, -1);
if(ret == -1)
{
发生错误;
}
if(fd_poll[i].revents & POLLIN)
{
执行相应的操作,读数据或者写数据;
}
}
poll函数的使用例子:
模拟服务器端来实现对多个客户端的信息的接受,以及广播消息到客户端··
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include<fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <poll.h>
#define SERV_PORT 8000
int main(int argc, const char* argv[])
{
int lfd, cfd,t_fd;
struct sockaddr_in serv_addr, clien_addr;
int serv_len, clien_len;
// 创建套接字
lfd = socket(AF_INET, SOCK_STREAM, 0);
// 初始化服务器 sockaddr_in
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET; // 地址族
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有的IP
serv_addr.sin_port = htons(SERV_PORT); // 设置端口
serv_len = sizeof(serv_addr);
// 绑定IP和端口
bind(lfd, (struct sockaddr*)&serv_addr, serv_len);
// 设置同时监听的最大个数
listen(lfd, 36);
printf("Start accept ......\n");
// poll结构体数组
struct pollfd allfd[1024];
int max_index = 0;
// 初始化结构体数组
for(int i=0; i<1024; ++i)
{
allfd[i].fd = -1;//fd==-1表示该fd还未用于和客户端相连接
}
allfd[0].fd = lfd;
allfd[0].events = POLLIN;
//用只读和非阻塞的方式打开文件dev/tty,以方便读取终端输入
t_fd=open("/dev/tty",O_RDONLY|O_NONBLOCK);
while(1)
{
int i = 0;
int ret = poll(allfd, max_index+1, -1);
if(ret == -1)
{
perror("poll error");
exit(1);
}
// 判断是否有连接请求
if(allfd[0].revents & POLLIN)
{
clien_len = sizeof(clien_addr);
// 接受连接请求
int cfd = accept(lfd, (struct sockaddr*)&clien_addr, &clien_len);
printf("客户端连接成功\n");
// cfd添加到poll数组
for(i=1; i<1024; ++i)
{
if(allfd[i].fd == -1)
{
allfd[i].fd = cfd;
allfd[i].events=POLLIN;
break;
}
}
// 更新最后一个元素的下标
max_index = max_index < i ? i : max_index;
}
//向客户端广播信息,这儿需要采用非阻塞的方式来读取终端文件
char buf1[1024]={0};
if(read(t_fd,buf1,1024)>0)
{
for(i=1;i<=max_index;i++)
{
if(allfd[i].fd!=-1)
{
send(allfd[i].fd, buf1, strlen(buf1)+1, 0);
}
}
}
// 遍历数组,判断是否有客户端数据可读
for(i=1; i<=max_index; ++i)
{
int fd = allfd[i].fd;
if(fd == -1)
{
continue;
}
if(allfd[i].revents & POLLIN)
{
// 接受数据
char buf[1024] = {0};
int len = recv(fd, buf, sizeof(buf), 0);
if(len == -1)
{
perror("recv error");
exit(1);
}
else if(len == 0)
{
allfd[i].fd = -1;
close(fd);
printf("客户端断开连接\n");
}
else
{
printf("recive message: %s\n", buf);
char buf2[1024]="message been recived";
send(fd,buf2,sizeof(buf2),0);
}
}
}
}
close(t_fd);
close(lfd);
return 0;
}
4.客户端测试代码
/*************************************************************************
> File Name: client.c
> Author: zhou
> Mail:none
> Created Time: Fri 01 Jan 2021 01:37:48 AM EST
************************************************************************/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<fcntl.h>
#define SERV_PORT 8000
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[1024];
int sockfd, n;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
int flag=fcntl(sockfd,F_GETFL);
flag|=O_NONBLOCK;
fcntl(sockfd,F_SETFL,flag);
//用只读和非阻塞的方式打开文件dev/tty,以方便读取终端输入
while (1)
{
int t_fd=open("/dev/tty",O_RDONLY|O_NONBLOCK);
char tem[1024]={0};
if(read(t_fd,tem,1024)>0)
{
write(sockfd, tem, sizeof(tem));
}
close(t_fd);
//读取信息
memset(tem,0,1024);
n = read(sockfd,tem, 1024);
if (n == 0)
{
printf("the other side has been closed.\n");
break;
}
else if(n>0)
write(STDOUT_FILENO, tem, n);
}
close(sockfd);
return 0;
}