服务器模型学习
服务器模型大体分为三种:循环模型、并发模型及IO复用模型。
循环模型
循环服务器指的是对客户端的请求和连接,服务器处理完一个之后再处理另一个,即串行处理客户端的请求。循环服务器又叫迭代服务器。循环服务器常用于UDP服务程序。
并发模型
单客户端单进程,统一accept()
该模型是由主进程统一处理客户端的连接,当客户端的连接请求到来时,服务器的accept()函数成功返回,此时服务器端的进程调用fork()分叉。父进程等待客户端的连接,子进程去处理已连接客户端的业务。
单客户端单进程,各线程独立accept()
简单并发服务器可以同时对多个客户端的请求进行处理。原理主要是主进程提前构建多个子进程,当客户端请求来时,系统从进程池中选取一个子进程处理客户端的连接,每个进程处理一个客户端的请求,在全部进程处理能力得到满足之前服务器的网络负载基本不变。
单客户端单线程,统一accept()
该模型是由主进程统一处理客户端的连接,当客户端的连接请求到来时,服务器的accept()函数成功返回,此时服务器端的进程调用pthread_create()函数创建线程。主进程等待客户端的连接,新创建的线程去处理已连接客户端的业务。
单客户端单线程,各线程独立accept(),使用互斥锁
该模型是由主进程提前分别分配多个线程同时处理客户端的连接,也就是每个线程都可以调用accept()函数去处理连接,为了防止冲突,线程处理连接时必须使用线程互斥锁,在调用accept()函数之前锁死,调用完成后释放锁。
I/O复用模型
原理解析
并发服务器有一个比较大的缺陷,它需要建立多个并行的处理单元。当客户端增加时,随着处理单元的增加,系统的负责会逐渐的转移到并行处理单元的切换上,处理业务能力就会降低。因此出现了一种I/O复用模型,该模型在服务器启动时建立多个不同的处理单元,如:连接处理单元,业务处理单元等。当客户端连接到来时,将客户端的连接放在一个状态池中,对所有的客户端连接在一个处理单元中轮询处理。与之前的并发服务器相比,客户端的增加不会造成处理单元的增加,而处理与cpu和内存的速度直接相关。
该模型首先调用socket()函数建立一个套接字描述符,调用bind()函数将套接字与本地地址和监听端口绑定,然后调用listen()设置侦听队列长度。然后建立两个线程,一个为连接业务处理线程,一个请求业务处理线程。
连接业务处理线程调用accept()函数接收客户端的连接,得到客户端连接套接字文件描述符并将其放入客户端连接状态表中,该状态表是由连接业务处理线程与请求业务处理线程共享。
请求业务处理线程用于处理客户端的业务请求,例如请求的分析、IO数据的读取等。该线程根据连接业务处理线程获取的客户端的套接字文件描述符(状态表),建立文件描述符集合,使用select()函数对文件描述符集合进行超时等待。当有客户端请求到来时,请求处理线程接收并对其进行处理。当请求完毕时,客户端退出时请求业务处理线程将此客户端的文件描述符从客户端连接状态表中取出。
代码实例
服务器端代码 select.c 如下:
/******************************************************************************
版权所有 (C), 2018-2019, xxx Co.xxx, Ltd.
******************************************************************************
文 件 名 : select.c
版 本 号 : V1.0
作 者 : lijd
生成日期 : 2018年12月24日
功能描述 : I/O复用之select函数
修改历史 :
******************************************************************************/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <ctype.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <unistd.h>
#include <signal.h>
#include <arpa/inet.h>
#include <errno.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define BUFFLEN 1024
#define SERVER_PORT 8888
#define BACKLOG 5
#define CLIENTNUM 512
int connect_host[CLIENTNUM];
int connect_number = 0;
static void *handle_request(void *argv)
{
time_t now;
char buff[BUFFLEN];
int n = 0;
int maxfd = -1;
fd_set scanfd;
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
int i = 0;
int err = -1;
while(1)
{
maxfd = -1;
FD_ZERO(&scanfd);
for(i = 0; i < CLIENTNUM; i++)
{
if(connect_host[i] != -1)
{
FD_SET(connect_host[i], &scanfd);
if(connect_host[i] > maxfd)
{
maxfd = connect_host[i];
}
}
}
err = select(maxfd+1, &scanfd, NULL, NULL, &timeout);
switch(err)
{
case 0:
break;
case -1:
printf("error~~~~~\n");
break;
default:
{
if(connect_number <= 0)
break;
for(i = 0; i < CLIENTNUM; i++)
{
if(connect_host[i] != -1)
{
if(FD_ISSET(connect_host[i], &scanfd))
{
memset(buff, 0, BUFFLEN);
n = recv(connect_host[i], buff, BUFFLEN, 0);
if(n > 0 && !strncmp(buff, "TIME", 4))
{
memset(buff, 0, BUFFLEN);
now = time(NULL);
sprintf(buff, "%24s\r\n", ctime(&now));
printf("buff : %s\n", buff);
send(connect_host[i], buff, strlen(buff), 0);
}
close(connect_host[i]);
connect_host[i] = -1;
connect_number--;
}
}
}
break;
}
}
}
}
static void *handle_connect(void *argv)
{
int s_s = *((int *)argv);
struct sockaddr_in from;
socklen_t len = sizeof(from);
while(1)
{
int i = 0;
int s_c = accept(s_s, (struct sockaddr *)&from, &len);
printf("from client link IP : %s\n", inet_ntoa(from.sin_addr));
for(i = 0; i < CLIENTNUM; i++)
{
if(connect_host[i] == -1)
{
connect_host[i] = s_c;
connect_number++;
break;
}
}
}
}
int main(int argc, char* argv[])
{
int s_s;
struct sockaddr_in local;
int i = 0;
memset(connect_host, -1, CLIENTNUM);
s_s = socket(AF_INET, SOCK_STREAM, 0);
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = htonl(INADDR_ANY);
local.sin_port = htons(SERVER_PORT);
bind(s_s, (struct sockaddr *)&local, sizeof(local));
listen(s_s, BACKLOG);
pthread_t thread_do[2];
pthread_create(&thread_do[0], NULL, handle_connect, (void *)&s_s);
pthread_create(&thread_do[1], NULL, handle_request, NULL);
for(i = 0; i < 2; i++)
{
pthread_join(thread_do[i], NULL);
}
close(s_s);
return 0;
}
服务端程序运行平台IP为:10.0.13.111,IP客户端连接平台:10.0.13.222。运行程序执行结果如下:
对于此篇博客存在的建议,欢迎各位大佬指正留言。