De acordo com as Leis 12.965/2014 e 13.709/2018, que regulam o uso da Internet e o tratamento de dados pessoais no Brasil, ao me inscrever na newsletter do portal DICAS-L, autorizo o envio de notificações por e-mail ou outros meios e declaro estar ciente e concordar com seus Termos de Uso e Política de Privacidade.
Colaboração: Alessandro de Oliveira Faria
Data de Publicação: 19 de Dezembro de 2004
As informações contidas neste texto foram baseadas em um artigo escrito por Rob Tougher (<rtougher (a) yahoo com>), publicado na LinuxGazette em Janeiro de 2002. Inclusive os fontes-exemplo são apontados para o link original.
O motivo principal da elaboração deste texto, pois a dificuldade de encontrar algo simples para iniciantes que desejam programar socket em C++.
Sockets são mecanismos usados para a troca de dados entre processos, que podem estar todos em uma máquina local ou em diversas máquinas.
Estou desenvolvendo uma aplicação, na qual diversos clientes em diferentes plataformas e linguagens precisam enviar comandos para o software executar determinadas tarefas.
Este texto contém fundamentos básicos para a criação de aplicações que se comunicam via socket, assim aprenderemos a usar as classes ClientSocket e ServerSocket em C++.
Antes de partirmos para o código-fonte, vamos entender o funcionamento dos processos Cliente e Servidor:
Este é o funcionamento básico de uma comunicação via socket. Primeiro, o servidor entra em modo escuta, aguardando um processo cliente solicitá-lo.
Um processo cliente ao criar um socket tenta imediatamente conectar-se com o processo SERVER. Assim que a conexão foi estabelecida, ambos os processos começam a trocar dados. Qualquer um dos lados pode encerrar a conexão, fechando o socket.
Vamos agora colocar a mão na massa! Criaremos simples aplicações cliente-servidor suficientes para o aprendizado e para fundamentarmos conceitos de comunicação via socket.
A primeira ação de uma aplicação Server é de entrar em modo escuta por meio de uma porta especificada, por onde a aplicação cliente solicitará a conexão.
#include "ServerSocket.h"
#include "SocketException.h"
#include
int main ( int argc, int argv[] )
{
try
{
// Criação do server socket na porta 30000
ServerSocket server ( 30000 );
// insira aqui o restante do código
}
catch ( SocketException& e )
{
// Tratamento de erro
std::cout << "Exceção foi capturada:" << e.description() << " Finalizando. ";
}
return 0;
}
O mais importante na listagem acima é a instanciação da Classe ServerSocket que coloca a aplicação em modo escuta. Se algum método dessa classe falhar, o tratamento de erro invocará o método desctription(), pertencente à classe SocketException.
O segundo passo é criar uma típica conexão cliente-servidor. A função da aplicação cliente é de tentar se conectar com a aplicação server. Vejamos como efetuar esse procedimento na listagem abaixo:
Listagem 2 : criando um cliente socket ( parte do fonte simple_client_main.cpp )
#include "ClientSocket.h"
#include "SocketException.h"
#include
#include
int main ( int argc, int argv[] )
{
try
{
// Criando um client socket e solicitando a conexão na porta 30000 e IP local.
ClientSocket client_socket ( "localhost", 30000 );
// O restante do código vem aqui.
}
catch ( SocketException& e )
{
std::cout << "Exceção foi capturada:" << e.description() << " ";
}
return 0;
}
Resumindo na instanciação da classe ClientSocket, a aplicação tenta conectar no IP e na porta especificada. Se o construtor falhar, o tratamento de erro invoca o método description() conforme mencionado no exemplo do Server.
O próximo passo é o servidor estabilizar a conexão solicitada pela aplicação Client. Após estabilizar a conexão, um canal de comunicação entre os sockets é criado.
Listagem 3: Estabilizando a conexão com a aplicação cliente (parte do fonte simple_server_main.cpp)
#include "ServerSocket.h"
#include "SocketException.h"
#include
int main ( int argc, int argv[] )
{
try
{
// Criação do server socket na porta 30000
ServerSocket server ( 30000 );
while ( true )
{
// Aceitando a conexão solicitada
ServerSocket new_sock;
server.accept ( new_sock );
// O restante do código vem aqui
}
}
catch ( SocketException& e )
{
// Tratamento de erro
std::cout << "Exceção foi capturada:" << e.description() << " Finalizando. ";
}
return 0;
}
A conexão é estabelecida justamente na chamada do método accept, que aceita a conexão e preenche em new_sock as informações referentes à conexão atual.
Agora que estabelecemos conexão, vamos para a parte de envio e recebimento de dados através do socket.
Umas das grandes características do C++ é a habilidade de sobrecarga de operadores. Usando a sobrecarga de com os operadores << e >>, podemos escrever e ler dados nas classes ClientSocker e ServerSocket.
Listagem 4: Implementações no programa fonte ( simple_server_main.cpp )
#include "ServerSocket.h"
#include "SocketException.h"
#include
int main ( int argc, int argv[] )
{
try
{
// Criação do server socket na porta 30000
ServerSocket server ( 30000 );
while ( true )
{
// Aceitando a conexão solicitada
ServerSocket new_sock;
server.accept ( new_sock );
try
{
while ( true )
{
std::string data;
// Recebendo dados
new_sock >> data;
// Enviando dados
new_sock << data;
}
catch ( SocketException& ) {}
}
catch ( SocketException& e )
{
// Tratamento de erro
std::cout << "Excecão foi capturada:" << e.description() << " Finalizando. ";
}
return 0;
}
O new_sock é uma variável que contém todas as informações usadas para a troca de dados com o client. O comando "new_sock >> data;" armazena os dados recebidos na variável data.
A próxima linha é muito parecida, mas executa a tarefa inversa, ou seja, transmite o conteúdo da variável data para o Client.
Resumindo: o programa Server ecoa os dados recebidos pelo Client. Para comprovarmos essa funcionalidade, o fonte abaixo envia uma string ao cliente e aguarda o recebimento do Server, imprimindo o resultado no vídeo.
Listagem 5: implementações no client ( simple_client_main.cpp )
#include "ClientSocket.h"
#include "SocketException.h"
#include
#include
int main ( int argc, int argv[] )
{
try
{
// Criando um client socket e solicitando a conexão na porta 30000 e IP local.
ClientSocket client_socket ( "localhost", 30000 );
std::string reply;
try
{
// Envia a string "Test message" ao server
client_socket << "Test message.";
// Recebe dados do server
client_socket >> reply;
std::cout< catch ( SocketException& ) {}
}
catch ( SocketException& e )
{
std::cout << "Excecão foi capturada:" << e.description() << " ";
}
return 0;
}
A aplicação acima, após enviar a string "Test Message" para o Server, aguarda a resposta do Server e ao recebê-la, a string é mostrada no vídeo.
Como mencionei no início desde texto, em www.linuxgazette.com, podemos encontrar todos os fontes citados neste documento. Segue abaixo a relação dos arquivos fontes:
A compilação é simples. Primeiro, salve todos os projeto em um diretório. Logo após, entre com o seguinte comando:
[root@athlon socket]# make
Este comando compilará todos os arquivos do projeto e criará o binário simple_server e simple_client. Para testá-los, devemos executá-los um em cada terminal (primeiro o server e depois o cliente).
[root@athlon socket]# cd /mnt/D/linux/fontes/c++/tcp-socket/socket/ [root@athlon socket]# ./simple_server running.... Test message. [root@athlon root]# cd /mnt/D/linux/fontes/c++/tcp-socket/socket/ [root@athlon socket]# ./simple_client We received this response from the server: "Test message." [root@athlon socket]#
O cliente envia a string para o server e aguarda o recebimento dos dados. Após o retorno do Server, a resposta é mostrada no vídeo. Nota-se que o client termina assim que a mensagem é retornada.
Mas o server ficará no modo escuta até que seja interrompido com o CTRL+C.
O Socket é uma maneira simples e eficiente para troca de dados entre processos. Com esse documento, você poderá implementar os recursos sockets em suas aplicações.
Ressalto que este documento é uma versão traduzida/adaptadação do artigo LinuxGazette elaborada por Rob Tougher.
Alessandro de Olivera Faria (Cabelo) <alessandrofaria (a) netitec com br>