Postado por Yoshio Iwamoto em 10/09/2007
Um socket é um mecanismo de
comunicação que permite que dois ou mais aplicativos troquem informações, seja
no mesmo computador ou em computadores distintos, como os programas de chats,
games on-line, navegadores, etc. No geral, programas que utilizam Network Communication ouInterprocess Communication utilizam sockets.
Sockets são importantes, por isso eu
tinha que tomar vergonha na cara e aprender a usá-los, comecei a ler sobre o
assunto e consegui fazer uma pequena aplicação que envia mensagens para outra.
Primeiramente vou mostrar o código e depois ir
(tentando) descrever as funções. Não vou me aprofundar, é só para ter uma idéia
de como a coisa funciona, mesmo porque eu acho que aprofundei demais na
explicação (mas quanto mais se sabe, mas se sabe que não se sabe nada XD).
O código foi feito para Windows, mas
a implementação no Linux é bem parecida. No Windows você deve usar a WinSock API para trabalhar com sockets, não se
esqueça de linkar a lib na hora de compilar. Eu uso o Dev-C++ e a lib é a libwsock32.a.
1. /*
2. SERVIDOR
3. */
4.
5. #include <stdio.h>
6. #include <stdlib.h>
7. #include <string.h>
8. #include <winsock.h>
9.
10. #define BACKLOG_MAX 5
11. #define BUFFER_SIZE 128
12. #define EXIT_CALL_STRING "#quit"
13.
14. int local_socket = 0;
15. int remote_socket = 0;
16.
17. int remote_length = 0;
18. int message_length = 0;
19.
20. unsigned short local_port = 0;
21. unsigned short remote_port = 0;
22.
23. char message[BUFFER_SIZE];
24.
25. struct sockaddr_in local_address;
26. struct sockaddr_in remote_address;
27.
28. WSADATA wsa_data;
29.
30. /* Exibe uma mensagem de erro e termina o programa */
31. void msg_err_exit(char *msg)
32. {
33. fprintf(stderr, msg);
34. system("PAUSE");
35. exit(EXIT_FAILURE);
36. }
37.
38. int main(int argc, char **argv)
39. {
40. // inicia o Winsock 2.0 (DLL), Only for Windows
41. if (WSAStartup(MAKEWORD(2, 0), &wsa_data) != 0)
42. msg_err_exit("WSAStartup() failed\n");
43.
44. // criando o socket local para o servidor
45. local_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
46. if (local_socket == INVALID_SOCKET)
47. {
48. WSACleanup();
49. msg_err_exit("socket() failed\n");
50. }
51.
52. printf("Porta local: ");
53. scanf("%d", &local_port);
54. fflush(stdin);
55.
56. // zera a estrutura local_address
57. memset(&local_address, 0, sizeof(local_address));
58.
59. // internet address family
60. local_address.sin_family = AF_INET;
61.
62. // porta local
63. local_address.sin_port = htons(local_port);
64.
65. // endereco
66. local_address.sin_addr.s_addr = htonl(INADDR_ANY); // inet_addr("127.0.0.1")
67.
68. // interligando o socket com o endereço (local)
69. if (bind(local_socket, (struct sockaddr *) &local_address, sizeof(local_address)) == SOCKET_ERROR)
70. {
71. WSACleanup();
72. closesocket(local_socket);
73. msg_err_exit("bind() failed\n");
74. }
75.
76. // coloca o socket para escutar as conexoes
77. if (listen(local_socket, BACKLOG_MAX) == SOCKET_ERROR)
78. {
79. WSACleanup();
80. closesocket(local_socket);
81. msg_err_exit("listen() failed\n");
82. }
83.
84. remote_length = sizeof(remote_address);
85.
86. printf("aguardando alguma conexao...\n");
87. remote_socket = accept(local_socket, (struct sockaddr *) &remote_address, &remote_length);
88. if(remote_socket == INVALID_SOCKET)
89. {
90. WSACleanup();
91. closesocket(local_socket);
92. msg_err_exit("accept() failed\n");
93. }
94.
95. printf("conexao estabelecida com %s\n", inet_ntoa(remote_address.sin_addr));
96. printf("aguardando mensagens...\n");
97. do
98. {
99. // limpa o buffer
100. memset(&message, 0, BUFFER_SIZE);
101.
102. // recebe a mensagem do cliente
103. message_length = recv(remote_socket, message, BUFFER_SIZE, 0);
104. if(message_length == SOCKET_ERROR)
105. msg_err_exit("recv() failed\n");
106.
107. // exibe a mensagem na tela
108. printf("%s: %s\n", inet_ntoa(remote_address.sin_addr), message);
109. }
110. while(strcmp(message, EXIT_CALL_STRING)); // sai quando receber um "#quit" do cliente
111.
112. printf("encerrando\n");
113. WSACleanup();
114. closesocket(local_socket);
115. closesocket(remote_socket);
116.
117. system("PAUSE");
118. return 0;
119. }
A função WSAStartup() inicia o Windows Sockets Dynamic Link (WinSock DLL). Ela também é usada para
confirma a versão do WinSock DLL. Estamos utilizando a 2.0.
Declaração:
1. int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
O parâmetro wVersionRequested é número da maior versão que a
aplicação pode usar. Repare que ele é uma WORD onde o byte de maior ordem especifica
a número da minor version e o byte de ordem menor indica a major version. Por
isso eu utilizo a função MAKEWORD para retornar uma WORD (e facilitar minha vida), por exemplo,
se quiser usar a versão do WinSock 1.1 é só alterar para MAKEWORD(1, 1). Fácil demais...
O parâmetro lpWSAData é um ponteiro para um estrutura WSADATA que receberá os detalhes da
implementação do WinSock.
Se der tudo certo a função retornará
0. Se ocorrer um erro você pode usar a função WSAGetLastError() para saber mais detalhes.
Use a função socket() parar criar o socket \o/.
Declaração:
1. SOCKET WSAAPI socket(int af, int type, int protocol);
O parâmetro af especifica o "address
family" que este socket usa. Nós iremos usar o AF_INET. A versão 1.1 do WinSock só
suporta o formato AF_INET.
Alguns formatos possíveis:
AF_INET: Arpa Internet
Protocols
AF_UNIX:
Unix Internet Protocols
AF_ISSO:
Iso Protocols
AF_NS:
Xerox Network System Protocols
Se você olhar no header (winsock.h)
vai encontrar outros como oAF_FIREFOX, mas é melhor ver quais realmente podem ser
usados no MSDN.
O parâmetro type especifica o tipo do socket, no nosso
caso é SOCK_STREAM(utilizado
na maioria dos programas), mas você pode usar o SOCK_DGRAM(UDP),
mas não vou abordar as diferenças entre eles.
Em protocol deixe 0 (zero), isto indica que o
socket irá usar o valor padrão. Oprotocol pode ser o padrão porque a combinação
doAF_INET+SOCK_STREAM já indica que o protocolo é TCP. Eu
coloqueiIPPROTO_TCP no código, mas não precisa, é só
deixar como 0.
Alguns valores possíveis para o
protocol:
IPPROTO_IP: Internet
Protocol (0)
IPPROTO_ICMP:
Internet Control Message Protocol (1)
IPPROTO_IGMP:
Internet Group Multicast Protocol (2)
IPPROTO_GGP:
Gateway-Gateway Protocol (3)
IPPROTO_TCP:
Transmission Control Protocol (6)
IPPROTO_UDP:
User Datagrama Protocol (17)
Se der tudo certo a função irá
retornar o socket descriptor,
que é um número que identifica o socket criado. Se falhar irá retorna INVALID_SOCKET, você pode usar a
função WSAGetLastError() para saber mais detalhes do erro.
Agora precisamos dar nomes aos bois,
precisamos dizer que o nosso socket se chama "192.168.0.1", por
exemplo. Usaremos a função bind() para isso.
Declaração:
1. int bind(SOCKET s, const struct sockaddr *addr, int namelen);
O parâmetro s é
o socket descriptor retornado pela função socket(). O parâmetro addr é um ponteiro para uma estrutura do
tipo sockaddr.
Onamelen é o tamanho em bytes da estrutura, use
a função sizeof() para isto.
Esta parte pode confundir, antes de
usar a função bind() nós precisamos conhecer as estruturas sockaddr, sockaddr_in e in_addr,
mas a que iremos utilizar é a sockaddr_in.
Definições da estrutura sockaddr:
1. struct sockaddr
2. {
3. u_short sa_family; // address family
4. char sa_data[14]; // address
5. };
O item sa_data da estrutura vai depende do
"address family". No WinSock 1.1, como eu disse, apenas o AF_INET é suportado, logo somente um "endereçamento
de internet" é suportado no sa_data.
Lembrando que você não vai utilizar esta estrutura, mas eu coloquei porque ela
aparece em um "cast" de um dos parâmetros da função bind() que estamos utilizando. Você deve
utilizar uma outra estrutura no lugar da sockaddr quando chamar a função bind(), use a sockaddr_in.
Definições da estrutura sockaddr_in:
1. struct sockaddr_in
2. {
3. short sin_family; // address family
4. u_short sin_port; // port
5. struct in_addr sin_addr; // internet address
6. char sin_zero[8]; // to make a beautiful cast
7. };
Primeiro você deve zerar a estrutura e depois preencher alguns
itens. No código que eu fiz está assim:
1. struct sockaddr_in local_address;
2. /* ... */
3. memset(&local_address, 0, sizeof(local_address)); // zera a estrutura
4.
5. local_address.sin_family = AF_INET;
6. local_address.sin_port = htons(local_port);
7. local_address.sin_addr.s_addr = htonl(INADDR_ANY);
Importante!
Nem todos os computadores armazenam os dados na mesma ordem na memória. Existem
computadores que trabalham no formato "Big Endian", onde byte menos
significativo fica no endereço de memória de maior valor. Por exemplo, para
armazenar o número 0x01234567 ficaria assim:
endereço 0x101: 01
endereço
0x102: 23
endereço
0x103: 45
endereço
0x104: 67
O maior endereço é o 0x104 e o byte menos significativo de 0x01234567 é o último da direita (67).
Já nos computadores "Little
Endian", ocorre o inverso, o byte menos significativo fica no endereço de
memória de menor valor:
endereço 0x101: 67
endereço
0x102: 45
endereço
0x103: 23
endereço
0x104: 01
O menor endereço é o 0x101 e o byte menos significativo de 0x01234567 é o último da direita (67).
Os computadores com processadores
baseados no Intel x86 são "Little Endian", mas a ordem dos bytes na
rede (network) é "Big Endian". Precisamos converter os dados antes de
enviá-los usando as funções htons() e htonl():
htons(): converte um unsigned short (host-to-network)
htonl():
converte um unsigned long (host-to-network)
Existem outras funções para esta converter os dados, mas nós
iremos utilizar apenas esses.
Voltando a estrutura sockaddr_in, vamos falar do item sin_addr. Veja na declaração que
este item é uma estrutura do tipo in_ddr.
Definições da estrutura in_addr:
1. struct in_addr
2. {
3. union
4. {
5. struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
6. struct { u_short s_w1,s_w2; } S_un_w;
7. u_long S_addr;
8. } S_un;
9. #define s_addr S_un.S_addr
10. #define s_host S_un.S_un_b.s_b2
11. #define s_net S_un.S_un_b.s_b1
12. #define s_imp S_un.S_un_w.s_w2
13. #define s_impno S_un.S_un_b.s_b4
14. #define s_lh S_un.S_un_b.s_b3
15. };
Repare nos defines que ela possui, você pode acessar o
dados da estrutura através deles. Quando você acessa local_address.sin_addr.s_addr na verdade esta acessando local_address.sin_addr.S_un.S_addr.
Mas sempre use os defines para manter compatibilidade, não
acesse os dados diretamente.
O valor indicado para local_address.sin_addr.s_addr éhtonl(INADDR_ANY), isto
indica que usaremos todos os endereços locais designados ao nosso servidor. Por
exemplo, se sua máquina possui duas placas de rede (192.168.0.1 e 192.168.0.2),
você pode usar os dois endereços para o seu socket. Mas se você quiser
especificar apenas uma placa, use a função inet_addr().
Nós usaremos esta função para fazer o programa cliente.
Sobre o item sin_zero da estrutura sockaddr_in, ele server apenas para
ocupar espaço, de modo que a estrutura tenha o mesmo tamanho (em bytes) da
estrutura sockaddr.
Assim podemos fazer "casts" de uma estrutura para outra (utilizado no
parâmetro da função bind()).
Por isso a estrutura deve ser preenchida com zeros antes de ser utilizada.
Se der tudo certo a função bind() retornará 0, caso contrário retornaráSOCKET_ERROR,
use o WSAGetLastError() para analisar o erro.
A função listen() coloca o socket para escutar as
conexões. Seria como ligar o socket, agora ele pode receber por conexões dos
clientes.
Declaração:
1. int listen(SOCKET s, int backlog);
O parâmetro s é
o socket descriptor que você já conhece. O backlog indica quantas conexões pendentes o
socket pode deixar na fila para serem processadas, quando as conexões são
aceitas elas são removidas da fila. O mínimo para o backlog é 1 e o máximo 5, mas não achei alguma
coisa no site do MSDN que indicasse que o limite é sempre 5.
Se tudo der certo ele vai retornar 0,
caso contrário ele retornará umSOCKET_ERROR. Novamente você pode
usar a função WSAGetLastError()para
analisar o erro se quiser.
O accept() aceita a conexão quando ela é
detectada.
Declaração:
1. SOCKET accept(SOCKET s, struct sockaddr* addr, int* addrlen);
O parâmetro s é
o socket descriptor. O
parâmetro addr é um ponteiro para uma estrutura do
tipo sockaddr (igual ao do função bind()), nela a função irá
armazenar a o endereço (estrutura) da entidade (cliente) que requisitou a
conexão. Você não precisa armazenar este valor se não quiser obter informações
do cliente, pode colocar apenas um NULL no lugar.
O parâmetro addrlen é um ponteiro onde a função irá
colocar o tamanho em bytes da estrutura addr recebida do cliente. Se você passar um
NULL para o parâmetro addr então o addrlen retornará NULL para o ponteiro. Você
também pode apenas colocar um NULL no parâmetro do addrlen.
Repare que o programa ficará esperando por uma conexão, ele
ficará meio travado enquanto isso. Você terá que fazer ele rodar de forma
assíncrona, mas isso não é importante agora, primeiro temos que fazer o
servidor e o cliente funcionarem.
A função accept() irá retornar um socket descriptor do cliente. Se der algum problema será
retornado um INVALID_SOCKET.
Novamente use oWSAGetLastError() para
verificar os erros.
O recv() recebe os dados de uma conexão.
Declaração:
1. int recv(SOCKET s, char* buf, int len, int flags);
O parâmetro s é
o socket descriptor do cliente. O parâmetro buf é o buffer onde serão armazenadas as
informações recebidas. O len é o tamanho deste buffer.
O parâmetro flags indica o modo como serão recebidos os
dados, eu deixei como 0 (zero) assim ele não fará nenhuma ação especial. Para
mais detalhes sobre outras opções olhe no site MSDN.
O recv() irá retornar o total de bytes
recebidos, mas você não irá receber mais bytes do que especificou em len. Ele
retorna 0 (zero) quando a conexão é fechada normalmente. Se der algum erro ele
retorna um SOCKET_ERROR.
Use o WSAGetLastError() para saber mais do erro.
Repare que eu também usei a função inet_ntoa() para exibir junto com a mensagem. Essa
função converte um endereço de Internet (IPv4) em uma string do endereço
formatado nos padrões de Internet com pontos decimais. Ela já retorna em
"Little Endian".
O recv() está dentro de um loop, antes de
receber uma mensagem o buffer (variável message no código) deverá ser limpo. Após
receber a mensagem, ela é exibida. O loop só pára quando o cliente enviar uma
mensagem "#quit". Não é a melhor maneira de ser terminar o programa,
mas serve por enquanto.
Depois de tudo pronto, nós devemos
finalizar a aplicação com as funçõesWSACleanup() e closesocket().
Você só precisa olhar o código para entender como usa-las. E é isso, o
micro-nano-humilde-servidor tá pronto agora só falta fazer o cliente.
1. /*
2. CLIENTE
3. */
4.
5. #include <stdio.h>
6. #include <stdlib.h>
7. #include <string.h>
8. #include <winsock.h>
9.
10. #define BUFFER_SIZE 128
11. #define EXIT_CALL_STRING "#quit"
12.
13. int remote_socket = 0;
14. int message_length = 0;
15.
16. unsigned short remote_port = 0;
17.
18. char remote_ip[32];
19. char message[BUFFER_SIZE];
20.
21. struct sockaddr_in remote_address;
22.
23. WSADATA wsa_data;
24.
25. /* Exibe uma mensagem de erro e termina o programa */
26. void msg_err_exit(char *msg)
27. {
28. fprintf(stderr, msg);
29. system("PAUSE");
30. exit(EXIT_FAILURE);
31. }
32.
33. int main(int argc, char **argv)
34. {
35. if (WSAStartup(MAKEWORD(2, 0), &wsa_data) != 0)
36. msg_err_exit("WSAStartup() failed\n");
37.
38. printf("IP do servidor: ");
39. scanf("%s", remote_ip);
40. fflush(stdin);
41.
42. printf("Porta do servidor: ");
43. scanf("%d", &remote_port);
44. fflush(stdin);
45.
46. remote_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
47. if (remote_socket == INVALID_SOCKET)
48. {
49. WSACleanup();
50. msg_err_exit("socket() failed\n");
51. }
52.
53. // preenchendo o remote_address (servidor)
54. memset(&remote_address, 0, sizeof(remote_address));
55. remote_address.sin_family = AF_INET;
56. remote_address.sin_addr.s_addr = inet_addr(remote_ip);
57. remote_address.sin_port = htons(remote_port);
58.
59. printf("conectando ao servidor %s...\n", remote_ip);
60. if (connect(remote_socket, (struct sockaddr *) &remote_address, sizeof(remote_address)) == SOCKET_ERROR)
61. {
62. WSACleanup();
63. msg_err_exit("connect() failed\n");
64. }
65.
66. printf("digite as mensagens\n");
67. do
68. {
69. // limpa o buffer
70. memset(&message, 0, BUFFER_SIZE);
71.
72. printf("msg: ");
73. gets(message);
74. fflush(stdin);
75.
76. message_length = strlen(message);
77.
78. // envia a mensagem para o servidor
79. if (send(remote_socket, message, message_length, 0) == SOCKET_ERROR)
80. {
81. WSACleanup();
82. closesocket(remote_socket);
83. msg_err_exit("send() failed\n");
84. }
85. }
86. while(strcmp(message, EXIT_CALL_STRING)); // sai quando enviar um "#quit" para o servidor
87.
88. printf("encerrando\n");
89. WSACleanup();
90. closesocket(remote_socket);
91.
92. system("PAUSE");
93. return 0;
94. }
Essa parte vai ser mais rápida, pois já vimos o básico sobre
sockets no servidor.
Você deve criar um socket no cliente
para poder conectar ao servidor. Ele precisa preencher uma estrutura do tipo socketaddr_in (que nós já vimos) com as informações
do servidor, olhe o código para entender melhor.
A função connect() irá conectar o cliente com o servidor.
Declaração:
1. int connect(SOCKET s, const struct sockaddr* name, int namelen);
Ele recebe parâmetros iguais ao da
função bind().
Se der algum erro ele retorna um SOCKET_ERROR.
A função connect() tem o mesmo problema da função accept(), enquanto ele estiver
tentando conectar ficará travado (precisa ser assíncrono), mas não vou mostra
como se faz isso, fica como lição de casa XD.
E o send() é a função responsável por enviar
informações ao servidor.
Declaração:
1. int send(SOCKET s, const char* buf, int len, int flags);
Os parâmetro que ele recebe também
são iguais ao da função recv().
Osend() retonará o número de bytes enviados,
esse número não será maior dolen (e ele não enviará mais o que o
especificado em len). Se
der um erro ele retornará SOCKET_ERROR.
Use o WSAGetLastError() para mais informações.
Lembre-se de limpar o buffer de mensagens, senão você irá enviar
"lixo" para o servidor.
Compile os dois programas, depois execute primeiro o servidor
depois o cliente. Se estiver rodando na mesma máquina você pode conectar o
cliente no IP "127.0.0.1" que dará no mesmo.