开源:linux高性能并发服务器代码
Linux开发架构之路 2024-08-07

一.网络服务器

1.1 普通循环网络服务器

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

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

#include#include#include #include#include #include#includeint 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 buf[i]=toupper(buf[i]); } //将处理结果回送客户端 write(c_fd,buf,r); //关闭本次连接 close(c_fd); }  return 0;}

这是最简单的循环服务器代码,功能是将客户传过来的字符串全部转换为大写,这个最简单代码希望大家能全部弄懂

2.2 简单并发网络服务器

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

2.2.1简单的并发服务器模型

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

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

2.2.2使用进程的并发服务器

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

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

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

2.2.3使用线程的并发服务器

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

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

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

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. 线程退出:处理完成后,线程退出或返回到池中等待下一个请求

  8. 2.2服务器代码模板

    #include  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#include#include#include #include #include #include #include#include#include#include  #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 pthread_create(&thread_do[i] , NULL, handle_request , (void *)&s_s); } //等待线程结束 for(i = 0; 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 #include #include #include #include #include #include #include #include #include #include 

这里包含了必要的头文件,包括标准输入输出、文件操作、网络编程、字符串操作、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” 开头的,则返回当前时间),然后关闭客户端套接字。


声明: 本文转载自其它媒体或授权刊载,目的在于信息传递,并不代表本站赞同其观点和对其真实性负责,如有新闻稿件和图片作品的内容、版权以及其它问题的,请联系我们及时删除。(联系我们,邮箱:evan.li@aspencore.com )
0
评论
  • 相关技术文库
  • 单片机
  • 嵌入式
  • MCU
  • STM
  • 基于C51单片机实现汽车座椅自动控制系统的软硬件设计

    引言 随着人们生活水平的提高,对汽车座椅的舒适性要求也越来越高,要求对汽车座椅地调节能够更加简单、方便、快捷。目前,汽车座椅位置的调节多采用基于手动调节方式的机械和电动控制两种方式。汽车座椅位置的调节...

    昨天
  • MCS51单片机程序设计时堆栈的计算方法解析

    用C语言进行MCS51系列单片机程序设计是单片机开发和应用的必然趋势。Keil公司的C51编译器支持经典8051和8051派生产品的版本,通称为Cx51。应该说,Cx51是C语言在MCS51单片机上的扩展,既有C语言的共性,又有它自己...

    昨天
  • 51单片机定时器工作原理及用法

    TMOD : 控制定时器的工作方式。8个bit,高四位 bit 控制 T1,、低四位 bit 控制 T0。因为定时器有4种工作方式;TMOD = 0x00(工作方式0),TMOD = 0x01(工作方式0),TMOD = 0x02(工作方式2),TMOD = 0x03(工作方式3)。...

    昨天
  • 51单片机学习单片机之路总结

    学习单片机有一学期了,现在也由51转到STM32了。一直想对51的学习做一个总结。也希望对别人有一些启发。也给后学者提供一些建议。当然本文是我对自己学习过程的总结,若有不对的地方,还请高手指出。 我想,再看本...

    昨天
  • hot51增强型单片机开发板原理图

    功能要求: 一):绿灯25s倒计时,绿灯过度红灯有5s黄灯时间,红灯25s后直接跳绿灯。 二):按键按下模拟闯红灯输入,产生5s蜂鸣器鸣叫。 开发环境: 软件:Keil uVision4 硬件:HOT51增强型单片机开发板 程序代码:...

    前天
  • 51单片机的延时子程序

    延时程序在单片机编程中使用非常广泛,但一些读者在学习中不知道延时程序怎么编程,不知道机器周期和指令周期的区别,不知道延时程序指令的用法, ,本文就此问题从延时程序的基本概念、机器周期和指令周期的区别和联系...

    前天
  • 什么是Flash盘?Flash盘的结构是什么样的?

    Flash是大家常使用的存储之一,对于Flash,大家或多或少有所了解。上篇文章中,小编对Flash闪存的类型有所介绍。为继续增进大家对Flash的认识,本文将对Flash盘、Flash盘结构以及Flash读写操作予以介绍。如果你对本...

    前天
  • 深谈嵌入式系统,嵌入式系统是如何组成的?

    嵌入式系统在生活中有诸多应用,大家对于嵌入式系统或多或少有所耳闻。在前两篇文章中,小编对嵌入式系统进行过详细介绍。为继续增进大家对嵌入式系统的认识,本文将对嵌入式系统的组成加以说明。如果你对嵌入式系...

    06-27
  • 嵌入式系统秘籍共享,最全嵌入式系统解析

    嵌入式系统的应用十分广泛,因此越来越多的人学习嵌入式系统。由此,在学习嵌入式系统之前,我们应当对嵌入式系统具备一些认识。所以在本文余下部分,小编将对嵌入式系统进行全面解析。如果你对嵌入式系统具有兴趣...

    06-27
  • 51单片机超声波测距程序详解

    51单片机超声波测距程序详解 超声波四通道测距:超声波测距实现分为三大块: 其一是12864带字库的液晶驱动程序: 代码如下: /////////////////12864驱动程序/////////////////////////// //1写数据 void WriteDat...

    06-25
  • 51系列单片机的引脚图

    51系列单片机的引脚图 端子介绍 l P0.0~P0.7 P0口8位双向口线(在引脚的39~32号端子)。 l P1.0~P1.7 P1口8位双向口线(在引脚的1~8号端子)。 l P2.0~P2.7 P2口8位双向口线(在引脚的21~28号端子)。 l P3.0~P3.7 P2口8...

    06-25
  • 51单片机串口通信需要加超时中断吗?

    接收数据时,超过一定时间就算出错. 这个超时的时间是单片机自己算出的吗?超时的时间是由编程序的人定的,他定多长就多长从一段程序开始 实现电脑向 单片机发送一些数据,单片机返回Iget +数据 #include #define u...

    06-25
下载排行榜
更多
评测报告
更多
广告