Linux network programming practice three (multithreading + io multiplexing select)

 foreword

        Reminder: You don't need to read the title and code, just take the code to test and you will understand.

topic:

On the basis of programming practice 2, multi-threading technology and I0 multiplexing are used to realize that one thread serves multiple clients , and multiple threads in the service process serve a large number of clients . Require:

  1. Source code is well formatted and appropriately commented;
  2. In addition to the functions clearly required above, other issues should also be paid attention to, such as thread mutual exclusion, the number of threads supported by the service program, and the configurability of each thread to support client calculations , etc.;

    3. Submit a report, which includes program source code and screenshots of test results.

Screenshot of test effect:

        Similar to programming practice 2, I created three folders, one represents the server side, and the other two represent the two clients of client and client2.

        Let's test the program to see if the client can communicate with the server:

  The communication is normal. Next, I want to upload the file test.txt from the client client, and then download the test.txt uploaded to the server to client2 on the client2 side.

        The picture below shows the files in each folder before file upload and download.

        Client Client starts uploading files:

        The upload is successful, the client client exits the connection and downloads the test.txt file on the client2 client:

        The download is successful, and you can see that the test.txt file appears on both server and client2.

        Next, test the number of clients that the server can access:

        Manual testing is too cumbersome to measure the upper limit.

        So I modified the code of the client to let the client use the continuous creation of threads to connect with the server (without communication), and then compile and run, and found that the program stopped when the number of threads was created around 3400.

        You can also use the ulimit -a command to view the thread stack size, as shown in the figure below, the default stack size is 8192 (8MB), so the threads that can be created can also be roughly estimated. At the same time, you can use ps -a to view the current process, and use ps -T -p pid to view the threads under the process pid.

        Program testing is here.

Program source code:

Server code:

#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PORT 8888	/*侦听端口地址*/
#define BACKLOG 32	/*侦听队列长度*/

#define Maxline 1024	/*最大读取文件行数*/

char file_name[105];	/*设置一个全局变量用来保存文件名*/
int ss,sc;	/*ss为服务器的socket描述符,sc为客户端的socket描述符*/
struct sockaddr_in server_addr;		/*服务器地址结构*/
struct sockaddr_in client_addr;		/*客户端地址结构*/
socklen_t len = sizeof(client_addr);	/*客户端地址结构长度*/
char ipstr[128],ip[128];	/*用于输出ip*/
int sum_client=0;	/*记录接入的客户端个数*/
pthread_mutex_t mutex; /*互斥区*/
fd_set fds;	/*文件描述符集合*/

void get_filename(char buffer[]);	/*从buffer字符串中提取出文件名*/
void upload_file(char buffer[],int s);		/*文件上传函数*/
void download_file(char buffer[],int s);	/*接收文件函数*/
void process_conn_server(int s);  /*服务端与客户端通信函数*/
void *start_routine(void *arg);	 /*线程处理函数*/
void *server_init(void *arg);	/*进行服务器的相关操作,包括select*/

int main(int argc, char *argv[])
{
	int err;	/*返回值*/
	pthread_mutex_init(&mutex,NULL);	/*初始化互斥区*/
	pthread_t threadid = 0;
	err = pthread_create(&threadid,NULL,server_init,NULL);/*创建线程,再进行服务端的设置*/
	if(err != 0){
		printf("创建线程出错\n");
		return 0;
	}
	else pthread_join(pthread_self(),NULL);
	while(1)sleep(10);	/*防止主线程关闭*/
	pthread_mutex_destroy(&mutex);	/*销毁互斥*/
	return 0;
}

void *server_init(void *arg){	/*进行服务器的相关操作,包括select*/
	int err;	/*返回值*/
	/*建立一个流式套接字*/
	ss = socket(AF_INET, SOCK_STREAM, 0);
	if(ss < 0){	/*出错*/
		printf("socket error\n");
		return NULL;	
	}
	
	/*设置服务器地址*/
	bzero(&server_addr, sizeof(server_addr));	/*清零*/
	server_addr.sin_family = AF_INET;	/*协议族*/
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);	/*本地地址*/
	server_addr.sin_port = htons(PORT);	/*服务器端口*/
	
	/*绑定地址结构到套接字描述符*/
	err = bind(ss, (struct sockaddr*)&server_addr, sizeof(server_addr));
	if(err < 0){	/*出错*/
		printf("bind error\n");
		exit(-1);
		//return NULL;	
	}
	
	/*设置侦听*/
	err = listen(ss, BACKLOG);
	if(err < 0){	/*出错*/
		printf("listen error\n");
		return NULL;	
	}
	while(1){
		FD_ZERO(&fds);		//描述符集合初始化
		FD_SET(ss,&fds);
		
		struct timeval timeout = {30, 0};	//超时时间设置为30s
		
		err = select(ss+1,&fds,NULL,NULL,&timeout);
		if(err == -1){
			printf("select error\n");
			return NULL;
		}
		else if(err == 0){
			printf("没有接收到请求(超时)\n");
			continue;
		}
		else{
			if(FD_ISSET(ss,&fds)){
				int size=sizeof(client_addr);
				bzero(&client_addr,size);//清空客户端
				
				sc = accept(ss,(struct sockaddr*)&client_addr,&size);
				if(sc < 0){//出错
					continue;//结束本次循环
				}
            			strcpy(ip,inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ipstr,sizeof(ipstr)));
            			int port = ntohs(client_addr.sin_port);
            			printf("新的客户端接入,ip为:%s ,端口号为:%d \n",ip,port);/*输出接如服务端的客户端ip和端口*/
            			sum_client++;
            			printf("当前连接的客户端数(也是线程的个数)为:%d\n",sum_client);
				pthread_t pth;
				err = pthread_create(&pth,NULL,start_routine,NULL);
				if(err != 0){
					printf("创建子线程出错\n");
					FD_CLR(ss,&fds);
					return NULL;
				}
				else pthread_join(pthread_self(),NULL);
				
			}
			
		}
		
	}
}

void *start_routine(void *arg){
	printf("正在通信的线程ID为: %lu\n",pthread_self());
	process_conn_server(sc);	/*调用通信函数*/
}

void process_conn_server(int s)
{
	ssize_t size = 0;
	char buffer[1024];	/*数据的缓冲区*/
	char message[1024];	/*用来传递消息*/
	
	while(1){/*循环处理过程*/
		memset(buffer,0,1024);	/*清空缓冲区*/	
		size = recvfrom(s,buffer,sizeof(buffer),0,(struct sockaddr*)&client_addr,&len);/*从套接字中读取数据放到缓冲区buffer中*/
		if(size == 0){/*没有数据*/
            		strcpy(ip,inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ipstr,sizeof(ipstr)));
            		int port = ntohs(client_addr.sin_port);
            		sum_client--;
                        printf("客户端关闭连接,其ip为:%s,端口号为:%d;\n",ip,port);
                        printf("因客户端关闭连接,ID为:%lu的线程关闭\n",pthread_self());
                        printf("当前连接的客户端数为:%d\n",sum_client);
			return;	
		}
		if(strstr(buffer,"上传文件")!=NULL){	/*收到客户端上传文件的请求*/
			strcpy(message,"服务器端开始接收文件...");
			sendto(s,message,sizeof(message),0,(struct sockaddr*)&client_addr,sizeof(client_addr));/*发给客户端*/
			printf("%s\n接收中...\n",message);
			upload_file(buffer, s);	/*调用函数进行文件接收*/
			sleep(1);	/*用1s时间来进行缓冲*/
		}
		else if(strstr(buffer,"下载文件")!=NULL){	/*收到客户端下载文件的请求*/
			printf("开始上传文件...\n");
			size = recvfrom(s,message,sizeof(message),0,(struct sockaddr*)&client_addr,&len);/*从客户端读取数据*/
			printf("%s\n上传中....\n",message);
			download_file(buffer,s);		/*调用函数进行文件上传*/
			sleep(1);	/*用1s时间来进行缓冲*/
		}
		else {   /*不上传、下载文件则进行消息交互*/
			/*构建响应字符,为接收到客户端字节的数量*/
			sprintf(buffer, "收到你的消息啦!\n");
			sendto(s,buffer,sizeof(buffer),0,(struct sockaddr*)&client_addr,sizeof(client_addr));/*发给客户端*/
		}
		
	}	
}

void get_filename(char buffer[]){	/*从buffer字符串中提取出文件名*/
	memset(file_name,0,strlen(file_name));	/*因为file_name是全局变量,所以需要先清零*/
	int j=0;
	for(int i=0;i<strlen(buffer);i++)
	if((buffer[i]>='a'&&buffer[i]<='z')||(buffer[i]>='A'&&buffer[i]<='Z')||(buffer[i]>='0'&&buffer[i]<='9')||(buffer[i]=='.'))
		file_name[j++]=buffer[i];
}

void download_file(char buffer[],int s){		/*服务端接受文件函数*/
	get_filename(buffer);	/*取出文件名*/

	int fd = open(file_name,O_RDONLY);	//打开文件
	if(fd < 0){
		char message2[] = "打开文件失败!上传失败!";
		printf("%s\n",message2);
		/*将打开文件失败的消息传送到客户端,防止客户端卡死*/
		sendto(s,message2,sizeof(message2),0,(struct sockaddr*)&client_addr,sizeof(client_addr));
		return ;
	}
	char message[1024]={0};
	while(read(fd,message,1000)){//每次读取1000大小的内容
		/*将读取到的文件内容上传的客户端*/
		sendto(s,message,sizeof(message),0,(struct sockaddr*)&client_addr,sizeof(client_addr));
		sleep(1);	/*每上传一行就暂停1秒,防止客户端来不及接收消息*/
	}
	strcpy(message,"上传结束");
	/*将上传结束的消息传递给客户端*/
	sendto(s,message,sizeof(message),0,(struct sockaddr*)&client_addr,sizeof(client_addr));
	printf("\n上传成功\n");
	close(fd);
	return;	
}

void upload_file(char buffer[],int s){	/*服务端上传文件函数*/
	get_filename(buffer);	/*获取文件名*/

	int fd = open(file_name,O_WRONLY|O_CREAT,S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
	/*打开文件,如果文件不存在会新建一个,同时设置参数,防止open文件加权限使文件无法打开*/
	if(fd < 0){
		printf("打开文件失败!接收失败!\n");
		/*因为此时客户端处于持续发送消息状态,所以把消息传回客户端的话可能会有bug,所以不将消息回传*/
		return;
	}
	size_t size=0;
	char message[1024]={0};		/*创建一个字符串用来传递消息*/
	while(1){
		size = recvfrom(s,message,sizeof(message),0,(struct sockaddr*)&client_addr,&len);
		if(size < 0){		/*没有数据*/
			printf("接收失败!\n");
			return;
		}
		if(strstr(message,"打开文件失败") != NULL){	/*接受到客户端打开文件失败的消息*/
			printf("客户端打开文件失败!接收失败!\n");
			return;
		}
		
		//printf("接收中...\n");
		if(strstr(message,"上传结束")!=NULL) break;	/*接收到上传结束的消息就退出死循环*/
		write(fd,message,1000);
	}
	printf("接收成功!\n");
	close(fd);/*关闭文件*/
	return;
}

Client code:

#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PORT 8888	/*侦听端口地址*/
#define Maxline 1024	/*最大读取文件行数*/

char file_name[105];	/*设置一个全局变量用来保存文件名*/
struct sockaddr_in server_addr;		/*服务器地址结构*/
socklen_t len = sizeof(server_addr);	/*服务端地址结构长度*/

void get_filename(char buffer[]);/*从buffer字符串中提取出文件名*/
void upload_file(char buffer[],int s);		/*文件上传函数*/
void download_file(char buffer[],int s);	/*接收文件函数*/
void process_conn_client(int s);   		/*客户端通信函数*/

int main(int argc, char *argv[])
{
	int s;		/*s为socket描述符*/
	s = socket(AF_INET, SOCK_STREAM, 0); 	/*建立一个流式套接字 */
	if(s < 0){	/*出错*/
		printf("socket error\n");
		return -1;
	}	
	
	/*设置服务器地址*/
	bzero(&server_addr, sizeof(server_addr));	/*清零*/
	server_addr.sin_family = AF_INET;	/*协议族*/
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);	/*本地地址*/
	server_addr.sin_port = htons(PORT);	/*服务器端口*/
	
	/*将用户输入的字符串类型的IP地址转为整型*/
	inet_pton(AF_INET, /*argv[1]*/"127.0.0.1", &server_addr.sin_addr);	
	/*连接服务器*/
	printf("直接输入:上传文件+文件名 即可上传文件\n");
	printf("直接输入:下载文件+文件名 即可下载文件\n");
	printf("其他无特殊情况则保持与服务端进行消息交互\n");
	connect(s, (struct sockaddr*)&server_addr, sizeof(struct sockaddr));
	process_conn_client(s);		/*客户端处理过程*/
	close(s);	/*关闭连接*/
	return 0;
}


void get_filename(char buffer[]){	/*从buffer字符串中提取出文件名*/
	memset(file_name,0,strlen(file_name));	/*因为file_name是全局变量,所以需要先清零*/
	int j=0;
	for(int i=0;i<strlen(buffer);i++)
		if((buffer[i]>='a'&&buffer[i]<='z')||(buffer[i]>='A'&&buffer[i]<='Z')||(buffer[i]>='0'&&buffer[i]<='9')||(buffer[i]=='.'))
			file_name[j++]=buffer[i];
}

void upload_file(char buffer[],int s){		/*文件上传函数*/
	get_filename(buffer);	/*取出文件名*/

	int fd = open(file_name,O_RDONLY);/*打开文件*/
	if(fd < 0){
		char message2[] = "打开文件失败!上传失败!";
		printf("%s\n",message2);
		/*将打开文件失败的消息传送到服务端,防止服务端卡死*/
		sendto(s,message2,sizeof(message2),0,(struct sockaddr*)&server_addr,sizeof(server_addr));/*发给客户端*/
		return ;
	}
	char message[1024]={0};

	while(read(fd,message,1000)){//每次读1000大小的文件内容
		/*将读取到的文件内容上传的服务端*/
		sendto(s,message,sizeof(message),0,(struct sockaddr*)&server_addr,sizeof(server_addr));/*发给客户端*/
		sleep(1);	/*每上传一行就暂停1秒,防止服务端来不及接收消息*/
	}
	strcpy(message,"上传结束");
	/*将上传结束的消息传递给服务端*/
	sendto(s,message,sizeof(message),0,(struct sockaddr*)&server_addr,sizeof(server_addr));/*发给客户端*/
	printf("\n上传成功\n");
	close(fd);
	return;	
}

void download_file(char buffer[],int s){	/*接收文件*/
	get_filename(buffer);	/*获取文件名*/

	int fd = open(file_name,O_WRONLY|O_CREAT,S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
	/*打开文件,如果文件不存在会新建一个,同时设置参数,防止open文件加权限使文件无法打开*/
	if(fd < 0){
		printf("打开文件失败!接收失败!\n");
		/*因为此时服务端处于持续发送消息状态,所以把消息传回服务端的话可能会有bug,所以不将消息回传*/
		return;
	}
	size_t size=0;
	char message[1024]={0};		/*创建一个字符串用来传递消息*/
	while(1){
		size = recvfrom(s,message,sizeof(message),0,(struct sockaddr*)&server_addr,&len);
		if(size < 0){		/*没有数据*/
			printf("接收失败!\n");
			return;
		}
		if(strstr(message,"打开文件失败") != NULL){	/*接受到客户端打开文件失败的消息*/
			printf("服务端打开文件失败!接收失败!\n");
			return;
		}
		if(strstr(message,"收到你的消息啦!")!=NULL){
			/*经运行检测发现有额外输入文件的数据,这里进行隐形处理*/
			continue;
		}
		
		//printf("接收中...\n");
		if(strstr(message,"上传结束")!=NULL) break;	/*接收到上传结束的消息就退出死循环*/
		write(fd,message,1000);
	}
	printf("接收成功!\n");
	close(fd);/*关闭文件*/
	return;
}

void process_conn_client(int s)
{
	ssize_t size = 0;
	char buffer[1024];	/*数据的缓冲区*/
	char message[1024];	/*用来传递消息*/
	
	while(1){	/*循环处理过程*/
		/*从标准输入中读取数据放到缓冲区buffer中*/
		size = read(0, buffer, 1024);
		if(size > 0){	/*读到数据*/
			sendto(s,buffer,sizeof(buffer),0,(struct sockaddr*)&server_addr,sizeof(server_addr));/*发给服务器*/
			if(strstr(buffer,"上传文件")!=NULL){	/*上传文件*/
				printf("开始上传文件...\n");
				size = recvfrom(s,message,sizeof(message),0,(struct sockaddr*)&server_addr,&len);/*从服务器读取数据*/
				printf("%s\n上传中....\n",message);
				upload_file(buffer, s);		/*调用对应函数进行上传*/
				sleep(1);	/*用1s时间来进行缓冲*/
			}
			else if(strstr(buffer,"下载文件")!=NULL){	/*接收文件*/
				strcpy(message,"客户端开始下载文件...");
				sendto(s,message,sizeof(message),0,(struct sockaddr*)&server_addr,sizeof(server_addr));/*发给服务端*/
				printf("%s\n下载中...\n",message);
				download_file(buffer,s);	/*调用接收文件的函数*/
				sleep(1);	/*用1s时间来进行缓冲*/
			}	
			else {
				size = recvfrom(s,buffer,sizeof(buffer),0,(struct sockaddr*)&server_addr,&len);/*从服务器读取数据*/
				printf("%s\n",buffer);
			}
		}
	}	
}

Bugs and deficiencies in the code

        1. It is still the problem of the previous blog, using functions such as fopen, fgets, and fprintf to read text files, and files are not just text files, you should use read and write functions to read files (solved )

        2. You should use better functions such as sendto and recvfrom for peering (solved)

        3. The display of the code test results is too sloppy. It is a bad habit to write the text explanation in the picture. You should take a screenshot and explain it behind the picture.

        4. The use of thread mutual exclusion is wrong, and the concept of thread mutual exclusion is not clear. Mutual exclusion should only be performed when multiple threads access the same resource. Simply put, thread mutual exclusion is required only if there are shared resources. And I saw that the topic requires the use of thread mutual exclusion, and thread mutual exclusion is used no matter what happens. (Of course, I have removed the thread mutex in the above source code to prevent only one thread from communicating at the same time) (Solved)

        5. There is no need to create a main thread for processing, just initialize the server directly. (I tried this method, but because I am not familiar with select, it will create redundant threads, so I did this.)

        Modifications have been made in the code marked resolved above. If it doesn't work out, I may have misremembered it. You can comment or private message me.

epilogue

        I am in a hurry to do the programming practice this time, although the teacher said that the deadline is not limited (just finish this semester), because usually there is not much time, and the previous practice didn’t take much time, so I wanted to hurry up I made it and handed it in to avoid trouble later on. I didn’t expect it to take almost a week to do it (usually I have to attend classes and do other homework), and then finally finished it at the end of the weekend, and then quickly wrote the report and handed it in. There are other experiments to be done.

        It turned out that I was the first to submit it, and then the teacher of the first Linux network programming class next week actually took my report to comment on it, evaluated it on the spot, and found out a bunch of mistakes (most of which were mentioned above. ). I died on the spot. At this time, I realized that the problems I encountered were covered in the chapters at the back of the book, but because of inertial thinking, I kept focusing on the chapter where this practice was arranged. So thinking is still too rigid and not flexible enough.

        After class, I quickly went to the teacher to explain the problems that the teacher didn't understand, and then I also chatted with the teacher, let's continue to work hard (xiangbailanle)

Guess you like

Origin blog.csdn.net/xiexieyuchen/article/details/129759725