要想支持多个客户端同时访问,就必须解决两个问题
1.多个客户端
2.同时访问
单循环服务器(tcp)
docket
bind
listen
while(1)
{
accept
while(1)
recv
send
}
特点:实现简单,可以处理多个客户端
2.无法同时处理
多进程服务器
socket
bind
listen
while(1)
{
confd = accept
pit_t pid = fork();
if(pid == 0)
{
while(1)
{
recv
send
}
}
}
服务器:不断地处理客户端请求,但是accept//本身阻塞
通信过程中如果与客户端通信耗时,甚至是死循环,导致
整个服务器流程会卡在通信过程,没法处理新的客户端
注意:
1.将来要考虑回收资源
2.进程的切换和开销大
多线程服务器
void *do_hander (void * arg)
{
while(1)
{
recv;
send;
if()
{
close;
break;
}
}
}
socket
bind
litsten
while(1)
{
confd = accept
pthread_t tid;
ret=pthread_create()
pthread_detach();
}
注意:相比进程,效率确实提高了
但多线程回收资源,资源竞争;
IO多路复用
基本思想:尽可能的减少要创建的数量;将进程或者线程复用
IO多路复用的最大优势:
不需要创建和维护进程;系统开销小
IO模型
阻塞IO(有数据直接读取,没数据就一直等)
recv,read,fgets,scanf
特点:简单易用
非阻塞IO
读取数据
没数据时不阻塞,直接返回;但是他要轮询
说明:
一般相关的文件操作默认都是阻塞
可通过fcntl来设置对应的io模型
int fcntl(int fd, int cmd, ... /* arg */ );
修改文件的非阻塞属性:
int flag ;
flag = fcntl(fd,F_GETFL,0); ///获取fd文件的默认属性到flag变量中。
flag = flag | O_NONBLOCK; ///将变量的值调整并添加非阻塞属性
fcntl(fd,F_SETFL,flag); ///将新属性flag设置到fd对应的文件生效。
以上代码执行后的阻塞IO将变成非阻塞方式。
信号驱动IO
有信号来表示有数据可用;此时信号处理函数中运用
步骤:
1.为内核发送通知信号安装一个信号处理历程;如signal(SIGIO,handler)
2.设定文件描述符的属主,就是当文件描述符上可执行IO时会接受到
这个通知信号的进程或线fcntl(fd,F_SETOWN,pid);
3.设定O_NONBLOCK标志能非阻塞IO;
4.打开O_ASYNC标志信号使能信号驱动IO;
flags=fcntl(fd,F_GETTFL)
fcntl(fd,F_SETFL,flags|O_ASYNC|O_NONBLOCK);
问题:如果是多路IO无法处理;
多路复用IO
select
int select( int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout)
nfds:最大文件描述符+1;
readfds:存放关心读操作的那些文字描述符
*writefds:存放关心写操作的那些文字描述符
exceptfds,:存放关心出错的那些文字描述符
timeout:设置一个超时时间//NULL表示阻塞;//0表示非阻塞
注意:
- select 函数在一次就绪后,会将表中未就绪的文件描述符清除
select的调用一般要注意几点:
① readfds等是指针结果参数,会被函数修改,
所以一般会另外定义一个all read_fdset,
保持全部要监听读的句柄,将它的拷贝传递给select函数,
返回可读的句柄集合,类型fdset支持赋值运算符=;
② 要注意计算nfds,当新增监听句柄时比较容易修改,
当减少监听句柄时较麻烦些,
如果要精确修改需要遍历或者采用最大堆等数据结构维护这个句柄集,
以方便的找到第二大的句柄,或者干脆在减少监听句柄时不管nfds;
3 4 100 101 //4?
③timeout如果为NULL表示阻塞等,
如果timeout指向的时间为0,表示非阻塞,否则表示select的超时时间;
④ select返回-1表示错误,返回0表示超时时间到没有监听到的事件发生,
返回正数表示监听到的所有事件数(包括可读,可写,异常),
通常在处理事件时 会利用这个返回值来提高效率,
避免不必要的事件触发检查。
(比如总共只有一个事件,已经在可读集合中处理了一个事件,
则可写和异常就没必要再去遍历句柄集 判断是否发生事件了);
⑤ Linux的实现中select返回时会将timeout修改为剩余时间,
所以重复使用timeout需要注意。
//缺点:
select的缺点在于:
① 由于描述符集合set的限制,每个set最多只能监听FD_SETSIZE(在Linux上是1024)个句柄
(不同机器可能不一样);
② 返回的可读集合是个fdset类型,
需要对所有的监听读句柄一一进行FD_ISSET的测试来判断是否可读;
③ nfds的存在就是为了解决select的效率问题
(select遍历nfds个文件描述符,
判断每个描述符是否是自己关心的,对关心的描述符判断是否发生事件)。
但是解决不彻底,
比如: 如果只监听0和1000两个句柄,select需要遍历1001个句柄来检查事件。
//缺点:
(1).文件描述符数量受限
(2).底层监控是通过遍历实现的,效率不高
(3).返回值的表示的是就绪个数,
还需要应用层进一步处理
心得:
天高地炯,觉宇宙之无穷
兴尽悲来,识盈虚之有数
望长安于日下,目吴会于人间
地势极而南溟深,天柱高而北辰远
关山难越,谁悲失路之人
萍水相逢,尽是他乡之客