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)。如果让快递站代签,我们就可以做其他事情,当有快递到了,快递站的工作人员就会通知你,你有快递到了,这时候你可以选择让快递站的工作人员送到你家(异步)你仍然可以继续扫地,也可以直接去快递站点拿(同步),这时候你去拿快递的时间你不能扫地。

相关文章
|
6月前
|
编解码 Linux C语言
探索C++与Live555实现RTSP服务器的艺术(一)
探索C++与Live555实现RTSP服务器的艺术
529 1
|
1月前
|
缓存 NoSQL Ubuntu
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
55 3
|
1月前
|
Linux C语言 C++
vsCode远程执行c和c++代码并操控linux服务器完整教程
这篇文章提供了一个完整的教程,介绍如何在Visual Studio Code中配置和使用插件来远程执行C和C++代码,并操控Linux服务器,包括安装VSCode、安装插件、配置插件、配置编译工具、升级glibc和编写代码进行调试的步骤。
193 0
vsCode远程执行c和c++代码并操控linux服务器完整教程
|
1月前
|
存储 监控 NoSQL
Redis的实现二: c、c++的网络通信编程技术,让服务器处理多个client
本文讨论了在C/C++中实现服务器处理多个客户端的技术,重点介绍了事件循环和非阻塞IO的概念,以及如何在Linux上使用epoll来高效地监控和管理多个文件描述符。
27 0
|
6月前
|
编解码 C++ 流计算
探索C++与Live555实现RTSP服务器的艺术(三)
探索C++与Live555实现RTSP服务器的艺术
227 1
|
6月前
|
存储 编解码 算法
探索C++与Live555实现RTSP服务器的艺术(二)
探索C++与Live555实现RTSP服务器的艺术
269 1
|
6月前
|
消息中间件 Java Linux
2024年最全BATJ真题突击:Java基础+JVM+分布式高并发+网络编程+Linux(1),2024年最新意外的惊喜
2024年最全BATJ真题突击:Java基础+JVM+分布式高并发+网络编程+Linux(1),2024年最新意外的惊喜
|
5月前
|
缓存 NoSQL Java
Java高并发实战:利用线程池和Redis实现高效数据入库
Java高并发实战:利用线程池和Redis实现高效数据入库
483 0
|
3月前
|
监控 算法 Java
企业应用面临高并发等挑战,优化Java后台系统性能至关重要
随着互联网技术的发展,企业应用面临高并发等挑战,优化Java后台系统性能至关重要。本文提供三大技巧:1)优化JVM,如选用合适版本(如OpenJDK 11)、调整参数(如使用G1垃圾收集器)及监控性能;2)优化代码与算法,减少对象创建、合理使用集合及采用高效算法(如快速排序);3)数据库优化,包括索引、查询及分页策略改进,全面提升系统效能。
48 0
|
5月前
|
存储 NoSQL Java
探索Java分布式锁:在高并发环境下的同步访问实现与优化
【6月更文挑战第30天】Java分布式锁在高并发下确保数据一致性,通过Redis的SETNX、ZooKeeper的临时节点、数据库操作等方式实现。优化策略包括锁超时重试、续期、公平性及性能提升,关键在于平衡同步与效率,适应大规模分布式系统的需求。
169 1