domingo, 20 de julho de 2008

VirtualBox image of Slackware 12.1

Put more of your developer skills together.
VirtualBox image of Slackware 12.1 (Torrent file) Developer Edition
Slackware Linux 12.1 with several development tools.
Ready for Java EE, Mobile (J2ME and Android), Web development and Modeling.
Includes Netbeans 6.1, Eclipse, Judy Community, JOGL (Opengl programming with Java) and more.
Check it out here.

sábado, 19 de julho de 2008

VirtualBox image of Ubuntu 8.04

Put your developer skills together.

VirtualBox image of Ubuntu 8.04
Ubuntu 8.04 with several development tools.
Ready for C/C++, Java, Ruby, Python, Kernel and Mobile development.
Contains Eclipse, Netbeans, ViM and several other libraries and tools.

You can get it here.
user name : developer
password: developer

quinta-feira, 28 de fevereiro de 2008

POSIX Sockets e Windows Sockets (Parte III)

Para fechar esta seqüência de artigos, vamos ver como trabalhar com sockets assíncronos, usando a winsock, agora em uma aplicação sem janelas.
Lembrando que podemos utilizar estas técnicas para trabalhar com qualquer tipo de comunicação via socket no Windows, tanto para aplicações do tipo cliente como servidor, sockets com e sem conexão (UDP, TCP, etc). Os passos que seguimos para criar estes recursos são:
  1. Criar um tratador para o socket;
  2. Para um servidor, fazer bind, entrar em listen e aceitar conexões;
  3. Para um cliente, conectar;
  4. Enviar e receber dados;
  5. Trabalhar de forma assíncrona e definir timeouts;
  6. Tratar múltiplos clientes em um servidor;
No artigo anterior, observamos que a winsock pode ser bastante compatível com as implementações de sockets POSIX. Porém desde o processamento assíncrono através de mensagens, esta compatibilidade deixa de existir, e o que veremos a seguir é ainda mais particular da winsock.

Para realizar comunicação assíncrona, precisávamos ter antes um tratador de janela no qual a aplicação receberia mensagens pertinentes a operações com sockets. Mas o que fazer quando não temos aplicações com janelas? - tá, Microsoft, Windows, janelas... para que alguém iria querer fazer uma aplicação sem janelas? por vários e bons motivos, podem acreditar.
A solução é utilizar a função WSAEventSelect, que é similar a WSAAsyncSelect, porém não utiliza o tratador de mensagens para processar as operações da Winsock, mas sim um objeto de evento Win32.

Eventos de rede podem ser passados a objetos em uma aplicação, e para isto basta criar um objeto de evento com a função WSACreateEvent, e configurar quais eventos desejamos receber na aplicação com a função WSAEventSelect.
Vamos ver então como utilizar esta função, que tem como assinatura:
int WSAEventSelect(
__in SOCKET s,
__in WSAEVENT hEventObject,
__in long lNetworkEvents
);
o parâmetro s diz respeito ao socket criado (usando o processo e funções já citadas anteriormente, tanto para criar uma aplicação do tipo servidor ou do tipo cliente); hEventObject é o tratador de evento criado pela função WSACreateEvent; e lNetworkEvents é a máscara de eventos que deseja-se receber naquele objeto (similar a máscara usada em WSAAsyncSelect, descrita antes aqui).

Então a coisa funciona assim: crie um tratador de socket, para o fim que quiseres. Depois, crie um objeto de evento com WSACreateEvent. Com este objeto, passe os eventos do socket criado que deseja processar. Mas e daí, como processar os eventos recebidos?
Para isto, um laço de tratamento de eventos precisa ser criado, e dentro dele aguardar que o objeto seja sinalizado pela Winsock, e depois identificar o evento recebido. Para aguardar os eventos, utlizamos a função WSAWaitForMultipleEvents, e para identificar o evento utilizamos a função WSAEnumNetworkEvents.
O pseudo código seria algo assim:

criar socket e prepará-lo como desejado;
criar evento com WSACreateEvent;
...
while(TRUE)
{
WSAWaitForMultipleEvents()
WSAEnumNetworkEvents()
...
processar socket conforme evento recebido
...
}

Vamos ver então como usar estas duas funções.
DWORD WSAWaitForMultipleEvents(
__in DWORD cEvents,
__in const WSAEVENT* lphEvents,
__in BOOL fWaitAll,
__in DWORD dwTimeout,
__in BOOL fAlertable
);
Usar WSAWaitForMultipleEvents é bastante direto, sendo os dois principais parâmetros o número de objetos de eventos que estará sendo trabalhado, cEvents e a lista de objetos de eventos, lphEvents. O parâmetro fWaitAll determina se deseja-se retornar da função quanto todos os objetos forem sinalizados ou qualquer um deles; dwTimeout é o tempo (em milisegundos) a ser aguardado pelo evento, e fAlertable determina se a thread corrente deve ser colocada em estado de wait (normalmente utilizado com completion routines, que vamos falar mais adiante).

A função WSAEnumNetworkEvents é bastante simples, e possui apenas três parâmetros:
int WSAEnumNetworkEvents(
__in SOCKET s,
__in WSAEVENT hEventObject,
__out LPWSANETWORKEVENTS lpNetworkEvents
);

onde o parâmetro s é o socket que recebeu o evento, hEventObject é o objeto de evento que deseja-se reinicializar, e lpNetworkEvents é um ponteiro para a estrutura WSANETWORKEVENTS, que possui a máscara FD* de eventos recebidos e uma lista de erros ocorridos. Apesar do parâmetro hEventObject ser opcional, eu recomendo ser informado na própria função, pelo seguinte fato: uma vez recebido um evento, é necessário reinicializar (reset) o objeto, isto poderia ser feito utilizando WSAResetEvent, porém, passando o objeto para WSAEnumNetworkEvents, a própria função encarrega-se disto, com a vantagem de fazer a operação atomicamente.

Importante observar que a chamada a WSAEventSelect deixa o socket automaticamente como não bloqueante, e as operações de envio/recebimento de dados (ou ainda connect e accept) retornam imediatamente, e WSAGetLastError pode retornar WSAEWOULDBLOCK, significando que não foi possível completar a operação imediatamente, o que parece sensato já que o socket é não bloqueante.
Também vale lembrar que as funções de reativação para WSAEventSelect funcionam da mesma forma que para WSAAsyncSelect.

Então, se projetarmos uma aplicação, seja ela cliente ou servidor onde cada socket possui seu próprio laço de tratamento de eventos em uma thread, conseguimos então que esta aplicação seja capaz de processar comunicação em diversas vias, assincronamente. Vale observar que, no caso de uma aplicação servidor, com sockets conectados, um socket criado através de accept irá receber os eventos no mesmo objeto associado ao socket que estava em listen. Para que seja alterado o objeto de evento para este novo socket, deve-se então chamar novamente WSAEventSelect para ele.
Uma aplicação bem construída com estes recursos pode ter escalabilidade suficiente para o tamanho desejado, construindo um código relativamente simples.

Uma forma interessante de saber se uma operação de envio ou recebimento passou um certo tempo desejado (i.e, saber se deu timeout), pode ser concebida através de eventos de timers, usando a função SetTimer, mas para isto, algum identificador de janelas válido deve existir. Outra forma seria controlar manualmente mesmo, medindo o tempo passado desde a solicitação, e verificando se o laço de processamento de eventos recebe algum evento (como enviar ou receber) ainda dentro de um tempo desejado.

Escalabilidade do jeito Microsoft de ser

Outro recurso interessante disponível na winsock é poder trabalhar com o que é chamado overlapped I/O com sockets, e completion routines. A vantagem de utilizar estes recursos é deixar o Windows se preocupar com as tarefas de enviar e receber dados assincronamente, tirando a responsabilidade da aplicação.
Não entrarei em detalhes sobre a implementação destes recursos, uma vez que pode ser bastante complexo para iniciantes. Talvez escreva um outro artigo sobre isto.
Mas para quem quiser tentar, ai vai algumas dicas:
  • usar o flag WSA_FLAG_OVERLAPPED na criação do socket (último parâmetro da função WSASocket);
  • Criar um completion port com CreateIoCompletionPort, ou um objeto de evento que deverá ser sinalizado quando a operação terminar;
  • usar as funções WSASend e WSAReceive para comunicar. Observar os parâmetros lpOverlapped e lpCompletionRoutine, que devem ser informados para que o processamento assíncrono possa ser tratado corretamente pela aplicação;
Bem, aqui encerro os artigos sobre sockets POSIX/BSD e Winsock. Cada um com sua parcela de facilidade e complexidades. Qual melhor abordagem a usar? Depende muito da sua necessidade, e da aplicação que se vai construir.
É importante ter em mente que nem sempre a abordagem mais complexa gera os melhores resultados.

Até a próxima.

Referências
- Windows Sockets 2: http://msdn2.microsoft.com/en-us/library/ms740673(VS.85).aspx
- Write Scalable Winsock Apps Using Completion Ports: http://msdn2.microsoft.com/en-us/magazine/cc302334.aspx

quinta-feira, 7 de fevereiro de 2008

POSIX Sockets e Windows Sockets (Parte II)

Seguindo nossa aventura no mundo do sockets, vamos entrar agora no mundo da Winsock.
Nossas intenções continuam as mesmas, realizar um conjunto básico de operações com sockets, que permita criar funcionalidades comuns. Para relembrar, listamos:
  1. Criar um tratador para o socket;
  2. Para um servidor, fazer bind, entrar em listen e aceitar conexões;
  3. Para um cliente, conectar;
  4. Enviar e receber dados;
  5. Trabalhar de forma assíncrona e definir timeouts;
  6. Tratar múltiplos clientes em um servidor;
Bem, em muitos aspectos a Winsock é bastante compatível com POSIX. Eu diria que as operações mais básicas, como criar socket, fazer bind, send/recv são 100% compatíveis, com as mesmas estruturas de dados e assinaturas de funções.
Os items de 1 até 4 na nossa lista podem ser implementados na winsock da mesma forma que visto no post anterior, com POSIX. Então não vamos entrar em detalhes nestes items, mas vamos estudar algumas peculiaridades.

Primeiramente, a winsock precisa ser inicializada. Talvez seja herança do Windows noventa e alguma coisa, ou para podermos trabalhar com diversos comportamentos e versões, mas é algo que precisa ser feito.
A função WSAStartup faz o trabalho, recebendo 2 parâmetros que indicam a versão que se quer trabalhar, e retornando a versão que foi inicializada. A versão atual é chamada de Winsock2, introduzida com o Windows 95 se não me engano. E tem varias minor version desde então.

Algo que não devemos ignorar ao estudar a Winsock é são as funções que tem o prefixo WSA. Elas são "melhorias" da versão da função compativel com POSIX (ao menos penso que esta foi a visão dos engenheiros que criaram ela). Estas funções WSA* permitem um nivel mais refinado de controle e uma maior quantidade de recursos, como veremos adiante.

Bem, uma vez inicializada a winsock, podemos fazer as operações da nossa lista praticamente da mesma forma que com sockets POSIX. Mas partindo do item 4 algumas coisas começam a ficar interessantemente diferentes. A função fcntl não existe na API documentada do windows, então definir um socket como não síncrono é feito utilizando IOCTL, que no mundo winsock é feito com a função ioctlsocket, usando a constante FIONBIO como parâmetro. A lista dos códigos IOCTL documentados pode ser encontrada aqui.
Assim temos um socket assíncrono, compatível com POSIX sockets, e que combinado com threads permitem codificar tranquilamente os itens 5 e 6 da mesma forma que POSIX sockets. Ao menos em teoria :)
Ah, e uma função importante é a WSAGetLastError, que como o nome diz, retorna o erro da ultima função de socket chamada. Também é possível retornar o erro por referência (para quando é necessário ser thread-safe) usando as funções com prefixo WSP*.

Mas, lembrando das funções WSA*, temos como fazer processamento assíncrono de várias formas, e que fornece bastante emoção.
A primeira delas, é utilizando a pilha de mensagens do Windows para interpretar e processar as operações com sockets, usando a função WSAAsyncSelect. Quando chamada, a função coloca os socket como não bloqueante, e programa a aplicação para receber notificações de eventos de rede na pilha de mensagens de algum identificador de janela do programa. Isto vale para qualquer operação de socket, seja em uma aplicação cliente, seja uma aplicação servidor.
Funciona da seguinte forma: da mesma maneira que a função select retorna a disponibilidade do socket para trafegar dados, conectar ou aceitar conexões, uma mensagem é recebida pelo programa quando estas operações estiverem disponíveis. Por exemplo, o programa receberá uma mensagem com um código específico para leitura de dados, quando o socket estiver disponível para receber dados.

Uma breve introdução sobre janelas e tratamento de mensagens.

Uma aplicação Windows com janelas, precisa ter obrigatoriamente um laço de processamento de mensagens, o laço principal do programa. Este laço tem 4 parâmetros para trabalhar: o identificador da janela, a mensagem recebida e dois parâmetros inteiros de 32 bits com informações particulares de cada mensagem, conhecidos como wParam e lParam.
Geralmente uma função que processa estes parâmetros (chamada de WindowProc) é associada no momento da criação da janela, e disparada automaticamente quando a janela recebe alguma mensagem.
Este é um aspecto bastante particular e importante na programação para Windows, e muitas ferramentas de desenvolvimento RAD já montam esta estrutura no código, facilitando a vida do programador.

Analisando WSAAsyncSelect

Para trabalhar com sockets assíncronos através da fila de mensagens, nada muda nos passos iniciais de criação e configuração do socket. Ou seja, até o momento da chamada de WSAAsyncSelect, basta criar o socket, configurar porta/endereço, conect/bind/listen, da mesma forma que se faria para trabalhar com processamento assíncrono.

A função WSAAsyncSelect aceita parâmetros que permite ao programador informar como sua função de processamento de mensagens deve se comportar ao receber eventos de socket. Vamos ver sua assinatura (em C, direto da MSDN) :
int WSAAsyncSelect(
__in SOCKET s,
__in HWND hWnd,
__in unsigned int wMsg,
__in long lEvent
);
  • SOCKET s é o identificador do socket que está sendo trabalhado;
  • HWND hWnd é o identificador da janela que processará as mensagens de rede;
  • unsigned int wMsg é o código númerico da mensagem (mensagem passada a janela)
  • long lEvent é a combinação de flags que define quais mensagens a janela em questão estará processando.
O parâmetro hWnd deve ser o identificador de uma janela já com uma função WindowProc associada. Já wMsg deve ser um número que identifique uma mensagem que não esteja sendo usada. A API do Windows utiliza constantes base para mensagens de usuário, que podem ser valores acima de WM_USER ou acima de WM_APP.

Os flags (parâmetro lEvent) que determinam os tipos de evento de rede que serão processados para o par socket/janela, podem ser FD_READ, FD_WRITE, FD_ACCEPT, FD_CONNECT, FD_CLOSE e mais alguns outros que não são interessantes para nosso propósito no momento.
Estes flags podem ser usado individualmente ou compostos através de OR. Isto quer dizer que para que o programa receba uma mensagem dizendo que existe algo disponível para ser lido, lEvent deve conter FD_READ, para saber quando pode ler e/ou gravar, FD_READ|FD_WRITE, e assim por diante. Chamadas consecutivas de WSAAsyncSelect anulam as últimas chamadas. Por exemplo, realizar
WSAAsyncSelect(..., FD_READ); WSAAsyncSelect(..., FD_WRITE);
a última chamada anulará a primeira, fazendo com que o programa receba apenas eventos correspondentes a FD_WRITE.
Ao processar a mensagem de rede, a função WindowProc recebe em wParam o socket que recebeu o evento, e em lParam o código de erro (caso ocorrido) e o código do evento recebido (flag). O código de erro pode ser extraído com a macro WSAGETSELECTERROR, e o flag com a macro WSAGETSELECTEVENT. Vamos analisar cada um destes flags:
  • FD_READ, recebido quando o socket está pronto para receber dados, e pode chamar funções como recv/recvfrom;
  • FD_WRITE, recebido quando o socket está disponível para enviar dados, com send/sendto;
  • FD_ACCEPT, recebido quando um socket em listen está disponível para aceitar uma conexão;
  • FD_CONNECT, recebido quando uma chamada a connect foi realizada, e o socket está conectado (aplicável a sockets com conexão);
  • FD_CLOSE, recebido quando o socket foi encerrado
O Windows toma cuidado para não sobrecarregar a aplicação com múltiplos eventos de um mesmo tempo. Isto quer dizer que quando é recebido um evento de disponibilidade de leitura (FD_READ), eventos de leitura de dados subsequentes não serão passados a aplicação, até que esta execute alguma função de reativação. Cada tipo de mensagem recebida possue um conjunto de funções de ativação, onde usa-se para
  • FD_READ - recv, recvfrom, WSArecv, WSArecvfrom
  • FD_WRITE - send, sendto, WSAsend, WSASendto
  • FD_ACCEPT - accept ou WSAAccept
  • FD_CLOSE e FD_CONNECT não necessitam de reativação.
Um tratador de socket resultante de um accept, tem as mesmas características do socket que estava em listen e aceitou a conexão. Ou seja, os eventos para aquele socket serão tratados da mesma forma (com os mesmos flags) que o socket que recebeu a conexão.
Vale observar que, para cada mensagem recebida com FD_READ, apenas um recv (ou similar é necessário), mesmo que ainda existam dados no buffer de leitura. Caso queria chamar multiplas vezes um recv, é recomendado desabilitar FD_READ para o socket, pois se ocorrer o acaso de uma mensagem do tipo FD_READ ser recebida pelo programa e um recv estiver sendo executado, este deve falhar com WSAEWOULDBLOCK.

Pra finalizar, a organização do código poderia ficar assim:

HANDLE wnd;
SOCKET s;

#define NETWORK_MSG WM_USER+100
...
/* criar um socket que servira como cliente de conexao */
...

/* configura para receber mensagens quando disponibilizar gravacao, leitura, conexao e fechamento */
WSAAsyncSelect(s, wnd, NETWORK_MSG, FD_READ|FD_WRITE|FD_CONNECT|FD_CLOSE);
...

/* aqui o tratador de mensagens da aplicacao */
LRESULT CALLBACK AppWindowProc(HWND hwnd, unsigned int msg, WPARAM wparam, LPARAM lparam)
{

/* aqui, hwnd é igual a wnd definido anteriormente */
switch(msg)
{
/* provavelmente varias mensagens obrigatorias do programa estarao aqui */
case WM_...:
...
/* agora processamos os eventos do socket */
case FD_READ:
recv(...);
...
case FD_WRITE:
send(...);
...
case FD_ACCEPT:
SOCKET sock = accept(...);
...

}
return DefWindowProc(
}


E a referência clássica na internet:

- Windows Sockets 2: http://msdn2.microsoft.com/en-us/library/ms740673(VS.85).aspx

domingo, 3 de fevereiro de 2008

POSIX Sockets e Windows Sockets (Parte I)

Certa vez, vi uma entrevista (creio que foi no channel 9 da Microsoft) sobre funcionamento do Kernel do Windows Vista, e o engenheiro da Microsoft entrevistado falou sobre modelos de programar com sockets, no estilo Winsock e estipo POSIX.
Segundo o engenheiro, programar a Winsock no estilo Posix seria subutilizar a API, e que os programadores apenas precisam aprenas reaprender a programar no estilo MS.

Bem, tendo trabalhado com sockets utilizando a Winsock e implementações de sockets Posix em diversos Sistemas operacionais, decidi escrever três posts sobre o assunto, para auxiliar aqueles que, como eu, quebram a cabeça com o assunto. Os exemplos citados são sobre o protocolo IP, naturalmente por ser o mais consultado pelos leitores.

Primeiro vamos enumerar algumas tarefas comuns e necessárias ao programar com sockets.
  1. Criar um tratador para o socket;
  2. Para um servidor, fazer bind, entrar em listen e aceitar conexões;
  3. Para um cliente, conectar;
  4. Enviar e receber dados;
  5. Trabalhar de forma assíncrona e definir timeouts;
  6. Tratar múltiplos clientes em um servidor;
Este me parece um conjunto básico bem arranjado, mas se alguém quiser sugerir alterações, fique a vontade em comentar. Neste primeiro post então vamos descrever como fazer as coisas nesta lista utilizando sockets Posix, e como algumas implementações em alguns SOs podem variar, vou tentar ser o mais "compatível" possível.

1. Criar o socket
Bem, criar o socket é bastante simples e direto. A função Posix a ser utilizada é chamada socket. Geralmente, possui 3 parâmetros como ilustra a assinatura (em C) abaixo.

socket_handle = socket(int socket_family, int socket_type, int socket_protocol)

Não vamos entrar em detalhes sobre os parâmetros, mas é aqui que definimos se socket é do tipo com ou sem conexão, TCP/UDP/RAW, IPv4, IPv6, etc. O importante é o tratador socket_handle resultante, que é utilizado para qualquer outra operação sobre este socket.

2. Colocando um servidor para receber conexões
Para fazer um socket de um servidor (ou serviço de socket) aceitar conexões, são necessários 3 passos: fazer a vinculação do socket a uma porta e interface de rede, colocar para "ouvir" e aceitar conexões. São tarefas bastante simples, na maioria dos casos.
Vincular o socket a uma porta e interface de rede é possível utilizando a função bind. Os parâmetros da função recebem o tratador do socket (criado no passo 1) e uma estrutura cujos campos informam à qual porta o socket estará vinculado, i.e, em qual porta o servidor ficará ouvindo. O importante para uma vinculação controlada, é acertar corretamente os parâmetros da estrutura sockaddr passada como parâmetro. O campo sin_port define a porta do socket, e é utilizado na notação de rede (network byte order) então precisa ser convertido utilizando alguma função como por exemplo, htons.
Isto quer dizer que, se deseja-se colocar o socket para receber conexões na porta 6600, não é possível atribuir diretamente sockaddr.sin_port=6600, o correto seria sockaddr.sin_port=htons(6600).

Outro campo importante é sin_addr. Neste campo define-se em qual interface o socket será vinculado. Isto é aplicável quando se tem diversas placas de rede e quer colocar o programa ouvindo em apenas uma, ou apenas na inteface de loopback (127.0.0.1). Algumas constantes podem ser utilizadas, como por exemplo, INET_ADDR_ANY para ouvir em todas as intefaces, e INET_ADDR_LOOPBACK para ouvir na interface de loopback. Para definir uma interface em específico, este campo recebe o endereço IP da interface também na notação de rede. Por exemplo, se deseja-se vincular o socket a interface 10.10.1.5, converte-se o endereço para a notação de rede, e atribui-se em sin_addr, que também é uma estrutura, e possui geralmente apenas 1 campo in_addr_t. O exemplo então ficaria:

sockaddr.sin_addr.in_addr_t=inet_addr("10.10.1.5")

Configurado tudo, chama-se a função bind com os parâmetros desejados. Em caso de sucesso (pode falhar como, por exemplo, se a porta estiver sendo já utilizada) chamamos a função listen para colocar o socket para ouvir.
A chamada a listen tem 2 parâmetros, sendo um o socket e o outro o chamado backlog. Este último é importante pois determina o tamanho da fila que existirá para as conexões pendentes. Este parâmetro deve ser cuidadosamente ajustado quando deseja-se criar serviços de alta disponibilidade e velocidade de resposta.

Aceitar conexões é pertinente apenas para sockets conectados (do tipo stream por exemplo e aqui se encaixa sockets TCP) e é feita com a função accept. Neste ponto o servidor irá párar de executar, até que algum cliente conecte, isto é, a função só retorna quando alguém conectar na porta definida. O resultado da função é um tratador para o socket cliente, por onde pode-se fazer todo o futuro envio/recebimento de dados. Informações extras são retornadas na estrutra sockaddr, passada como parâmetro para a função.


3. Conectando um cliente em um servidor
Esta parte é bastante simples na maioria das vezes, e é pertinente apenas para sockets conectados. Esta etapa é feita utilizando a função connect. Com o sucesso da função, informações sobre a conexão realizada retornam no parâmetro sockaddr passado à função. Uma vez que conectado, o tratador do socket cliente é válido para realizar envio e recebimento de dados.

4. Enviando e recebendo dados
Enviar dados em sockets conectados é normalmente feito com a função send, e para sockets não conectados utiliza-se a função sendto. Ambas recebem parâmetros que informam o socket, os dados e a quantidade de dados (tamanho) que será enviado.
Para receber dados, utiliza-se normalmente a função recv, para sockets conectados, e recvfrom para sockets não conectados. Estas também recebem parâmetros com informações dos dados a serem recebidos.
Neste ponto é muito importante verificar os buffers utilizados e seus tamanhos. É um ponto comum para falhas de segurança ao lidar-se com sockets.

5. Trabalhar de forma assíncrona
Imagine que o programa que se deseja fazer necessita ao mesmo tempo processar os dados na conexão via socket (enviar e receber) e ainda receber entradas do usuário em uma janela. Uma forma de fazer isto é tratando as operações de socket de forma assíncrona.
POSIX sockets assíncronos são muito utilizados, e apesar de não ser obrigatório para fazer uma aplicação funcionar, é obrigatório para que as aplicações tenham boa usabilidade e tolerância. Talvez a maneira mais simples de codificar isto seria utilizando threads, e fazendo todas operações com os sockets dentro de threads.
Algo bem simples seria, colocar todo o processo de criação do socket, conexão (entrar em listen ou conectar em um servidor), envio e recebimento dentro da thread.
Isto funciona suficientemente bem para uma aplicação simples. Mas não tem-se muito controle
Por exemplo, é preciso determinar tempo máximo para uma conexão tentar encontrar um servidor, ou tempo máximo de envio de dados, ou até mesmo quantas conexões por segundo um servidor vai querer tratar. Para isto, é interessante utilizar uma combinação de processamento assíncrono com threads e sockets não bloqueantes.

No universo POSIX funciona assim: utiliza-se a função fcntl para colocar um socket como não bloqueante, passando e ativando o flag O_NONBLOCK. Este flag deve ser ativado antes de qualquer operação que pode bloquear e afeta o funcionamento das funções de accept, connect, recv/recvfrom e send/sendto. Estas funções retornam de suas chamadas imediatamente e permitem que o código continue rodando normalmente.
Assim pode-se ter mais controle sobre o fluxo de execução do código, permitindo sair do tratamento do socket, retornando em caso de erros ou em caso de mal funcionamento de alguma coisa. Por exemplo, imagine que estamos codificando a conexão de um cliente a um servidor que está super utilizado, e demorando para atender as solicitações. A chamada connect, por padrão bloqueante, não retornará até que a conexão seja estabelecida, ou algum erro ocorra (algumas implementações definem um tempo limite de algumas horas como padrão, o que é bastante tempo).
Deixar o usuário esperando por horas para dizer algo ao usuario, por que estamos esperando a resposta do connect, não parece algo muito prático.
Assim, com o socket não bloqueante, podemos definir o tempo que queremos pois a função vai retornar imediatamente. Mas como saber se teve resultado?
Bem, para qualquer operação com sockets POSIX é sempre interessante verificar a variavel errno, que no caso de sockets não bloqueantes retornam EAGAIN ou EWOLDBLOCK ao serem chamadas.
Mas, para saber com mais precisão quando um socket está disponível para gravação ou leitura, utiliza-se as funções select e/ou poll. Ambas possuem parâmetros para definir um timeout da operação, e para dizer se o socket está disponível para ser lido, gravado, ou com erro.
Combinando isto tudo, com threads, temos um processamento assíncrono, não-bloqueante com sockets, sejam conectados ou não. Alguns detalhes devem ser observados, no que diz respeito a pequenas diferenças de comportamento de sockets não-bloquantes, conectados ou não (especialmente TCP e UDP). Também os sinais passados ao programa (POSIX signals). Isto é bem documentado em qualquer implementação de sockets POSIX, e de fácil identificação.

Um exemplo do fluxo de código para um cliente IP não-bloqueante que deseja apenas enviar um fluxo de dados, poderia ser:
  • Criar o socket cliente e configurar o servidor de conexão;
  • Definir o socket como não bloqueante (com fcntl)
  • Fazer a chamada a connect();
  • Fazer uma chamada a select com um tempo limite de 10s para verificar se o socket está disponível para gravação;
  • Caso estiver, enviar os dados;
  • Fechar o socket;
Nunca esqueça de fechar o socket com a chamad close (em algumas implementações, closesocket) para libera o tratador utilizado pelo programa.

6. Tratar múltiplos clientes em um servidor
Um servidor de sockets mais elaborado deve ser capaz de tratar múltiplos clientes ao mesmo tempo.
Para isto, basta combinar sockets não-bloqueantes com threads no momento certo. E com alguns passos básicos é possível fazer algo funcional:
  • Crie um socket e configure porta e interface de rede que deseja-se abrir as conexões;
  • Configure o socket para não bloqueante;
  • Coloque dentro de um laço uma chamada para accept;
  • Para cada resultado de accept com sucesso, crie uma outra thread, passando o tratador recebido no resultado do accept como parâmetro;
  • Esta nova thread se encarregará do envio/recebimento e processamento dos dados;
  • Uma vez criada a thread, o laço faz voltar ao accept, e estamos prontos para processar outra conexão, enquanto a anterior está sendo tratada dentro da thread;
Vale lembrar que nem sempre o socket resultante de um accept em um socket não-bloqueante também é não-bloqueante. Contudo, pode-se definí-lo como não bloqueante após o accept, utilizando novament fcntl.

Esta primeira parte tratou então das diversas opções em trabalhar com sockets POSIX. De forma bem simples, tentamos definir os passos básicos para o uso geral de sockets, mas sem entrar em detalhes da API, que podem ter sua documentação encontrada facilmente.
No próximo post estaremos mostrando como realizar as mesmas tarefas, porém utilizando a Winsock 2.
Até lá.

Referências
- Linux manpages, incluindo funções POSIX sockets: http://linux.die.net/man/
- GNU Sockets: http://www.gnu.org/software/libc/manual/html_node/Sockets.html
-