Função shutdown() encerrando servidor [RESOLVIDO]

1. Função shutdown() encerrando servidor [RESOLVIDO]

Perfil removido
removido

(usa Nenhuma)

Enviado em 19/06/2021 - 22:56h

Recentemente comecei a estudar a API de sockets do Unix (Berkeley sockets) e acabei encontrando uma função bastante interessante e comumente utilizada em conexões half-duplex e full-duplex: a shutdown().

int shutdown(int sockfd, int how); 


Pelo o que eu entendi essa função é utilizada para finalizar toda ou uma parte de uma conexão full-duplex. Para tal, ela conta com três constantes que podem ser passadas para o parâmetro how informando como será realizado o desligamento do socket em questão. Constantes:

SHUT_RD - desabilita as recepções;
SHUT_WR - desabilita as transmissões;
SHUT_RDWR - desabilita recepções e transmissões.

Até o momento eu não cheguei a utilizar essa função em códigos half-duplex ou full-duplex, porém andei realizando alguns experimentos em troca de dados do tipo simplex.

Um dos meus experimentos consistiu em chamar shutdown() com o argumento SHUT_WR antes de enviar dados para o cliente usando a função write().

Como muitos de vocês devem imaginar, a função write() não enviou os dados (como esperado desde o início), mas ao mesmo tempo também obtive um resultado curioso: shutdown() não só impediu write() de enviar dados para o cliente como também simplismente encerrou a execução do servidor (por que será?).

Segue abaixo o código para uma melhor análise:


#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>

#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 9009
#define ADDR "127.0.0.1"
#define MAXCONN 5

int main(void) {

int sockfd;
struct sockaddr_in addr;

memset(&addr, 0, sizeof(addr));

if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) {

fprintf(stderr, "socket() -> ERROR %d: %s\n", errno, strerror(errno));

exit(EXIT_FAILURE);
}

addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
inet_pton(AF_INET, ADDR, &addr.sin_addr.s_addr);

if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0 ) {

fprintf(stderr, "bind() -> ERROR %d: %s\n", errno, strerror(errno));

close(sockfd);

exit(EXIT_FAILURE);
}

if (listen(sockfd, SOMAXCONN) < 0 ) {

fprintf(stderr, "listen() -> ERROR %d: %s\n", errno, strerror(errno));

close(sockfd);

exit(EXIT_FAILURE);
}

for (size_t i = 0; i < MAXCONN; i++) {

struct sockaddr_in client;
socklen_t size;
char caddr[INET_ADDRSTRLEN];
int new_sockfd;

memset(&client, 0, sizeof(client));

if ((new_sockfd = accept(sockfd, (struct sockaddr*)&client, &size)) < 0 ) {

fprintf(stderr, "accept() -> ERROR %d: %s\n", errno, strerror(errno));

} else {

if (inet_ntop(client.sin_family, &client.sin_addr.s_addr, caddr, sizeof(caddr)) == NULL ) {

fprintf(stderr, "inet_ntop -> ERROR %d: %s\n", errno, strerror(errno));

} else {

printf("%s\n", caddr);
}

const char msg[] = "What's up, _Bitch. Names John. You here to help me?\n";

shutdown(new_sockfd, SHUT_WR); //Usando new_sockfd o programa para, já o mesmo não acontece com o descritor sockfd

if (write(new_sockfd, msg, sizeof(msg)) < 0 ) {

fprintf(stderr, "write() -> ERROR %d: %s\n", errno, strerror(errno));
}

close(new_sockfd);
}
}

close(sockfd);

return EXIT_SUCCESS;
}


Agora vem outro fato interessante: ao chamar shutdown() com o descritor sockfd o programa não fecha e muito menos write() é impedida de enviar dados ao cliente.

Portanto, tenho as seguintes dúvidas em relação a tudo isso:

1. Por que o servidor encerrou a execução com new_sockfd, mas não com sockfd?
2. Esse "problema" está realmente relacionado ao funcionamento da função shutdown() ou é um erro de codificação da minha parte?



  


2. MELHOR RESPOSTA

Paulo
paulo1205

(usa Ubuntu)

Enviado em 20/06/2021 - 04:12h

PC300-ITS44 escreveu:

Agora vem outro fato interessante: ao chamar shutdown() com o descritor sockfd o programa não fecha e muito menos write() é impedida de enviar dados ao cliente.

Portanto, tenho as seguintes dúvidas em relação a tudo isso:

1. Por que o servidor encerrou a execução com new_sockfd, mas não com sockfd?


Se você estiver no Linux, experimente rodar seu programa sob a supervisão do comando strace, que intercepta e exibe todas as invocações que o seu programa faz a chamadas do sistema operacional. Isso deve ajudá-lo a entender o que acontece com o programa.

Eu não testei aqui (até porque agora estou num computaodr do trabalho, que roda Windows), mas eu acho provável que seu programa seja interrompido porque recebe um sinal SIGPIPE do sistema operacional. SIGPIPE é enviado a um processo sempre que ele tenta escrever num descritor de um pipe ou socket no qual a operação de escrita não esteja disponível. Como você chamou shutdown() justamente para desabilitar escrita, não é de causar surpresa receber SIGPIPE ao tentar escrever.

Quanto ao porquê de ocorrer com um socket mas não com outro, é porque os sockets são independentes entre si. Quando você chama accept(), o valor de retorno da função é um socket completamente novo e independente daquele sobre o qual você chamou listen(). Esse comportamento permite que o servidor continue escutando no socket original, enquanto a comunicação com cada cliente que chegar vai ter seu próprio socket novo, independente de todos os demais e completamente dedicado. Isso lhe permite escolher se, para lidar com múltiplos clientes (mais o socket que fica escutando novas conexões), você vai usar processos ou threads separados para cada socket, que é útil para simplificar a codificação de serviços que não têm interação entre diferentes clientes, ou multiplexação de múltilpos sockets dentro do mesmo processo, usando funções como select(), poll(), epoll() (Linux) ou kqueue() (BSD).

2. Esse "problema" está realmente relacionado ao funcionamento da função shutdown() ou é um erro de codificação da minha parte?


Tudo indica que, nesse caso, foi você que cometeu um erro, por não conhecer detalhes de funcionamento de sockets.


... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)