赞
踩
hello !大家好呀! 欢迎大家来到我的网络编程系列之洪水网络攻击,在这篇文章中,你将会学习到在网络编程中如何搭建一个高性能的并发服务器,并且我会给出源码进行剖析,以及手绘UML图来帮助大家来理解,希望能让大家更能了解网络编程技术!!!
希望这篇文章能对你有所帮助,大家要是觉得我写的不错的话,那就点点免费的小爱心吧!
目录
对于普通的循环网络服务器,其实就是服务器使用循环的方法逐个对客户的连接进行处理,处理完一个连接后再处理下一个连接,其过程如下:
最简单的代码模型我还是给大家:
- #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博客
并发网络服务器是指能够同时处理多个客户端请求的网络服务器。这种服务器的设计允许它在任何时刻处理多个客户端的连接和请求,而不会因为某个请求的处理而阻塞其他请求。并发服务器可以提高资源的利用率,增强服务器的响应能力,是现代网络应用的基础。下面我将介绍几种常见的并发网络服务器模型:
迭代服务器(Iterative Server): 这种服务器一次处理一个请求。它接收一个请求,处理完该请求,然后才接收下一个请求。这种模型简单,但效率低下,因为它在处理一个请求时不能处理其他请求。
并发服务器(Concurrent Server): 并发服务器可以同时处理多个请求。这通常通过多进程或多线程来实现。服务器的主进程或线程监听端口,接受新的连接,然后为每个连接创建一个新的进程或线程来处理请求。
多进程服务器(Multiprocess Server): 在这个模型中,服务器的主进程监听端口,接受新的连接。每当有一个新的连接时,主进程就fork一个子进程来处理这个连接。每个子进程都可以独立地与客户端通信,处理请求。这种模型的优点是代码简单,缺点是进程创建和销毁的开销较大。
预派生子进程服务器(Pre-forking Server): 这种服务器在启动时就预先创建一定数量的子进程,每个子进程都阻塞在accept调用上等待新的连接。当一个连接到达时,其中一个子进程接受连接并处理请求。这种模型减少了进程创建的开销,但需要预先分配资源。
这里我手绘一个UML图来帮助大家理解如何利用进程池:
多线程服务器(Multithreaded Server): 在这个模型中,服务器的主线程监听端口,接受新的连接。每当有一个新的连接时,主线程就创建一个新的线程来处理这个连接。由于线程共享内存空间,因此它们之间可以更容易地共享数据,但这也带来了同步问题。
线程池服务器(Thread Pool Server): 线程池服务器预先创建一定数量的工作线程,这些线程都阻塞在等待任务队列上。当一个新的连接到达时,主线程将连接放入任务队列,工作线程从队列中取出连接并处理请求。这种模型可以限制线程的数量,减少线程创建和销毁的开销。
这里我手绘一个UML图来帮助大家理解如何利用线程池:
对于进程和线程大家有不了解的,可以看我前面博客:[C++/Linux] Linux线程详解-CSDN博客
事件驱动服务器(Event-Driven Server): 事件驱动服务器使用非阻塞IO和事件循环来处理多个客户端连接。服务器注册感兴趣的事件(如可读、可写事件),然后在一个循环中等待这些事件的发生。当事件发生时,服务器处理相应的事件。这种模型可以非常高效地处理大量连接。
异步IO服务器(Asynchronous I/O Server): 异步IO服务器使用操作系统提供的异步IO接口来处理请求。服务器发起IO操作,然后继续处理其他任务。当IO操作完成时,操作系统通知服务器。这种模型可以充分利用CPU资源,因为它不需要为每个请求都创建一个线程或进程。
这里我们使用互斥锁来对每个进行上锁,实现单客户单进程处理,
初始化互斥锁:在服务器启动时,初始化一个互斥锁。
接受连接:服务器的主线程循环接受客户端连接。
创建服务线程:每当接受一个新连接时,服务器创建一个新的服务线程来处理该连接。
加锁处理:在每个服务线程中,当开始处理客户请求之前,首先尝试获取互斥锁。如果互斥锁已被其他线程持有,线程将阻塞直到互斥锁被释放。
处理请求:线程获取互斥锁后,开始处理客户请求。
释放锁:处理完请求后,线程释放互斥锁,以便其他线程可以获取该锁并处理下一个请求。
线程退出:处理完成后,线程退出或返回到池中等待下一个请求
- #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” 开头的,则返回当前时间),然后关闭客户端套接字。
好啦!到这里这篇文章就结束啦,关于实例代码中我写了很多注释,如果大家还有不懂得,可以评论区或者私信我都可以哦!! 感谢大家的阅读,我还会持续创造网络编程相关内容的,记得点点小爱心和关注哟!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。