Sie sind auf Seite 1von 28

Utilizando o Winsock em Delphi

muito comum o uso dos componentes ClientSocket e ServerSocket quando queremos


trabalhar com aplicaes que utilizam sockets em Delphi.
Neste breve tutorial, iremos abordar o uso da API nativa do Windows para trabalharmos
com sockets - WSA ou Winsock API.
Na tentativa de simplificar e melhorar a compreenso do artigo, iremos trabalhar no
modo console ao invs do modo grfico - eu acho console muito estiloso ^^.
Neste artigo, utilizei a verso 7 do Delphi.
Para criar um novo programa console, faa o seguinte:
1. V at o Menu File New Other (Arquivo, novo, outro);
2. Na aba New (Novo) selecione Console Application (Aplicao Console);
3. OK
Bem, vejamos um simples programa para console em Delphi:
program programa1; // Nome do programa
{$APPTYPE CONSOLE}
uses
SysUtils;
begin
// Cdigo aqui !
end.
Assim como nas linguagens C, C++, Perl e Visual Basic, por exemplo, devemos incluir
headers/mdulos/bibliotecas para podermos trabalhar com sockets.
Como iremos trabalhar com o Winsock, deveremos incluir todas as referncias
necessrias, veja:
program programa1;
{$APPTYPE CONSOLE}
uses
SysUtils,Winsock; // inclui-se a unit Winsock para trabalharmos com a API do Winsock
begin
// Cdigo aqui !
end.
O primeiro passo a ser tomado para que possamos trabalhar com o Winsock,
inicializar sua biblioteca - WSOCK32.DLL - atravs da funo WSAStartup:
program programa1;
{$APPTYPE CONSOLE}

uses
SysUtils,Winsock;
var
wsa: WSADATA;
begin
if(WSAStartup($101,wsa) = -1) then
begin
writeln('Ocorreu um erro ao inicializar o Winsock.');
exit;
end;
end.
Temos nossa primeira varivel:
var
wsa: WSADATA;
Uma varivel do tipo WSADATA armazena informaes sobre a inicializao do
Winsock. Esta passada como segundo argumento da funo WSAStartup:
if(WSAStartup($101,wsa) = -1) then // Se ocorrer um erro
begin
writeln('Ocorreu um erro ao inicializar o Winsock.'); // Mostra mensagem
exit; // Encerra
end;
No trecho acima, tentamos inicializar a verso 1.1 do Winsock (o smbolo $ utilizado
para nmeros hexadecimais. $101 = 257).
Veja a sintaxe:
WSAStartup(VERSO: WORD,var VARIVEL_WSA: WSADATA);
VERSO:
verso do winsock a ser inicializada;
VARIVEL_WSA:
varivel do tipo WSADATA.
A funo ir retornar o valor -1 se falhar. Do contrrio, retornar ZERO.
A funo WSAStartup(), na verdade, requer um ponteiro para uma varivel do tipo
WSADATA como segundo parmetro, no entanto, s necessrio passar o nome da
varivel. Isso se explica devido declarao do parmetro:
var VARIVEL_WSA: WSADATA);
Quando temos "var" antes do parmetro, significa que ser passado o endereo
(ponteiro) da varivel, e no o seu valor =)
Muito bem, aps inicializarmos o Winsock, podemos utilizar as funes contidas na
Winsock API.
Temos agora que criar um socket:
program programa1;

{$APPTYPE CONSOLE}
uses
SysUtils,Winsock;
var
wsa: WSADATA;
sock: integer;
begin
if(WSAStartup($101,wsa) = -1) then
begin
writeln('Ocorreu um erro ao inicializar o Winsock.');
exit;
end;
sock := socket(AF_INET,SOCK_STREAM,0);
if(sock = -1) then
begin
writeln('Ocorreu um erro ao criar o socket.');
exit;
end;
end.
Temos mais uma varivel declarada:
sock: integer;
Esta varivel ir armazenar a identificao do nosso socket criado. Geralmente, ao invs
do tipo Integer (inteiro), variveis deste tipo so declaradas como SOCKET. Entretanto,
"SOCKET" apenas um "novo nome" para o tipo inteiro, ou seja, o tipo "SOCKET" ,
na verdade, o tipo inteiro =)
Vejamos a criao do socket:
sock := socket(AF_INET,SOCK_STREAM,0);
if(sock = -1) then
begin
writeln('Ocorreu um erro ao criar o socket.');
exit;
end;
O trecho acima tenta criar um socket para ser utilizado com o protocolo TCP. Isto
definido pelo segundo parmetro: SOCK_STREAM.
Sintaxe:
sock := socket(FAMLIA: Integer,PROTOCOLO: Integer,TIPO: Integer);
sock:
varivel do tipo inteiro que ir identificar o socket;
FAMLIA:

famlia do socket. Embora existam diversas constantes, use-se a AF_INET =


INTERNET.
PROTOCOLO:
protocolo com o qual o socket ir trabalhar:
SOCK_STREAM = TCP
SOCK_DGRAM = UDP
SOCK_RAW = RAW
TIPO:
opcional, define opes relacionados ao tipo do protocolo:
IPPROTO_IP = protocolo IP;
IPPROTO_TCP = protocolo TCP;
IPPROTO_UDP = protocolo UDP;
IPPROTO_RAW = protocolo RAW;
IPPROTO_TCP = protocolo ICMP;
O parmetro s obrigatrio quando estamos trabalhando com raw sockets, podendo
ser passado como ZERO, caso contrrio.
A funo ir retornar o valor -1 se falhar. Do contrrio, retornar ZERO.
Iremos comear pelo protocolo TCP por ser mais utilizado
Aps termos criado o socket, iremos definir uma tarefa para este: atuar como cliente ou
servidor.
Para tal, teremos que configurar este socket de acordo com uma estrutura denominada
"SOCKADDR_IN".
Vamos comear definindo o socket para trabalhar com servidor:
program programa1;
{$APPTYPE CONSOLE}
uses
SysUtils,Winsock;
var
wsa: WSADATA;
sock: integer;
addr: sockaddr_in;
begin
if(WSAStartup($101,wsa) = -1) then
begin
writeln('Ocorreu um erro ao inicializar o Winsock.');
exit;
end;

sock := socket(AF_INET,SOCK_STREAM,0);
if(sock = -1) then
begin
writeln('Ocorreu um erro ao criar o socket.');
exit;
end;
addr.sin_family := AF_INET;
addr.sin_port := htons(1234);
addr.sin_addr.S_addr := INADDR_ANY;
if(bind(sock,addr,sizeof(addr)) = -1) then
begin
writeln('Ocorreu um erro ao configurar o socket.');
exit;
end;
if(listen(sock,1) = -1) then
begin
writeln('Ocorreu um erro ao colocar o socket na escuta.');
exit;
end;
sock := accept(sock,nil,nil);
if(sock = -1) then
begin
writeln('Ocorreu um erro ao aceitar uma conexo.');
exit;
end;
writeln('Um cliente conectou-se!');
closesocket(sock);
WSACleanup();
end.
Antes de tudo, para configurarmos um socket, devemos declarar uma estrutura do tipo
SOCKADDR_IN:
addr: sockaddr_in;
Vamos ver como o socket configurado:
addr.sin_family := AF_INET; // Corresponde famlia a qual o socket pertence
addr.sin_port := htons(1234); // Corresponde porta na qual o socket ir aguardar
conexes
addr.sin_addr.S_addr := INADDR_ANY; // Permite que o socket aceite conexo de
qualquer host
H dois detalhes que devem ser observados:
addr.sin_port := htons(1234); // Correto
O membro "sin_port", da estrutura "addr", um inteiro de 2 bytes e requer um valor
expresso em network byte. Para convertemos um valor para tal, utilizamos a funo
htons(). A forma abaixo estaria incorreta:

addr.sin_port := 1234; // INCORRETO =(


O membro "sin_addr" armazena o endereo IP do host remoto.
Este membro, na verdade, pertence a uma estrutura chamada "in_addr" que armazena
endereos IP. Quando queremos transformar um IP para network byte, utilizamos a
funo inet_addr():
addr.sin_addr.S_addr := inet_addr('127.0.0.1'); // Transforma o IP 127.0.0.1 para
network byte.
Se voc observar bem, utilizamos um membro dentro desta estrutura: "S_addr". Note
ainda que seria incorreto fazer:
addr.sin_addr := inet_addr('127.0.0.1'); // Errado ;(
Vamos ver o por qu.
addr -> estrutura sockaddr_in;
sin_addr -> estrutura in_addr dentro de "addr";
S_addr -> membro dentro de "sin_addr", um inteiro de 4 bytes.
A funo "inet_addr()" retorna um valor inteiro (tambm de 4 bytes), expresso em
network byte, de um respectivo endereo IP.
Quando tentamos fazer:
addr.sin_addr := inet_addr('127.0.0.1'); // Errado ;(
Estamos querendo atribuir um valor inteiro de 4 bytes de forma direta a uma estrutura,
por isso ocorreria o erro.
J no outro exemplo:
addr.sin_addr.S_addr := inet_addr('127.0.0.1'); // CERTO
O membro "S_addr" (dentro de "sin_addr") um inteiro de 4 bytes e a funo
inet_addr() tambm retorna um inteiro de 4 bytes, por isso a atribuio vlida
importante notar que s podemos utilizar a funo inet_addr() com endereos IP:
addr.sin_addr.S_addr := inet_addr('www.google.com.br'); // INCORRETO!
Veremos como obter o endereo IP de um host pelo seu nome mais adiante.
Aps configuramos o socket, devemos chamar a funo bind() para prepar-lo no
computador local, permitindo-o aguardar conexes:
if(bind(sock,addr,sizeof(addr)) = -1) then
begin
writeln('Ocorreu um erro ao configurar o socket.');
exit;
end;
Vejamos a sintaxe:
bind(sock: Integer; var addr: sockaddr_in; tamanho: Integer);
sock:
nome do nosso socket;
addr:
varivel pertencente estrutura "sockaddr_in";
tamanho:
tamanho da estrutura "sockaddr_in".

A funo ir retornar o valor -1 se falhar. Do contrrio, retornar ZERO.


Como j havia dito antes, um socket identificado por uma varivel do tipo inteiro. Isso
pode ser constatado observando o tipo do primeiro parmetro: "Integer"
Havia dito tambm que, quando temos "var" antes de um parmetro, signfica que
iremos passar o endereo de uma varivel e no seu valor - justamente o endereo da
varivel "addr" que temos que passar.
Veja que, para passar o tamanho da estrutura "sockaddr_in", utilizamos o operador
sizeof(). Tanto faz escrever "sizeof(addr)" ou "sizeof(sockaddr_in)" - embora esta
ltima forma seja mais adequada.
Aps configurado localmente, podemos colocar o socket em modo de escuta:
if(listen(sock,1) = -1) then
begin
writeln('Ocorreu um erro ao colocar o socket na escuta.');
exit;
end;
No exemplo acima, fazemos com que o socket aguarde uma conexo na porta
configurada previamente.
Vejamos a sintaxe da funo:
listen(sock: Integer; num: Integer);
sock:
nome do nosso socket;
num:
nmero de conexes que podem ser aceitas;
Assim como as outras funes, se a funo listen() tiver xito, esta retorna ZERO,
seno, -1 retornado.
O trecho abaixo faz com que o programa fique aguardando at que um pedido de
conexo seja feito:
sock := accept(sock,nil,nil);
if(sock = -1) then
begin
writeln('Ocorreu um erro ao aceitar uma conexo.');
exit;
end;
Se, aps recebido o pedido de conexo, o valor retornado for -1, sinal que houve um
erro ao aceitar esta conexo.
Veja a sintaxe:
novo_sock := accept(sock: Integer; var pt_addr: PSOCKADDR; pt_tamanho: PInteger);
novo_sock:

uma nova varivel (inteiro) que ir armazenar a identificao do novo socket; se o nome
do prprio socket for utilizado, no ser possvel aceitar novas conexes;
sock:
nome do nosso socket;
pt_addr:
ponteiro para uma varivel do tipo "SOCKADDR" que ir armazenar informaes sobre
o cliente que se conectou;
tamanho:
ponteiro para uma varivel do tipo inteiro que armazena o tamanho da estrutura
"SOCKADDR";
A funo ir retornar o valor -1 se falhar. Do contrrio, retornar ZERO.
No nosso exemplo, usamos:
sock := accept(sock,nil,nil);
No caso, a varivel "sock" ser utilizada para armazenar a identificao do novo socket
aps um pedido de conexo ter sido feito. Observe que, como no iremos armazenar
informaes sobre o cliente, passamos os dois ltimos argumentos como NULOS, isto
, passando o ponteiro nulo: "nil".
Vejamos agora:
closesocket(sock);
WSACleanup();
So duas funes ainda no vistas anteriormente. Utiliza-se a funo closesocket() para
fechar um socket aps seu uso e WSACleanup() para finalizar o uso do Winsock.
Sintaxe:
closesocket(sock);
sock:
nome do socket.
A funo WSACleanup() no possui parmetros.
Mais adiante, quando abordaremos algumas funes, veremos como obter informaes
do cliente conectado.
No exemplo acima, criamos um socket para atuar com servidor. A seguir, iremos criar
um socket para atuar como cliente:
program programa2;
{$APPTYPE CONSOLE}
uses
SysUtils,Winsock;
var
wsa: WSADATA;
sock: integer;

addr: sockaddr_in;
begin
if(WSAStartup($101,wsa) = -1) then
begin
writeln('Ocorreu um erro ao inicializar o Winsock.');
exit;
end;
sock := socket(AF_INET,SOCK_STREAM,0);
if(sock = -1) then
begin
writeln('Ocorreu um erro ao criar o socket.');
exit;
end;
addr.sin_family := AF_INET;
addr.sin_port := htons(1234);
addr.sin_addr.S_addr := inet_addr('127.0.0.1');
if(connect(sock,addr,sizeof(addr)) = -1) then
begin
writeln('Ocorreu um erro ao conectar-se.');
exit;
end;
writeln('Conectado!');
closesocket(sock);
WSACleanup();
end.
Como voc pode observar, o cdigo necessrio para tal bem menor
Vejamos as diferenas:
addr.sin_addr.S_addr := inet_addr('127.0.0.1');
A linha acima configura o socket para se conectar no IP "127.0.0.1".
Em seguida, fazemos com que o socket tente conectar-se:
if(connect(sock,addr,sizeof(addr)) = -1) then
begin
writeln('Ocorreu um erro ao conectar-se.');
exit;
end;
A sintaxe da funo connect() similar da funo bind() - que no necessria quando
estamos trabalhando com um socket cliente - veja:
connect(sock: Integer; var addr: sockaddr_in; tamanho: Integer);
sock:
nome do nosso socket;
addr:

varivel pertencente estrutura "sockaddr_in";


tamanho:
tamanho da estrutura "sockaddr_in".
A funo ir retornar o valor -1 se falhar. Do contrrio, retornar ZERO.
Novamente, fechamos o socket e finalizamos o winsock (respectivamente):
closesocket(sock);
WSACleanup();
O prximo passo trabalhar com o envio e recebimento de dados atravs do socket.
Quando estamos trabalhando com o protocolo TCP, utilizamos as funes recv() e
send(), respectivamente:
Enviando dados:
var
buffer: array[0..99] of char;
begin
buffer := 'Apenas um exemplo!';
send(sock,buffer,strlen(buffer),0);
end.
No exemplo acima, declaramos um array de caracteres (uma string em C) com
capacidade de armazenar 100 elementos, isto , 100 caracteres: buffer.
Escrevemos "Apenas um exemplo!" neste array e o enviamos.
A sintaxe :
send(sock: Integer; var Buf; tam_buffer: Integer; Flags: Integer);
sock:
nome do nosso socket;
Buf:
array de caracteres ou um ponteiro para char que contm os dados que sero enviados;
tam_buffer:
nmero de bytes que sero enviados;
flags:
valores opcionais que especificam o modo de envio.
A funo, em situao normal, retorna o nmero de bytes enviados. Se algum erro
ocorrer, a funo retorna -1.
Note que utilizamos a funo "strlen()" para retornar o tamanho do buffer:
strlen(buffer);
Recebendo dados:

var
buffer: array[0..99] of char;
begin
ZeroMemory(@buffer,100); // Limpa o buffer, necessrio incluir "Windows" na
clsula "Uses".
recv(sock,buffer,100,0);
end.
A sintaxe :
recv(sock: Integer; var Buf; tam_buffer: Integer; Flags: Integer);
sock:
nome do nosso socket;
Buf:
buffer que ir armazenar os dados recebidos;
tam_buffer:
tamanho do buffer;
flags:
valores opcionais que especificam o modo de recebimento.
A funo, em situao normal, retorna o nmero de bytes recebidos. Se algum erro
ocorrer, a funo retorna -1.
Vejamos um exemplo:
servidor:
program servidor;
{$APPTYPE CONSOLE}
uses
SysUtils,Winsock, Windows; // Windows -> para usar ZeroMemory()
var
wsa: WSADATA;
sock: integer;
addr: sockaddr_in;
buffer: array[0..100] of char; // Buffer para enviar/receber dados
envia: string; // Armazenar uma string digitada
bytes: integer; // Nmero de bytes recebidos
begin
if(WSAStartup($101,wsa) = -1) then
begin
writeln('Ocorreu um erro ao inicializar o Winsock.');

exit;
end;
sock := socket(AF_INET,SOCK_STREAM,0);
if(sock = -1) then
begin
writeln('Ocorreu um erro ao criar o socket.');
exit;
end;
addr.sin_family := AF_INET;
addr.sin_port := htons(1234);
addr.sin_addr.S_addr := INADDR_ANY;
if(bind(sock,addr,sizeof(addr)) = -1) then
begin
writeln('Ocorreu um erro ao configurar o socket.');
exit;
end;
if(listen(sock,1) = -1) then
begin
writeln('Ocorreu um erro ao colocar o socket na escuta.');
exit;
end;
sock := accept(sock,nil,nil);
if(sock = -1) then
begin
writeln('Ocorreu um erro ao aceitar uma conexo.');
exit;
end;
bytes := 0; // 0 byte recebido
while(bytes <> -1) do // Enquanto o nmero de bytes retornando for diferente de -1 =
conectado
begin
ZeroMemory(@buffer,100); // Zera buffer
recv(sock,buffer,100,0); // Recebe dados do cliente
writeln(buffer); // Mostra-os
ZeroMemory(@buffer,100); // Limpa o buffer novamente
readln(envia); // L uma string
StrLCopy(buffer,PChar(envia),100); // Copia at 100 caracteres para o buffer
send(sock,buffer,strlen(buffer),0); // Envia os dados
end;
// Encerra
closesocket(sock);
WSACleanup();

end.
cliente:
program cliente;
{$APPTYPE CONSOLE}
uses
SysUtils,Winsock, Windows; // Windows -> para usar ZeroMemory()
var
wsa: WSADATA;
sock: integer;
addr: sockaddr_in;
buffer: array[0..100] of char; // Buffer para enviar/receber dados
envia: string; // Armazenar uma string digitada
bytes: integer; // Nmero de bytes recebidos
begin
if(WSAStartup($101,wsa) = -1) then
begin
writeln('Ocorreu um erro ao inicializar o Winsock.');
exit;
end;
sock := socket(AF_INET,SOCK_STREAM,0);
if(sock = -1) then
begin
writeln('Ocorreu um erro ao criar o socket.');
exit;
end;
addr.sin_family := AF_INET;
addr.sin_port := htons(1234);
addr.sin_addr.S_addr := inet_addr('127.0.0.1');
if(connect(sock,addr,sizeof(addr)) = -1) then
begin
writeln('Ocorreu um erro ao conectar-se.');
exit;
end;
bytes := 0; // 0 byte recebido
while(bytes <> -1) do // Enquanto o nmero de bytes retornando for diferente de -1 =
conectado
begin
ZeroMemory(@buffer,100); // Limpa o buffer

readln(envia); // L uma string


StrLCopy(buffer,PChar(envia),100); // Copia at 100 caracteres para o buffer
send(sock,buffer,strlen(buffer),0); // Envia os dados
ZeroMemory(@buffer,100); // Limpa o buffer novamente
recv(sock,buffer,100,0); // Recebe dados do cliente
writeln(buffer); // Mostra-os
end;
// Encerra
closesocket(sock);
WSACleanup();
closesocket(sock);
WSACleanup();
end.
O programa acima o clssico chat cliente-servidor. Geralmente, exemplos como este
so apresentados quando estamos estudando sockets
Veremos agora duas funes para trabalharmos com endereos IP e hostname's:
1) gethostbyname()
Vamos retomar:
addr.sin_addr.S_addr := inet_addr('www.google.com.br'); // INCORRETO!
Ocorreria um erro acima pois "www.google.com.br" no um endereo IP, e sim um
hostname.
Antes de podermos configurar o socket para se conectar neste host, devemos obter o seu
IP utilizando a funo gethostbyname():
var
...
host: PHostEnt; // PHostEnt = ponteiro para a estrutura hostent
addr: sockaddr_in;
begin
...
host := gethostbyname('www.google.com.br');
if(host = nil) then
writeln('Erro ao resolver o host!')
else
addr.sin_addr := PInAddr(host.h_addr^)^;
Declaramos um ponteiro para a estrutura "hostent" que ir armazenar as informaes
sobre o computador remoto: "host".
A funo retorna um ponteiro nulo (nil) caso no consiga resolver o hostname. O
endereo deste host armazenando no membro "h_addr" desta estrutura, expresso em
network byte.
Veja:
addr.sin_addr := PInAddr(host.h_addr^)^;
A linha acima parece ser um pouco complicada, mas no . Sabemos que "host" um

ponteiro para a estrutura "hostent". O membro "host.h_addr" um ponteiro para CHAR


que aponta para o endereo IP do host, expresso em network byte.
Para acessar o valor de um membro apontado por um pointeiro, fazemos:
ponteiro.membro^ que equivale a host.h_addr^; // retorna o endereo apontado pelo
membro "h_addr";
No caso acima, como o membro "h_addr" um ponteiro e no uma simples varivel, o
seu valor o endereo para onde ele aponta.
PInAddr um ponteiro global para a estrutura "in_addr"(mesmo tipo do membro
"sin_addr").
Quando temos "PInAddr(host.h_addr^)", estamos fazendo com que o ponteiro
"PInAddr" aponte para o mesmo endereo que "host.h_addr" aponta, isto , aquele
endereo que armazena o endereo IP do host. A diferena que o valor deste endereo
ser obtido como prprio para a estrutura "in_addr" e no mais como "CHAR".
At a, temos:
addr.sin_addr = ENDEREO apontado por "host.h_addr" j obtido como "in_addr";
No entanto, "sin_addr" requer um valor "in_addr" e no um ponteiro, por isso fazemos:
PInAddr(host.h_addr^)^; // Valor "in_addr" do endereo apontado
Portanto, quando temos:
addr.sin_addr := PInAddr(host.h_addr^)^;
Estamos atribuindo ao membro "sin_addr" o valor apontado pelo membro "host.h_addr"
convertido para "in_addr".
Para compreender melhor o processo, recomendvel o estudo de ponteiros
2) inet_ntoa()
Com esta funo, podemos transformar um IP expresso em network byte para string.
Esta funo faz o processo inverso da funo inet_addr().
Veja:
var
...
addr: sockaddr_in;
begin
...
addr.sin_addr.S_addr = inet_addr("127.0.0.1"); // Converte o IP de string para network
byte
writeln('O IP e: ' + inet_ntoa(addr.sin_addr)); // Converte de network byte para string
importante notar que, para utilizar a funo inet_ntoa(), devemos passar um valor do
tipo "in_addr" como parmetro.
Veja outro exemplo:
var
...
host: PHostEnt; // PHostEnt = ponteiro para a estrutura hostent

addr: sockaddr_in;
begin
...
host := gethostbyname('www.google.com.br');
if(host = nil) then
writeln('Erro ao resolver o host!')
else
writeln('O IP e: ' + inet_ntoa(PInAddr(host.h_addr^)^));
No exemplo acima, tentamos resolver o host "www.google.com.br" e mostrar o seu
respectivo IP. Como vimos anteriormente, a expresso "PInAddr(host.h_addr^)^"
retorna o endereo IP de um host como valor "in_addr" - justamente o tipo de valor
requirido pela funo inet_ntoa().
FIM da primeira parte
Na continuao do artigo, veremos melhor como trabalhar com a funo
gethostbyname() e outras funes. Veremos ainda o uso do protocolo UDP.
Pois bem, continuemos com o nosso artigo.
Abaixo segue uma pequena lista de aspectos abordados no artigo anterior:
1) Vimos o que necessrio para trabalhar com a API do Winsock (WSA) no Delphi;
2) Foram mostrados os passos que devem ser seguidos para criar um socket
simples, abordando, exclusivamene, o protocolo TCP;
3) Ainda com base no protocolo TCP, foram ilustrados exemplos de um programa
que atuava como cliente e um outro servidor;
4) Utilizando as funes send() e recv(), aprendemos a como enviar e receber
dados, respectivamente atravs de um socket;
5) Algumas noes de variveis, estruturas, funes e converses;
Neste ltimo item, ficou pendente a explicao do uso de funo gethostbyname()
aplicada em outras situaes, alm de outras funes do ramo, como
getservbyport(), que tambm ser aborda nesta parte do artigo.
Para comear, vamos retomar o ltimo exemplo da primeira parte do tutorial:
var
...
host: PHostEnt; // PHostEnt = ponteiro para a estrutura hostent
addr: sockaddr_in;
begin
...
host := gethostbyname('www.google.com.br');
if(host = nil) then
writeln('Erro ao resolver o host!')
else
writeln('O IP e: ' + inet_ntoa(PInAddr(host.h_addr^)^));
No exemplo acima, tentamos resolver o host "www.google.com.br" e mostrar o seu
respectivo IP. Como vimos anteriormente, a expresso "PInAddr(host.h_addr^)^"
retorna o endereo IP de um host como valor "in_addr" - justamente o tipo de valor
requirido pela funo inet_ntoa().

Como se sabe, devemos especificar dados sobre um host remoto para que seja
possvel uma conexo entre o computador local e este. Sabemos ainda que, para
tanto, devemos utilizar uma estrutura denominada "sockaddr_in", que contm trs
principais membros:
sin_family -> indica a famlia do socket;
sin_port -> indica a porta na qual o socket ir atuar;
sin_addr -> indica o endereo utilizado pelo socket, seja este local ou remoto;
Dando nfase ao ltimo, sabemos que o utilizamos quando queremos, por exemplo,
estabelecer uma conexo entre o computador local e um outro remoto cujo IP
200.123.123.123:
addr.sin_addr.S_addr = inet_addr('200.123.123.123');
Como se sabe, com o auxlio da funo inet_addr(), podemos converter um
endereo IP escrito na forma de string ('200.123.123.123') para seu valor
correspondente na forma in_addr (network byte) que a adequada.
Existem casos, no entanto, em que no sabemos o endereo IP do computador
remoto, e pior ainda: s vezes, at sabemos, entretanto, este IP pode ser
dinmico, isto , no-fixo. Em situaes assim, se tivssimos que tomar como base
apenas o endereo IP de hosts remotos, teramos grande dificuldades de
comunicao. Felizmente, podemos utlizar o "hostname" de um computador como
referncia, dessa forma, no importa qual endereo IP que este computador esteja
utlizando, pois, ao contrrio de endereos IPs, o hostname fixo, ou seja, mesmo
que o endereo IP de um servidor seja alterado, atravs do seu hostname podemos
acess-lo.
Mas a questo : como aplicar isso em nossos programas? para responder a essa
pergunta que este artigo foi comeado com ltimo exemplo da parte 1 do tutorial
xD
Vamos fazer uma anlise rpida no seguinte trecho:
var
wsa: WSADATA; // Para inicializar o winsock
host: PHostEnt; // PHostEnt = ponteiro para a estrutura hostent
begin
if(WSAStartup($101,wsa) = -1) then
exit; // Encerra se falhar
host := gethostbyname('www.google.com.br');
end.
um exemplo um pouco que repetitivo, eu diria. Mas continuemos:
var
wsa: WSADATA;
host: PHostEnt; // PHostEnt = ponteiro para a estrutura hostent
begin
if(WSAStartup($101,wsa) = -1) then
exit;
host := gethostbyname('www.google.com.br');
if(host = nil) then
// Erro
else
// Host resolvido com sucesso xD

end.
Quando a funo gethostbyname() falha em resolver um hostname, um ponteiro
nulo (nil) retornado. Fazemos o tratamento de erros com base nessa propriedade.
var
wsa: WSADATA;
host: PHostEnt; // PHostEnt = ponteiro para a estrutura hostent
addr: sockaddr_in; // Uma estrutura do tipo sockaddr_in
begin
if(WSAStartup($101,wsa) = -1) then
exit;
host := gethostbyname('www.google.com.br');
if(host = nil) then
exit // Encerra se falhar
else
addr.sin_addr := PInAddr(host.h_addr^)^;
end.
Com base no cdigo acima, voc consegue responder a questo inicial xD ?
O que exatamente ocorre que, ao utilizar a funo gethostbyname() passando
como parmetro o hostname 'www.google.com.br', esta funo tentar resolver
este hostname, isto , obter seu endereo IP, seu nome oficial, dentre outros
dados. Como vimos na parte anterior do texto (e no incio desta), a combinao
PInAddr(host.h_addr^)^ nos retorna o endereo IP de um host j convertido para
"in_addr" que nos possibilita us-lo como valor para o membro "sin_addr" da
estrutura "sockaddr_in".
A partir deste ponto, j se pode utilizar a funo connect(), por exemplo, para criar
uma conexo com o computador cujo hostname utlizamos.
Veja o exemplo:
program exemplo;
{$APPTYPE CONSOLE}
uses
SysUtils,Winsock;
var
wsa: WSADATA;
sock: integer;
addr: sockaddr_in;
host: PHostEnt;
begin
if(WSAStartup($101,wsa) = -1) then
begin
writeln('Ocorreu um erro ao inicializar o Winsock.');
exit;
end;
sock := socket(AF_INET,SOCK_STREAM,0);

if(sock = -1) then


begin
writeln('Ocorreu um erro ao criar o socket.');
exit;
end;
host := gethostbyname('www.google.com.br');
if(host = nil) then
begin
writeln('Ocorreu um erro ao resolver o hostname.');
exit;
end;
addr.sin_family := AF_INET;
addr.sin_port := htons(80);
addr.sin_addr := PInAddr(host.h_addr^)^;
if(connect(sock,addr,sizeof(addr)) = -1) then
begin
writeln('Erro ao se conectar.');
exit;
end
else
begin
writeln('Conectado xD');
closesocket(sock);
end;
WSACleanup();
end.
Um exemplo bem simples. Tentamos resolver o hostname 'www.google.com.br' e,
posteriormente, utilizamos a funo connect() para que o programa tente conectarse ao host pela porta 80. Se a conexo falhar, o programa simplesmente encerra,
do contrrio, uma mensagem avisa que a conexo foi feita com sucesso e o socket
fechado logo em seguida.
Uma outra funo muito interessante existente na API do Winsock a
getservbyport(). Com esta funo, podemos obter o servio associado a uma
determinada porta, como por exemplo, o servio HTTP que geralmente roda sob a
porta 80.
Vejamos um exemplo de uso:
program exemplo;
{$APPTYPE CONSOLE}
uses
SysUtils,Winsock;
var
wsa: WSADATA;
serv: PServEnt; // Ponteiro para a estrutura servent
begin
if(WSAStartup($101,wsa) = -1) then

begin
writeln('Ocorreu um erro ao inicializar o Winsock.');
exit;
end;
serv := getservbyport(htons(80),'tcp'); // Tenta obter o servico associado porta
80 tcp
if(serv = nil) then // Falha ao obter
writeln('Servico desconhecido')
else
writeln('Servico: ' + serv.s_name); // Imprime o nome do servio
WSACleanup();
end.
Neste exemplo, tentamos obter o nome do servio associado porta 80 sob o
protocolo TCP. Para tal, usamos a funo getservbyport(), cuja sintaxe simpificada
:
getservbyname (porta_em_network_byte,protocolo_string);
porta_em_network_byte:
a porta utilizada pelo servio que queremos obter, convertida para network byte
(htons());
protocolo_string:
o protocolo sob o qual o servio atua;
Geralmente, quando a funo retorna um ponteiro nulo, porque no foi
encontrado um servio associado porta especificada. No entanto, a funo
tambm pode retornar este tipo de ponteiro mesmo que o servio exista. Veja
como isso possvel:
program exemplo;
{$APPTYPE CONSOLE}
uses
SysUtils,Winsock;
var
wsa: WSADATA;
serv: PServEnt;
begin
{
Comentamos esta parte do cdigo responsvel pela inicializao do Winsock;
Como se sabe, sem esta inicializao prvia, todas as outras funes
dependentes retornaro em erro, assim como getservbyname()
if(WSAStartup($101,wsa) = -1) then
begin
writeln('Ocorreu um erro ao inicializar o Winsock.');
exit;
end;

}
serv := getservbyport(htons(80),'tcp');
if (serv = nil) then
writeln('Servico desconhecido.')
else
writeln('Servico: ' + serv.s_name);
WSACleanup();
end.
Provalmente, ao executar o primeiro cdigo, a sada do programa deveria ter sido:
Servico: http
E neste ltimo, a sada seria:
Servico desconhecido
Como se v no cdigo, o programa no chama pela funo WSAStartup() e, por
esta razo, a funo getservbyname() retornou em erro. Se, por algum motivo, o
cdigo fosse aplicado em um programa, o bug estaria evidenciado.
Para resolver este impasse, podemos utitilizar uma funo muito til:
WSAGetLastError(), que nos retorna um inteiro (integer) com ltimo cdigo de erro
ocorrido no uso do winsock.
Veja:
program exemplo;
{$APPTYPE CONSOLE}
uses
SysUtils,Winsock;
var
wsa: WSADATA;
serv: PServEnt;
begin
{
Comentamos esta parte do cdigo responsvel pela inicializao
do Winsock;
Como se sabe, sem esta inicializao prvia, todas as outras funa
dependentes retoraram em erro, assim como getservbyname()
if(WSAStartup($101,wsa) = -1) then
begin
writeln('Ocorreu um erro ao inicializar o Winsock.');
exit;
end;
}
serv := getservbyport(htons(80),'tcp');
if (serv = nil) and (WSAGetLastError() = 0) then
writeln('Servico desconhecido.')

else if(serv = nil) and (WSAGetLastError() <> 0) then


begin
writeln('Erro na funcao getservbyport(). ID: ' + IntToStr(WSAGetLastError()));
exit
end
else writeln('Servico: ' + serv.s_name);
WSACleanup();
end.
Agora, o programa no mais mostra que o servio desconhecido, mas sim que um
erro ocorreu e a ID deste erro. Quando a funo WSAGetLastError() retorna ZERO,
sinal que nenhum erro ocorreu at o ponto em que foi chamada. Ento, a lgica
seria:
Se "serv" = nil e "WSALastError()" = 0 -> no ocorreram erros no winsock, no
entanto, a funo getservbyport() no conseguiu obter o servio;
Se "serv" = nil e "WSALastError" no for ZERO, ocorreu um erro no uso do
Winsock;
Se "serv' no retornar um ponteiro nulo, o servio foi obtido com sucesso.
No exemplo, foram utilizados if, elseif e else, mas voc pode reorganizar a lgica
tornando o cdigo mais legvel e bonito xD
Uma curiosidade: existe um arquivo no qual existem todos os servios/portas
reconhecidos pelo sistema. Este arquivo acessado pela funo getservbyport()
para nos retornar um servio desejado. No Windows, sua localizao :
C:\WINDOWS\SYSTEM32\Drivers\etc\services.
Ainda falando sobre erros gerados durante o uso do winsock, a funo
WSAGetLastError() possui uma outra oposta: WSASetLastError. Esta ltima, por sua
vez, utilizada para definir a ID do ltimo erro ocorrido. Exemplo:
WSASetLastError(0);
Na linha acima, simplesmente zeramos o status de erro. No final desta pgina,
postarei uma tabela contendo os principais cdigos de erro, suas constantes e seus
respectivos significados.
Voltando a falar um pouco sobre hostname's. Existe um endereo denominado
loopback, que o prprio endereo da mquina local, geralmente atribudo ao
hostname "localhost". No entanto, podemos ter um nome alternativo para esse
endereo, como por exemplo, o nome da mquina. possvel obter este nome
atravs de uma funo bem simples: gethostname(). Veja:
program exemplo;
{$APPTYPE CONSOLE}
uses
SysUtils,Winsock;
var

wsa: WSADATA;
nome_local: array[0..99] of char;
begin
if(WSAStartup($101,wsa) = -1) then
begin
writeln('Ocorreu um erro ao inicializar o Winsock.');
exit;
end;
if(gethostname(nome_local,100) = 0) then
writeln('Host Name local: ' + nome_local)
else
writeln('Erro ao obter o hostname local.');
WSACleanup();
end.
Observe que declaramos o array de char "nome_local" de 100 caracteres. neste
buffer em que o hostname local do computador ser armazenado. A sintaxe da
funo :
gethostname(buffer,tamanho);
buffer:
um array de char no qual o hostname local ser armazenado;
tamanho:
o tamanho do buffer em bytes
Se a funo falhar, o valor retornado -1; em caso de xito, a funo retornar
ZERO.
Muito bem. Vamos abordar o uso do protocolo UDP atravs do winsock. Porm,
antes de faz-lo, vejamos algumas diferenas bsica entre os protocolo TCP e este
ltimo:
Protocolo TCP
1) um protocolo orientado a conexo, isto , um fluxo de dados entre
cliente/servidor s poder ser feito caso haja uma conexo entre estes dois hosts;
2) Por retransmitir pacotes de dados perdidos, considerado um protocolo confivel
no aspecto de entrega/recibo de dados;
3) Como conseqncia das duas caractersticas acima, o protocolo TCP mais lento
do que o UDP
Protocolo UDP
1) No orientado a conexo, ou seja, so somente necessrias as informaes de
porta/endereo remoto para que dados possam ser enviados/recebidos
2) Por no ser um protocolo dependente de conexo, no h garantia de entrega de
um determinado pacote enviado (ao contrrio do TCP) o que torna o protocolo noconfivel.
3) Em contra-partida, o protocolo mais rpido do que o TCP quando se trata de

fluxo de dados.
Uma vez em que o protocolo UDP no orientado a conexo, podemos deduzir que
o uso da funes connect() e listen()/accept() so desnecessrias em sua aplicao.
Como foi dito anteriormente, basta saber o endereo remoto de um host e sua
porta para que possamos enviar dados para este, utilizando o protocolo UDP. O
mesmo se aplica entrada de dados. Com o protocolo TCP, utitlizamos,
respectivamente, as funes send() e recv(). No protocolo UDP, as coisas mudam
um pouco: utiliza-se sendto() para o envio de dados, e recvfrom() para recebermos
dados. Segue abaixo um cdigo que mostra a criao de um socket que trabalhe
sob o protocolo UDP:
program exemplo;
{$APPTYPE CONSOLE}
uses
SysUtils,Winsock;
var
wsa: WSADATA;
sock: integer;
addr: sockaddr_in;
buffer: array [0..99] of char;
begin
if(WSAStartup($101,wsa) = -1) then
begin
writeln('Ocorreu um erro ao inicializar o Winsock.');
exit;
end;
sock := socket(AF_INET,SOCK_DGRAM,0); // Utilizamos SOCK_DGRAM e no
SOCK_STREAM
if(sock = -1) then
begin
writeln('Ocorreu um erro ao criar o socket.');
exit;
end;
addr.sin_family := AF_INET;
addr.sin_port := htons(1234);
addr.sin_addr.S_addr := inet_addr('200.123.123.123');
buffer := 'Enviando um string atravs do protocolo UDP!';
if(sendto(sock,buffer,strlen(buffer), 0,addr,sizeof(addr)) = -1) then
begin
writeln('Erro ao enviar dados!');
exit;
end;
closesocket(sock);
WSACleanup();
end.
O primeiro ponto a ser observado no cdigo acima que, ao invs de utilizarmos
SOCK_STREAM como segundo parmetro da funo socket(), utilizamos
SOCK_DGRAM que a constante correta para o protocolo UDP.

Nota-se que, no cdigo, apenas especificamos as informaes do host e utilizamos


a funo sendto() para enviar a string contida no array "buffer" para este
computador (200.123.123.123). A sintaxe da funo sendto() bastante
semelhante da funo send(), com exceo apenas dos dois ltimos parmetros:
send(sock: Integer; var Buf; tam_buffer: Integer; Flags: Integer, addr:
sockaddr_in; tam_addr: integer);
sock:
nome do nosso socket;
Buf:
array de caracteres ou um ponteiro para char que contm os dados que sero
enviados;
tam_buffer:
nmero de bytes que sero enviados;
flags:
valores opcionais que especificam o modo de envio.
addr:
varivel do tipo sockaddr_in que contm as informaes de endereo/porta do host
remoto;
tam_addr:
tamanho da estrutura sockaddr_in;
Assim como a funo send(), sendto() tambm retorna o nmero de bytes
enviados. Se algum erro ocorrer, a funo retorna -1.
Vejamos um exemplo de um servidor UDP:
program exemplo;
{$APPTYPE CONSOLE}
uses
SysUtils,Winsock,Windows; // ZeroMemory -> definida em Windows
var
wsa: WSADATA;
sock: integer;
addr,addr_remoto: sockaddr_in;
buffer: array [0..99] of char;
tam_addr: integer;
begin
if(WSAStartup($101,wsa) = -1) then
begin
writeln('Ocorreu um erro ao inicializar o Winsock.');
exit;
end;
sock := socket(AF_INET,SOCK_DGRAM,0);
if(sock = -1) then
begin

writeln('Ocorreu um erro ao criar o socket.');


exit;
end;
addr.sin_family := AF_INET;
addr.sin_port := htons(1234);
addr.sin_addr.S_addr := INADDR_ANY ;
if(bind(sock,addr,sizeof(addr))=-1) then
begin
writeln('Erro na funcao bind()');
exit;
end;
ZeroMemory(@buffer,100);
tam_addr := sizeof(addr);
if(recvfrom(sock,buffer,100,0,addr_remoto,tam_addr ) = -1) then
begin
writeln('Erro na funcao recvfrom()');
exit;
end;
writeln('Dados recebidos!' + #10);
writeln('IP: ' + inet_ntoa(addr_remoto.sin_addr));
writeln('Porta: ' + inttostr(ntohs(addr_remoto.sin_port)));
writeln('Dados:' + buffer);
closesocket(sock);
WSACleanup();
end.
Neste exemplo, o programa utiliza a funo bind() para que, posteriormente,
possau utilizar a porta 1234 para receber dados de um possvel cliente. Vamos ver
mais detalhadamente:
addr,addr_remoto: sockaddr_in;
buffer: array [0..99] of char;
tam_addr: integer;
Observe que o cdigo h pontos importantes em negrito. Alm da comum
declarao da varivel "addr", declare-se outra varivel, do mesmo tipo
(sockaddr_in), denominada "addr_remoto". Esta varivel ser utilizada como
parmetro para a funo recvfrom(), seu uso ser explicado mais adiante.
A varivel "buffer" um array de caracteres de 100 bytes e ser utilizada para
armazenar os dados recebidos atravs da funo recvfrom().
Por fim, declaramos "tam_addr" para passarmos seu valor como ltimo parmetro
da funo. O valor desta varivel deve ser o tamanho da estrutura "sock_addr" em
bytes.
if(bind(sock,addr,sizeof(addr))=-1) then
begin
writeln('Erro na funcao bind()');
exit;
end;

Nada de novo. Apenas configura-se localmente o socket de tal forma que este
utilize a porta 1234 para receber/enviados dados.
ZeroMemory(@buffer,100);
tam_addr := sizeof(sockaddr_in);
if(recvfrom(sock,buffer,100,0,addr_remoto,tam_addr ) = -1) then
begin
writeln('Erro na funcao recvfrom()');
exit;
end;
Neste trecho, preenche-se com zero todo o buffer, ou podemos simplesmente dizer
que limpa-se o "buffer" para que ele possa armazenar ocasionais dados. Na
prxima linha, estamos atribuindo varivel "tam_addr" o tamanho, em bytes, da
estrutura "sockaddr_in". E, desmembrando um pouco mais o cdigo, temos:
if(recvfrom(sock,buffer,100,0,addr_remoto,tam_addr ) = -1) then
begin
writeln('Erro na funcao recvfrom()');
exit;
end;
no trecho acima que, de fato, aguardamos por dados vindos de algum cliente.
Vejamos a sintaxe:
recvfrom(sock: Integer; var Buf; tam_buffer: Integer; flags: Integer,
addr:sockaddr_in, var tam);
sock:
nome do nosso socket;
Buf:
buffer que ir armazenar os dados recebidos;
tam_buffer:
tamanho do buffer;
flags:
valores opcionais que especificam o modo de recebimento.
addr:
varivel do tipo sockaddr_in que contm as informaes de endereo/porta do host
remoto;
tam_addr:
varivel que armazena o tamanho da estrutura sockaddr_in;
A funo, assim como recv(), retornar o nmero de bytes recebidos, exceto
quando algum erro ocorrer, onde o valor -1 retornado.
Para finalizar:
writeln('Dados recebidos!' + #10);
writeln('IP: ' + inet_ntoa(addr_remoto.sin_addr));
writeln('Porta: ' + inttostr(ntohs(addr_remoto.sin_port)));
writeln('Dados:' + buffer);

Nesta parte, apenas so mostrados informaes sobre um cliente remoto que


enviou determinados dados: ip, porta e os dados em si.
A funo inet_ntoa() voc j conhece: responsvel por transformar um IP
expresso em network byte para string.
Uma funo que ainda no foi abordada ntohs(). Esta faz o trabalho inverso ao da
funo htons(). Enquanto esta ltima transforma um valor denominado host byte
order para network byte order, a funo ntohs() obtm um valor em network byte e
o transforma em host byte order. Network byte order o tipo de valor utilizado para
comunicao, sobretudo, nas estruturas de sockets, no qual o byte mais
significativo (tambm chamado octeto) o primeiro. Em host byte order, o byte
menos significativo o primeiro. Exemplo:
Valor em network byte order: 5376
Valor em host byte order: 21
Vale lembrar que as funes htons() e ntohs() retornam valores numricos (de 2
bytes). Por esta razo, utilizamos IntToStr() - para conveter de inteiro para string na seguinte linha:
writeln('Porta: ' + inttostr(ntohs(addr_remoto.sin_port)));
Bem, antes de encerrar esta parte do artigo, irei disponibilizar uma tabela contendo
os tipos mais comuns de erros gerados pelo winsock, sobretudo, para serem usados
em conjunto com as funes WSAGetLastError() e WSASetLastError():
Cdigo Constante Significado
WSA_INVALID_HANDLE 6 Identificador invlido.
WSA_NOT_ENOUGH_MEMORY 8 Memria insuficiente.
WSA_NOT_ENOUGH_MEMORY 8 Memria insuficiente.
WSA_INVALID_PARAMETER 87 Algum parmetro invlido foi passado a uma funo.
WSAEACCES 10013 Permisso negada.
WSAEFAULT 10014 Endereo invlido.
WSAENOTSOCK 10038 Uma funo foi utilizada com um socket invlido.
WSAEPROTOTYPE 10041 O tipo de socket no suporta o uso de uma determinada
funo.
WSAENOPROTOOPT 10042 Protocolo invlido especificado.
WSAESOCKTNOSUPPORTM 10044 Tipo de socket no suportado.
WSAEPFNOSUPPORT 10046 Famlia de protocolo no suportada.
WSAEADDRINUSE 10048 Endereo j em uso.
WSAEADDRNOTAVAIL 10049 Erro ao atribuir endereo requisitado.
WSAECONNRESET 10054 Conexo resetada pelo host remoto.
WSAEISCONN 10056 Socket j conectado.
WSAENOTCONN 10057 Socket no conectado.
WSAECONNREFUSED 10061 Conexo recusada.
WSANOTINITIALISED 10093 O winsock no foi inicializado.
A tabela acima est bem simplificada.

Das könnte Ihnen auch gefallen