Como colocar varios clientes em um servidor c++

Minha professora passou esses codigos comunicando só entre o servidor e um unico cliente, mas o objetivo é comunicar entre varios clientes com o servidor.

SERVIDOR.C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio_ext.h>

#define porta 6666 // Porta em que o servidor irá subir
#define tmb 1024 // tamanho do buffer
 
char buffer[tmb], bufaux[tmb], *loc;
char comandos[] = "/x /mem /disc /time /pros /port /help";
int pontarq, tbuf, skt, tskt, escolha;
pthread_t threads;
pthread_t threads2;
pthread_t threads3;

struct sockaddr_in serv; // estrutura do tipo socket
 
	void *comeco(){
		
			bind(skt,(struct sockaddr *)&serv,sizeof(struct sockaddr)); // cria o vínculo, ou seja, sobe o serviço
		
			listen(skt,1); // coloca o servidor em modo escuta
			printf(">> Servidor esta escutando na porta %d\n\n",porta);
			
			skt = accept(skt,(struct sockaddr *)&serv,&tskt); // permite que o socket aceite conexões
			printf(">> A Conexao com o endereco %s foi estabelecida\n\n",inet_ntoa(serv.sin_addr)); 
     
			// Envia ack para o cliente
			strcpy(buffer,"Servidor Comunicando OK!!!");
			strcpy(bufaux,buffer);
			send(skt,buffer,strlen(buffer), 0);
    	
			// Recebe ack do cliente
			tbuf = recv(skt, buffer,tmb, 0);
			buffer[tbuf]=0x00;
			printf(">: %s\n",buffer);
			
		pthread_exit(NULL);
	}
 
	void *naescuta(){
		
		do{
    		tbuf = recv(skt,buffer,tmb,0); //recebe dados do cliente
    		buffer[tbuf]=0x00;
		
		if (strncmp(buffer,"/",1) == 0){
 
        	loc = strstr(comandos,buffer);
   		escolha = loc - comandos;
 
			switch(escolha){
			case 0: 
				break;
			case 3:
				puts(">> Memoria Disponivel Requisitada");
				system("free -mot > temp");
				pontarq = open("temp",O_RDONLY,0666);
				if(pontarq < 0){
					puts("Erro no arquivo temporario!");
				}      
				tbuf = read(pontarq,buffer,sizeof(buffer));
				close(pontarq);
				send(skt,buffer,tbuf,0);
				system("rm -r temp");
				break;
			  
			case 8:
				puts(">> Partiçoes de disco Requisitadas");
				system("df -h > temp");
				pontarq = open("temp", O_RDONLY, 0666);
				if(pontarq < 0){
					puts("Erro no arquivo temporario!");
				}      
				tbuf = read(pontarq,buffer,sizeof(buffer));
				close(pontarq);
				send(skt,buffer,tbuf,0);
				system("rm -r temp");
				break;

			case 14:
				puts(">> Tempo Logado Requisitado");
				system("uptime > temp");
				pontarq = open("temp", O_RDONLY, 0666);
				if(pontarq < 0){
					puts("Erro no arquivo temporario!");
				}      
				tbuf = read(pontarq,buffer,sizeof(buffer));
				close(pontarq);
				send(skt,buffer,tbuf,0);
				system("rm -r temp");
				break;
			case 20:
				puts(">> Processos Rodando no servidor");
				system("ps -a > temp");
				pontarq = open("temp", O_RDONLY, 0666);
				if(pontarq < 0){
					puts("Erro no arquivo temporario!");
				}      
				tbuf = read(pontarq,buffer,sizeof(buffer));
				close(pontarq);
				send(skt,buffer,tbuf,0);
				system("rm -r temp");
				break;
			case 26:
				puts(">> Portas abertas no servidor");
				system("netstat -ant > temp");
				pontarq = open("temp", O_RDONLY, 0666);
				if(pontarq < 0){
					puts("Erro no arquivo temporario!");
				}      
				tbuf = read(pontarq,buffer,sizeof(buffer));
				close(pontarq);
				send(skt,buffer,tbuf,0);
				system("rm -r temp");
				break;
			case 32:
				puts(">> Help Solicitado");
				pontarq = open("help", O_RDONLY, 0666);
				if(pontarq < 0){
					puts("Erro no arquivo temporario!");
				}      
				tbuf = read(pontarq,buffer,sizeof(buffer));
				close(pontarq);
				send(skt,buffer,tbuf,0);
				break;

			}
 
		}else{
			while(strcmp(bufaux,buffer) != 0){
				printf(">: %s\n",buffer);    
				strcpy(bufaux,buffer);
			}						        
		}
    	}while(strcmp(buffer,"/x") != 0);
     
		pthread_exit(NULL);
	
	}
	
	void *naescrita(){
		A:
		if(strcmp(buffer,"x") != 0){
			scanf("%[^\n]s", buffer);
			__fpurge(stdin);
			strcpy(bufaux,buffer);
	     
			send(skt, buffer, strlen(buffer), 0);

		}
		goto A;
		pthread_exit(NULL);
	}

int main(){
	 
	system("clear");
    
        /* man socket()
    	 * AF_INET: tipo do endereço do host
	 * SOCK_STREAM: Informa ao kernel o tipo do socket. Pode ser SOCK_STREAM ou SOCK_DGRAM
	 * 0: tipo do protocolo. Quando seta o valor com zero o socket escolhe o protocolo correto de acordo com o tipo do socket
	 * veja lista de protocolos em /etc/protocol
	 */	
    	skt = socket(AF_INET, SOCK_STREAM, 0);
 
	/* Estrutura do tipo sockaddr_in
	 * struct sockaddr_in {
    	 * 	u_char  sin_len;
    	 * 	u_short sin_family;        // Família do endereço
    	 * 	u_short sin_port;          // Número da porta
    	 * 	struct  in_addr sin_addr;  // Endereço IP. Alguns endereços INADDR_LOOPBACK, INADDR_ANY, INADDR_BROADCAST, INADDR_NONE
    	 * 	char    sin_zero[8];       // Deve ser igual ao tamanho da estrutura sockaddr
	 * };
	 * 
	 * 
	 */ 
    	serv.sin_family = AF_INET; 
    	serv.sin_addr.s_addr = INADDR_ANY;
    	serv.sin_port = htons (porta);
    	memset(&(serv.sin_zero),0x00,sizeof(serv.sin_zero));
    	tskt = sizeof(struct sockaddr_in);
     
    	printf("\n    xXxXxX Servidor XxXxXx\n\n");
    	
    	int aux=0;
    	
    	do{
   
			pthread_create(&threads3, NULL, comeco, "comeco");
			pthread_create(&threads2, NULL, naescuta, "naescuta");
			pthread_create(&threads, NULL, naescrita, "naescrita");
			pthread_join(threads3, NULL);
			pthread_join(threads, NULL);
			pthread_join(threads2, NULL);
		
		aux++;
		
		}while(aux==1);
		
    	close(skt);
    	printf(">> A Conexao com o host %s foi encerrada!!!\n\n",inet_ntoa(serv.sin_addr));
    	exit(0);
}

CLIENTE.C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio_ext.h>
#include <pthread.h>
#include <fcntl.h>
 
/*#define ip "127.0.0.1"*/
#define tmb 1024
#define porta 6666

char ip[16], buffer[tmb], bufaux[tmb];
int tbuf, skt;
struct sockaddr_in serv;//importa a estrutura das bibliotecas de socket
pthread_t threads;
pthread_t threads2;

void *naescuta(){
		do{
    		tbuf = recv(skt,buffer,tmb,0); //recebe dados do cliente
    		buffer[tbuf]=0x00;
		
		if (strncmp(buffer,"/",1) == 0){
 
		}else{
			while(strcmp(bufaux,buffer) != 0){
				printf(">: %s\n",buffer);    
				strcpy(bufaux,buffer);
			}	
			
					        
		}
    	}while(strcmp(buffer,"/x") != 0);
     
		pthread_exit(NULL);
	
	}
	
void *naescrita(){

	while(strcmp(buffer,"x") != 0){
		scanf("%[^\n]s", buffer);
		__fpurge(stdin);
		strcpy(bufaux,buffer);
	     
			send(skt, buffer, strlen(buffer), 0);

	}
	
	pthread_exit(NULL);
}	

int main ()
{

	system("clear");

	printf("\n    xXxXxX Cliente XxXxXx\n\n");

	printf("\n>> Digite o IP do Servidor ou /x para sair: ");
	scanf("%s", ip);
	__fpurge(stdin);

	skt = socket(AF_INET, SOCK_STREAM, 0);//cria o socket e manda para inteiro skt "Essa função é das bibliotecas de socket"

	serv.sin_family = AF_INET;//Familia da porta"Essa tbm é da biblioteca dos socket"
	serv.sin_addr.s_addr = inet_addr(ip); //envio o ip a se conectar
	serv.sin_port = htons (porta); //envia porta a se conectar

	memset (&(serv.sin_zero), 0x00, sizeof (serv.sin_zero)); 
	
	if(connect (skt, (struct sockaddr *)&serv, sizeof (struct sockaddr)) != 0)
	{
		puts("\n> Servidor nao Encontrado\n");
		exit(0);
	}
	printf(">> A Conexao com o Servidor %s foi estabelecida na porta %d \n\n",ip,porta);

	// Recebe ack do servidor
	tbuf = recv (skt, buffer, tmb, 0);
	buffer[tbuf] = 0x00;
	printf (">: %s\n",buffer);

	// Envia ack p/ servidor
	strcpy(buffer, "Cliente comunicando OK!!!");
	send(skt, buffer, strlen(buffer), 0 );    

	pthread_create(&threads, NULL, naescrita, "naescrita");
	pthread_create(&threads2, NULL, naescuta, "naescuta");
	pthread_join(threads2, NULL);
	pthread_join(threads2, NULL);

	close(skt);
	printf (">>A conexao com o servidor foi finalizada!!!\n\n");
	exit(0);    
}

Primeiramente, isso é C, e não C++. C++ não possui uma API nativa para lidar com sockets, infelizmente. Contudo, C++ é backward compatible com C, por isso você pode compilar um código fonte em C com um compilador C++.

Em segundo lugar, a função bind não “sobe o serviço”. Ela associa o socket a um endereço especificado. Tanto é que se você só fizer um bind e tentar conectar nesse endereço através de um cliente, não vai funcionar. A função listen coloca o socket em modo passivo (vai ser usado para aceitar conexões). O socket precisa estar em modo passivo para a função accept funcionar. Portanto, a função listen seria a responsável por “subir o serviço”. Quando você chama o listen, as conexões dos clientes começam a ser enfileiradas pelo kernel, e você pode acessa-las serialmente através da chamada para a função accept. No caso de não haver ninguém esperando, a função accept não retorna, e o código fica “travado” ali até alguém se conectar.

Você já aprendeu a usar fork? Essa solução vai utiliza-la.

Uma técnica muito comum é fazer o seguinte, vou te dar uma espécie de pseudo-código (cheio de lacunas e faltando argumentos para simplificação) e você escreve direito:

int main() {
    // 1. cria o server socket e todas as firulas (bind, listen)
    // 2. cria variáveis auxiliares necessárias
    while(servidor_deve_estar_rodando) {
        int socket_do_cliente = accept(socket_servidor);
        if (fork() == 0) { // processo filho
            processa(socket_do_cliente);
        }
        close(socket_do_cliente);
    }
    close(socket_servidor);
    return 0;
}

O que esse algoritmo faz:

  1. Cria o socket do servidor
  2. Cada vez que um cliente novo se conecta, spawna um processo novo que será responsável por fazer a “lógica de negócio” da aplicação
  3. O socket do cliente deve ser fechado duas vezes, tanto pelo processo pai quanto pelo filho, por isso o close está fora do if
  4. A função processa(socket_do_cliente) recebe o file descriptor do socket do cliente e pode fazer toda a comunicação necessária. Você pode bolar aqui uma maneira de fazer algum cliente matar o servidor se quiser, basta dar um jeito de “breakar” o while

Dessa forma, você pode conectar quantos clientes quiser. Um processo novo será criado para cada conexão, e uma conexão não vai interferir na outra por causa da maneira como o kernel trabalha com sockets passivos e aceita conexões. Existe um livro MUITO bom sobre isso, que inclusive é a fonte desse exemplo:

UNIX network programming, volume 1: The sockets networking API

Se você ler o livro inteiro e entender tudo, vai virar o ninja dos sockets.

Boa sorte!

EDIT: Estava lendo o código que tu colocou aqui, e queria fazer algumas observações. Entretanto, por se tratar de um contexto acadêmico, talvez seja um exagero meu, mas vamos lá.

Tá estranho. São criados buffers globais e uma thread para cada função. Isso não faz muito sentido. Funciona com um cliente, como você falou, mas você vai ter que reescrever toda essa lógica. Quando uma thread chama o fork, só a stack dela (e a heap inteira do processo pai) é copiada para o processo filho. Portando, as outras threads não vão junto. Não precisa mexer com thread no exemplo que eu te dei, só processos.

Ainda sobre essa abordagem multi-thread: o código está consumindo muito mais recursos do que deveria. Existem loops que ficam tentando ler da memória indefinidamente, até que apareça alguma coisa. Se um cliente demorar para conectar, um monte de processamento vai ser ocupado sem nenhuma necessidade.

Uma analogia para explicar melhor esse problema: imagine que você está viajando com um amigo dentro de um carro. Você está no banco do carona e ele está dirigindo. Suponhamos que você quer saber quando chegou ao destino, mas por algum motivo não consegue enxergar fora do carro. Você tem duas opções:

  1. Perguntar para o teu amigo de meio em meio segundo: “Já chegou?”
  2. Pedir pra ele te avisar quando chegar, e ir dormir nesse meio tempo

Qual você acha que é mais eficiente (e gera menos estresse)?

Outra coisa: por que diabos existe um do...while no main que só executa uma vez? kkkkkkkkk

Dá pra melhorar bastante isso daí, sem usar loops desnecessários. Como eu te disse ali, quando um cliente conecta, um novo processo é spawnado. É nesse momento, quando a função processa é chamada, que você vai começar a ler do buffer do cliente, do SOCKET diretamente, e não de um buffer global. Quando você lê do socket e não há nada escrito, a chamada bloqueia a thread até que algo apareça. Isso é muito mais eficiente do que tentar ficar lendo loucamente de um buffer global, e não gera condições de corrida.

Valeu Obrigado ajudou bastante. Não acredito que você responde problemas do guj. O pessoal de videira esta se tornando fodôes na informatica kkkkk.

@lvbarbosa você pode me ajudar com esse mesmo problema? Estou interpretando seu pseudocódigo, mas estou com alguns problemas :confused:

Pseudocódigo? Onde?

@davidbuzatto @Ailton_Aires, depende a plataforma que ele rodou, tem tres possibilidades para esse codigo dar certo, rodar no windows com visual studio, ou pode rodar no ide devc++ ou no sistema linux em qualquer ide.