关于linux网络编程——3
一、单循环服务器
特点:
1.可以处理多个客户端 (不能同时)
2.效率不高
//单循环服务器:
socket
bind
listen
while (1)
{connfd = accept();//通信
}特点:简单 可以处理多客户端 不能同时
二、并发服务器 --- 同时可以处理多个客户端
1、设置一个选项(开启一个功能) ---让地址重用
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
功能:设置socket的属性参数: @sockfd --- 要设置的socket @level --- 设置socket层次 //socket本身 tcp ip @optname --- 选项名字 @optval --- 选项值 @optlen --- 长度 设置一个选项(开启一个功能) ---让地址重用 地址重用:
//并发服务器 --- 同时处理多个客户端
socket
bind
listen
while (1)
{
connfd = accept();
//通信
//进程
// ---专门创建一个子进程 --- 负责通信过程
//线程
// ---专门创建一个线程 --- 负责通信过程
}
//并发服务器 --- 同时处理多个客户端
socket
bind
listen
while (1)
{connfd = accept();//通信 //进程// ---专门创建一个子进程 --- 负责通信过程 //线程// ---专门创建一个线程 --- 负责通信过程
}
将两个任务:
1.负责完成连接操作
accept
2.负责通信的
多进程 和 多线程
并发服务器 ---多进程方式的效率 肯定 低于 多线程
三、更高效的方式:多路IO复用
多路IO
I --- input
O --- output
复用
一个进程
一个线程 可以处理多路IO
//这多路IO 复用了这个一个进程 或是线程
比较:
//并发服务器 --- 同时处理多个客户端
socket
bind
listen
while (1)
{
connfd = accept();
//通信
//进程
// ---专门创建一个子进程 --- 负责通信过程
//线程
// ---专门创建一个线程 --- 负责通信过程
}
//一路io
accept
//另一路io
负责通信多路IO
把负责通信的那个进程 或 线程
用来处理多个客户端的通信/多进程或多线程的并发服务器
一个客户端 ----> 一个进程 或 线程
//IO多路复用
N个客户端 -----> 一个进程 或 线程提高并发的能力
socket
|
bind
|
listen
|
accept
|
do_client1 do_client2 do_client3
socket
|
bind
|
listen
|
accept
|
do_client1 do_client2 do_client3
| | |
cli1 cli2 cli3 cli4 cli5 cli6 cli7 cli8 cli9
四、多路IO复用功能的相关函数
select
poll
epoll
五、IO处理的模型
阻塞IO模型:i -- 读 scanfgetcharfgetsreadrecvo -- 写写有阻塞 //管道 读端存在 写管道
阻塞IO模型:
以读为例:
读操作--->内核中读取数据--->如果没有数据,一直等到,直到有数据--->之后将数据带回到用户空间
非阻塞IO模型:
以读为例:
读操作--->内核中读取数据--->如果没有数据,不等,直接返回用户空间
阻塞 与 非阻塞 是操作对象的特性
5.1设置非阻塞的函数 fcntl
//设置非阻塞
fcntl
#include <unistd.h>
#include <fcntl.h>int fcntl(int fd, int cmd, ... /* arg */ );
功能:维护文件描述符
参数:@fd --- 要操作的fd@cmd --- 要做的一些操作 //command@... --- 可变参数
返回值 取决于所做的操作
int printf(const char *format, ...);printf("hello world!\n");
printf("a = %d\n",a);
printf("a = %d b = %c\n",a,b);设置非阻塞
int flags;
flags = fcntl(fd,F_GETFL,0); //读
flags = flags | O_NONBLOCK; //修改
fcntl(fd,F_SETFL,flags); //写 点对点聊天client <----------> serverfgetswrite/send -----> recv/read printf fgetsread<------writeprintf
5.2信号驱动IO:
信号驱动IO//signal
1.fcntl --- 设置 信号接受者 flags = fcntl(fd,F_GETFL); //获得当前标志 fcntl(fd,F_SETFL,flags|O_ASYNC); O_ASYNC //开启异步操作 //同步 //A->B//异步 //A//B
2.将该程序 和 SIGIO信号关联起来 fcntl(fd,F_SETOWN,pid);//OWNER
3.设置信号处理函数 signal(SIGIO)如果 程序要处理多路IO 缺点:处理的数量有限
IO多路复用方式
//可以处理多路IO
//不需要 阻塞
//不需要 轮询
因此
六、IO多路复用方式:
6.1slect函数;
select
0 --- 读
1 --- 写
2 --- 出错int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);功能:实现IO多路复用 @nfds //是关心的文件描述符中最大的那个文件描述符 + 1
@readfds //代表 要关心 的 读操作的文件描述符的集合
@writefds //代表 要关心 的 写操作的文件描述符的集合
@exceptfds //代表 要关心 的 异常的文件描述符的集合
@timeout //超时 --- 设置一个超时时间 //NULL 表示select是一个阻塞调用 //设置时间 // 0 --- 非阻塞 // n (>0) --- 阻塞n这么长时间 //注意: 这个值 每次 自动在往下减少 --直到减少到0struct timevalstruct timeval {long tv_sec; /* seconds */long tv_usec; /* microseconds */};struct timeval t = {0,0};返回值:成功 返回就绪的文件描述符的数量 失败 -1
6.2select函数使用的整体思路:
//select使用时的整体思路:
1.建立一张表 监控fd_set readfds; //一张表 FD_ZERO(&readfds); //清空这张表
2.将要监控的文件描述符 添加表中 FD_SET(0,&readfds);FD_SET(fd,&readfds);3. nfds = fd + 1;
select(nfs,&readfds,NULL,NULL,NULL)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集合清空
6.3使用:
多路IO复用的服务器: //实现并发 --- 可以同时处理多个客户端listenfd = socket
bind
listen
//connfd = accept
//1.准备表fd_set readfds;FD_ZERO(&readfds);
//2.添加要监控的文件描述符 FD_SET(listenfd,&reafds);
//3.准备参数 maxfds = listenfd + 1;fd_set backfds;
while (1)
{ backfds = readfds;int ret = select(maxfds,&backfds,NULL,NULL,NULL);if (ret > 0){int i = 0;for (i = 0; i < maxfds;++i){if (FD_ISSET(i,&backfds)){if (i == listenfd) //连接 {connfd = accept();//connfd 要被添加到 监控表FD_SET(connfd,&readfds);if (connfd + 1 > maxfds)maxfds = connfd + 1;}else //负责与客户端通信 {// i = ?//fd 此时就绪 printf("buf = %s\n",buf);if (strncmp(buf,"quit",4) == 0){FD_CLR(i,&readfds); //清除对应的客户端的fdclose(i); }}}}}
}//优化
int i = maxfds;
for (i = maxfds-1; i >= 0; --i)
{if (FD_ISSET(i,&readfds)){maxfds = i + 1;}
}
不足:
1. 最大监听数受限:`FD_SETSIZE` 默认 1024(Linux)
2. 每次调用需重置 fd_set:内核会修改集合,必须每次重新 `FD_SET`
3. 用户态与内核态拷贝开销大
4. 返回后仍需遍历所有 fd 才能知道哪个就绪
5. 效率随 fd 数量增长下降明显