Linux网络编程(五)

Linux服务器模型。

Linux服务器模型

分类

  • 循环服务器——同一时刻只能处理一个客户端请求
  • 并发服务器——同一时刻可以处理多个客户端的请求

UDP服务器通常采用循环服务器模型,TCP服务器通常采用并发服务器模型

TCP循环服务器模型

UDP循环服务器模型

UDP并发服务器模型

客户端请求在一个数据报中完成

客户端请求在多个数据报中完成

TCP并发服务器模型

一个子进程对应一个客户端

  • 模型简单
  • 每个子进程完全独立
  • 可以同时处理多个客户端
  • 创建子进程开销较大,适合处理时间长的客户请求,例如FTP文件传输
  • 当客户端数量大、客户请求处理时间短时会大大降低效率,如HTTP服务器

延迟创建子进程

  • 循环与并发混合的服务器模型
  • 处理时间短的客户请求以循环方式完成
  • 处理时间长的客户请求以并发方式完成
  • 减少创建子进程的开销
    • 建立进程表项的开销
    • 复制数据段和堆栈段的开销

预创建子进程(数量固定)

实现思路:服务器建立侦听socket,并创建子进程。所有子进程调用accept,无连接时将睡眠。有连接到来时所有子进程被唤醒。某一个子进程接受连接后,其他进程继续睡眠。

  • 响应速度快,节省创建子进程时间
  • 需要预先估计创建进程的数量
    • 预创建子进程数量少时将导致客户端等待
    • 预创建子进程数量多时将浪费系统资源

一个预先创建子进程的例子。

//
// Created by prime on 17-6-21.
//

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <cstdio>
#include <signal.h>
#define BUFFLEN 1024
#define SERVER_PORT 8888
#define BACKLOG 5
#define PIDNUMB 3

static void handle_connect(int s_s)
{
    int s_c;
    struct sockaddr_in client;
    socklen_t len= sizeof(client);

    while (1)
    {
        s_c=accept(s_s,(struct sockaddr*)&client,&len);
        char buff[BUFFLEN];
        int n=0;
        memset(buff,0, sizeof(buff));
        n=recv(s_c,buff,BUFFLEN,0);
        printf("receive %d bytes",n);
        memset(buff,0, sizeof(buff));
        sprintf(buff,"I receieved!");
        send(s_c,buff,strlen(buff),0);
        close(s_c);
    }
}

void sig_int(int num)
{
    exit(1);
}

int main()
{
    int s_s;
    signal(SIGINT,sig_int);
    struct sockaddr_in server;
    s_s=socket(AF_INET,SOCK_STREAM,0);
    bzero(&server, sizeof(server));

    server.sin_family=AF_INET;
    server.sin_port=htons(SERVER_PORT);
    server.sin_addr.s_addr=htonl(INADDR_ANY);

    bind(s_s,(struct sockaddr *)&server, sizeof(server));
    listen(s_s,BACKLOG);

    pid_t pid[PIDNUMB];
    for (int i=0;i<PIDNUMB;++i)
    {
        pid[i]=fork();
        if(pid[i]==0)
        {
            handle_connect(s_s);
            exit(0);
        }
    }
    while(1);//不让父进程退出,不然所有子进程都被init收养了(其实也没关系,依旧可以运行)。
    close(s_s);
    return 0;
}

预创建子进程(动态)

实现思路1:

  • 服务器建立socket,并创建一定数量子进程
  • 服务器父进程维护所有子进程的状态表,父进程和子进程通过管道通信
  • 子进程接受连接时给父进程发1,关闭连接时发0。
  • 父进程收到1时检查空闲子进程数目是否小于下限,小于下限则创建新的子进程。
  • 父进程收到0时检查空闲子进程数目是否大于下限,大于下限则终止一些子进程。

实现思路:

  • 服务器建立侦听socket,并创建固定数量子进程
  • 服务器父进程维护所有子进程的状态表,父进程和子进程通过UNIX域socket通信
  • 父进程在有空闲子进程时接收连接,将已连接socket描述符传递给空闲子进程
  • 子进程处理客户请求,结束后通知父进程,父进程修改子进程状态

和上面的区别在于,这里由父进程完成连接,然后把描述符传给子进程。

多路复用IO

#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);

该函数可以先对需要操作的描述符进行查询,看是否可读、可写或有错误。

各个参数解释如下:

nfds:一个整型变量,比文件描述符集合中的最大的文件描述符大1。

readfds:可读的描述符集合。函数监视描述符集合中是否有描述符可读,函数返回时将清除集合中不可读的描述符。

writefds:同上,只不过是写的描述符集合。

exceptfds:监视文件集中的任何文件是否发生错误。

timeout:设置最长等待时间,如果为NULL就工作在阻塞模式。

返回值大于零表示三个描述符集合中至少一个不为空,返回0表示超时,-1表示错误。


服务器实现思路:

  • 设置读和写描述符集合,其中读描述符集合包括侦听socket
  • 调用select测试socket描述符是否就绪
  • 侦听socket就绪则接收新连接
  • 其他socket就绪则执行读写操作

要求将所有socket描述符设置为非阻塞方式。

特点:

  • 优点
    • 只需要一个进程来处理所有客户机请求
    • 没有创建和管理进程的开销,系统资源消耗少
    • 没有进程间通信
  • 缺点
    • 服务器必须依次处理所有的请求,编程较复杂
    • 服务器循环处理所有就绪客户端,可能会造成延时较长

适合于请求多但请求内容少的服务器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值