三、TCP的高级并发服务器模型
1、单客户端单进程,统一accept()
不预先分叉进程,而是由主进程统计处理客户端的连接。当客户端连接请求到来时,才临时fork()进程,由子进程处理客户端的请求。将客户端的连接请求和业务处理进行了分离。
当客户端连接请求到来时,服务端的accept()函数成功返回,此时服务端进行fork(),父进程继续等待客户端的连接请求,而子进程则处理客户端的业务请求,接收客户端的数据,分析数据并返回结果。
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#define BUFFLEN 1024
#define SERVER_PORT 8888
#define BACKLOG 5
//处理客户端请求函数
static void handelReq(int sc)
{
char buff[BUFFLEN] = {0};//缓冲区
ssize_t n = recv(sc,buff,BUFFLEN,0);//接收数据
//....进行处理
send(sc,buff,strlen(buff),0);//回复
//...
close(sc);//关闭连接
return ;
}
int main()
{
int ss = socket(AF_INET,SOCK_STREAM,0);//tcp
//初始化地址
sockaddr_in local;
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(ss,(sockaddr*)&local,sizeof(local));//绑定
listen(ss,BAKCLOG)//侦听
//处理客户端连接
socklen_t len = sizeof(sockaddr_in);
while(1)
{//主处理过程
sockaddr_in from;
int sc = accept(ss,(scokaddr*)&from,&len);//接收连接
if(sc > 0 )//成功连接
{
//创建子进程
if(fork() > 0)//父进程
close(sc);//父进程关闭客户端连接套接字
else
{
handleReq(sc);//子进程处理连接请求
return 0;
}
}
}
close(ss)
return 0;
}
2、单客户端单线程,统一accept()
与进程相比,线程优点很多,例如速度快、占用资源少、数据可以共享等。
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
#define BUFFLEN 1024
#define SERVER_PORT 8888
#define BACKLOG 5
//处理客户端请求函数
static void handelReq(void *arg)
{
int sc = *(static_cast<int*>(arg));
char buff[BUFFLEN] = {0};//缓冲区
ssize_t n = recv(sc,buff,BUFFLEN,0);//接收数据
//....进行处理
send(sc,buff,strlen(buff),0);//回复
//...
close(sc);//关闭连接
return ;
}
int main()
{
int ss = socket(AF_INET,SOCK_STREAM,0);//tcp
//初始化地址
sockaddr_in local;
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(ss,(sockaddr*)&local,sizeof(local));//绑定
listen(ss,BAKCLOG)//侦听
//处理客户端连接
socklen_t len = sizeof(sockaddr_in);
while(1)
{//主处理过程
sockaddr_in from;
int sc = accept(ss,(scokaddr*)&from,&len);//接收连接
if(sc > 0 )//成功连接
{
//创建线程处理
pthread_t threadDo ;
pthread_create(&threadDo,NULL,(void*)handleReq,&sc);
}
}
close(ss)
return 0;
}
3、单客户端单线程,各线程独自accept(),使用互斥锁
预先分配线程。在线程的accept()函数,多个线程都可以使用此函数处理客户端的连接。为了防止冲突,使用了线程互斥锁。
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
#define BUFFLEN 1024
#define SERVER_PORT 8888
#define BACKLOG 5
#define CLIENTNUM 2
pthread_mutex_t ALOCK = PTHREAD_MUTEX_INITIALIZER;
//处理客户端请求函数
static void *handleReq(void *arg)
{
int ss = *(static_cast<int*>(arg));
socklen_t len = sizeof(sockaddr_in);
while(1)
{
char buff[BUFFLEN] = {0};//缓冲区
sockaddr_in from;
pthread_mutex_lock(&ALOCK);//进入互斥区
int sc = accept(ss,(sockaddr*)&from,&len);//接收客户端请求
pthread_mutex_unlock(&ALOCK);//离开互斥
ssize_t n = recv(sc,buff,BUFFLEN,0);//接收数据
//....进行处理
send(sc,buff,strlen(buff),0);//回复
//....
close(sc);//关闭客户端
}
return nullptr;
}
int main()
{
int ss = socket(AF_INET,SOCK_STREAM,0);//tcp
//初始化地址
sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family= AF_INET;
local.sin_addr.s_addr = htonl(INADDR_ANY);//本地任意地址
local.sin_port = htonl(SERVER_PORT);//端口
bind(ss,(sockaddr*)&local,sizeof(local)); //绑定
listen(ss,BACKLOG);//侦听
pthread_t treadDo[CLIENTNUM];//线程ID
for(int i =0;i<CLIENTNUM;i++)
{//创建线程
pthread_create(&treadDo[i],NULL,handeleReq,(void*)&ss);
}
//等待线程结束
for(int i=0;i<CLIENTNUM;i++)
pthread_join(treadDo[i],NULL);
close(ss);
return 0;
}
并发服务器有一个比较重大的缺陷,需要建立多个并行的处理单元,随着处理单元的增加,系统的负载会逐渐地转移到并行单元的现场切换上。
四、IO复用循环服务器
在系统开始的时候,建立多个不同工作类型的处理单元。在客户端连接到来的时候,将客户端的连接放到一个状态池中,对所有客户端的连接状态在一个处理单元中进行轮询处理。处理能力与CPU和内存的速度直接相关。
在调用listen()之后,建立两个线程,一个用于处理客户端的连接,另一个线程用于处理客户端的请求。
连接业务处理线程接收客户端的连接,当连接到来时,将客户端的描述符放入客户端连接状态表。这个状态表与请求业务处理线程共享。
请求业务处理线程用于处理客户端的业务请求。根据连接线程获得的客户端的套接字文件描述符,建立文件描述符集合,使用select()函数对文件描述符集合进行超时等待。当有请求到来时进行处理。当处理完毕,客户端退出的时候请求业务处理线程将此客户端的文件描述符从客户端连接状态表取出。
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/select.h>
#define BUFFLEN 1024
#define SERVER_PORT 8888
#define BACKLOG 5
#define CLIENTNUM 1024 //最大支持客户端数量
//可连接客户端的文件描述符数组
int connectHost[CLIENTNUM];
int connectNumber = 0;
//客户端连接处理函数
static void *handleConnect(void* arg)
{
int ss = *(static_cast<int*>(arg));
socklen_t len = sizeof(sockaddr_in);
while(1)
{
sockaddr_in from;
int sc = accept(ss,(sockaddr*)&from,&len);//接收连接
//查找合适位置,将其描述符放入
for(int i =0;i<CLIENTNUM;i++)
{
if(-1 == connectHost[i])//找到
{
connectHost[i] = sc;
connectNumber++;
break;
}
}
}
return nullptr;
}
//客户端请求处理函数
static void *handelReq(void* arg)
{
timeval timeout;//超时
timeout.tv_sec = 1;//阻塞1s后超时返回
timeout.tv_usec = 0;
while(1)
{
int maxfd = -1;//最大侦听文件描述集合
fd_set = scanfd;//侦听描述符集合
FD_ZERO(&scanfd);//清空集合
for(int i=0;i<CLIENTNUM;i++)//将文件描述符放入集合
{
if(connectHost[i] != -1)//合法的描述符
{
FD_SET(connectHost[i],&scanfd);//放入集合
if(maxfd < connectHost[i])//更新最大描述符值
maxfd = connectHost[i];
}
}
//select等待
int err = select(maxfd+1,&scanfd,NULL,NULL,&timeout);
switc(err)
{
case 0://超时
break;
case -1://错误
break;
default: //可读
if(connectNumber<=0)
break;
for(int i = 0;i<CLIENTNUM;i++)
{
//查找激活的文件描述符
if(connectHost[i] != -1)
if(FD_ISSET(connectHost[i],&scanfd))
{
char buff[BUFFLEN] = {0};
ssize_t n = recv(connectHost[i],buff,BUFFLEN,0);//接收数据
//....进行处理
send(connectHost[i],buff,strlen(buff),0);//回复
//....
//更新描述符在数组中的值
close(connectHost[i]);
connectHost[i] = -1;
connectNumber--;//客户端计数减1
}
}
}
}
return nullptr;
}
int main()
{
int ss = socket(AF_INET,SOCK_STREAM,0);//TCP
//初始化地址
sockaddr_in local;
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(ss,(sockaddr*)&local,sizeof(local));//绑定
listen(ss,BACKLOG);//侦听
pthread_t threadDo[2];//线程
//创建线程处理客户端连接
pthread_create(&threadDo[0],NULL,handleConnect,(void*)&ss);//线程id,属性,回调函数,线程参数
//创建线程处理客户端请求
pthread_create(&threadDo[1],NULL,handleReq,NULL);
//等待线程结束
pthread_join(threadDo[0],NULL);
pthread_join(threadDo[1],NULL);
close(ss);
return 0;
}
注:
linux网络编程 第14章读书笔记