(4)并发服务器设计原理及多进程服务器
注:所以文章红色字体代表需要特别注意和有问题还未解决的地方,蓝色字体表示需要注意的地方
1. 本文所介绍的程序平台
开发板:arm9-mini2440
虚拟机为:Red Hat Enterprise Linux 5
开发板上系统内核版本:linux-2.6.32.2
2.服务器分类
(1)按连接类型划分:
面向连接的服务器:服务器采用连接型的通信协议,比如TCP协议;
面向无连接的服务器:服务器采用无连接型的通信协议,比如UDP协议;
(2)按处理方式分类
重复**器:每次只处理一个客户请求;
并发服务器:每次可处理多个客户请求;
3.unix主要并发技术
(1)进程:每个单独的进程运行在自己的虚拟地址空间,并且只能通过安全的内核管理机制才能与其他进程交互,若一个进程崩溃不会引起另一个进程崩溃。
(2)线程:与进程不同,在同一进程中的所有线程共享相同的全局变量以及系统分配给进程的资源,因此线程占用资源较少,若一个线程崩溃会引起处于同一进程里面的线程崩溃。
(3)I/O多路复用:select()函数实现,它可在多个描述符选择被激活的相应操作的描述符。
2. 并发服务器算法
(1) 并发无连接服务器算法:由于无连接服务器不需要等待每个已连接上的数据,因此不必采用并发技术,反而如果使用多线程/多进程需要大量的系统开销。
(2) 并发连接服务器算法:由于面向连接的服务器需要守候每个连接,所以并发很重要。
(3) 单进程/线程的并发服务器算法:由于进程/线程需要的系统开销较多,所以有时可以采用多路复用I/O来实现处理多个客户连接,而不必产生多个进程/线程。
4.多进程服务器设计
注意:不同的进程可能维护相同的系统资源(如文件),因而对于进程而言,存在可再入性的问题(可再入性:指的是一段代码或者一相同功能同时被执行多次,例如某一段代码是打开文件写入一段数据,当多个进程同时打开同一个文件写入就会出现问题了)
创建进程:
#include
#include
pid_t fork( void );
返回值:子进程为0,父进程中为子进程ID,出错为-1
创建子进程后,子进程拥有的是父进程的副本,意思是两者不是共享这些存储空间。
4.1以下是多进程服务器设计实例
功能:
服务器等候客户连接请求,一旦连接成功显示客户地址,接收该客户的名字并显示,然后接收来自用户的信息,每收到一个字符串则显示,并将字符串反转,再将反转的字符串发回客户端。
客户端首先与服务器相连,接着发送客户端名字,然后发送客户信息,接收到服务器信息并显示,之后等待用户输入Crtl+D,就关闭连接并退出。
//server.c
#include /* These are the usual header files */
#include /* for close() */
#include
#include
#include
#include
#include
#include /* for bzero() */
#define PORT 1234 /* Port that will be opened */
#define BACKLOG 2 /* Number of allowed connections */
#define MAXDATASIZE 1000
void process_cli(int connectfd, sockaddr_in client);
int main()
{
int listenfd, connectfd; /* socket descriptors */
pid_t pid;
struct sockaddr_in server; /* server's address information */
struct sockaddr_in client; /* client's address information */
socklen_t sin_size;
/* Create TCP socket */
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
/* handle exception */
perror("Creating socket failed.");
exit(1);
}
/* 如果服务器终止后,服务器可以第二次快速启动而不用等待一段时间 */
int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(PORT);
server.sin_addr.s_addr = htonl (INADDR_ANY);
if (bind(listenfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) {
/* handle exception */
perror("Bind error.");
exit(1);
}
if(listen(listenfd,BACKLOG) == -1){ /* calls listen() */
perror("listen() error\n");
exit(1);
}
sin_size=sizeof(struct sockaddr_in);
while(1)
{
/*accept connection */
if ((connectfd = accept(listenfd,(struct sockaddr *)&client,&sin_size))==-1) {
perror("accept() error\n");
exit(1);
}
/* Create child process to service client */
if ((pid=fork())>0) {
/* parent process */
//父进程把连接描述符关闭
close(connectfd);
continue;
}
else if (pid==0) {
//子进程继承了父进程的描述符 所以利用子进程处理
/* 子进程处理客户端的连接 */
/*child process*/
//子进程把侦听描述符关闭
close(listenfd);
process_cli(connectfd, client);
exit(0);
}
else {
printf("fork error\n");
exit(0);
}
}
close(listenfd); /* close listenfd */
}
void process_cli(int connectfd, sockaddr_in client)
{
int num;
char recvbuf[MAXDATASIZE], sendbuf[MAXDATASIZE], cli_name[MAXDATASIZE];
int len;
printf("You got a connection from %s. ",inet_ntoa(client.sin_addr) ); /* prints client's IP */
/* Get client's name from client */
num = recv(connectfd, cli_name, MAXDATASIZE,0);
if (num == 0) {
close(connectfd);
printf("Client disconnected.\n");
return;
}
cli_name[num - 1] = '\0';
printf("Client's name is %s.\n",cli_name);
while (num = recv(connectfd, recvbuf, MAXDATASIZE,0)) {
recvbuf[num] = '\0';
printf("Received client( %s ) message: %s",cli_name, recvbuf);
for (int i = 0; i < num - 1; i++) {
sendbuf[i] = recvbuf[num - i -2];
}
sendbuf[num - 1] = '\0';
len = strlen(sendbuf);
send(connectfd,sendbuf, len ,0); /* send to the client welcome message */
}
close(connectfd); /* close connectfd */
}
// client.c
#include
#include
#include
#include
#include
#include
#include /* netbd.h is needed for struct hostent =) */
#include
#define PORT 1234 /* Open Port on Remote Host */
#define MAXDATASIZE 100 /* Max number of bytes of data */
void process(FILE *fp, int sockfd);
char* getMessage(char* sendline,int len, FILE* fp);
int main(int argc, char *argv[])
{
int fd; /* files descriptors */
struct hostent *he; /* structure that will get information about remote host */
struct sockaddr_in server; /* server's address information */
if (argc !=2) {
printf("Usage: %s \n",argv[0]);
exit(1);
}
if ((he=gethostbyname(argv[1]))==NULL){ /* calls gethostbyname() */
printf("gethostbyname() error\n");
exit(1);
}
if ((fd=socket(AF_INET, SOCK_STREAM, 0))==-1){ /* calls socket() */
printf("socket() error\n");
exit(1);
}
bzero(&server,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT); /* htons() is needed again */
server.sin_addr = *((struct in_addr *)he->h_addr);
if(connect(fd, (struct sockaddr *)&server,sizeof(struct sockaddr))==-1){ /* calls connect() */
printf("connect() error\n");
exit(1);
}
process(stdin,fd);
close(fd); /* close fd */
}
void process(FILE *fp, int sockfd)
{
char sendline[MAXDATASIZE], recvline[MAXDATASIZE];
int numbytes;
printf("Connected to server. \n");
/* send name to server */
printf("Input name : ");
if ( fgets(sendline, MAXDATASIZE, fp) == NULL) {
printf("\nExit.\n");
return;
}
send(sockfd, sendline, strlen(sendline),0);
/* send message to server */
while (getMessage(sendline, MAXDATASIZE, fp) != NULL) {
send(sockfd, sendline, strlen(sendline),0);
if ((numbytes = recv(sockfd, recvline, MAXDATASIZE,0)) == 0) {
printf("Server terminated.\n");
return;
}
recvline[numbytes]='\0';
printf("Server Message: %s\n",recvline); /* it prints server's welcome message */
}
printf("\nExit.\n");
}
char* getMessage(char* sendline,int len, FILE* fp)
{
printf("Input string to server:");
return(fgets(sendline, MAXDATASIZE, fp));
}