Linux 三种Socket网络通信模型(多进程、多线程、I\O复用)性能测试
服务器设计技术有很多,按使用的协议来分有 TCP 服务器和 UDP 服务器,按处理方式来分有循环服务器和并发服务器。
目前最常用的服务器模型有:
·循环服务器:服务器在同一时刻只能响应一个客户端的请求
·并发服务器:服务器在同一时刻可以响应多个客户端的请求
一个好的服务器,一般都是并发服务器(同一时刻可以响应多个客户端的请求)。并发服务器设计技术一般有:多进程服务器、多线程服务器、I/O复用服务器等。
本文实现三种Socket网络通信模型(多进程、多线程、I\O复用),并进行对比分析。
测试步骤:
1.分别编写3种socket网络通信服务器模型:多进程、多线程、I\O复用(select、epoll)。并测试通信是否正常。
2.编写shell脚本文件MultClient.sh,模拟100个客户器多并发。
3.编写shell脚本CountKillClient.sh,统计有客户端连接成功数量,并关闭相应客户端。
4.测试不同模型同时连接多少个客户端。
5.分析模型。
1.分别编写三种socket网络通信服务器模型:多进程、多线程、I\O复用(select、epoll)。并测试通信是否正常。
源码上传至:https://gitee.com/zhaoyuxinHIT/projects
2.编写shell脚本文件MultClient.sh,模拟N个客户器多并发。
#!/bin/sh
myfile="/home/zhaoyuxin/SocketTest/client/countsuc.log"
if [[ -f "$myfile" ]]; then
rm -f $myfile
fi
starttime=`date +'%Y-%m-%d %H:%M:%S'`
echo "多客户端并发 N=3000"
#模拟3000个客户端
for i in {1..3000}
do
./socket_client &
done
endtime=`date +'%Y-%m-%d %H:%M:%S'`
start_seconds=$(date --date="$starttime" +%s);
end_seconds=$(date --date="$endtime" +%s);
echo "本次运行时间: "$((end_seconds-start_seconds))"s"
3.编写shell脚本CountKillClient.sh,统计有客户端连接成功数量,并关闭相应客户端。
#! /bin/sh
echo "client连接数量:" $(ps aux | grep socket_client | grep -v grep | wc -l)
kill -9 $(ps -ef|grep ./socket_client|grep -v grep|awk '{print $2}')
4.测试不同模型同时连接3000个客户端。
bash MultiClient.sh
bash CountKillClient.sh
测试结果:
多进程模型:3000个
多线程模型:约1355个
Select模型:约1000个
Epoll模型:3000个
5.分析模型:
-
多进程模型:
在 Linux 环境下多进程的应用很多,其中最主要的就是网络/客户服务器。多进程服务器是当客户有请求时,服务器用一个子进程来处理客户请求。父进程继续等待其它客户的请求。这种方法的优点是当客户有请求时,服务器能及时处理客户,特别是在客户服务器交互系统中。对于一个 TCP 服务器,客户与服务器的连接可能并不马上关闭,可能会等到客户提交某些数据后再关闭,这段时间服务器端的进程会阻塞,所以这时操作系统可能调度其它客户服务进程,这比起循环服务器大大提高了服务性能。TCP 并发服务器的思想是每一个客户机的请求并不由服务器直接处理,而是由服务器创建一个子进程来处理
//多进程服务器模板 #include <> int main() { sockfd = socket(...) bind(...) listen(...) while(1) { int connfd = accept(...); pid = fork(); if(pid < 0) if(pid > 0)//父进程 { close(client_fd); continue; } else if (pid == 0)//子进程 { close(sockfd); fun();//用于客户端操作 close(client_fd); exti(0); } } close(sockfd) }
-
多线程模型:
多线程服务器是对多进程的服务器的改进,由于多进程服务器在创建进程时要消耗较大的系统资源,所以用线程来取代进程,这样服务处理程序可以较快的创建。据统计,创建线程与创建进程要快 10100 倍,所以又把线程称为“轻量级”进程。线程与进程不同的是:一个进程内的所有线程共享相同的全局内存、全局变量等信息,这种机制又带来了同步问题。//多线程服务器模板 #include <> int main() { sockfd = socket(...) bind(...) listen(...) while(1) { int connfd = accept(...); pthread_t thread_id; pthread_create(&tid, NULL, thread_workbody, (void *)connfd); pthread_detach(tid); } close(sockfd) } void *thread_workbody(void *arg) { int connfd = (int)arg; fun(); //用于客户端操作 close(connfd); }
-
I\O复用:
I/O 复用技术是为了解决进程或线程阻塞到某个 I/O 系统调用而出现的技术,使进程不阻塞于某个特定的 I/O 系统调用。I/O 多路复用的最大优势是系统开销小,系统不需要建立新的进程或者线程,也不必维护这些线程和进程。- select:
//select()服务器模板 #include <> int main() { sockfd = socket(...) bind(...) listen(...) memset(&fds_array, -1, sizeof(fds_array));//初始化fds_array数组 fds_array[0] = listenfd;//第一个监听套接字 while(1) { if(select(...) > 0) // 检测监听套接字是否可读 { if(FD_ISSET(...)>0) // 套接字可读,证明有新客户端连接服务器 { accpet(...);// 取出已经完成的连接 fun(...);// 处理请求,反馈结果 } } close(...); // 关闭连接套接字:accept()返回的套接字 } close(sockfd) }
- epoll:
//select()服务器模板 #include <> int main() { sockfd = socket(...) bind(...) listen(...) epollfd = epoll_create(MAX_EVENTS) epoll_ctl(..) while(1) { events = epoll_wait(...); for(i = 0; i < events; i++) { if(event_array[i].data.fd == listenfd)//新的客户端建立连接 { connfd = accept(...); epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &event); } else // 客户端操作 { fun(); epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL); close(event_array[i].data.fd); } } } close(sockfd) }