服务器模型
tcp服务器:
socket
bind
listen
accept
recv/send
close
1.支持多客户端访问
//单循环服务器
socket
bind
listen
while(1)
{
accept
while(1)
{
recv/send
}
}
close
2.支持多客户端同时访问 (并发能力)
并发服务器
socket
bind
listen
while(1)
{
connf = accept
pid_t pid = fork();
if(pid > 0)
{
continue;
}else if (pid == 0)
{
while(1) //负责 与 客户端通信的
{
recv/send
}
}
}
close
2.1进程
socket
bind
listen
while(1)
{
connf = accept
pid_t pid = fork();
//出错处理
if (pid == 0)
{
while(1) //负责 与 客户端通信的
{
recv/send
}
}
}
close
2.2线程
void *handle_client(void *arg)
{
while(1) //子线程中 负责 与 客户端通信的
{
recv/send
}
}
socket
bind
listen
while(1)
{
connf = accept
pthread_create();
//出错处理
}
close
-------------------------------------------------------------------------
三种服务器模型比较
1.单循环服务器
2.并发服务器
进程
线程
1、简单循环服务器
http
web 服务器,apache--》cgi,php,perl,IIS--》asp,NGIX,Nlighty
while(1)
{
newfd = accept();
recv();
close(newfd);
}
特点:可以接入多个客户端的信息。
缺点:数据通信过程短,客户端只能一次有效。
实时性效果差。
2、fork循环服务器===>每次有链接则fork一个子进程为该
链接处理通信过程,父进程继续等待新链接。
while(1)
{
newfd = accept();
pid = fork()
if(pid == 0)
{
///接收数据
}
if(pid < 0)
{
perror("fork");
return -1;
}
waitpid()
}
特点:可以完成多个进程的实时交互,信息的完整性可以保证。
缺点:回收资源不方便,每次fork 占用系统资源多。
可能出现僵尸进程
多线程:
特点:
创建速度快,调度快
缺点:
线程共享进程资源,稳定性,安全性 较差
3.并发的服务器模型 ---更高程度上的并发
IO模型
阻塞IO
非阻塞IO
1、阻塞IO
用的最多。
读阻塞。
写阻塞。
2、非阻塞IO
-1 errno EAGAIN whild(1){read()break;}忙等待
control
cntl
3、IO多路复用
4、信号驱动IO SIGIO ---异步
5, 并行模型 进程,线程
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
功能:修改指定文件的属性信息。
参数:fd 要调整的文件描述符
cmd 要调整的文件属性宏名称
... 可变长的属性值参数。
返回值:成功 不一定,看cmd
失败 -1;
int fcntl(int fd, int cmd, ... /* arg */ );
//驱动:
//1.驱动程序 ---- 驱使硬件工作起来的程序
让灯亮起来
eg:修改文件的非阻塞属性:
int flag ;
flag = fcntl(fd,F_GETFL,0); ///获取fd文件的默认属性到flag变量中。
flag = flag | O_NONBLOCK; ///将变量的值调整并添加非阻塞属性
fcntl(fd,F_SETFL,flag); ///将新属性flag设置到fd对应的文件生效。
以上代码执行后的阻塞IO将变成非阻塞方式。
信号驱动IO
//signal
1.fcntl --- 设置 信号接受者
flags = fcntl(fd,F_GETFL); //获得当前标志
fcntl(fd,F_SETFL,flags|O_ASYNC); //异步通信的标志
//同步 通信
//异步 通信
2.将该程序 和 SIGIO信号关联起来
fcntl(fd,F_SETOWN,pid);
3.设置信号处理函数
signal
owner //所有者
缺点:
处理的数量有限 (超过了1个不好判断了)
/---client1
/
server <--------------->|-----client2
\
\---client3
| --->A
/---client1(子进程/线程)-- | --->B
/ | --->C
server <--------------->|-----client2(子进程/线程)-- | --->D
\ | --->E
\---client3(子进程/线程)-- | --->F
| --->G
stdin //读
stdout //写
stderr //--写出错信息
IO多路服用
多路 --- 多个输入输出
复用 --- 复用同一个进程或线程
select //linux提供实现Io多路复用函数
#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);
功能:
io多路复用函数
参数:
nfds //三个集合中最大的文件描述符 + 1
readfds //关心的 读操作的 文件描述符的集合
writefds //关心的 写操作的 文件描述符的集合
exceptfds //关心的 异常操作的 文件描述符的集合
timeout //
NULL --- select 阻塞操作
timeout --- 设定超时时间 > 0 等 这么长时间
== 0 不阻塞
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
返回值:
成功 表示 就绪的文件描述符的数量
失败 -1 && errno设置
void FD_CLR(int fd, fd_set *set); //clear --- 将fd从 set中清除
int FD_ISSET(int fd, fd_set *set); //判断 set 表中 fd是否就绪
void FD_SET(int fd, fd_set *set); //将fd设置(添加)到set中
void FD_ZERO(fd_set *set); //表示将set表清零
注意:
1. select 函数在监控到 有fd就绪后,
它会把未就绪的fd清除掉,每次需要重新获取
2. 超时一次后,时间的变量值为为0
如果,需要每次都有超时时间,需要每次重新给值
//单循环服务器
//第1路IO fgets --收键盘 --- 打印出来 --- stdin 能不能读?
//第2路IO 读管道数据 -- 修改 --- 发回去 --- 管道 能不能读?
select
1.建一张表
放 要监控 的文件描述符
readfds();
2.添加 要监控的文件描述符 到表中
FD_SET()
3.select
eg:
1. 管道双向通信:
A.c B.c
---------A2B----------------->
<--------B2A-----------------
A.c代码如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>
//./a.out fifo_A2B fifo_B2A
int main(int argc, const char *argv[])
{
if (argc != 3)
{
printf("Usage: %s <fifo_A2B> <fifo_B2A>\n",argv[0]);
return -1;
}
if(mkfifo(argv[1],0666) < 0 && errno != EEXIST)
{
perror("mkfifo fail");
return -1;
}
if(mkfifo(argv[2],0666) < 0 && errno != EEXIST)
{
perror("mkfifo fail");
return -1;
}
int fd1 = open(argv[1],O_WRONLY);
int fd2 = open(argv[2],O_RDONLY);
if (fd1 < 0 || fd2 < 0)
{
perror("open fail");
return -1;
}
//1.准备表
fd_set readfds;
FD_ZERO(&readfds);
//2.添加要监控的 fd2
FD_SET(0,&readfds);
FD_SET(fd2,&readfds);
int nfds = fd2 + 1;
int i = 0;
char buf[1024];
while (1)
{
fd_set backfds = readfds;
int ret = select(nfds,&backfds,NULL,NULL,NULL);
if (ret < 0)
{
perror("select fail");
return -1;
}
if (ret > 0)
{
for (i = 0; i < nfds; ++i)
{
if (FD_ISSET(i,&backfds))
{
if (i == 0)
{
printf("> ");
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1] = '\0';
write(fd1,buf,strlen(buf)+1);
if (strncmp(buf,"quit",4) == 0)
{
printf("1 exit......\n");
exit(0);
}
}else if (i == fd2)
{
printf("c> ");
read(fd2,buf,sizeof(buf));
printf("%s \n",buf);
if (strncmp(buf,"quit",4) == 0)
{
printf("2 exit......\n");
exit(0);
}
}
}
}
}
}
return 0;
}
B.c代码如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>
//./a.out fifo_A2B fifo_B2A
int main(int argc, const char *argv[])
{
if (argc != 3)
{
printf("Usage: %s <fifo_A2B> <fifo_B2A>\n",argv[0]);
return -1;
}
if(mkfifo(argv[1],0666) < 0 && errno != EEXIST)
{
perror("mkfifo fail");
return -1;
}
if(mkfifo(argv[2],0666) < 0 && errno != EEXIST)
{
perror("mkfifo fail");
return -1;
}
int fd1 = open(argv[1],O_RDONLY);
int fd2 = open(argv[2],O_WRONLY);
if (fd1 < 0 || fd2 < 0)
{
perror("open fail");
return -1;
}
//1.准备表
fd_set readfds;
FD_ZERO(&readfds);
//2.添加要监控的 fd2
FD_SET(0,&readfds);
FD_SET(fd1,&readfds);
int nfds = fd1 + 1;
int i = 0;
char buf[1024];
while (1)
{
fd_set backfds = readfds;
int ret = select(nfds,&backfds,NULL,NULL,NULL);
if (ret < 0)
{
perror("select fail");
return -1;
}
if (ret > 0)
{
for (i = 0; i < nfds; ++i)
{
if (FD_ISSET(i,&backfds))
{
if (i == 0)
{
printf("> ");
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1] = '\0';
write(fd2,buf,strlen(buf)+1);
if (strncmp(buf,"quit",4) == 0)
{
printf("1 exit......\n");
exit(0);
}
}else if (i == fd1)
{
printf("c> ");
read(fd1,buf,sizeof(buf));
printf("%s \n",buf);
if (strncmp(buf,"quit",4) == 0)
{
printf("2 exit......\n");
exit(0);
}
}
}
}
}
}
return 0;
}