TCP多线程并发服务器(c)

其他关联文章@丶4ut15m:

TCP网络编程(c)

UDP网络编程(c)

多进程并发服务器(c)

IO复用(c)

多线程并发服务器

多线程并发服务器和多进程兵法服务器的区别也不大,重点同样在于交互的处理

服务器

->创建套接字

->绑定地址结构

->监听套接字

->接收连接请求

->创建线程.pthread_create

->转存已连接客户端及套接字信息

->调用响应函数

->关闭套接字

首先需要自定义一个结构体用来存放客户端的信息(已连接套接字,地址结构等),而后调用响应函数与客户端进行交互.

子线程可以显式终止(调用pthread_exit函数),也可以隐式终止(不调用pthread_exit函数,子线程执行完响应函数后会自动终止)

关于线程安全.

使用pthread_key_create创建线程私有空间(TSD),这个函数的第二个参数为可选,是析构函数.如果设置了析构函数,那么在线程终止的时候会自动调用该函数.

线程有入口函数,也即是创建线程时线程自动执行的函数;析构函数则是线程终止之时调用的函数.

细节地方见作业四和代码.

多线程编程流程

下面给个实例

服务器:

	服务器等待接收客户的连接请求,连接成功则显示客户地址;
	接收客户端的名称并显示;
	然后接收来自该客户的字符串,对接收的字符串按分组进行加密(分组长度为个人学号,密钥为个人序号,分组不够补0);
	将加密后的字符串发送给客户端;
	继续等待接收该客户的信息,直到客户关闭连接;
	要求服务器具有同时处理多个客户请求的能力。

客户端:

	客户首先与相应的服务器建立连接;
	接着接收用户输入的客户端名称,并将其发送给服务器;
	接收用户输入的字符串并发送给服务器;
	接收服务器发回的加密后的字符串并显示。
	继续等待用户输入字符串;
	若用户输入的是quit,则关闭连接并退出。

	使用多线程实现

服务器代码

#include <stdio.h>		//基本输入输出
#include <arpa/inet.h>		//地址结构体所在头文件
#include <sys/socket.h>		//套接字函数所在头文件
#include <string.h>		//字符串处理函数所在头文件
#include <stdlib.h>		//exit所在头文件
#include <pthread.h>		//线程函数所在头文件
#include <unistd.h>		//close所在头文件 

#define PORT 3333

//自定义一个ARG结构体用以存放已连接套接字和客户端地址结构
struct ARG{
	int connectfd;
	struct sockaddr_in client;
};

//声明encode函数
void encode(int connectfd,struct sockaddr_in client);		
//声明response函数
void response(int connectfd);			
//声明function函数
void *function(void *arg);				

int main(){
	int listenfd,connectfd;					//监听套接字和已连接套接字
	struct sockaddr_in server,client;		//地址结构
	socklen_t len;							
	struct ARG *arg;						//ARG结构体
	pthread_t tid;							//线程id


	if((listenfd = socket(AF_INET,SOCK_STREAM,0))==-1){
		perror("Socket error!\n");
		exit(0);
	}

	//设置地址重用
	int opt;
	opt = SO_REUSEADDR;
	setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
	
	//配置地址结构,用于给套接字绑定地址结构
	memset(&server,'\0',sizeof(server));			//清空服务器地址结构
	server.sin_addr.s_addr = htonl(INADDR_ANY);		//设置监听地址为0.0.0.0也即是任意地址
	server.sin_port = htons(PORT);					//设置端口
	server.sin_family = AF_INET;					//设置协议族为IPv4

	//绑定地址结构
	if(bind(listenfd,(struct sockaddr *)&server,sizeof(server))==-1){
		perror("Bind error!\n");
		exit(0);
	}

	if(listen(listenfd,5) ==-1){
		perror("Listen error!\n");
		exit(0);
	}

	len = sizeof(client);
	
	while(1){
		if((connectfd = accept(listenfd,(struct sockaddr *)&client,&len)) ==-1){
			perror("Accept error!\n");
			exit(0);
		}
		//malloc一段空间将至类型转换为ARG* 类型,使得arg指向该段空间
		arg = (struct ARG *)malloc(sizeof(struct ARG));
		//存储已连接套接字和客户端地址结构
		arg->connectfd = connectfd;
		memcpy((void *)&arg->client,&client,sizeof(client));
		/*创建线程
		*函数原型
		*pthread_create((pthread_t  *thread, pthread_attr_t *attr, void *(*start_routine)(void  *),void *arg)
		*第一个参数为存放线程id的变量,第二个参数为线程属性默认为NULL,第三个参数为线程入口函数,第四个参数为入口函数的参数,如果不需要可以写NULL
		*/
		pthread_create(&tid,NULL,function,(void *)arg);
	}

	//关闭监听套接字
	close(listenfd);
	return 0;
}

void *function(void *arg){
	//定义线程入口函数function
	struct ARG *info;			//新建一ARG指针类型结构体info用以指向上方arg指向的内存空间
	
	/*每次获得连接arg都会新malloc一段区域,在线程入口函数里使得info指向该段内存空间
	*便可以使得每个线程存放的已连接套接字不会丢失。
	*若是不如此做,直接用arg来存放的话,每次新建线程arg里面的值都会便,会造成
	*前面存放的数据(已连接套接字和客户端地址结构)丢失
	*/
	info = (struct ARG *)arg;
	//调用response函数,用以获取并显示客户端名称
	response(info->connectfd);
	//调用encode函数用以将客户输入的数据进行加密并且发送给客户密文
	encode(info->connectfd,info->client);		

	free(arg);
	pthread_exit(NULL);				//显式终止线程,如果不加该函数,在function完毕之后线程仍旧会终止,这便是隐式终止。
}

void response(int connectfd){
	//获取用户输入的客户端名称
	int num;
	char client_name[20];				//存放客户端名称
		
	memset(&client_name,'\0',sizeof(client_name));				//清空client_name变量
	if((num = recv(connectfd,client_name,20,0) == -1)){
		//接收客户端名字
		perror("Recv error!\n");
		exit(0);
	}
	client_name[num-1] ='\0';		//将最后一个字符置为空字符,用以作为字符串结尾
	printf("Client name is %s\n",client_name);

}

//根据学号进行字符串加密
void encode(int connectfd,struct sockaddr_in client){
	int i,j,num;
	int snum[] = {2,0,1,7,1,2,2,1,1,9};		//密钥
	char data[100],miwen[100];				//data用以接收用户输入的数据,miwen用以存放密文
	j = 0;

	while(1){	
	memset(&data,'\0',sizeof(data));
	memset(&miwen,'\0',sizeof(miwen));


	if((num = recv(connectfd,data,sizeof(data),0)) ==-1){
		perror("Recv error!\n");
		exit(0);
	}

	if(!strcmp(data,"quit")){
		//如果用户输入的是quit则退出
		break;
	}
	//密钥长度为学号长度,能整除该长度就说明不需要在字符串后面补零可以直接加密
	for(i = 0; i<strlen(data); i++){
		if((data[i] >= 'a' && data[i] <='z') || (data[i] >= 'A' && data[i] <='Z')){
			//字符加密与数字加密需要分开,因为字符移位可能会越过字母的区域,需要另做操作
			if(((data[i]+snum[j]) > 'z') && (data[i] >= 'a') && (data[i] <= 'z')){
				//越界情况一
				miwen[i] = data[i] + snum[j]-26;
			}
			else if(((data[i]+snum[j])>'Z') && (data[i] >= 'A') && (data[i] <= 'Z')){
					//越界情况二
					miwen[i] = data[i] + snum[j] -26;
			}
			else{
				//这是正常情况执行的代码
				miwen[i] = data[i] + snum[j];
			}
			//每次加密结束,j增加移至下一个密钥
			j++;
			if(j %10 == 0){
				//如果j%10等于0,就说明已经加密完一轮,该让密钥回到第一个值的位置了
				j = 0;
			}
		}
	}

	if(strlen(data) % 10 != 0){
		//如果不能整除则需要填补。

		for(i=0;i<(10-strlen(data)%10);i++){

			miwen[strlen(data)+i] += 48;
			miwen[strlen(data)+i] += snum[j];
			j++;
			if(j %10 == 0){
			//如果j%10等于0,就说明已经加密完一轮,该让密钥回到第一个值的位置了
			j = 0;
			}
		}

	}
	send(connectfd,miwen,sizeof(miwen),0);				//发送密文
}
	//循环完毕,关闭已连接套接字	
	close(connectfd);
}

下面是线程安全实例

服务器:

	接收客户的连接请求,连接成功则显示客户地址;
	接着接收客户端的名称并显示;
	然后接收来自该客户的字符串,对接收的字符串按分组进行加密(分组长度为个人学号,密钥为个人序号,分组不够补0);
	将加密后的字符发回客户端;
	继续等待接收该客户的信息,直到客户关闭连接;
	服务器将每个连接的用户所发来的所有数据存储起来;
	当连接终止后,服务器将显示客户的名字及相应的所有数据;
	要求服务器具有同时处理多个客户请求的能力。

客户端:

	客户首先与相应的服务器建立连接;
	接着接收用户输入的客户端名称,并将其发送给服务器;
	继续接收用户输入的字符并将字符串发送给服务器;
	同时接收服务器发回的加密后的字符串并显示;
	继续等待用户输入字符串,直到用户输入的是quit,则关闭连接并退出。

打印出程序运行结果,并结合程序进行分析。

客户端代码和前面博客(多进程)相同,不再贴出

服务器代码

#include <stdio.h>				//标准输入输出
#include <sys/socket.h>			//套接字函数所在头文件
#include <string.h>				//字符串处理函数所在头文件(例如memset)
#include <arpa/inet.h>			//地址结构体所在头文件
#include <stdlib.h>				//exit所在头文件
#include <pthread.h>			//线程函数所在头文件
#include <unistd.h>				//close所在头文件

#define PORT 3333


/*程序在savedata函数中调用createkey_once函数(自定义的)
*createkey_once函数调用pthread_key_create函数创建tsd
*并设置析构函数destructor.该析构函数用来释放线程创建的tsd
*
*/

//定义结构体
struct ARG{
	int connectfd;
};

struct ST_DATA{
	int index;
};

//声明全局变量
pthread_key_t key;
pthread_once_t once = PTHREAD_ONCE_INIT;			//确保savedata中的pthread_once只执行一次

//定义静态函数
static void destructor(void *ptr){
	//线程终止时自动调用该函数,用以释放线程私有空间key所占用的空间
	free(ptr);
}

static void creatkey_once(void){
	/*创建线程私有空间
	*函数原型
	*pthread_key_create(pthread_key_t *key,void(* destructor)(void*value));
	*第一个参数指向创建的关键字,第二个参数可选,为析构函数
	*/
	pthread_key_create(&key,destructor);
}


void *function(void *arg);								//声明函数
void response(int connectfd);							
void encode(char *data,int len,char *cli_data,int connectfd);
void savedata(char *recvbuf,int len,char *cli_data);


int main(){
	int listenfd,connectfd;								//套接字
	struct sockaddr_in server,client;					//地址结构
	struct ARG *arg;
	pthread_t tid;										//存放线程id
	socklen_t len;

	if((listenfd = socket(AF_INET,SOCK_STREAM,0)) ==-1){
		//创建套接字
		perror("Socket error!\n");
		exit(0);
	}

	//设置地址重用
	int opt = SO_REUSEADDR;							
	setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

	//配置地址结构
	memset(&server,'\0',sizeof(server));
	server.sin_port = htons(PORT);						//设置端口
	server.sin_addr.s_addr = htonl(INADDR_ANY);			//设置监听地址
	server.sin_family = AF_INET;						//设置协议

	//绑定地址结构
	if(bind(listenfd,(struct sockaddr *)&server,sizeof(server)) ==-1){
		perror("Bind error!\n");
		exit(0);
	}

	//监听
	if(listen(listenfd,5) ==-1){
		perror("Listen error!\n");
		exit(0);
	}

	len = sizeof(client);
	while(1){
		//接受用户连接请求
		if((connectfd = accept(listenfd,(struct sockaddr *)&client,&len)) ==-1){
			perror("Accept error!\n");
			exit(0);
		}

		//配置arg结构体
		arg = (struct  ARG *)malloc(sizeof(struct ARG));				//让arg指向内存中的一段空间
		arg->connectfd = connectfd;								//赋值
		pthread_create(&tid,NULL,function,(void *)arg);				//创建新线程
	}



	close(listenfd);					//关闭监听套接字
	return 0;
}

void *function(void *arg){
	//线程入口函数
	//保存arg参数的值
	struct ARG *info;
	
	info = (struct ARG *)arg;			//使info指向arg指向的区域
	response(info->connectfd);							//调用响应函数
	//响应完毕,释放arg空间
	free(arg);

	//显式终止线程,也可以没有下面这个代码,没有就是隐性终止线程
	//线程入口函数运行完毕自动终止线程便是隐性终止
	//调用函数进行终止则是显式终止
	pthread_exit(NULL);
}

void response(int connectfd){
	//前者用来存放客户端名称,中者用来存放客户端发送的所有消息,后者存放当前接收信息
	char cli_name[30],cli_data[1000],data[100];					
	int num;
	
	//清空cli_name
	memset(&cli_name,'\0',sizeof(cli_name));
	
	//接收客户端名称
	if((num=recv(connectfd,cli_name,30,0)) ==-1){
		perror("Recv cli_name error!\n");
		exit(0);
	}

	
	//输出已连接信息
	printf("Client's name is %s\n",cli_name);
	while(1){
		//每次接收消息前清空data
		memset(&data,'\0',sizeof(data));		

		//接收消息
		if((num = recv(connectfd,data,sizeof(data),0)) ==-1){
			perror("Recv messsage error!\n");
			exit(0);
		}

		if(!strcmp(data,"quit") || !strcmp(data,"quit\n")){
			//若用户发送的是quit则退出
			break;
		}
		num = strlen(data);
		//将用户发送的消息进行保存
		savedata(data,num,cli_data);

		//加密消息
		encode(data,num,cli_data,connectfd);
	}
	
	//交互完毕打印客户端名称及其发送的所有数据,并关闭已连接套接字
	printf("Client name is %s\n",cli_name);
	printf("The data is: %s\n",cli_data);
	close(connectfd);
}

void encode(char *data,int len,char *cli_data,int connectfd){
	int i,j,num;
	int snum[] = {2,0,1,7,1,2,2,1,1,9};		//密钥
	char miwen[100];						//data用以接收用户输入的数据,miwen用以存放密文
	j = 0;

	//清空密文数组
	memset(&miwen,'\0',sizeof(miwen));


	//密钥长度为学号长度,能整除该长度就说明不需要在字符串后面补零可以直接加密
	for(i = 0; i<strlen(data); i++){
		if((data[i] >= 'a' && data[i] <='z') || (data[i] >= 'A' && data[i] <='Z')){
			//字符加密与数字加密需要分开,因为字符移位可能会越过字母的区域,需要另做操作
			if(((data[i]+snum[j]) > 'z') && (data[i] >= 'a') && (data[i] <= 'z')){
				//越界情况一
				miwen[i] = data[i] + snum[j]-26;
			}
			else if(((data[i]+snum[j])>'Z') && (data[i] >= 'A') && (data[i] <= 'Z')){
					//越界情况二
					miwen[i] = data[i] + snum[j] -26;
			}
			else{
				//这是正常情况执行的代码
				miwen[i] = data[i] + snum[j];
			}
			//每次加密结束,j增加移至下一个密钥
			j++;
			if(j %10 == 0){
				//如果j%10等于0,就说明已经加密完一轮,该让密钥回到第一个值的位置了
				j = 0;
			}
		}
	}

	if(strlen(data) % 10 != 0){
		//如果不能整除则需要填补。

		for(i=0;i<(10-strlen(data)%10);i++){

			miwen[strlen(data)+i] += 48;
			miwen[strlen(data)+i] += snum[j];
			j++;
			if(j %10 == 0){
			//如果j%10等于0,就说明已经加密完一轮,该让密钥回到第一个值的位置了
			j = 0;
			}
		}

	}
	send(connectfd,miwen,sizeof(miwen),0);				//发送密文

}

void savedata(char *recvbuf,int len,char *cli_data){
	//保存用户输入的所有信息
	int i=0;									///每次存放数据的索引
	struct ST_DATA *data;
	pthread_once(&once,creatkey_once);			//pthread_once函数与once参数确保creatkey_once函数只执行一次
	if((data = (struct ST_DATA *)pthread_getspecific(key)) ==NULL){
		//获取线程私有空间
		//如果是初次创建则进行初始化
		data = (struct ST_DATA *)malloc(sizeof(struct ST_DATA));
		pthread_setspecific(key,data);
		data->index = 0;
	}
	while(i<len){
		cli_data[data->index++] = recvbuf[i];
		i++;
	}
	cli_data[data->index] = '\0';
}


  • 5
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
TCP 多线程并发服务器是一种常见的服务器架构,它可以同时处理多个客户端的请求,提高服务器的处理性能和并发能力。 实现 TCP 多线程并发服务器的方法有多种,其中一种比较常见的方法如下: 1. 创建一个主线程,用于监听客户端的连接请求。 2. 当有客户端连接请求到来时,主线程会创建一个新的工作线程,将客户端的请求交给工作线程处理。 3. 工作线程负责与客户端进行通信,处理客户端的请求并返回响应信息。 4. 当客户端断开连接时,工作线程会关闭与客户端的连接并退出。 下面是一个基于 Python 的 TCP 多线程并发服务器的示例代码: ```python import socket import threading def handle_client(client_socket): while True: data = client_socket.recv(1024) if not data: break client_socket.send(data) client_socket.close() def run_server(): server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind(('localhost', 8888)) server_socket.listen(5) while True: client_socket, addr = server_socket.accept() client_thread = threading.Thread(target=handle_client, args=(client_socket,)) client_thread.start() if __name__ == '__main__': run_server() ``` 在这个示例代码中,我们创建了一个主线程来监听客户端的连接请求,当有客户端连接请求到来时,主线程会创建一个新的工作线程来处理该客户端的请求。工作线程通过循环不断地接收客户端的数据,处理完成后将结果发送给客户端,并在客户端断开连接后关闭与客户端的连接并退出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值