【Socket编程】篇四之并发服务器

前三章中,服务器端一次只能处理一个客户,当多个客户端连接几乎同时到达服务端时,系统内核将其排入队列,accept函数依次处理连接,那么如何使得服务器端能够同时服务于多个客户呢?多进程编程


并发服务器最简单的实现方法就是使用fork函数为每个客户端创建各自进程。

并发服务器的框架如下:

server_socket  = socket()
bind(server_socket,... )
listen(server_socket, 5)
 
while (1)
{
    confd = accept(server_socket, );
    pid_t pid = fork();
    if (pid == 0) { // 子进程
        close(server_socket);
        do_something(connfd); // read or write
        close(connfd);
        exit(0);
    } else if (pid == -1) { // error
 
    } else {       // 父进程
         close(connfd);
    }
 
}
close(server_socket);

思路还是蛮清楚的,据此我们得到如下并发服务端:

#include <cstdio>
#include <unistd.h>
#include <stdlib.h>
#include <cstring>
#include <cassert>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

const int BUFFER_SIZE = 4096;
const int SERVER_PORT = 2222;

int main()
{
	int server_socket;
	char buff[BUFFER_SIZE];
	int n;

	server_socket = socket(AF_INET, SOCK_STREAM, 0);
	assert(server_socket != -1);

	struct sockaddr_in server_addr;
	memset(&server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	assert(bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) != -1);
	assert(listen(server_socket, 5) != -1);
	
	struct sockaddr_in client_addr;
	socklen_t client_addr_len = sizeof(client_addr);

	while(1)
	{
		printf("waiting...\n");
		int connfd = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
		if(connfd == -1)
			continue;
		printf("connect from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
		pid_t pid = fork();
		if(pid == 0)
		{	
			close(server_socket);
			n = recv(connfd, buff, BUFFER_SIZE, 0);
			send(connfd, buff, n, 0);
			close(connfd);
			exit(0);
		}
		else if(pid < 0)
			printf("ERROR\n");
		else
			close(connfd);
	}
	close(server_socket);

	return 0;
}

上述实现的并发服务端,只是一个最简单的雏形,分析可以发现一些问题:

1.每次新连接一个客户,都创建一个进程,其实是很不合理的。原因如下:
频繁的创建和销毁进程开销会很大,因此为每一个客户都创建一个新的进程会很浪费很多服务器资源,如内存、CPU等;

2.每次调用系统函数 recv 或者 send 进行读写数据其实是有缺陷的。比如我们要接收(发送)长度为len的数据,recv(send)成功时返回实际读取(写入)的数据长度,可能小于长度len,因此我们需要多次调用 recv(send),循环读写以保证完整接收(发送)数据;

3.信号处理机制缺乏,上述服务端未有效处理僵尸进程,对于EINTR错误也未有效处理。



据此,得到以下几点建议以供后续学习的方向:

1、使用多线程编程,而不是多进程编程,因为线程是程序执行的最小单元,也称为轻量级进程。多线程编程可以减少处理器的空转时间,支持多处理器以及减少上下文切换开销;

2、使用线程池技术,即预先创建若干个线程,没必要不断动态创建和销毁线程,减少消耗;

3、使用I/O复用技术,应用selectpoll或者epoll提高系统效率;

4、利用高效的事件处理方式:Reactor模式Proactor模式

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值