网络编程IO模型:
1.主要的4种IO模型:
①阻塞IO:最常用,简单,效率最低
②非阻塞IO:可防止进程阻塞在IO操作上,需要轮询。。。
③IO多路复用:允许同时对多个IO进行控制。
④信号驱动IO:
2.阻塞IO:
--read函数:----阻塞,需要内核去唤醒该进程。。。。
--write阻塞:主要发生的情况??
用户缓冲区:???数组。。
UDP无发送缓存区,写操作sendto永远都不会阻塞。()
3.非阻塞IO------使用比较少,了解有这种模式
--缓冲区满,不阻塞,它返回一个错误编号
--例如 waitpid(-1,&a, WHOHANG )
--例如 fifo open()参数中加入非阻塞参数
fnctl(fd, cmd, arg)函数:改变一个描述符的属性,设置为O_NONBLOCK来实现非阻塞。
--cmd:命令 F_GETFL获得属性,并存放到flag中,arg忽略,传入0即可;F_SETFL设置属性
--arg:
4.多路复用IO:(重点)
--一个进程处理多个人进程。。。。
--基本思想:先构造一张有关描述符的表fd_set rdfs,然后调用select函数。
①fd_set 类似申明了一个一维数组 int rdfs[256];
②FD_ZERO 作用相当于数组清零-----00000000000000
③FD_SET(0, &rdfs) 将文件描述符为0的地方(套接字缓存区),置一
FD_SET (LIStenfd,&rdfs)
③如果select 返回值大于0;则遍历数组
for (i= 0; i < ; i++)
{
if (FD_ISSET(i, &rdfs)>0)
{
if (i==0) fgets。。。。
if (i== listenfd)
accept。。。。
}
}
select函数特点:
① 返回 n = select函数(,,,,, 0)就绪的描述符的个数。。
②它把没就绪的位清0. 使用FD_ISSET函数遍历数组,为0的位置调用accept函数
③作用查看缓冲区状态:
服务器模型:
1循环服务器
―――TCP服务器
―――UDP服务器
2.并发服务器
*********IO多路复用并发服务器*********
初始化(socket---bind---listen)
①fd_set 类似申明了一个一维数组 int rdfs[256];
②FD_ZERO 作用相当于数组清零-----00000000000000
② FD_SET(0, &rdfs) 将文件描述符为0的地方(套接字缓存区),置一
FD_SET (LIStenfd,&rdfs)
fd_set f1,f2;
FD_ZERO(&f1)
FD_SET( ,&f1)
while(1)
{
f2 = f1;
if (select(。。。f2.。。。。)>0)
{
for (i= 0; i <= maxfd; i++)
{
if (FD_ISSET(i, &f2)>0)
{
if (i== listenfd)
connfd = accept();
FD_SET(f1);
}
else
{
recv(…….);
}
}
}
}
TCP并发服务器:
while(1){
connfd = accept(。。。);
fork()
if (pid == 0) -------
close(listenfd);
while (1)
{
。。。。。。
}
exit(0);//发送结束信号,异步方式解决僵尸问题
else------父进程
close(connfd);
}
void SignalHander(int signo)异步方式解决僵尸问题
{
while(waitpid(-1, NULL, WHOHANG)>0);
}
网络属性设置:
getsockopt()
setsockopt(sockfd, level, optname, *optval, socklen_t)
level: SOL_SOCKET
optname: SO_BROADCAST
optval: 存放选项值的缓冲区长度地址
socklen_t: 缓冲长度
网络超时检测:
i.在网络通信中,很多操作都会使进程阻塞;TCP套接字中的recv/accept/connet和UDP套接字中的recvform;
超时检测的必要性:
i.避免进程在没有数据时无限的阻塞;ii.当设定时间到时,进程从原来的操作返回继续进行。
三种方法检测网络超时:
方法一:
设置套接字属性:SO_RCVTIMEO
struct timeval timev;
timev.tv_sec = 5;
timev.tv_usec = 0;
…….
setsocketopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &TIMEV, sizeof(timev)); //设置接收超时
………
recv()/recvfrom…..//从套接字读取数据。。。
方法二:
用select检测socket是否准备就绪;
struct fd_set rdfs;
struct timeval tv = {5, 0}; // 设置5秒时间
FD_ZERO(&rdfs);
FD_SET(sockfd, &rdfs);
if (select (sockfd, &rdfs,NULL,NULL,NULL,&tv) > 0) // sockfd就绪
{
recv()/recvfrom()// 从socket中读取数据
}
方法三:
设置定时器timer,捕捉SIGALRM信号
void hander(int signo){ return ;}
struct sigaction act;
sigaction(SIGALRM, NULL,&act);
act.sa_hander = hander();
act.sa_flags &= ~SA_RESTART; //将某位清0
sigaction(SIGALRM, &act, NULL);
alarm(5);
if (recv(,,,) <0) ………
记住一两个错误编号和宏:
errno = 11 是连接超时
errno = 4 是中断system call 宏:EINTR
广播:
1.数据包发送方式只有一个接收方,叫单播;
2.同时发给局域网中的所有主机,称为广播;广播只能用户数据报,即UDP协议,套接字才能广播;
3.广播地址:
广播发送流程: 创建数据报套接字---à设置setsockopt套接字属性---à指定接收方为广播地址--à指定端口信息---à发送数据包
sockfd = socket(; ; );
………..
int on = 1;
setsockopt(socket,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));
……..
sendto(………);
广播的接收流程:创建数据报套接字---à绑定本机IP地址和端口---à绑定的端口必须和发送方指定的端口相同----à等待接收数据;
注意绑定本机IP地址一般为(0.0.0.0) 端口 htonl(INADDR_ANY)
组播:
只用加入某个多播组的主机才能收到数据;
网络地址:ABCD类地址
下面是广播和组播图:
组播的发送方流程:
socket--àsendto---à选择D类地址---àsleep(1)
组播的接收方流程:创建用户数据报套接字--à加入多播组---à绑定本机的IP地址和端口--à等待接收数据
socket--àsetsockopt---àbind---àrecvfrom
libcap编程: