Linux的一个应用优势是可用于设计各种高性能网络服务程序,高性能的一个特点就是实现并发访问处理,及服务程序能够同时为多个在线用户提供服务,高性能服务程序的应用非常广泛,在当前流行的Web服务器,各种游戏服务器中都能看到它的身影,本次重点讲解多进程网络服务程序,多线程网络服务程序,以及线程池网络服务程序的实现原理。
高性能网络服务程序简介
高性能网络服务程序在当前的LINUX环境下应用非常广泛,LINUX本身就是高性能的体现,不管是流行的WEB服务,还是运转繁忙的各主流游戏软件服务器端,都能看到背后运转的高性能网络服务程序。
高性能网络服务程序之所以能够在LINUX世界里占有较大的份额,主要有以下3个方面的原因:
1. 开源特性:通过LINUX内核级的裁剪达到网络服务的最优支持。
2. 使用方便:LINUX自身提供很多便捷的方式从事网络程序开发。
3. 共享特性:跟第一点类似,众多LINUX开发爱好者的积极参与,不断向社区贡献自己的力量,也提供了导向性力量,这一点应该是关键点,所谓“众人拾柴火焰高”。
高性能网络服务程序分类
网络服务程序的设计一般以TCP服务程序为例(UDP的方式其实也是类似)。高性能网络服务程序一般可分为如下几类:
一、单线程重复式
单线程重复式是最基本的一种TCP服务模式,在以前的TCP服务程序就是采用这种模式。
1). 实现原理 :
单线程重复式的实现原理是,在主服务线程里进行阻塞式等待,侦听当前到来的客户连接,若当前处于服务空闲状态,则转入连接服务状态,即为当前连接提供服务。当服务结束时,关闭与客户连接的套接字,并转入服务空前状态,同时继续等待新的连接,如发现当前处于连接状态,则当前连接要进入排队队列等待服务。
2).优缺点:
单线程重复式的优点是实现简单,但缺点也比较明显,就是每次只能为一个用户提供服务,若服务比较费时,对排队的连接的用户体验会产生很多不利影响。
在实际商用环境下,很少采用这种模式。
3).代码实现:
int main(int argc, char *argv[])
{
int sockSer = socket(AF_INET, SOCK_STREAM, 0);
if(sockSer == -1)
perror("socket");
struct sockaddr_in addrSer, addrCli;
addrSer.sin_family = AF_INET;
addrSer.sin_port = htons(SERVER_PORT);
addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);
socklen_t addrlen = sizeof(struct sockaddr);
int res = bind(sockSer, (struct sockaddr*)&addrSer, addrlen);
if(res == -1)
perror("bind");
listen(sockSer, QUEUE_SIZE);
sockConn = accept(sockSer, (struct sockaddr*)&addrCli, &addrlen);
if(sockConn == -1)
perror("accept");
printf("Client[%d] Connect OK.\n",addrCli.sin_port);
while(1)
{
void* thread_handler(void *arg)
{
}
}
return 0;
二,多进程网络服务
多进程网路服务模式是利用LINUX系统中的父子进程关系为多用户提供并发服务,是一种比较流行的并发服务技术,其基本理念是来一个用户,启动一个服务进程。
1).实现原理:
多进程网络服务模式的实现原理是:主要服务在端口进行绑定侦听,同时设置被绑定的地址与端口是可重用的(注:这种设置是基础,因为存在多个进程要在同一个端口进行侦听),启动侦听。若当前有新连接到来,则启动以个子进程与其交互,服务结束后子进程自动退出。
2). 优缺点:
多进程网络服务模式的优点是方法通用,有很多成功的服务软件均采用这种模式。缺点是每次启动并关闭子进程会带来很大的开销,在大用户量并发的前提下,会产生不小的负担,另外父子进程间的数据共享、同步等具体逻辑实现上也会有一定的困难。
在实际商用环境下,这是一种可选的模式。
3).代码实现如下:
void* thread_handler(void *arg)
{
}
int main(int argc, char *argv[])
{
int sockSer = socket(AF_INET, SOCK_STREAM, 0);
if(sockSer == -1)
perror("socket");
struct sockaddr_in addrSer, addrCli;
addrSer.sin_family = AF_INET;
addrSer.sin_port = htons(SERVER_PORT);
addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);
int yes = 1; //端口重用必不可少
if(setsockopt(sockSer,SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1)
perror("setsockopt");
socklen_t addrlen = sizeof(struct sockaddr);
int res = bind(sockSer, (struct sockaddr*)&addrSer, addrlen);
if(res == -1)
perror("bind");
listen(sockSer, QUEUE_SIZE);
int sockConn;
while(1)
{
//多进程中必须将accept函数写在循环内
sockConn = accept(sockSer, (struct sockaddr*)&addrCli, &addrlen);
if(sockConn == -1)
perror("accept");
printf("Client[%d] Connect OK.\n",addrCli.sin_port);
pid_t pid;
pid = fork(); //创建进程
if(pid == 0)
{
process_handler(sockConn); //子线程执行任务process_handler()
exit(0);
}
//父线程将端口关闭(子线程已获得端口),等待新的客户端口连接
else if(pid > 0)
{
close(sockConn);
continue;
}
else
{
perror("fork child process fail.\n");
}
}
close(sockSer);
return 0;
}
三,多线程网络服务
多线程网络服务模式类似于多进程网络服务模式,不同之处为多线程是新到一个客户,连接启动一个服务线程多。由于线程比进程损耗资源小,所以多线程效率一般高过多进程。多线程是LINUX的一大优势,多线程网络服务程序应用比较广泛。
1).实现原理:
在主服务线程里进行阻塞式等待,在绑定的端口进行侦听:若当前有连接到来,则启动一个新线程为其服务,服务结束后,释放线程资源。
2).优缺点:
正如前面所讲,多线程网络服务程序应用比较广泛,它的优点是便捷、高效,缺点是仍存在动态线程申请与释放,还是有一定的开销,若存在大用户量在线,可能会带来很大的线程间切换开销。
在实际商用环境中,这是一种可行的模式。
3).代码实现:
void* thread_handler(void *arg)
{
}
int main(int argc, char *argv[])
{
int sockSer = socket(AF_INET, SOCK_STREAM, 0);
if(sockSer == -1)
perror("socket");
struct sockaddr_in addrSer, addrCli;
addrSer.sin_family = AF_INET;
addrSer.sin_port = htons(SERVER_PORT);
addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);
int yes = 1;
if(setsockopt(sockSer,SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1)
perror("setsockopt");
socklen_t addrlen = sizeof(struct sockaddr);
int res = bind(sockSer, (struct sockaddr*)&addrSer, addrlen);
if(res == -1)
perror("bind");
listen(sockSer, QUEUE_SIZE);
int sockConn;
while(1)
{
sockConn = accept(sockSer, (struct sockaddr*)&addrCli, &addrlen);
if(sockConn == -1)
perror("accept");
printf("Client[%d] Connect OK.\n",addrCli.sin_port);
pthread_t tid;
//创建线程并执行thread_handler(),线程结束自动释放资源
int res = pthread_create(&tid, NULL, thread_handler, &sockConn);
if(res < 0)
perror("pthread_create");
}
close(sockSer);
return 0;
}
四,线程池网络服务
线程池网络服务时针对多线程网络服务模式的一些不足之处而提出的改进模式。池是在实际工程中非常流行的一个概念,如多线程池、数据库连接池等,基本理念是先创建一批资源,当有用户到来时,直接分配已创建好资源,它的主要目的是减少系统在频繁创建资源时的开销。
1).实现原理:
线程池网络服务模式的实现原理是:主服务线程创建既定数量的服务线程,同时在指定端口进行侦听,若当前有新连接到来,则从线程池中找出空闲的服务线程,为其服务,服务完毕,线程不进行释放,重新放回线程池;若当前线程池已满,则将当前的连接加入等待队列。
2).优缺点:
线程池网络服务模式的优点是性能高效,在实际商用环境下,很多都会采用这种模式。缺点是新用户如果在等待服务队列里耗时过长,会影响用户体验,针对此问题,有两种改进方案:
1、动态创建新的服务线程,服务结束后,该线程加入到线程池,这种改进得好处是用户体验得到提升,但潜在得问题是线程得规模在长时间、大并发用户的状态下,会变得很大,最后会产生因资源消耗过多,系统退出。
2、增加一个线程资源回收机制,当线程池得规模达到一定程度或满足某种既定规则时,会主动杀死一些线程,以达到在用户体验与系统运行稳定性之间得折中。
3).代码实现:
#define INIT_THREAD_NUM 5
typedef struct thread_struct
{
int sockConn;
bool flag; //0 idel 1 busy
}thread_struct;
typedef struct thread_struct threadpool[INIT_THREAD_NUM];
threadpool pool;
void* thread_handler(void *arg)
{
//任务执行完毕时记得先把flag置为0,在退出
}
int main(int argc, char *argv[])
{
int sockSer = socket(AF_INET, SOCK_STREAM, 0);
if(sockSer == -1)
perror("socket");
struct sockaddr_in addrSer, addrCli;
addrSer.sin_family = AF_INET;
addrSer.sin_port = htons(SERVER_PORT);
addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);
int yes = 1;
if(setsockopt(sockSer,SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1)
perror("setsockopt");
socklen_t addrlen = sizeof(struct sockaddr);
int res = bind(sockSer, (struct sockaddr*)&addrSer, addrlen);
if(res == -1)
perror("bind");
listen(sockSer, QUEUE_SIZE);
pthread_t tid[INIT_THREAD_NUM];
for(int i=0; i<INIT_THREAD_NUM; ++i) //创建线程池并初始化
{
pool[i].sockConn = 0;
pool[i].flag = 0;
}
for(int i=0; i<INIT_THREAD_NUM; ++i) //调用线程执行任务thread_handler()
{
pthread_create(&tid[i], NULL, thread_handler, &i);
}
int sockConn;
while(1)
{
sockConn = accept(sockSer, (struct sockaddr*)&addrCli, &addrlen);
if(sockConn == -1)
perror("accept");
printf("Client[%d] Connect OK.\n",addrCli.sin_port);
for(int i=0; i<INIT_THREAD_NUM; ++i)
{
if(pool[i].flag == 0)
{
pool[i].sockConn = sockConn;
pool[i].flag = 1;
break;
}
}
}
close(sockSer);
return 0;
}