【网络编程】高性能并发服务器源码剖析

 

hello !大家好呀! 欢迎大家来到我的网络编程系列之洪水网络攻击,在这篇文章中,你将会学习到在网络编程中如何搭建一个高性能的并发服务器,并且我会给出源码进行剖析,以及手绘UML图来帮助大家来理解,希望能让大家更能了解网络编程技术!!!

希望这篇文章能对你有所帮助,大家要是觉得我写的不错的话,那就点点免费的小爱心吧!

               

目录

一.网络服务器 

 1.1 普通循环网络服务器

2.2 简单并发网络服务器

2.2.1简单的并发服务器模型

2.2.2使用进程的并发服务器

2.2.3使用线程的并发服务器

2.2.4其他并发服务器模型

二.使用互斥锁实现单线程处理单个客户

2.1 具体步骤

2.2服务器代码模板

三.源码剖析


一.网络服务器 

 1.1 普通循环网络服务器

对于普通的循环网络服务器,其实就是服务器使用循环的方法逐个对客户的连接进行处理,处理完一个连接后再处理下一个连接,其过程如下:

最简单的代码模型我还是给大家:

#include<t_stdio.h>
#include<sys/types.h>          
#include <sys/socket.h>
#include<arpa/inet.h>
#include <sys/socket.h>
#include<ctype.h>
#include<unistd.h>
int main(void){
    struct sockaddr_in serv,cli;
    socklen_t cli_len;
    char buf[128];
    char IP[32];
    //创建一个通讯端点,返回该端点的文件描述符
    //创建一个ipv4的tcp连接端口
    int s_fd=socket( AF_INET ,SOCK_STREAM ,0);

    //需要对server变量成员初始化
    serv.sin_family=AF_INET;
    serv.sin_port=htons(5556);
    serv.sin_addr.s_addr=htonl(INADDR_ANY);
    //将s_fd和本地地址,端口号绑定
    int b=bind(s_fd,(struct sockaddr *)&serv,sizeof(serv));

    if(b==-1)E_MSG("bind",-1);
    if(s_fd==-1)E_MSG("socket",-1);
    //将s_fd设置为被动连接,监听客户端连接的到来 
    //将客户端到来的连接放入未决连接队列中
    //指定未决连接队列的长度
    listen(s_fd,5);
    while(1){
        //从s_fd设备的未连接队列中提取一个进程进行处理
        //返回一个连接描述符,使用这个连接描述符与客户端进行通讯
        int c_fd=accept(s_fd,(struct sockaddr *)&cli,&cli_len);
        if(c_fd==-1)E_MSG("accept",-1);
        //binary--->text
        inet_ntop(AF_INET,&cli.sin_addr,IP,32); 
        printf("client ip: %s\n",IP);
        //代码执行到这里,三次握手以及完成,可以进行数据传输了
        //从c_fd中读取客户端发送过来的请求信息
        int r = read(c_fd,buf,128);
        //处理客户端的请求信息
        int i;
         for(i=0;i<r;i++){
            buf[i]=toupper(buf[i]);
         }
         //将处理结果回送客户端
         write(c_fd,buf,r);
        //关闭本次连接
        close(c_fd);
    }

    return 0;
}

这是最简单的循环服务器代码,功能是将客户传过来的字符串全部转换为大写,这个最简单代码希望大家能全部弄懂,关于里面还有不懂的,可以去看我我前面的博客:[C++/Linux] socket套接字函数-CSDN博客

2.2 简单并发网络服务器

并发网络服务器是指能够同时处理多个客户端请求的网络服务器。这种服务器的设计允许它在任何时刻处理多个客户端的连接和请求,而不会因为某个请求的处理而阻塞其他请求。并发服务器可以提高资源的利用率,增强服务器的响应能力,是现代网络应用的基础。下面我将介绍几种常见的并发网络服务器模型:

2.2.1简单的并发服务器模型

  1. 迭代服务器(Iterative Server): 这种服务器一次处理一个请求。它接收一个请求,处理完该请求,然后才接收下一个请求。这种模型简单,但效率低下,因为它在处理一个请求时不能处理其他请求。

  2. 并发服务器(Concurrent Server): 并发服务器可以同时处理多个请求。这通常通过多进程或多线程来实现。服务器的主进程或线程监听端口,接受新的连接,然后为每个连接创建一个新的进程或线程来处理请求。

2.2.2使用进程的并发服务器

  1. 多进程服务器(Multiprocess Server): 在这个模型中,服务器的主进程监听端口,接受新的连接。每当有一个新的连接时,主进程就fork一个子进程来处理这个连接。每个子进程都可以独立地与客户端通信,处理请求。这种模型的优点是代码简单,缺点是进程创建和销毁的开销较大。

  2. 预派生子进程服务器(Pre-forking Server): 这种服务器在启动时就预先创建一定数量的子进程,每个子进程都阻塞在accept调用上等待新的连接。当一个连接到达时,其中一个子进程接受连接并处理请求。这种模型减少了进程创建的开销,但需要预先分配资源。

这里我手绘一个UML图来帮助大家理解如何利用进程池:

2.2.3使用线程的并发服务器

  1. 多线程服务器(Multithreaded Server): 在这个模型中,服务器的主线程监听端口,接受新的连接。每当有一个新的连接时,主线程就创建一个新的线程来处理这个连接。由于线程共享内存空间,因此它们之间可以更容易地共享数据,但这也带来了同步问题。

  2. 线程池服务器(Thread Pool Server): 线程池服务器预先创建一定数量的工作线程,这些线程都阻塞在等待任务队列上。当一个新的连接到达时,主线程将连接放入任务队列,工作线程从队列中取出连接并处理请求。这种模型可以限制线程的数量,减少线程创建和销毁的开销。

这里我手绘一个UML图来帮助大家理解如何利用线程池:

对于进程和线程大家有不了解的,可以看我前面博客:[C++/Linux] Linux线程详解-CSDN博客

2.2.4其他并发服务器模型

  1. 事件驱动服务器(Event-Driven Server): 事件驱动服务器使用非阻塞IO和事件循环来处理多个客户端连接。服务器注册感兴趣的事件(如可读、可写事件),然后在一个循环中等待这些事件的发生。当事件发生时,服务器处理相应的事件。这种模型可以非常高效地处理大量连接。

  2. 异步IO服务器(Asynchronous I/O Server): 异步IO服务器使用操作系统提供的异步IO接口来处理请求。服务器发起IO操作,然后继续处理其他任务。当IO操作完成时,操作系统通知服务器。这种模型可以充分利用CPU资源,因为它不需要为每个请求都创建一个线程或进程。

二.使用互斥锁实现单线程处理单个客户

这里我们使用互斥锁来对每个进行上锁,实现单客户单进程处理,

2.1 具体步骤

  1. 初始化互斥锁:在服务器启动时,初始化一个互斥锁。

  2. 接受连接:服务器的主线程循环接受客户端连接。

  3. 创建服务线程:每当接受一个新连接时,服务器创建一个新的服务线程来处理该连接。

  4. 加锁处理:在每个服务线程中,当开始处理客户请求之前,首先尝试获取互斥锁。如果互斥锁已被其他线程持有,线程将阻塞直到互斥锁被释放。

  5. 处理请求:线程获取互斥锁后,开始处理客户请求。

  6. 释放锁:处理完请求后,线程释放互斥锁,以便其他线程可以获取该锁并处理下一个请求。

  7. 线程退出:处理完成后,线程退出或返回到池中等待下一个请求

2.2服务器代码模板

#include <pthread.h>

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void *handle_client(void *client_socket) {
    int socket = *(int *)client_socket;

    // 加锁
    pthread_mutex_lock(&lock);

    // 处理客户请求
    // ...

    // 释放锁
    pthread_mutex_unlock(&lock);

    // 关闭客户端套接字
    close(socket);
    return NULL;
}

int main() {
    // 创建监听套接字
    // ...

    while (1) {
        int client_socket = accept(listen_socket, NULL, NULL);

        // 创建线程来处理客户端
        pthread_t thread;
        pthread_create(&thread, NULL, handle_client, &client_socket);
        pthread_detach(thread); // 使线程独立运行
    }

    // 关闭监听套接字
    // ...

    return 0;
}

三.源码剖析

#include<t_stdio.h>
#include<t_file.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include<unistd.h>
#include<time.h>
#include<pthread.h>
#include <string.h>

#define bufferlen 1024 //发送/接收数据缓冲区大小
#define server_port 8888 //端口
#define backlog 5 //监听队列
#define max_pthread 3 //最大线程数

//线程处理业务函数
pthread_mutex_t ALOCK = PTHREAD_MUTEX_INITIALIZER ;//创建互斥量

static void * handle_request(void * argv){
    int s_s = *((int *) argv);
    int s_c;
    struct sockaddr_in from ;
    socklen_t len = sizeof(from);
    for(;;){
        time_t now;
        char buf [bufferlen];
        int n=0;

        pthread_mutex_lock(&ALOCK); //进入互斥区
        s_c = accept(s_s , (struct sockaddr *)&from , &len);//接收请求
        pthread_mutex_unlock(&ALOCK); //离开互斥区
        memset(buf , 0 ,bufferlen);
        n = recv(s_c , buf , bufferlen , 0);//接收数据
        
        if(n > 0 && !strncmp(buf , "TIME" , 4))//判断是否为合法接收数据
        {
            memset(buf ,0 ,bufferlen);
            now = time(NULL);
            sprintf(buf , "%24s\r\n",ctime(&now));//时间写入buf
            send(s_c , buf , strlen(buf) , 0);//发送给客户端

        }
        close(s_c);
    }
    return ;

}

//线程创建函数
static void handle_connect(int s){
    int s_s =s;
    pthread_t thread_do[max_pthread];//创建线程数组
    int i=0;
    //创建线程,每一次创建调用线程处理函数
    for(i = 0; i<max_pthread;i++){
        pthread_create(&thread_do[i] , NULL, handle_request , (void *)&s_s);
    }
    //等待线程结束
    for(i = 0; i<max_pthread;i++){
        pthread_join(thread_do[i] , NULL);

    }

}

int main(int argc ,char * argvp[]){
    int s_s;
    struct sockaddr_in local ;//本地地址
    s_s = socket(AF_INET , SOCK_STREAM , 0);
    memset(&local , 0 , sizeof(local));
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = htonl(INADDR_ANY);
    local.sin_port = htons(server_port);

    bind(s_s , (struct sockaddr *)&local ,sizeof(local));//连接本地地址
    listen(s_s , backlog);//创建监听队列
    handle_connect(s_s);
    close(s_s);

    return 0;
}

这段代码是一个简单的网络服务器示例,它使用了 POSIX 线程(pthread)来处理客户端请求。下面我将逐行解释代码的功能:

#include <t_stdio.h>
#include <t_file.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>
#include <string.h>

这里包含了必要的头文件,包括标准输入输出、文件操作、网络编程、字符串操作、IP地址转换、非阻塞I/O等。

#define bufferlen 1024 //发送/接收数据缓冲区大小
#define server_port 8888 //端口
#define backlog 5 //监听队列
#define max_pthread 3 //最大线程数

定义了一些宏,用于设置缓冲区大小、服务器端口、监听队列大小和最大线程数。

//线程处理业务函数
pthread_mutex_t ALOCK = PTHREAD_MUTEX_INITIALIZER ;//创建互斥量

定义了一个互斥量 ALOCK,用于线程间的同步。

static void * handle_request(void * argv){
    int s_s = *((int *) argv);
    int s_c;
    struct sockaddr_in from ;
    socklen_t len = sizeof(from);
    for(;;){
        time_t now;
        char buf [bufferlen];
        int n=0;

        pthread_mutex_lock(&ALOCK); //进入互斥区
        s_c = accept(s_s , (struct sockaddr *)&from , &len);//接收请求
        pthread_mutex_unlock(&ALOCK); //离开互斥区
        memset(buf , 0 ,bufferlen);
        n = recv(s_c , buf , bufferlen , 0);//接收数据
        
        if(n > 0 && !strncmp(buf , "TIME" , 4))//判断是否为合法接收数据
        {
            memset(buf ,0 ,bufferlen);
            now = time(NULL);
            sprintf(buf , "%24s\r\n",ctime(&now));//时间写入buf
            send(s_c , buf , strlen(buf) , 0);//发送给客户端

        }
        close(s_c);
    }
    return ;
}

handle_request 函数是线程处理业务的核心。它接受一个整数参数 s_s,这是服务器套接字。函数进入一个无限循环,接收客户端的连接(accept 调用),接收数据(recv 调用),处理数据(如果数据是以 “TIME” 开头的,则返回当前时间),然后关闭客户端套接字。

  好啦!到这里这篇文章就结束啦,关于实例代码中我写了很多注释,如果大家还有不懂得,可以评论区或者私信我都可以哦!! 感谢大家的阅读,我还会持续创造网络编程相关内容的,记得点点小爱心和关注哟! 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/543075.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Vue】面试题

vue的组建通信方式 父子关系&#xff1a;props & $emit 、 $parent / $children 、 ref / $refs 、 插槽跨层级关系&#xff1a; provide & inject通用方案&#xff1a;Vuex 或 eventbus 插播&#xff1a;兄弟组建怎么通信&#xff1f; eventbusVuex通过中间件&…

【游戏开发之热更新技术】

游戏开发之热更新技术 热更新技术是指在不重新发布和安装应用的情况下&#xff0c;对已部署的应用程序进行更新和修补的技术。这种技术在现代软件开发中变得越来越重要&#xff0c;因为它能够为用户提供更加及时的服务和更好的体验。以下是一篇关于热更新技术的文章&#xff0…

HttpServletRequest/Response

HttpServletRequest 一些常用类的用法 package Demo;import javax.jws.WebService; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import ja…

【前端】es-drager 图片同比缩放 缩放比 只修改宽 只修改高

【前端】es-drager 图片同比缩放 缩放比 ES Drager 拖拽组件 (vangleer.github.io) 核心代码 //初始宽 let width ref(108)//初始高 let height ref(72)//以下两个变量 用来区分是单独的修改宽 还是高 或者是同比 //缩放开始时的宽 let oldWidth 0 //缩放开始时的高 let o…

蓝桥杯-可获得最小值

前缀和思想: #include<bits/stdc.h>using namespace std;long long n,k;const int N200010;long long a[N],sum[N];int main() {cin>>n>>k;for(int i1;i<n;i)cin>>a[i];sort(a1,a1n);for(int i1;i<n;i){sum[i]sum[i-1]a[i];}long long ans1e18;…

【第十四届蓝桥杯省赛题目】

选择题&#xff1a; 1.设只含根结点的二叉树高度为1&#xff0c;共有62个结点的完全二叉树的高度为&#xff1f; A.4 B.5 C.6 D.7 解析&#xff1a;高度为K的满二叉树 节点数为 2k-1 &#xff0c;如果K6 最多有63个节点 故答案为6 选C 2.C中&#xff0c;bool类型的变量占用字…

LeetCode-热题100:226. 翻转二叉树

题目描述 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a; root [4,2,7,1,3,6,9] 输出&#xff1a; [4,7,2,9,6,3,1] 示例 2&#xff1a; 输入&#xff1a; root [2,1,3] 输出&#xff1a; […

华为ensp中aaa(3a)实现telnet远程连接认证配置命令

作者主页&#xff1a;点击&#xff01; ENSP专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年4月14日18点49分 AAA认证的全称是Authentication、Authorization、Accounting&#xff0c;中文意思是认证、授权、计费。 以下是详细解释 认证&#xff08;Authentic…

创新书荐|《哲学思维》- 信息过载时代保持独立思考12条关键原则

信息过载时代&#xff0c;我们都难以避免被信息投喂&#xff0c;被算法解读&#xff0c;独立思考的能力显得尤为宝贵。英国哲学家朱利安巴吉尼通过深入研究&#xff0c;在新书《哲学思维》中汇集了他20年间对58位全球顶尖哲学家的访谈和资料&#xff0c;精心提炼出了12条至关重…

Gradle 在 Spring 中的使用-ApiHug准备-工具篇-006

&#x1f917; ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱&#xff0c;有温度&#xff0c;有质量&#xff0c;有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin | Marketplace ApiHug …

AI预测小分子与蛋白的相关特征: MegaMolBART, MoFlow,ESM-1, ESM-2

1、小分子:MegaMolBART, MoFlow 1)MegaMolBART https://github.com/NVIDIA/MegaMolBART 基于 SMILES 的小分子药物发现与化学信息学深度学习模型。 2)MoFlow https://github.com/calvin-zcx/moflow 用flow流方式分子生成 2、蛋白质:ESM-1, ESM-2 https://github.com/fa…

21.5k Star , AI 智能体项目OpenDevin:少写代码,多创造(附部署教程)

Aitrainee | 公众号&#xff1a;AI进修生 这是一个旨在复制 Devin 的开源项目&#xff0c;Devin 是一位自主人工智能软件工程师&#xff0c;能够执行复杂的工程任务并在软件开发项目上与用户积极协作。该项目致力于通过开源社区的力量复制、增强和创新 Devin。 Devin 代表了一…

Solana 上创建自己的 SLPToken:简明指南

Solana 定义 Solana 是由 Solana Labs 创建的区块链平台&#xff0c;旨在提供高吞吐量和低延迟的去中心化应用&#xff08;DApps&#xff09;开发环境。它采用一系列创新技术&#xff0c;如 PoH&#xff08;Proof of History&#xff09;共识机制和 Tower BFT&#xff08;BFT …

java:课后笔记wk45

文章目录 1. class1.1 toString()和equals()1.2 overload-constructor1.3 static 2. Wrapper3. Maths4. array5. arrayList 1. class 1.1 toString()和equals() public class People{private int age;private String name;public People(int age, String name){this.age age…

每日一题:无重复字符的最长子串

给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串的长度。 示例 1: 输入: s "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是"abc"&#xff0c;所以其长度为 3。示例 2: 输入: s "bbbbb" 输出: 1 解释: 因为无重…

mysql题目5

tj11&#xff1a; select max(c.teacher_age) 最大的年龄 from tb_teacher c tj12: select a.class_name 班级名称,b.student_name 学生姓名,b.gender 学生性别 from tb_class a join tb_student b on a.class_idb.class_id join tb_teacher c on a.teacher_idc.teacher_id w…

【深度学习实战(1)】如何使用argparse模块设置自己的训练参数

一、argparse模块用法 1、argparse是一个python模块&#xff0c;用途是&#xff1a;命令行选项、参数和子命令的解释。 2、argparse库下载&#xff1a;pip install argparse 3、使用步骤&#xff1a; 导入argparse模块&#xff0c;并创建解释器 添加所需参数 解析参数 二、…

2024年【化工自动化控制仪表】考试内容及化工自动化控制仪表考试总结

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 化工自动化控制仪表考试内容是安全生产模拟考试一点通生成的&#xff0c;化工自动化控制仪表证模拟考试题库是根据化工自动化控制仪表最新版教材汇编出化工自动化控制仪表仿真模拟考试。2024年【化工自动化控制仪表】…

2009-2021年上市公司僵尸企业识别数据(含原始数据+计算代码+计算结果)

2009-2021年上市公司僵尸企业识别数据&#xff08;含原始数据计算代码计算结果&#xff09; 1、时间&#xff1a;2009-2021年 2、指标&#xff1a; 证券代码 、证券简称、上市日期、year、净利润、政府补助、流动负债合计、负债合计、财务费用明细利息支出、资产总计、长期负…

springboot 人大金仓 kingbase-备份还原,命令中带密码

命令带密码参考 Java代码实现国产人大金仓数据库备份还原需求-CSDN博客文章浏览阅读818次&#xff0c;点赞16次&#xff0c;收藏12次。本人在一次项目中&#xff0c;遇到了需要在系统管理中提供给用户备份还原系统数据的功能&#xff0c;由于项目特殊性&#xff0c;项目底层数…