一、IO模型
在网络编程中有时我们需要多用户连接,因此使用IO模型设置非阻塞IO,就很容易解决这个问题
一、IO模型概念
1、阻塞IO -> 一直等待数据的到达
2、非阻塞IO -> 询问数据是否到达,如果没有数据,则不会阻塞,会马上退出
二、设置非阻塞IO的步骤
1、 建立一个文件描述符 -> 默认都是阻塞!
2、 设置非阻塞属性给文件描述符 -> 文件描述符就是非阻塞!
3、 再调用accept()/read()/recv()/recvfrom() -> 非阻塞读取。
三、设置非阻塞IO
接口 | #include <unistd.h> #include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ ); |
参数 | fd :需要设置的文件描述符 cmd :
F_GETFL (void) -> 获取该文件描述符的属性 -> 不需要添加(arg)
F_SETFL (int) -> 设置该文件描述符的属性 -> 再添加一个属性(arg) Set the file status flags to the value specified by arg. File access mode (O_RDONLY, O_WRONLY, O_RDWR) and file creation flags (i.e., O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC) in arg are ignored. On Linux this command can change only the O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, and O_NONBLOCK flags. It is not possible to change the O_DSYNC and O_SYNC flags; see BUGS, below.
O_NONBLOCK:代表非阻塞的属性 |
返回 | F_GETFL 成功 文件描述符的属性 F_SETFL 成功 0 失败 -1 |
Example:
#include "head.h"
int main()
{
//1.创建一个TCP协议的未连接套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
//2.获取之前的属性
int state = fcntl(sockfd,F_GETFL);
//3.设置新属性
state = state|O_NONBLOCK
int ret = fcntl(sockfd,F_SETFL,state);
if(ret == -1)
{
printf("set state error!\n");
}
return 0;
}
二、多路复用
1、多路复用的概念:
先将需要进行监听的文件描述符加入一个集合中,然后在规定的时间或者在无限的时间等待并且去监听这个集合中的文件描述符。如果在规定的时间内,集合的某个文件描述符有数据到达,则其他没有数据的文件描述符就会剔除到集合之外。我们用户需要监听集合中有没有文件描述符即可。
2、如何实现多路复用
监听 -> 文件描述符的集合 -> 是否加入
用到的几个接口:
接口 | #include <sys/select.h> #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); |
参数 | 1、nfds 添加到集合中的最大文件描述符+1 2、readfds 监测可读的文件描述符的集合 3、writefds 监测可写的文件描述符的集合 (NULL) 4、exceptfds 监测发生异常的文件描述符的集合(NULL) 5、timeou 监测超时时间
5.1、NULL 永久等待 5.2、struct timeval 设置了超时时间,超过这个时间集 |
返回 | 在socket集合中准备好的socket个数 |
2、 将某个文件描述符从集合里清除
函数: | void FD_CLR(int fd, fd_set *set); |
参数: | 1、fd 要清除的文件描述符 2、set 要从哪个集合清除文件描述符 |
3、 将某个文件描述符添加到集合里
函数: | void FD_SET(int fd, fd_set *set); |
参数: | 1、fd 要添加的文件描述符 2、set 要将文件描述符添加到哪个集 |
4、 清空集合里的文件描述符
函数: | void FD_ZERO(fd_set *set); |
参数: | 1、set 要将哪个集合里的文件描述符清空 |
5、 检测某个文件描述符是否在集合里
函数: | int FD_ISSET(int fd, fd_set *set); |
参数: | 1、fd 要检测的文件描述符 2、set 要检测的集合 |
返回值: | 1、返回1: 要检测的文件描述符在集合里 2、返回0: 要检测的文件描述符不在集合里 |
设置超时等待
struct timeval {
long tv_sec; /* seconds 秒*/
long tv_usec; /* microseconds 微妙*/
};
struct timeval v; //每次都需要重新设置时间
v.tv_sec = 10;
v.tv_usec = 0;
select(maxfd+1,&rset,NULL,NULL,&v); //超时则都踢出去
下面这个例子是通过多路复用来实现键盘和触摸屏的同时使用输入功能
/*
功能: 监听哪个文件描述符有数据
*/
int fd_select(int fd1,int fd2)
{
int maxfd = fd1 > fd2? fd1 : fd2;
fd_set rset;
FD_ZERO(&rset);
FD_SET(fd2,&rset);
FD_SET(fd1,&rset);
//无限等待
select(maxfd+1,&rset, NULL,NULL,NULL);
//判断STDIN_FILENO是否在,在则发消息
if(FD_ISSET(fd1,&rset))
{
return 1;
}
if(FD_ISSET(fd2,&rset)) //如果触摸屏在
{
return 2;
}
}
//将键盘的文件描述符和触摸屏的文件描述符监听起来,你就可以去实现两个同时做输入工作了
fd_select(STDIN_FILENO,fd_event);
三、设置套接字的属性
一、如果不设置超时属性,那么套接字本身是无限等待数据的到达
Example:
int connfd = accept();
recv() ----> 正常时一直阻塞等待
二、设置超时属性
接口 | #include <sys/types.h> /* See NOTES */ #include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); |
参数 | sockfd 套接字描述符 level 优先级 SOL_SOCKET 套接字 IPPROTO_IP IP优先级 IPPRO_TCP TCP优先级
optname 选项名字(下面可以查询) optval 使能为1,不使能为0 optlen 值类型大小 |
返回 | 成功 0 失败 -1 |
实现UDP广播
//server.c
#include "head.h"
int main(int argc, char **argv)
{
//串口号
int port = htons(atoi(argv[1]));
//1、申请套接字
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
//设置广播属性
int on = 1;
setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));
//2、绑定套接字
//设置端口,自己IP
struct sockaddr_in svraddr;
socklen_t len = sizeof(struct sockaddr);
bzero(&svraddr,len);
svraddr.sin_family = AF_INET;
svraddr.sin_port = port;
svraddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd,(struct sockaddr*)&svraddr,len);
//接收信息
struct sockaddr_in rcvaddr;
bzero(&rcvaddr,sizeof(rcvaddr));
char buf[50];
while(1)
{
bzero(buf,50);
recvfrom(sockfd,buf,50,0,(struct sockaddr*)&rcvaddr,&len);
printf("from %s client: %s",(char*)inet_ntoa(rcvaddr.sin_addr),buf);
if(strncmp(buf,"quit",4) == 0)
break;
}
close(sockfd);
return 0;
}
//client.c
#include "head.h"
int main(int argc, char **argv)
{
//串口号
int port = htons(atoi(argv[1]));
//1、申请套接字
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
//接收信息
struct sockaddr_in sndaddr;
socklen_t sndlen = sizeof(sndaddr);
bzero(&sndaddr,sndlen);
sndaddr.sin_family = AF_INET;
sndaddr.sin_port = port;
inet_pton(AF_INET,argv[2],&sndaddr.sin_addr);
char buf[50];
while(1)
{
bzero(buf,50);
fgets(buf,50,stdin);
sendto(sockfd,buf,50,0,(struct sockaddr*)&sndaddr,sndlen);
if(strncmp(buf,"quit",4) == 0)
break;
}
close(sockfd);
return 0;
}
optname:
===========================SOL_SOCKET=================================
optname选项名字 optlen的大小
SO_BROADCAST 允许发送广播数据 int
SO_DEBUG 允许调试 int
SO_DONTROUTE 不查找路由 int
SO_ERROR 获得套接字错误 int
SO_KEEPALIVE 保持连接 int
SO_LINGER 延迟关闭连接 struct linger
SO_OOBINLINE 带外数据放入正常数据流 int
SO_RCVBUF 接收缓冲区大小 int
SO_SNDBUF 发送缓冲区大小 int
SO_RCVLOWAT 接收缓冲区下限 int
SO_SNDLOWAT 发送缓冲区下限 int
SO_RCVTIMEO 接收超时 struct timeval
SO_SNDTIMEO 发送超时 struct timeval
SO_REUSEADDR 允许重用本地地址和端口 int
SO_TYPE 获得套接字类型 int
SO_BSDCOMPAT 与BSD系统兼容 int
=========================IPPROTO_IP====================================
IP_HDRINCL 在数据包中包含IP首部 int
IP_OPTINOS IP首部选项 int
IP_TOS 服务类型
IP_TTL 生存时间 int
IP_ADD_MEMBERSHIP 加入组播 struct ip_mreq
=========================IPPRO_TCP=====================================
TCP_MAXSEG TCP最大数据段的大小 int
TCP_NODELAY 不使用Nagle算法 int