一、常见的服务器类型
在网络程序里面,通常都是一个服务器处理多个客户端。为了处理多个客户端的请求, 服务器端程序有不同的处理方式。
1. 迭代服务器
大多数UDP都是迭代运行,服务器等待客户端的数据,收到数据后处理该数据,送回其应答,在等待下一个客户端请求。
2. 并发服务器
- 并发服务器是指在同一个时刻可以响应多个客户端的请求
- 本质是创建 多进程/多线程 ,对多数用户的信息进行处理
- UDP协议一般默认是不支持多线程并发的 ,因为默认UDP服务器只有一个sockfd,所有的客户端都是通过同一个sockfd进行通信的。udp使用一个socket,如何做到做并发呢?
3. UDP并发服务器使用的场景
当UDP协议针对客户请求的处理需要消耗过长的时间时,我们期望UDP服务器具有某种形式的并发性。
二、UDP多进程并发服务器
1. 场景设计
多个udp客户端需要先验证密钥是否正确后,才允许进行数据交互。假设密钥为"root"。(类似于登录功能)服务器接收到客户端信息,需要考虑两种情况<1>A用户的密钥验证请求消息<2>B用户的数据交互接收消息
2. 框架图
3. 使用场景
当UDP服务器与客户端交互多个数据报。问题在于每个客户都是往服务器端的同一个的端口发送数据,并用的同一个sockfd。并发服务器的每一个子进程如何正确区分每一个客户的数据报(涉及到进程的调度问题,如何避免一个子进程读取到不该它服务的客户发送来的数据报)。
解决的方法是服务器(知名端口)等待客户的到来,当一个客户到来后,记下其IP和port,然后服务器fork一个子进程,建立一个socket再bind一个随机端口,然后建立与客户端的连接,并处理该客户的请求。父进程继续循环,等待下一个客户的到来。 在tftpd中就是使用这种技术的 。
UDP并发服务器的多进程并发是一种常见的并发处理方式,通过为每个客户端请求创建一个独立的子进程来实现并发处理。以下是关于UDP并发服务器多进程并发的详细讲解:
三、
1. 基本原理
UDP是一种无连接的协议,数据包独立发送,服务器不需要维护与客户端的长期连接。在多进程并发模型中,父进程负责监听UDP端口,接收客户端的请求,然后为每个请求创建一个子进程来处理具体的通信。
2. 实现步骤
(1)父进程初始化
父进程创建一个UDP套接字,并绑定到指定的IP地址和端口。然后进入循环,等待客户端的请求
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = INADDR_ANY;
bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
2)接收请求并创建子进程
父进程接收客户端发送的数据包,然后通过fork()
创建一个子进程来处理该请求
while(1) {struct sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);char buffer[1024];int n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&client_addr, &client_addr_len);if(n > 0) {pid_t pid = fork();if(pid == 0) { // 子进程close(sockfd); // 子进程关闭父进程的套接字// 子进程处理客户端请求handle_request(buffer, client_addr, client_addr_len);exit(0);}}
}
(3)子进程处理请求
子进程接收父进程传递的客户端信息后,使用自己的套接字与客户端进行通信
void handle_request(char* buffer, struct sockaddr_in client_addr, socklen_t client_addr_len) {int new_sockfd = socket(AF_INET, SOCK_DGRAM, 0);// 子进程绑定到一个新的端口struct sockaddr_in new_server_addr;memset(&new_server_addr, 0, sizeof(new_server_addr));new_server_addr.sin_family = AF_INET;new_server_addr.sin_port = htons(0); // 系统自动分配端口new_server_addr.sin_addr.s_addr = INADDR_ANY;bind(new_sockfd, (struct sockaddr*)&new_server_addr, sizeof(new_server_addr));// 向客户端发送响应sendto(new_sockfd, "Hello from server", 18, 0, (struct sockaddr*)&client_addr, client_addr_len);close(new_sockfd);
}
3. 优点
-
简单易实现:多进程模型相对简单,易于理解和实现。
-
资源隔离:每个子进程独立运行,互不干扰,即使一个子进程崩溃,也不会影响其他子进程。
4. 缺点
-
进程创建开销大:每次创建子进程都需要消耗系统资源,频繁创建和销毁进程可能导致性能下降。
-
资源占用高:每个子进程都需要占用一定的系统资源,包括内存和文件描述符。