Linux客户/服务器程序设计范式2——并发服务器(进程池)
引言
让服务器在启动阶段调用fork创建一个子进程池,通过子进程来处理客户端请求。子进程与父进程之间使用socketpair进行通信(为了方便使用sendmsg与recvmsg,如果使用匿名管道,则无法使用以上两个函数)。以下针对TCP进行分析。
server端使用select轮询用于监听客户端请求的被动套接字fd_listen以及用于父子之间通信的socketpair。每当客户端有请求时,server端会将由accept返回的用于与客户端通信的socket描述符通过socketpair发送给一个空闲的子进程,由子进程与客户端进行通信(处理请求)。因此服务器端需要维护一个子进程队列,队列中的每个元素存放着与子进程通信的socketpair以及标记子进程是否空闲的标志位,如下:
typedef struct tag_chd
{
int s_sfd ; //与子进程通信的socketpair描述符
int s_state ; //标记子进程是否空闲
}NODE, *pNODE;
每当子进程处理完客户端请求时,会通过socketpair向server端发送消息,server端select到该socketpair后,会将对应子进程标志位设置为空闲。
注意
1. 由于父进程是先创建子进程,之后才accept用于与客户端通信的socket描述符fd_client,因此子进程的pcb中并没有fd_client的信息。server端需要将fd_client发送子进程。如果只是用send来发送fd_client信息的话,子进程只会将其当成一个整型数。我们需要用sendmsg将fd_client连同其辅助(控制)信息一并发送,这样子进程才会将其当成一个socket描述符。
2. 父进程预先创建子进程池,该子进程如同server端一样是永远不会退出的。子进程中使用while死循环,如下:
while(1)
{
readn = read(sfd, &flag, 4); // 服务器分配的子进程在子进程队列中的下标
printf("readn: %d \n", readn);
printf("read from father: %d \n", flag);
recv_fd(sfd, &fd_client); // recv_fd中封装了recvmsg,接收与客户端通信的socket描述符
handle_request(fd_client); // 处理客户端请求
write(sfd, &pid, sizeof(pid)); // 处理完请求后通过socketpair通知服务器,服务器将该子进程状态设置为空闲
}
每当子进程处理完一个客户端请求后(也就是客户端退出了),子进程会阻塞在 read 处,等待接收下一个客户端请求。
由于是while死循环,且死循环中没有break语句,因此子进程不可能跳出这个while循环,也就不会执行while循环以下的内容了,这样可以保证子进程结尾没有exit也不会执行之后的内容。
3. 编译用到的动态库见Linux网络编程9——对TCP与UDP的简易封装2.0。
函数原型
#include <sys/types.h>
#include <sys/socket.h> ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); struct msghdr {
void *msg_name; /* optional address */
socklen_t msg_namelen; /* size of address */
struct iovec *msg_iov; /* scatter/gather array */
size_t msg_iovlen; /* # elements in msg_iov */
void *msg_control; /* ancillary data, see below */
socklen_t msg_controllen; /* ancillary data buffer len */
int msg_flags; /* flags on received message */
}; fields: struct iovec { /* Scatter/gather array items */
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes to transfer */
}; struct cmsghdr {
socklen_t cmsg_len; /* data byte count, including header */
int cmsg_level; /* originating protocol */ /* 如果是文件描述符,填SOL_SOCKET */
int cmsg_type; /* protocol-specific type */ /* 如果是文件描述符,填SCM_RIGHTS */
/* followed by unsigned char cmsg_data[]; */
}; /* 返回cmsghdr结构的cmsg_len成员的值,考虑到对齐,使用数据部分的长度作为参数。*/
size_t CMSG_LEN(size_t length);
/* 返回cmsghdr的数据部分指针。*/
unsigned char *CMSG_DATA(struct cmsghdr *cmsg); CMSG_DATA() returns a pointer to the data portion of a cmsghdr.
CMSG_LEN() returns the value to store in the cmsg_len member of the cmsghdr structure, taking into
account any necessary alignment. It takes the data length as an argument.
This is a constant expression.
NAME
socketpair - create a pair of connected sockets SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h> int socketpair(int domain, int type, int protocol, int sv[2]);
RETURN VALUE
On success, zero is returned. On error, -1 is returned, and errno is
set appropriately.
代码
server.h
#ifndef __SERVER_H__
#define __SERVER_H__
#include "my_socket.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/select.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <errno.h>
#define SER_IP "127.0.0.1"
#define SER_PORT 8888
#define ST_BUSY 1
#define ST_IDLE 2
#define SIZE 8192
#define MSG_SIZE (SIZE - 4) typedef struct tag_mag
{
int msg_len ;
char msg_buf[MSG_SIZE];//8188
}MSG, *pMSG; typedef struct tag_chd
{
int s_sfd ;
int s_state ;
}NODE, *pNODE; extern int errno ;
void make_child(pNODE arr, int cnt);
void child_main(int sfd) ;
void handle_request(int sfd);
void send_fd(int sfd, int fd_file) ;
void recv_fd(int sfd, int* fd_file) ;
void dispatch(pNODE arr, int cnt, int fd_client);
#endif
main.c
/*************************************************************************
> File Name: main.c
> Author: KrisChou
> Mail:zhoujx0219@163.com
> Created Time: Fri 05 Sep 2014 11:19:13 PM CST
************************************************************************/
#include "server.h"
int main(int argc, char* argv[])//exe chld_cnt
{
if(argc != 2)
{
printf("Usage: exe , child_cnt! \n");
exit(1);
}
int child_cnt = atoi(argv[1]);
pNODE arr_child = (pNODE)calloc(child_cnt, sizeof(NODE)) ; /* 动态数组维护子进程池 */
make_child(arr_child, child_cnt); int fd_listen, fd_client ;
my_socket(&fd_listen, MY_TCP, SER_IP, SER_PORT);
my_listen(fd_listen, 10); fd_set readset, readyset ;
FD_ZERO(&readset);
FD_ZERO(&readyset);
FD_SET(fd_listen, &readset);
int index ;
for(index = 0; index < child_cnt; index ++)
{
FD_SET(arr_child[index].s_sfd, &readset);
} int select_ret ;
struct timeval tm ;
while(1)
{
tm.tv_sec = 0 ;
tm.tv_usec = 1000 ;
readyset = readset ;
select_ret = select(1024, &readyset, NULL, NULL, &tm);
if(select_ret == 0) /* 轮询时间内,所有描述符均没有活动,返回0,继续轮询 */
{
continue ;
}else if(select_ret == -1) /* 信号 */
{
if(errno == EINTR)
{
continue ;
}else
{
exit(1);
}
}else
{
if(FD_ISSET(fd_listen, &readyset))
{
fd_client = accept(fd_listen, NULL, NULL) ;
dispatch(arr_child, child_cnt ,fd_client);
close(fd_client);
}
for(index = 0; index < child_cnt; index ++)
{
if(FD_ISSET(arr_child[index].s_sfd, &readyset))
{
int val ;
read(arr_child[index].s_sfd, &val, 4);
arr_child[index].s_state = ST_IDLE ;
}
} } }
}
server.c
/*************************************************************************
> File Name: server.c
> Author: KrisChou
> Mail:zhoujx0219@163.com
> Created Time: Fri 05 Sep 2014 11:17:56 PM CST
************************************************************************/
#include "server.h"
void make_child(pNODE arr, int cnt)
{
int index ;
for(index = 0; index < cnt; index ++)
{
pid_t pid ;
int fds[2] ;//fds[0] - c fds[1] - p
socketpair(AF_LOCAL, SOCK_STREAM, 0, fds);
pid = fork() ;
if(pid == 0)// child
{
close(fds[1]); /* 子进程用fds[0],关闭fds[1] */
child_main(fds[0]) ; /* 每创建一个子进程,子进程就进入该函数中(死循环),接收请求,处理请求,如此循环。*/ }else
{
/* 初始化进程池队列中的每一个子进程 */
arr[index].s_sfd = fds[1] ;
arr[index].s_state = ST_IDLE ;
close(fds[0]); /* 父进程用fds[1], 关闭fds[0] */
} } }
void child_main(int sfd)
{
int fd_client ;
int flag ;
int readn ;
pid_t pid = getpid();
while(1)
{
readn = read(sfd, &flag, 4);
printf("readn: %d \n", readn);
printf("read from father: %d \n", flag);
recv_fd(sfd, &fd_client);
handle_request(fd_client);
write(sfd, &pid, sizeof(pid));
}
}
void handle_request(int sfd)
{ MSG my_msg ;
int recvn ;
while(1)
{
memset(&my_msg, 0, sizeof(MSG));
my_recv(&recvn, sfd, &my_msg, 4);
if(my_msg.msg_len == 0)
{
break ;
}
my_recv(NULL, sfd, my_msg.msg_buf, my_msg.msg_len);
my_send(NULL, sfd, &my_msg, my_msg.msg_len + 4); } }
void send_fd(int sfd, int fd_file)
{
struct msghdr my_msg ;
memset(&my_msg, 0, sizeof(my_msg)); struct iovec bufs[1] ;
char buf[32] = "hello world ! \n";
bufs[0].iov_base = buf ;
bufs[0].iov_len = strlen(buf) ; my_msg.msg_name = NULL ;
my_msg.msg_namelen = 0 ;
my_msg.msg_iov = bufs ;
my_msg.msg_iovlen = 1 ;
my_msg.msg_flags = 0 ; struct cmsghdr *p ;
int cmsg_len = CMSG_LEN(sizeof(int)) ; /* 所传为文件描述符,因此sizeof(int) */
p = (struct cmsghdr*)calloc(1, cmsg_len) ;
p -> cmsg_len = cmsg_len ;
p -> cmsg_level = SOL_SOCKET ;
p -> cmsg_type = SCM_RIGHTS ;
*(int*)CMSG_DATA(p) = fd_file ; my_msg.msg_control = p ;
my_msg.msg_controllen = cmsg_len ; int sendn ;
sendn = sendmsg(sfd, &my_msg, 0);
printf("send masg len : %d \n", sendn);
}
void recv_fd(int sfd, int* fd_file)
{
struct msghdr my_msg ; struct iovec bufs[1] ;
char buf1[32]="" ;
bufs[0].iov_base = buf1 ;
bufs[0].iov_len = 31 ; my_msg.msg_name = NULL ;
my_msg.msg_namelen = 0 ;
my_msg.msg_iov = bufs ;
my_msg.msg_iovlen = 2 ;
my_msg.msg_flags = 0 ; struct cmsghdr *p ;
int cmsg_len = CMSG_LEN(sizeof(int)) ;
p = (struct cmsghdr*)calloc(1, cmsg_len) ;
my_msg.msg_control = p ;
my_msg.msg_controllen = cmsg_len ; int recvn ;
recvn = recvmsg(sfd, &my_msg, 0); *fd_file = *(int*)CMSG_DATA((struct cmsghdr*)my_msg.msg_control); //写成*(int*)CMSG_DATA(P)也可 printf("buf1: %s, recv msg len : %d \n", buf1, recvn); }
void dispatch(pNODE arr, int cnt, int fd_client)
{
int index ;
for(index = 0 ; index < cnt; index ++)
{
if(arr[index].s_state == ST_IDLE)
{
write(arr[index].s_sfd, &index, 4);
send_fd(arr[index].s_sfd, fd_client); /* 向空闲的子进程分配任务,将服务器accept返回的socket描述符发送给子进程*/
arr[index].s_state = ST_BUSY ;
break ;
}
}
}
client.c
/*************************************************************************
> File Name: client.c
> Author: KrisChou
> Mail:zhoujx0219@163.com
> Created Time: Fri 05 Sep 2014 03:48:27 PM CST
************************************************************************/
#include "my_socket.h"
#define MY_IP "127.0.0.1"
#define MY_PORT 6666
#define SER_IP "127.0.0.1"
#define SER_PORT 8888
#define SIZE 8192
#define MSG_SIZE (SIZE - 4)
typedef struct tag_mag//
{
int msg_len ;
char msg_buf[MSG_SIZE];//8188
}MSG, *pMSG;
int main(int argc, char* argv[])
{
int sfd ;
my_socket(&sfd, MY_TCP, MY_IP, atoi(argv[1]));
my_connect(sfd, SER_IP, SER_PORT);
MSG my_msg ;
while(memset(&my_msg, 0, sizeof(MSG)), fgets(my_msg.msg_buf, MSG_SIZE, stdin)!= NULL)
{
my_msg.msg_len = strlen(my_msg.msg_buf);
my_send(NULL, sfd, &my_msg, 4 + my_msg.msg_len );
memset(&my_msg, 0, sizeof(MSG));
my_recv(NULL, sfd, &my_msg, 4);
my_recv(NULL, sfd, &my_msg.msg_buf, my_msg.msg_len);
printf("recv from server : %s \n", my_msg.msg_buf); }
/* 客户端退出时,向服务器发送一个长度为0的消息 ,用于通知服务器退出 */
memset(&my_msg, 0, sizeof(MSG));
my_send(NULL, sfd, &my_msg, 4 + my_msg.msg_len);
close(sfd); }
编译如下:
gcc -o s server.c main.c -lmy_socket -I/home/purple/include
gcc -o c client.c -lmy_socket -I/home/purple/include
Linux客户/服务器程序设计范式2——并发服务器(进程池)的更多相关文章
- Linux客户/服务器程序设计范式1——并发服务器(多进程)
引言 本文会写一个并发服务器(concurrent server)程序,它为每个客户请求fork出一个子进程. 注意 1. 信号处理问题 对于相同信号,按信号的先后顺序依次处理.可能会产生的问题是,正 ...
- UNP学习笔记(第三十章 客户/服务器程序设计范式)
TCP测试用客户程序 #include "unp.h" #define MAXN 16384 /* max # bytes to request from server */ in ...
- Linux网络编程服务器模型选择之并发服务器(下)
前面两篇文章(参见)分别介绍了循环服务器和简单的并发服务器网络模型,我们已经知道循环服务器模型效率较低,同一时刻只能为一个客户端提供服务,而且对于TCP模型来说,还存在单客户端长久独占与服务器的连接, ...
- Linux C++服务器程序设计范式
<Unix网络编程>30章详细介绍了几种服务器设计范式.总结了其中的几种,记录一下: 多进程的做法: 1.每次创建一个新的请求,fork一个子进程,处理该连接的数据传输. 2.预先派生一定 ...
- Linux网络编程服务器模型选择之并发服务器(上)
与循环服务器的串行处理不同,并发服务器对服务请求并发处理.循环服务器只能够一个一个的处理客户端的请求,显然效率很低.并发服务器通过建立多个子进程来实现对请求的并发处理.并发服务器的一个难点是如何确定子 ...
- 网络编程并发 多进程 进程池,互斥锁,信号量,IO模型
进程:程序正在执行的过程,就是一个正在执行的任务,而负责执行任务的就是cpu 操作系统:操作系统就是一个协调.管理和控制计算机硬件资源和软件资源的控制程序. 操作系统的作用: 1:隐藏丑陋复杂的硬件接 ...
- python并发编程-进程池线程池-协程-I/O模型-04
目录 进程池线程池的使用***** 进程池/线程池的创建和提交回调 验证复用池子里的线程或进程 异步回调机制 通过闭包给回调函数添加额外参数(扩展) 协程*** 概念回顾(协程这里再理一下) 如何实现 ...
- Python多进程并发操作进程池Pool
目录: multiprocessing模块 Pool类 apply apply_async map close terminate join 进程实例 multiprocessing模块 如果你打算编 ...
- [转]Python多进程并发操作中进程池Pool的应用
Pool类 在使用Python进行系统管理时,特别是同时操作多个文件目录或者远程控制多台主机,并行操作可以节约大量的时间.如果操作的对象数目不大时,还可以直接使用Process类动态的生成多个进程,十 ...
随机推荐
- 安装MySQL的心得
1.去官网上下载适合自己电脑的安装包,最好在网上查查教程起码知道自己应该怎么下载,下载哪一个. 2.我遇到的问题不多:<1>.没在bin目录下安装,启动数据库时出现错误2:<2> ...
- RTP、RTCP
http://blog.chinaunix.net/uid-22670933-id-1771676.html
- locate 命令
ac OS X 下的 locate 在便利性上差了一些.主要是一些辅助工具.我在开始用 Mac OS X 下的locate 时,把过去知道的创立数据库的命令尝试了个遍,就是没有可行的.后来搜索网络才找 ...
- MEF(Managed Extensibility Framework) 微软平台插件化开发
体验Managed Extensibility Framework精妙的设计 MEF(Managed Extensibility Framework)是.NET Framework 4.0一个重要 ...
- 在ASP.NET中实现OAuth2.0(一)之了解OAuth
1.什么是OAuth2.0 是一个开放授权标准,允许用户让第三方应用访问该用户在某一个网站或平台上的私密资源(如照片.视频.联系人等),而无须将用户名和密码提供给第三方应用 2.OAuth2.0授权模 ...
- RSA和DES------加密和解密类
public class CryptogramUtil { //******************************************************************** ...
- 最基础的Hash
type thash=^node; node=record state:longint; next:thash; end; var a,i:longint; p:thash; hash:..]of t ...
- Machine Learning 学习笔记 (4) —— 广义线性模型
本系列文章允许转载,转载请保留全文! [请先阅读][说明&总目录]http://www.cnblogs.com/tbcaaa8/p/4415055.html 1. 指数分布族简介 之前的文章分 ...
- background-clip
background-clip 用来将背景图片做适当的裁剪以适应实际需要. 语法: background-clip : border-box | padding-box | content-box | ...
- win8.1 cygwin编译java轻量虚拟机avian
1.背景 昨天在网上看到别人用aauto写本地小程序写的很爽,我觉得如果java的jre能小一点,凭借java庞大的第三方类库写小工具也还算不错的.本人就经常用eclipse+一些commons包写些 ...