Exercício 2 - Parte 2: Servidor TCP concorrente
Critérios para entrega do exercício
- Este exercício é em dupla ou individual.
- A resolução do exercício deve ser enviada em um arquivo .zip,.rar ou .tar.gz para william@ic.unicamp.br até o dia 17/09/2019. O nome do arquivo deve ser "raXXXXX_e_raXXXXXY_exercicio2_p2", onde XXXXXX e XXXXXY deve ser substituído pelo número do RA dos membros da dupla;
- O arquivo comprimido deverá conter:
- um único arquivo .pdf contendo o relatório comprovando a execução de todos os passos descrito na ativadade (Devem ser incluídas as saídas das execuções dos programas, devem ser comentados os nomes/endereços das máquinas utilizadas e devem ser respondidas as questões).
- Os códigos
servidor.c
e cliente.c
modificados conforme solicitado e devidamente comentados.
- O assunto da mensagem deve ser "[MC833] Exercício 2 Parte 2".
- Importante: Não envie código com warnings ;)
Atividade
Passos:
- Adicione a função
sleep
no servidor.c
da
atividade prática anterior antes do socket ser fechado close(connfd)
de modo que o servidor ''segure'' a conexão do primeiro cliente que se conectar.
Com essa modificação, o servidor aceita a conexão de dois clientes de forma concorrente ?(realize o teste) Por que? (Este
código não precisa ser enviado por email)
- Escreva, utilizando sockets TCP, um programa cliente e um programa
servidor de echo que possibilitem a execução remota de comandos
enviados pelo cliente. Lembre-se que o servidor deve atender a vários
clientes de forma concorrente. O servidor deve receber como argumento
na linha de comando a porta na qual irá escutar. O cliente deve
receber como argumento na linha de comando o endereço IP do servidor e
a porta na qual irá conectar.
Detalhes do funcionamento:
- O cliente faz continuamente o seguinte:
- lê uma cadeia de caracteres do teclado (entrada padrão) e a envia ao servidor
- recebe um fluxo de dados do servidor, contendo a mesma
cadeia de caracteres que ele havia enviado
- exibe no vídeo (saída padrão) a cadeia recebida
- O servidor faz continuamente o seguinte:
- recebe uma cadeia de caracteres enviada pelo cliente
- reenvia esta cadeia ao cliente
- executa esta cadeia (que na verdade, deve ser um comando qualquer do Unix, por exemplo, pwd, ls, etc.)
Dica: utilize as funções system() ou exec() do Unix para executar o comando.
O que deve ser exibido:
- O cliente deverá exibir na saída padrão:
- dados do host servidor ao qual está se conectando (IP e PORTA)
- dados de IP e PORTA locais utilizados na conexão
- a linha de comando digitada pelo usuário
- a linha de comando devolvida pelo servidor (que deve ser idêntica à que foi digitada)
- O servidor deverá exibir na saída padrão:
- IP e PORTA dos hosts clientes assim que eles conectam
- saída gerada pelo comando que foi enviado por qualquer cliente
Detalhes da implementação:
- O código não pode apresentar nenhum warning quando
compilado. Para cada warning exibido na compilação será
descontado 10% da nota. (Certifique-se de compilar seu código com a
flag
-Wall
do gcc
).
- Não pode ser usada a função
getpeername()
no servidor
para descobrir o endereço IP e a PORTA dos clientes.
- **Devem** ser escritas e usadas "funções envelopadoras" (wrapper functions) para as chamadas
da API de sockets, a fim de tornar o seu código mais limpo. Utilize a
convenção do livro texto, dando o mesmo nome da função, com a 1ª letra
maiúscula. Veja abaixo um exemplo:
int Socket(int family, int type, int flags)
{
int sockfd;
if ((sockfd = socket(family, type, flags)) < 0) {
perror("socket");
exit(1);
} else
return sockfd;
}
- Uma boa ajuda para desenvolver esta atividade poderá ser encontrada nos exemplos do livro-texto da disciplina e nos programas utilizados no exercício anterior.
- Modifique os programas do passo 2 de modo a imprimir no lado do cliente
a saída da execução do comando e no lado do servidor a informação de quais
clientes estão executando quais comandos. Além disso o servidor deve
logar em um arquivo as informações referentes ao instante em que cada
cliente conecta e desconecta.
Detalhes das modificações:
- O cliente deve ser modificado de modo que, quando uma
certa string for digitada na entrada padrão (por exemplo:
exit
,
quit
, bye
, sair
, ...), a sua execução seja
finalizada (todas as conexões abertas devem ser corretamente fechadas
antes).
- O cliente exibirá, no lugar do "echo" do servidor:
- cadeias de caracteres enviadas pelo servidor, contendo a saída da
execução do comando enviado.
- O servidor exibirá, no lugar da saída de execução do comando:
- os dados de IP e PORTA seguidos da string que foi enviada
por aquele cliente, de modo a identificar qual comando foi
enviado por cada cliente.
- O IP e PORTA dos clientes que se desconectem, no momento da
desconexão.
- O servidor logará em um arquivo texto o endereço IP, porta,
instante de conexão e de desconexão para cada cliente.
Os detalhes da implementação do Passo 2 continuam valendo.
- No trecho de código abaixo, que muito provavelmente estará presente
nos códigos que você implementou, porque o servidor continua escutando e
os clientes continuam com suas conexões estabelecidas mesmo após as
chamadas dos Close? Explique o porque do uso de cada close e se algum deles está "sobrando" neste trecho de código.
for (;;) {
connfd = Accept (listenfd,...);
if ( (pid=Fork()) == 0) {
Close(listenfd);
doit(connfd); // Faz alguma operação no socket
Close(connfd);
exit(0);
}
Close(connfd);
}
- Com base ainda no trecho de código acima, é correto afirmar que os clientes nunca receberão FIN neste caso já que o servidor sempre ficará escutando (LISTEN)? Justifique.
- Comprove, utilizando ferramentas do sistema operacional, que os
processos criados para manipular cada conexão individual do servidor aos
clientes são filhos do processo original que foi executado.
- Utilizando ferramentas do sistema operacional, qual dos lados da
conexão fica no estado TIME_WAIT após o encerramento da conexão? Isso condiz
com a implementação que foi realizada? Justifique.
Dicas:
https://beej.us/guide/bgnet/html/multi/
///////////////////////////////
// Implementacao getsockname()
// http://man7.org/linux/man-pages/man2/getsockname.2.html
// https://beej.us/guide/bgnet/html/multi/inet_ntopman.html
///////////////////////////////
struct sockaddr_in servaddr, my_addr;
char myIP[16];
// ...
int len = sizeof(my_addr);
bzero(&my_addr, sizeof(my_addr));
getsockname(sockfd, (struct sockaddr *) &my_addr, &len);
inet_ntop(AF_INET, &my_addr.sin_addr, myIP, sizeof(myIP));
printf("Local IP address: %s\n", myIP);
printf("Local Port : %u\n", ntohs(my_addr.sin_port));
///////////////////////////////
// Implementacao getpeername()
// http://man7.org/linux/man-pages/man2/getpeername.2.html
// https://beej.us/guide/bgnet/html/multi/syscalls.html#getpeername
///////////////////////////////
struct sockaddr_in servaddr, peer_addr;
// ...
bzero(&peer_addr, sizeof(peer_addr));
len = sizeof(peer_addr);
getpeername(connfd, (struct sockaddr*)&peer_addr, &len);
printf("Peer IP address: %s\n", inet_ntoa(peer_addr.sin_addr));
printf("Peer Port : %d\n", ntohs(peer_addr.sin_port));