标签:
Linux的一个应用优势是可用于设计各种高性能网络服务程序,高性能得一个特点就是实现并发访问处理,及服务程序能够同时为多个在线用户提供服务,高性能服务程序得应用非常广泛,在当前流行得Web服务器,各种游戏服务器中都能看到它的身影,而多进程网络服务程序,多线程网络服务程序,以及线程池网络服务程序的实现会更加提高网络服务的性能。
高性能网络服务程序简介
高性能网络服务程序在当前的LINUX环境下应用非常广泛,LINUX本身就是高性能的体现,不管是流行的WEB服务,还是运转繁忙的各主流游戏软件服务器端,都能看到背后运转的高性能网络服务程序。
高性能网络服务程序之所以能够在LINUX世界里占有较大的份额,主要有以下3个方面的原因:
开源特性:通过LINUX内核级的裁剪达到网络服务的最优支持。
使用方便:LINUX自身提供很多便捷的方式从事网络程序开发。
共享特性:跟第一点类似,众多LINUX开发爱好者的积极参与,不断向社区贡献自己的力量,也提供了导向性力量。
1、进程网络服务:
多进程网路服务模式是利用LINUX系统中的父子进程关系为多用户提供并发服务,是一种比较流行的并发服务技术,其基本理念是来一个用户,启动一个服务进程。
多进程网络服务模式的实现原理是:主要服务在端口进行绑定侦听,同时设置被绑定的地址与端口是可重用的(注:这种设置是基础,因为存在多个进程要在同一个端口进行侦听),启动侦听。若当前有新连接到来,则启动以个子进程与其交互,服务结束后子进程自动退出。
多进程网络服务模式的流程图如下:
实现代码:
utili.h:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERVER_IP "192.168.3.169"
#define SERVER_PORT 5050
#define QUEUE_SIZE 5
#define BUFFER_SIZE 256
typedef enum{ADD, SUB, MUL, DIV, QUIT}OPER_STATE;
typedef struct Oper
{
OPER_STATE oper;
int opt1;
int opt2;
}Oper;
typedef struct thread_struct
{
int sockConn;
bool flag;
}thread_struct;
cli.cpp:
#include "utili.h" //头文件
void Input(Oper *pop); //输入信息
void help(); //帮助函数
int main()
{
//创建一个套接自豪,并检测是否创建成功
int sockCli = socket(AF_INET, SOCK_STREAM, 0);
if(sockCli == -1){
perror("socket");
}
//定义一个地址信息结构体,并设置内部成员 协议族(sin_family)、端口号(sin_port)、服务器ip(sin_addr.s_addr)
struct sockaddr_in addrSer;
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 = connect(sockCli, (struct sockaddr*)&addrSer, addrlen);
if(res == -1){
perror("connect");
}
printf("Client Connect Server Ok.\n");
char cmd[20]; //定义一个字符数组存放命令
Oper op; //定义一个op结构体,用来整合命令以操作数,用来发送给服务器
int result;
while(1){
printf("please input cmd:>");
scanf("%s", cmd);
//判断所出入命令,并执行相应的操作
if(strcmp(cmd, "add") == 0){ //如果为"add"
op.oper = ADD; //将op.oper设为ADD
Input(&op); //执行输入函数
}else if(strcmp(cmd, "sub") == 0){ //如果为"sub"
op.oper = SUB; //将op.oper设为SUB
Input(&op);
}else if(strcmp(cmd, "mul") == 0){ //如果为"mul"
op.oper = MUL; //将op.oper设为MUL
Input(&op);
}else if(strcmp(cmd, "div") == 0){ //如果为"div"
op.oper = DIV; //将op.oper设为DIV
Input(&op);
}else if(strcmp(cmd, "help") == 0){ //如果为"help",执行help()
help();
continue;
}else if(strcmp(cmd, "quit") == 0){ //如果为"quit"
op.oper = QUIT; //将op.oper设为QUIT
}else{
printf("please input currect cmd.\n");
continue;
}
//将含有命令以及操作数的op结构体发送给服务器,并检测是否成功
int res = send(sockCli, (char *)&op, sizeof(op), 0);
if(res == -1){
perror("send");
}
//如果操作命令为QUIT,则退出程序
if(op.oper == QUIT){
break;
}
//接收服务器传来的结果值。
recv(sockCli, &result, sizeof(int), 0);
printf("result = %d\n", result);
}
close(sockCli); //关闭连接套接字
return 0;
}
void Input(Oper *pop) //输入操作数
{
printf("input op1 and op2:>");
scanf("%d, %d", &pop->opt1, &pop->opt2);
}
void help() //打印帮助信息
{
printf("**********************************\n");
printf("* version : v1.0 *\n");
printf("* author : westos_bss *\n");
printf("* cmd describe *\n");
printf("* add result = a+b *\n");
printf("* sub result = a-b *\n");
printf("* mul result = a*b *\n");
printf("* div result = a/b *\n");
printf("* quit quit system *\n");
printf("* help print help info *\n");
printf("**********************************\n");
}
ser.cpp:
#include "../utili.h"
void process_hander(int sock) //进程处理函数
{
int sockConn = sock; //创建一个连接套接字号
Oper op;
char sendbuf[100]; //申请一个发送缓存区
int result;
while(1){
//接收客户端发送的包含命令、操作数信息的结构体,并判断是否接收成功
int res = recv(sockConn, (char*)&op, sizeof(op), 0);
if(res < 0){
perror("recv");
}
if(op.oper == ADD){ //如果该结构体中的操作命令成员位ADD,则对op中的两个操作数进行加法操作
result = op.opt1 + op.opt2;
}else if(op.oper == SUB){ //如果该结构体中的操作命令成员位SUB,则对op中的两个操作数进行减法操作
result = op.opt1 - op.opt2;
}else if(op.oper == MUL){ //如果该结构体中的操作命令成员位MUL,则对op中的两个操作数进行乘法操作
result = op.opt1 * op.opt2;
}else if(op.oper == DIV){ //如果该结构体中的操作命令成员位DIV,则对op中的两个操作数进行除法操作
result = op.opt1 / op.opt2;
}
//将计算出来的结果发送给客户端。因为每连接一个新的连接套接自豪,
//所以每个进程对相应连接套接字号的客户端进行服务,所以,它并不会将数据只发混。
send(sockConn,(char *)&result, sizeof(op), 0);
}
}
int main()
{
//创建一个新的服务器套接字号,并检测是否创建成功
int sockSer = socket(AF_INET, SOCK_STREAM, 0);
if(sockSer == -1){
perror("socket");
}
//定义一个地址信息结构体变量,并设置里边的协议族(sin_family)、端口号(sin_port)、ip地址(sin_addr.s_addr)等信息。
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);
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);
pid_t pid;
pid = fork(); //创建一个子进程给当前连接的客户端进行服务
if(pid == 0){ //子进程
process_hander(sockConn); //执行进程操作函数
exit(0);
}else if(pid > 0){ //父进程
close(sockConn); //光弼刚才创建的连接套接字号
continue;
}else{ //否则子进程创建失败
perror("fork child process fail.\n");
}
}
close(sockSer); //关闭服务器套接字,程序结束
return 0;
}
运行结果:
进程网络服务优缺点:
优点:方法通用有很多成功的服务软件均采用这种模式。
缺点:
开销大:每次启动并关闭子进程会带来很大的开销,在大用户量并发的前提下,会产生不小的负担,
较难实现:父子进程间的数据共享、同步等具体逻辑实现上也会有一定的困难。
在实际商用环境下,这是一种可选的模式。
2、多线程网络服务:
多线程网络服务模式类似于多进程网络服务模式,不同之处是前者为新到连接启动一个服务线程。多线程是LINUX的一大优势,多线程网络服务程序应用比较广泛。
多线程网络服务模式的实现原理是:在主服务线程里进行阻塞式等待,在绑定的端口进行侦听:若当前有连接到来,则启动一个新线程为其服务,服务结束后,释放线程资源。
多线程网络服务模式的流程图如下:
实现代码【客户端和头文件与上面多进程模式相同,此处只给出服务器断代码】:
ser.cpp:
#include "../utili.h" //头文件
void* pthread_hander(void *arg) //线程进行的服务
{
int sockConn = *(int *)arg; //取出传进来的连接套接字号
Oper op; //该枚举变量中包含所有操作命令的对应值,以及操作数
char sendbuf[100]; //定义一个大小位100的发送缓存区
int result;
while(1){
//接收客户端发送的信息,并判断是否接收成功
int res = recv(sockConn, (char*)&op, sizeof(op), 0);
if(res < 0){
perror("recv");
}
//判断客户端发送的操作命令
if(op.oper == ADD){ //如果是ADD,则执行相应的加法操作
result = op.opt1 + op.opt2;
}else if(op.oper == SUB){ //如果是SUB,则执行相应的减法操作
result = op.opt1 - op.opt2;
}else if(op.oper == MUL){ //如果是MUL,则执行相应的乘法操作
result = (op.opt1) * (op.opt2);
}else if(op.oper == DIV){ //如果位DIV,则执行相应的减法操作
result = (op.opt1) / (op.opt2);
}else if(op.oper == QUIT){ //如果位QUIT,则退出服务
//退出前打印是哪个客户端退出
struct sockaddr_in addrCli;
socklen_t addrlen = sizeof(struct sockaddr);
getpeername(sockConn, (struct sockaddr*)&addrCli, &addrlen); //得到对面客户端的名字
printf("Client[%d] QUIT.\n", addrCli.sin_port); //打印出是那个端口号的客户端退出。
break;
}
send(sockConn,(char *)&result, sizeof(op), 0); //进行相应的操作后,将结果值返回给客户端
}
close(sockConn); //关闭连接套接字
pthread_exit(NULL); //关闭线程
}
int main()
{
int sockSer = socket(AF_INET, SOCK_STREAM, 0); //创建一个套接字
//判断套接字是否创建成功
if(sockSer == -1){
perror("socket");
}
//定义一个地址结构体,并设置协议族(sin_family)、端口号(sin_port)、服务器ip号(sin_addr.s_addr)
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);
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;
//创建一个线程,该线程用来执行pthread_hender(),并将连接套接字号当作参数传给pthread_header.
int res = pthread_create(&tid, NULL, pthread_hander, &sockConn);
if(res == -1){
perror("pthread_create.\n");
}
}
close(sockSer); //关闭连接套接字号
return 0;
}
实验结果:
多线程网络服务的优缺点:
它的优点:便捷、高效。
缺点:
开销大:存在动态线程申请与释放,若存在大用户量在线,可能会带来很大的线程间切换开销。
3、线程池网络服务:
线程池网络服务是针对多线程网络服务模式的一些不足之处而提出的改进模式。池是在实际工程中非常流行的一个概念,如多线程池、数据库连接池等,基本理念是先创建一批资源,当有用户到来时,直接分配已创建好资源,它的主要目的是减少系统在频繁创建资源时的开销。
线程池网络服务模式的实现原理是:主服务线程创建既定数量的服务线程,同时在指定端口进行侦听,若当前有新连接到来,则从线程池中找出空闲的服务线程,为其服务,服务完毕,线程不进行释放,重新放回线程池;若当前线程池已满,则将当前的连接加入等待队列。
线程池网络服务时针对多线程网络服务模式的一些不足之处而提出的改进模式。池是在实际工程中非常流行的一个概念,如多线程池、数据库连接池等,基本理念是先创建一批资源,当有用户到来时,直接分配已创建好资源,它的主要目的是减少系统在频繁创建资源时的开销。
1) 实现原理
线程池网络服务模式的实现原理是:主服务线程创建既定数量的服务线程,同时在指定端口进行侦听,若当前有新连接到来,则从线程池中找出空闲的服务线程,为其服务,服务完毕,线程不进行释放,重新放回线程池;若当前线程池已满,则将当前的连接加入等待队列。
实现代码【客户端和头文件与上面多进程模式相同,此处只给出服务器断代码】:
ser.cpp:
#include "../utili.h"
#define PTHREAD_MAX_NUM 6 //创建线程的个数
//定义一个线程池存放线程
typedef struct thread_struct threadpool[PTHREAD_MAX_NUM];
threadpool pool;
void* pthread_hander(void *arg) //线程操作函数
{
int index = *(int *)arg; //将传进来的下标取出来
printf("[%d]thread startup.\n", index);
//创建一个结构体指针让他指向现在正在进行的线程
struct thread_struct *pthread = &pool[index];
int sockConn = pthread->sockConn; //取出它的连接套接字号
Oper op;
char sendbuf[100]; //定义一个发送缓存区
int result;
while(1){
if(pthread->flag){ //判断该线程的标志位,如果为1,就进行让该线程进行操作。
//(因为如果该表值为为0才能进入该函数,而此时为1,说明它是空闲的可用,
//所以才将它设为1让他进行while循环中的操作。这是防止一个线程被多个客户端同时连接而使结果出错)
printf("[%d]client working...\n", index);
//接收客户端发送来的操作命令、操作数等信息。并判断是否接收成功
int res = recv(pool[index].sockConn, (char*)&op, sizeof(op), 0);
if(res < 0){
perror("recv");
}
//判断客户端发送的操作命令
if(op.oper == ADD){ //如果是ADD,则执行相应的加法操作
result = op.opt1 + op.opt2;
}else if(op.oper == SUB){ //如果是SUB,则执行相应的减法操作
result = op.opt1 - op.opt2;
}else if(op.oper == MUL){ //如果是MUL,则执行相应的乘法操作
result = (op.opt1) * (op.opt2);
}else if(op.oper == DIV){ //如果位DIV,则执行相应的减法操作
result = (op.opt1) / (op.opt2);
}else if(op.oper == QUIT){ //如果位QUIT,则退出服务
//退出前打印是哪个客户端退出
struct sockaddr_in addrCli;
socklen_t addrlen = sizeof(struct sockaddr);
getpeername(pool[index].sockConn, (struct sockaddr*)&addrCli, &addrlen); //得到当前正在使用的客户端地址
pthread->flag = 0; //将该线程的状态设置为空闲状态
printf("Client[%d] QUIT.\n", addrCli.sin_port);
continue;
}
//将运算结果发送给客户端
send(pool[index].sockConn,(char *)&result, sizeof(op), 0);
}else{
//打印该客户端结束
printf("[%d]client idel.\n", index);
sleep(1);
continue;
}
close(pool[index].sockConn); //关闭相应的连接套接字
// pthread->sockConn = 0;
pthread_exit(NULL);
}
int main()
{
//创建一个套接字号,并检测是否创建成功
int sockSer = socket(AF_INET, SOCK_STREAM, 0);
if(sockSer == -1){
perror("socket");
}
//定义一个地址结构体,并设置协议族(sin_family)、端口号(sin_port)、服务器ip号(sin_addr.s_addr)
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);
//创建之前规定的个数个线程,等候后面使用,并检测是否创建成功
pthread_t tid[PTHREAD_MAX_NUM];
for(int i = 0; i < PTHREAD_MAX_NUM; ++i){
pool[i].sockConn = 0;
pool[i].flag = 0;
int res = pthread_create(&tid[i], NULL, pthread_hander, &i);
if(res == -1){
perror("pthread_create.\n");
}
sleep(1);
}
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 < PTHREAD_MAX_NUM; ++i){
if(pool[i].flag == 0){
pool[i].sockConn = sockConn; //将新连接的客户端套接字号给该线程
pool[i].flag = 1; //将该线程设为连接服务状态,防止其他客户端此时也在请求连接,而导致重复使用
printf("pool[%d].flag = %d\n", i, pool[i].flag); //将下标作为参数给线程操作函数
break;
}
}
}
close(sockSer);
return 0;
}
测试结果:
因为它在不停检测每个线程状态,并打印出每个线程的状态,所以结果不是很明显,但可以看到,每进类一个连接,他都会有一个线程开始工作。
线程池针对多线程实现本身进行了改进,减少了系统因线程频繁创建所带来得系统开
销。
线程池得核心设计思想就是系统在初始化时,创建一定数量得服务线程,并使他们处于空闲状态,若当前有新用户到来,则系统先查找当前有无空闲线程,若有则立即为其分配服务线程,若没有,则将新用户加入待服务队列,并在其他用户结束服务时,在重新为其分配服务线程。
标签:
原文地址:http://blog.csdn.net/dandelion_gong/article/details/51606525