C++项目实战-高并发服务器详析(一)

简介: C++项目实战-高并发服务器详析(一)

BIO模型

阻塞等待:不占用CPU宝贵的时间片,但是每次只能处理一个操作

       

BIO模型: 通过多线程/多进程解决每次只能处理一个操作的缺陷。但是线程/进程本身需要消耗系统资源,并且线程和进程的调度占用CPU.

BIO模型:

       1.线程或进程会消耗资源

        2.线程或进程的调度会消耗CPU

NIO模型

非阻塞、忙轮询:不断的去催,或者说每隔一端时间就去查看有没有操作

                             提高了程序的运行效率、但占用大量CPU资源和系统资源

NIO模型:

       

多进程并发服务器

使用多进程并发服务器时要考虑以下几点:

1.父进程最大文件描述符个数(父进程中需要close关闭accept返回的新文件描述符)

2.系统创建进程个数(与内存大小相关)

3.进程创建过多是否会降低整体服务器性能(进程调度)

父进程:用来专门负责监听,并把任务分给子进程(子进程与客户端进行数据交流)

子进程:与客户端进行数据交流

回收子进程:当每一个子线程结束时,父进程可能还在accept(慢系统调用)

                     子进程需要父进程去回收,子进程结束会发送SIGCHLD信号,默认处理动作是忽略,但是我们需要捕捉,并通过这个信号进程子进程的回收

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include "wrap.h"
#define MAXLINE     80              //最大的连接数
#define SERV_PORT   8080
int main(void)
{
   //创建socket
   int listenfd;
   listenfd = Socket(AF_INET,SOCK_STREAM,0);
   //bind 绑定端口和IP  sockfd
   struct sockaddr_in serveraddr;
   bzero(&serveraddr,sizeof(serveraddr));
   serveraddr.sin_port = htons(SERV_PORT);
   serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
   serveraddr.sin_family = AF_INET; 
   Bind(listenfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
   //listen 设置监听的最大数量 
   listen(listenfd,20); 
   //accept 阻塞等待,连接
   int pid,n,i;
   struct sockaddr_in clientaddr;
   socklen_t clientlen;
   int connfd;
   char buf[MAXLINE];
   char str[INET_ADDRSTRLEN];   // INET --> IPV4   ADDR-->sockaddr  str  len 
   while(1)
   {
        clientlen = sizeof(clientaddr);
        //这个一定要放在while里面,因为多进程,可能连接的客户端不同,connfd是不同的
        connfd = Accept(listenfd,(struct sockaddr *)&clientaddr,&clientlen);
        pid = fork();
        if(pid == 0)    
        {
            //子进程读取数据和处理数据,不用做监听工作。监听工作交给父进程
            Close(listenfd);
            //读写数据,阻塞的(网络IO的数据准备就绪)
            while(1)
            {
                n = read(connfd,buf,MAXLINE);
                if(n == 0)  //说明有客户端关闭了,socket的对端关闭
                {
                    printf("the other side has been closed\n");
                    break;
                }
                //打印连接的客户端信息
                printf("received from %s at PORT %d\n",
                    inet_ntop(AF_INET,&clientaddr.sin_addr,str,sizeof(str)),ntohs(clientaddr.sin_port));
                //业务处理
                for(i = 0;i<n;++i)
                {
                    buf[i] = toupper(buf[i]);   //小写转大写
                }   
                write(connfd,buf,n);
            }
            //客户端关闭,关闭 connfd文件描述符
            Close(connfd);
            return 0;
        }
        else if(pid > 0)        //父线程不需要读写数据,fork之后,父子线程的文件描述符表是相同的
        {
            Close(connfd);
        }
        else        //出错了
        {
            perr_exit("fork");
        } 
   }
    Close(listenfd);
    return 0;
    return 0;
}
#include <stdio.h>
#include <netinet/in.h>
#include "wrap.h"
#include <string.h>
#include <unistd.h>
#define MAXLINE 80
#define SERV_PORT 8080
#define SERV_IP   "127.0.0.1"
int main(void)
{
    //创建socket
    int socketfd;   
    socketfd = Socket(AF_INET,SOCK_STREAM,0);
    //连接connect
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET,SERV_IP,&serveraddr.sin_addr);
    Connect(socketfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
    //读写数据
    char buf[MAXLINE];
    int  n;
    while(fgets(buf,MAXLINE,stdin) != NULL) //fgets从键盘键入数据
    {
        Write(socketfd,buf,sizeof(buf));
        n = Read(socketfd,buf,MAXLINE);         //一个socketfd操作读写两个缓冲区
        if(n == 0)  //对端已经关闭
        {
            printf("the other side has been closed..\n");
            break;
        }
        else
        {
            Write(STDOUT_FILENO,buf,n);     //向标准终端中写入数据
        }
    }
    Close(socketfd);
    return 0;
    return 0;
}
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <error.h>
void perr_exit(const char *s)
{
    perror(s);
    exit(1);
}
int  Accept(int fd,struct sockaddr *sa,socklen_t *salenptr)
{
    int n; 
    //accept:阻塞,是慢系统调用。可能会被信息中断
    again:
    if((n = accept(fd,sa,salenptr)) < 0)
    {
        if((errno == ECONNABORTED) || (errno == EINTR))
        {
            goto again;   //重启
        }
        else
        {
            perr_exit("accept error");
        }
    }
    return n;
}
int  Bind(int fd,const struct sockaddr *sa,socklen_t salen)
{
    int n;
    if((n = bind(fd,sa,salen)) < 0)
    {
        perr_exit("bind error");
    }
    return n;
}
int  Connect(int fd,const struct sockaddr *sa,socklen_t salen)
{
    int n;
    if((n = connect(fd,sa,salen)) < 0)
    {
        perr_exit("connect error");
    }
    return n;
}
int  Listen(int fd,int backlog)
{
    int n;
    if((n = listen(fd,backlog)) < 0)
    {
        perr_exit("listen error");
    }
    return n;
}
int  Socket(int family,int type,int protocol)
{
    int n;
    if((n = socket(family,type,protocol)) < 0)
    {
        perr_exit("socket error");
    }
    return n;
}
ssize_t Read(int fd,void *ptr,size_t nbytes)
{
    ssize_t n;
    again:
    if((n = read(fd,ptr,nbytes)) == -1)
    {
        if(errno == EINTR)//被中断
        {
            goto again;
        }
        else
        {
            return -1;
        }
    }
    return n;
}
ssize_t Write(int fd,const void *ptr,size_t nbytes)
{
    ssize_t n;
    again:
    if((n = write(fd,ptr,nbytes)) == -1)
    {
        if(errno == EINTR)
        {
            goto again;
        }
        else
        {
            return -1;
        }
    }
    return n;
}
int Close(int fd)
{
    int n;
    if((n = close(fd)) == -1)
    {
        perr_exit("close error");
    }
    return n;
}
ssize_t Readn(int fd,void *vptr,size_t n)
{
    size_t nleft;
    ssize_t nread;
    char *ptr;
    ptr = vptr;
    nleft = n;
    while(nleft > 0)
    {
        if((nleft = read(fd,ptr,nleft)) < 0)
        {
           if(errno == EINTR)
           {
                nread = 0;
           }
           else
           {
                return -1;
           }
        }
        else if(nread == 0)
        {
            break;
        }
        nleft -= nread;
        ptr += nread;
    }
    return n-nleft;
}
ssize_t Writen(int fd,const void *vptr,size_t n)
{
    size_t nleft;
    ssize_t nwritten;
    const char *ptr;
    ptr = vptr;
    nleft = n;
    while(nleft > 0)
    {
        if((nwritten = write(fd,ptr,nleft)) <= 0)
        {
            if(nwritten < 0 && errno == EINTR)
            {
                nwritten = 0;
            }
            else
            {
                return -1;
            }
        }
        nleft -= nwritten;
        ptr += nwritten;
    }
    return n;
}
static ssize_t my_read(int fd,char *ptr)
{
    static int read_cnt;
    static char *read_ptr;
    static char read_buf[100];
    if(read_cnt <= 0)
    {
        again:
            if((read_cnt = read(fd,read_buf,sizeof(read_buf))) < 0)
            {
                if(errno == EINTR)
                {
                    goto again;
                }
                return -1;
            }
            else if(read_cnt == 0)
            {
                return 0;
            }
            read_ptr = read_buf;
    }
    read_cnt--;
    *ptr = *read_ptr++;
    return 1;
}
ssize_t Readline(int fd,void *vptr,size_t maxlen)
{
    ssize_t n,rc;
    char c,*ptr;
    ptr = vptr;
    for(n=1;n<maxlen;n++)
    {
        if((rc = my_read(fd,&c)) == 1)
        {
            *ptr++ = c;
            if(c == '\n')
            {
                break;
            }
        }
        else if(rc == 0)
        {
            *ptr = 0;
            return n-1;
        }
        else
        {
            return -1;
        }
    }
    *ptr = 0;
    return n;
} 
#ifndef _WRAP_H_
#define _WRAP_H_
// #include <arpa/inet.h>
// #include <stdlib.h>
// #include <string.h>
// #include <unistd.h>
// #include <stdio.h>
void perr_exit(const char *s);
int  Accept(int fd,struct sockaddr *sa,socklen_t *salenptr);
int  Bind(int fd,const struct sockaddr *sa,socklen_t salen);
int  Connect(int fd,const struct sockaddr *sa,socklen_t salen);
int  Listen(int fd,int backlog);
int  Socket(int family,int type,int protocol);
ssize_t Read(int fd,void *ptr,size_t nbytes);
ssize_t Write(int fd,const void *ptr,size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd,void *vptr,size_t n);
ssize_t Writen(int fd,const void *vptr,size_t n);
ssize_t my_read(int fd,char *ptr);
ssize_t Readline(int fd,void *vptr,size_t maxlen); 
#endif

多线程并发服务器

在使用线程模型开发服务器时需要考虑以下问题:

       1.调整进程内最大文件描述符上限

       2.线程如有共享数据,需要考虑线程同步

       3.服务于客户端线程退出时,退出处理。(退出值、分离态)

       4.系统负载,随着链接客户端增加,导致其它线程不能及时得到CPU

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <pthread.h>
#include <arpa/inet.h>
#include "wrap.h"
#define MAXLINE     80
#define SERV_PORT   8080
//一个连接对应一个客户端的信息
struct s_info
{
    struct sockaddr_in cliaddr;
    int    connfd;
};
//子线程处理的逻辑
void *do_work(void *arg)
{
    int n,i;
    //要进行业务处理的必要的信息  文件描述符  发送方的IP和动态端口
    struct s_info *ts = (struct s_info *)arg;
    //缓存
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN]; //存储点分十进制的 IP
    //设置线程分离
    pthread_detach(pthread_self());
    //业务处理
    while(1)
    {
        n = Read(ts->connfd,buf,MAXLINE);   //阻塞,阻塞状态不会消耗CPU
        if(n == 0)
        {
            printf("the other side has been closed.\n");
            break;
        }
    printf("recevied from %s at PORT %d\n",
                inet_ntop(AF_INET,&(*ts).cliaddr,str,sizeof(str)),ntohs((*ts).cliaddr.sin_port));
    //小写转大写
    for(i = 0;i < n; ++i)
    {
        buf[i] = toupper(buf[i]);
    }
    //传回给客户端
    Write(ts->connfd,buf,n);
    }
    Close(ts->connfd);
    return NULL;
}
int main(void)
{
    int i = 0;
    //创建套接字(监听)
    int listenfd;
    listenfd = Socket(AF_INET,SOCK_STREAM,0);
    //绑定
    struct sockaddr_in  servaddr;  //服务器端套接字
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);   //监听任何合理的IP
    Bind(listenfd,(struct servaddr *)&servaddr,sizeof(servaddr));
    //设置监听
    Listen(listenfd,20);
    //连接
    struct  s_info ts[256];     //最大的连接数 256 
    int connfd;
    struct sockaddr_in cliaddr;
    socklen_t cliaddr_len;
    pthread_t tid;
    while(1)
    {
        cliaddr_len = sizeof(cliaddr);
        connfd = Accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddr_len);
        ts[i].cliaddr = cliaddr;
        ts[i].connfd = connfd;
        //创建工作线程
        pthread_create(&tid,NULL,do_work,(void *)&ts[i]);
        i++;
        //为了安全起见
        if(i == 255)
        {
            break;
        }
    }
    return 0;
}
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "wrap.h"
#define MAXLINE     80
#define SERV_IP     "127.0.0.1"
#define SERV_PORT   8080
int main(void)
{
    //创建socket
    int sockfd;
    sockfd = Socket(AF_INET,SOCK_STREAM,0);
    //连接
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET,SERV_IP,&servaddr.sin_addr);
    Connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
    //通信
    int n;
    char buf[MAXLINE];
    while(fgets(buf,MAXLINE,stdin) != NULL)
    {
        Write(sockfd,buf,sizeof(buf));
        n = Read(sockfd,buf,MAXLINE);
        if(n == 0)
        {
            printf("the other side has been closed\n");
        }
        else
        {
            Write(STDOUT_FILENO,buf,n);
        }
    }
    Close(sockfd);
    return 0;
}

NIO模型

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include "wrap.h"
#define MAXLINE     80
#define SERV_PORT   8080
int main(void)
{
    //创建socket
    int listenfd;
    listenfd = Socket(AF_INET,SOCK_STREAM,0);
    //将listenfd设置为非阻塞
    fcntl(listenfd,F_SETFD,fcntl(listenfd,F_GETFD,0) | O_NONBLOCK);
    //绑定
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);
    Bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
    //设置监听
    Listen(listenfd,20);
    //连接
    struct sockaddr_in cliaddr;
    socklen_t  cliaddr_len = sizeof(cliaddr);
    int connfd;
    char buf[MAXLINE];
    int n,i=0;
    while(1)
    {
        connfd = Accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len);
        n = Read(connfd,buf,MAXLINE);
        fcntl(connfd,F_SETFD,fcntl(connfd,F_GETFD,0) | O_NONBLOCK);
        if(n == -1)
        {
            if(errno == EAGAIN || errno == EWOULDBLOCK)
            {
                continue;   //再次启动read
            }
            //出错  Read会处理
        }
        else if(n == 0)
        {
            break;
        }
        else
        {
            for(i = 0;i<n;++i)
            {
                buf[i] = toupper(buf[i]);
            }
            Write(connfd,buf,n);
        }
    }
    Close(connfd);
    return 0;
}

客户端跟之前几个一样的,就不写了

小结:

       上面无论是多线程还是多进程都是以BIO模型实现的,也就是阻塞的方式,很容易看出,主线程(主进程)负责监听,子线程负责读写数据并进行逻辑处理,即:Reactor模式

       创建线程或进程需要消耗系统资源

       进程(线程)之间的调度(切换)需要消耗系统资源

       线程(线程)的撤销也需要消耗系统资源

     

       与 NIO 模型相比,NIO通过轮询的方式实现的,需要占用大量的CPU  

       无论是NIO模型还是BIO模型,每次只能处理一个连接请求,有没有一种方式能够实现同时监听多个文件描述符,我们再看无论是监听工作还是数据读写或者业务逻辑处理都是由用户进程来完成的,这样一来的话用户进程的大部分时间都用来处理监听工作了,是很不划算的,我们是否可以将监听交给内核来完成呢?这等等的问题,可以通过多路复用技术来解决,多路复用包括三种方式:select、poll、epoll  我们一起来探究他们吧

I/O多路复用(I/O多路转接)

       多路I/O转接服务器也叫做多任务IO服务器。该服务器实现的主旨思想是,不再由应用程序自己监视客户端连接,取而代之由内核替应用程序监视文件文件描述符。

       I/O多路复用使得程序能够同时监听多个文件描述符,能够提高程序的性能,Linux下实现I/O多路复用的系统调用有:select、pool和epoll

       

       举个生活中的例子,帮助理解。我们不妨把server想象成自己,把内核想象成快递站,我们收快递有两种方式,第一是自己去接收快递,第二是让快递站代签。如果我们自己拿快递的话,要么一直等着快递员(这个时候阻塞 BIO),要么每个10分钟去催一次快递员(在每次催完的10分钟内我们可以去扫地,做饭等 NIO)。如果让快递站代签,我们就可以做其他事情,当有快递到了,快递站的工作人员就会通知你,你有快递到了,这时候你可以选择让快递站的工作人员送到你家(异步)你仍然可以继续扫地,也可以直接去快递站点拿(同步),这时候你去拿快递的时间你不能扫地。

相关文章
|
4月前
|
编解码 Linux C语言
探索C++与Live555实现RTSP服务器的艺术(一)
探索C++与Live555实现RTSP服务器的艺术
396 1
|
4月前
|
Linux C++
C++服务器开发之定时器设计方案
定时器应⽤:1.⼼跳检测 2.技能冷却 3.武器冷却 4.倒计时 5.其它需要使⽤超时机制的功能
82 0
C++服务器开发之定时器设计方案
|
4月前
|
编解码 C++ 流计算
探索C++与Live555实现RTSP服务器的艺术(三)
探索C++与Live555实现RTSP服务器的艺术
194 1
|
4月前
|
存储 编解码 算法
探索C++与Live555实现RTSP服务器的艺术(二)
探索C++与Live555实现RTSP服务器的艺术
201 1
|
4月前
|
存储 前端开发 数据处理
c++游戏服务器开发
c++游戏服务器开发
|
4月前
|
前端开发 Java API
构建异步高并发服务器:Netty与Spring Boot的完美结合
构建异步高并发服务器:Netty与Spring Boot的完美结合
|
4月前
|
JSON API 数据库
C++文件服务器项目—数据库表设计 与 后端接口设计—6(三)
C++文件服务器项目—数据库表设计 与 后端接口设计—6(三)
90 0
|
SQL 消息中间件 弹性计算
从ECS到C++软件工程师
从ECS到C++软件工程师
194 0
从ECS到C++软件工程师
|
7天前
|
Cloud Native Java 编译器
将基于x86架构平台的应用迁移到阿里云倚天实例云服务器参考
随着云计算技术的不断发展,云服务商们不断推出高性能、高可用的云服务器实例,以满足企业日益增长的计算需求。阿里云推出的倚天实例,凭借其基于ARM架构的倚天710处理器,提供了卓越的计算能力和能效比,特别适用于云原生、高性能计算等场景。然而,有的用户需要将传统基于x86平台的应用迁移到倚天实例上,本文将介绍如何将基于x86架构平台的应用迁移到阿里云倚天实例的服务器上,帮助开发者和企业用户顺利完成迁移工作,享受更高效、更经济的云服务。
将基于x86架构平台的应用迁移到阿里云倚天实例云服务器参考
|
5天前
|
编解码 前端开发 安全
通过阿里云的活动购买云服务器时如何选择实例、带宽、云盘
在我们选购阿里云服务器的过程中,不管是新用户还是老用户通常都是通过阿里云的活动去买了,一是价格更加实惠,二是活动中的云服务器配置比较丰富,足可以满足大部分用户的需求,但是面对琳琅满目的云服务器实例、带宽和云盘选项,如何选择更适合自己,成为许多用户比较关注的问题。本文将介绍如何在阿里云的活动中选择合适的云服务器实例、带宽和云盘,以供参考和选择。
通过阿里云的活动购买云服务器时如何选择实例、带宽、云盘