Programação de soquete em C
Programação de soquete é uma forma de conectar dois nós em uma rede para se comunicarem entre si. Um soquete (nó) escuta em uma porta específica em um IP enquanto o outro soquete alcança o outro para formar uma conexão. O servidor forma o soquete do ouvinte enquanto o cliente acessa o servidor.
A programação de soquete é amplamente utilizada em aplicativos de mensagens instantâneas, streaming binário e colaborações de documentos, plataformas de streaming on-line, etc.
Exemplo
Neste programa C estamos trocando uma mensagem de alô entre servidor e cliente para demonstrar o modelo cliente/servidor.
servidor.c
C #include #include #include #include #include #include #define PORT 8080 int main ( int argc char const * argv []) { int server_fd new_socket ; ssize_t valread ; struct sockaddr_in address ; int opt = 1 ; socklen_t addrlen = sizeof ( address ); char buffer [ 1024 ] = { 0 }; char * hello = 'Hello from server' ; // Creating socket file descriptor if (( server_fd = socket ( AF_INET SOCK_STREAM 0 )) < 0 ) { perror ( 'socket failed' ); exit ( EXIT_FAILURE ); } // Forcefully attaching socket to the port 8080 if ( setsockopt ( server_fd SOL_SOCKET SO_REUSEADDR | SO_REUSEPORT & opt sizeof ( opt ))) { perror ( 'setsockopt' ); exit ( EXIT_FAILURE ); } address . sin_family = AF_INET ; address . sin_addr . s_addr = INADDR_ANY ; address . sin_port = htons ( PORT ); // Forcefully attaching socket to the port 8080 if ( bind ( server_fd ( struct sockaddr * ) & address sizeof ( address )) < 0 ) { perror ( 'bind failed' ); exit ( EXIT_FAILURE ); } if ( listen ( server_fd 3 ) < 0 ) { perror ( 'listen' ); exit ( EXIT_FAILURE ); } if (( new_socket = accept ( server_fd ( struct sockaddr * ) & address & addrlen )) < 0 ) { perror ( 'accept' ); exit ( EXIT_FAILURE ); } // subtract 1 for the null // terminator at the end valread = read ( new_socket buffer 1024 - 1 ); printf ( '%s n ' buffer ); send ( new_socket hello strlen ( hello ) 0 ); printf ( 'Hello message sent n ' ); // closing the connected socket close ( new_socket ); // closing the listening socket close ( server_fd ); return 0 ; }
cliente.c
C #include #include #include #include #include #define PORT 8080 int main ( int argc char const * argv []) { int status valread client_fd ; struct sockaddr_in serv_addr ; char * hello = 'Hello from client' ; char buffer [ 1024 ] = { 0 }; if (( client_fd = socket ( AF_INET SOCK_STREAM 0 )) < 0 ) { printf ( ' n Socket creation error n ' ); return -1 ; } serv_addr . sin_family = AF_INET ; serv_addr . sin_port = htons ( PORT ); // Convert IPv4 and IPv6 addresses from text to binary // form if ( inet_pton ( AF_INET '127.0.0.1' & serv_addr . sin_addr ) <= 0 ) { printf ( ' n Invalid address/ Address not supported n ' ); return -1 ; } if (( status = connect ( client_fd ( struct sockaddr * ) & serv_addr sizeof ( serv_addr ))) < 0 ) { printf ( ' n Connection Failed n ' ); return -1 ; } // subtract 1 for the null // terminator at the end send ( client_fd hello strlen ( hello ) 0 ); printf ( 'Hello message sent n ' ); valread = read ( client_fd buffer 1024 - 1 ); printf ( '%s n ' buffer ); // closing the connected socket close ( client_fd ); return 0 ; }
Compilando
gcc client.c -o clientgcc server.c -o server
SaídaClient:Hello message sentHello from serverServer:Hello from clientHello message sentComponentes da programação de soquete
1. Soquetes
Soquetes são um dos principais componentes usados pelo programa para acessar a rede e se comunicar com outros processos/nós na rede. É simplesmente uma combinação de um endereço IP e um número de porta que atua como um ponto final para comunicação.
Exemplo: 192.168.1.1:8080 onde as duas partes separadas por dois pontos representam o Endereço IP (192.168.1.1) e o número da porta (8080).Tipos de soquete:
- Soquete TCP (soquete de fluxo): Fornece comunicação confiável baseada em conexão (ou seja, Protocolo TCP ).
- Soquete UDP (soquete de datagrama): Fornece comunicação sem conexão mais rápida, mas não confiável (ou seja, Protocolo UDP ).
2. Modelo Cliente-Servidor
O modelo cliente-servidor refere-se à arquitetura usada na programação de soquete onde um cliente e um servidor interagem entre si para trocar informações ou serviços. Esta arquitetura permite que o cliente envie solicitações de serviço e o servidor processe e envie respostas a essas solicitações de serviço.
Diagrama de estado para modelo de servidor e cliente
Diagrama de estado para modelo de servidor e cliente do Socket A programação de soquete em C é uma maneira poderosa de lidar com a comunicação em rede.
Criando um processo do lado do servidor
O servidor é criado usando as seguintes etapas:
1. Criação de soquete
Esta etapa envolve a criação do soquete usando a função socket().
Parâmetros:
- meia: descritor de soquete um número inteiro (como um identificador de arquivo)
- domínio: inteiro especifica o domínio de comunicação. Usamos AF_LOCAL conforme definido no padrão POSIX para comunicação entre processos no mesmo host. Para comunicação entre processos em diferentes hosts conectados por IPV4 usamos AF_INET e AF_I NET 6 para processos conectados por IPV6.
- tipo: tipo de comunicação
SOCK_STREAM: TCP (orientado a conexão confiável)
SOCK_DGRAM: UDP (sem conexão não confiável) - protocolo: Valor do protocolo para Internet Protocol (IP) que é 0. Este é o mesmo número que aparece no campo de protocolo no cabeçalho IP de um pacote. (man protocols para mais detalhes)
sockfd = socket ( domain type protocol )
2. Definir opção de soquete
Isso ajuda na manipulação de opções para o soquete referido pelo descritor de arquivo sockfd. Isso é totalmente opcional, mas ajuda na reutilização de endereço e porta. Evita erros como: endereço já em uso.
C setsockopt ( sockfd level optname optval socklen_t optlen );
3. Vincular
Após a criação do soquete, a função bind() vincula o soquete ao endereço e número da porta especificados em addr (estrutura de dados personalizada). No código de exemplo, vinculamos o servidor ao host local, portanto, usamos INADDR_ANY para especificar o endereço IP.
C++ bind ( sockfd sockaddr * addr socklen_t addrlen );
Parâmetros:
- meia : descritor de arquivo de soquete criado usando a função socket().
- endereço : ponteiro para uma estrutura sockaddr que contém o endereço IP e o número da porta para vincular o soquete.
- addrlen : comprimento da estrutura addr.
4. Ouça
Nesta etapa o servidor utiliza a função listen() que coloca o soquete do servidor em modo passivo onde espera o cliente se aproximar do servidor para fazer uma conexão. O backlog define o comprimento máximo que a fila de conexões pendentes para sockfd pode crescer. Se uma solicitação de conexão chegar quando a fila estiver cheia o cliente poderá receber um erro com a indicação ECONNREFUSED.
C listen ( sockfd backlog );
Parâmetros :
- meia : descritor de arquivo de soquete criado usando a função socket().
- pendências : número que representa o tamanho da fila que contém as conexões pendentes enquanto o servidor aguarda para aceitar uma conexão.
5. Aceite
Nesta etapa o servidor extrai a primeira solicitação de conexão da fila de conexões pendentes para o soquete de escuta sockfd cria um novo soquete conectado usando o comando aceitar() função e retorna um novo descritor de arquivo referente a esse soquete. Neste ponto a conexão é estabelecida entre cliente e servidor e eles estão prontos para transferir dados.
C new_socket = accept ( sockfd sockaddr * addr socklen_t * addrlen );
Parâmetros:
- meia : descritor de arquivo de soquete retornado por socket() e bind().
- endereço : ponteiro para uma estrutura sockaddr que conterá o endereço IP e o número da porta do cliente.
- addrlen : ponteiro para uma variável que especifica o comprimento da estrutura de endereço.
6. Enviar/Receber
Nesta etapa o servidor pode enviar ou receber dados do cliente.
Enviar(): enviar dados ao cliente
C send ( sockfd * buf len flags );
Parâmetros:
- meia : descritor de arquivo de soquete retornado pela função socket().
- bufante : ponteiro para o buffer que contém os dados a serem enviados.
- apenas : número de bytes de dados a serem enviados.
- bandeiras : inteiro especificando várias opções de como os dados são enviados normalmente 0 é usado para comportamento padrão.
Receber() : para receber os dados do cliente.
C recv ( sockfd * buf len flags );
Parâmetros:
- meia : descritor de arquivo de soquete retornado pela função socket().
- bufante : ponteiro para o buffer que contém os dados a serem armazenados.
- apenas : número de bytes de dados a serem enviados.
- bandeiras : inteiro especificando várias opções de como os dados são enviados normalmente 0 é usado para comportamento padrão.
6. Fechar
Após a conclusão da troca de informações, o servidor fecha o soquete usando a função close() e libera os recursos do sistema.
C close ( fd );
Parâmetros:
- fd: descritor de arquivo do soquete.
Criando Processo do Lado do Cliente
Siga as etapas abaixo para criar um processo do lado do cliente:
1. Conexão de soquete
Esta etapa envolve a criação do soquete que é feita da mesma forma que a criação do soquete do servidor
2. Conecte-se
A chamada de sistema connect() conecta o soquete referido pelo descritor de arquivo sockfd ao endereço especificado por addr. O endereço e a porta do servidor são especificados em addr.
C++ connect ( sockfd sockaddr * addr socklen_t addrlen );
Parâmetros
- meia : descritor de arquivo de soquete retornado pela função socket().
- endereço : ponteiro para struct sockaddr contendo o endereço IP e o número da porta do servidor.
- addrlen : tamanho do endereço.
3. Enviar/Receber
Nesta etapa, o cliente pode enviar ou receber dados do servidor, o que é feito usando as funções send() e recieve() semelhantes à forma como o servidor envia/recebe dados do cliente.
4. Fechar
Assim que a troca de informações for concluída, o cliente também precisa fechar o soquete criado e liberar os recursos do sistema usando a função close() da mesma forma que o servidor.
Problemas comuns e suas correções na programação de soquete
- Falhas de conexão: Para evitar falhas de conexão devemos garantir que o cliente está tentando se conectar ao correto Endereço IP e porta .
- Erros de ligação de porta: Esses erros ocorrem quando uma porta já está em uso por outro aplicativo. Nesse cenário, a ligação a essa porta falhará. Tente usar uma porta diferente ou feche o aplicativo anterior usando a porta.
- Bloqueio de soquetes: Por padrão, os soquetes estão bloqueando. Isso significa que chamadas como accept() ou recv() aguardarão indefinidamente se não houver conexão ou dados do cliente. Você pode definir o soquete para o modo sem bloqueio, se necessário.