Práctica tres de programación de red Linux (multihilo + selección de multiplexación io)

 prefacio

        Recordatorio: no necesita leer el título y el código, solo tome el código para probarlo y lo entenderá.

tema:

Sobre la base de la práctica de programación 2, la tecnología de subprocesos múltiples y la multiplexación I0 se utilizan para darse cuenta de que un subproceso sirve a múltiples clientes , y múltiples subprocesos en el proceso de servicio sirven a una gran cantidad de clientes . Requerir:

  1. El código fuente está bien formateado y debidamente comentado;
  2. Además de las funciones claramente requeridas anteriormente, también se debe prestar atención a otras cuestiones, como la exclusión mutua de subprocesos, la cantidad de subprocesos admitidos por el programa de servicio y la capacidad de configuración de cada subproceso para admitir los cálculos del cliente , etc.;

    3. Envíe un informe, que incluye el código fuente del programa y capturas de pantalla de los resultados de las pruebas.

Captura de pantalla del efecto de prueba:

        Similar a la práctica de programación 2, creé tres carpetas, una representa el lado del servidor y las otras dos representan los dos clientes de client y client2.

        Probemos el programa para ver si el cliente puede comunicarse con el servidor:

  La comunicación es normal A continuación, quiero cargar el archivo test.txt desde el cliente del cliente y luego descargar el archivo test.txt cargado en el servidor al cliente2 en el lado del cliente2.

        La siguiente imagen muestra los archivos en cada carpeta antes de cargar y descargar archivos.

        Cliente Cliente comienza a cargar archivos:

        La carga es exitosa, el cliente sale de la conexión y descarga el archivo test.txt en el cliente client2:

        La descarga se realizó correctamente y puede ver que el archivo test.txt aparece tanto en el servidor como en el cliente2.

        A continuación, pruebe la cantidad de clientes a los que puede acceder el servidor:

        Las pruebas manuales son demasiado engorrosas para medir el límite superior.

        Así que modifiqué el código del cliente para permitirle usar la creación continua de subprocesos para conectarse al servidor (sin comunicación), y luego compilar y ejecutar, y descubrí que el programa se detuvo cuando se crearon alrededor de 3400 subprocesos.

        También puede usar el comando ulimit -a para ver el tamaño de la pila de subprocesos, como se muestra en la figura a continuación, el tamaño de pila predeterminado es 8192 (8 MB), por lo que los subprocesos que se pueden crear también se pueden estimar aproximadamente. Al mismo tiempo, puede usar ps -a para ver el proceso actual y usar ps -T -p pid para ver los subprocesos bajo el pid del proceso.

        La prueba del programa está aquí.

Código fuente del programa:

Código del servidor:

#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;
}

Codigo del cliente:

#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);
			}
		}
	}	
}

Errores y deficiencias en el código.

        1. Sigue siendo el problema del blog anterior, usar funciones como fopen, fgets y fprintf para leer archivos de texto, y los archivos no son solo archivos de texto, debe usar funciones de lectura y escritura para leer archivos (resuelto )

        2. Deberías usar mejores funciones como sendto y recvfrom para peering (resuelto)

        3. La visualización de los resultados de la prueba de código es demasiado descuidada. Es un mal hábito escribir la explicación de texto en la imagen. Debe tomar una captura de pantalla y explicarla detrás de la imagen.

        4. El uso de la exclusión mutua de subprocesos es incorrecto y el concepto de exclusión mutua de subprocesos no está claro. La exclusión mutua solo debe realizarse cuando varios subprocesos acceden al mismo recurso. En pocas palabras, la exclusión mutua de subprocesos solo se requiere si hay recursos compartidos. Y vi que el tema requiere el uso de la exclusión mutua de subprocesos, y la exclusión mutua de subprocesos se usa sin importar lo que suceda. (Por supuesto, eliminé el hilo mutex en el código fuente anterior para evitar que solo un hilo se comunique al mismo tiempo) (Resuelto)

        5. No es necesario crear un subproceso principal para el procesamiento, simplemente inicialice el servidor directamente. (Probé este método, pero como no estoy familiarizado con select, creará subprocesos redundantes, así que hice esto).

        Se han realizado modificaciones en el código marcado como resuelto anteriormente. Si no funciona, es posible que lo haya olvidado. Puedes comentar o enviarme un mensaje privado.

epílogo

        Tengo prisa por hacer la práctica de programación esta vez, aunque el profesor dijo que el plazo no es limitado (solo terminar este semestre), porque generalmente no hay mucho tiempo, y la práctica anterior no tomó mucho tiempo, así que Quise darme prisa, lo hice y lo entregué para evitar problemas más adelante, no esperaba que tardara casi una semana en hacerlo (por lo general, tengo que asistir a clases y hacer otras tareas), y finalmente lo terminé. al final del fin de semana, y luego rápidamente escribió el informe y lo entregó. Hay otros experimentos por hacer.

        Resultó que fui el primero en enviarlo, y luego el profesor de la primera clase de programación de red de Linux la próxima semana tomó mi informe para comentarlo, lo evaluó en el acto y descubrió un montón de errores (la mayoría de que se mencionaron anteriormente). Morí en el acto. En ese momento, me di cuenta de que los problemas que encontré estaban cubiertos en los capítulos al final del libro, pero debido al pensamiento inercial, seguí enfocándome en el capítulo donde se organizaba esta práctica. De modo que el pensamiento sigue siendo demasiado rígido y no lo suficientemente flexible.

        Después de la clase, fui rápidamente al maestro para explicarle los problemas que el maestro no entendió, y luego también conversé con el maestro, sigamos trabajando duro (xiangbailanle)

Supongo que te gusta

Origin blog.csdn.net/xiexieyuchen/article/details/129759725
Recomendado
Clasificación