VB Tip: Conceitos de socket, conexão entre dois aplicativos remotamente.
Autor: Diego Silva | Publicado: 2011-12-05T12:29:00-02:00

Olá.
Devido à uma dúvida sobre o assunto na comunidade do orkut Visual Basic Brasil, irei postar hoje sobre soquetes e como se dá a conexão entre dois aplicativos (pois não só ocorre conexão entre dois aplicativos de computadores diferentes como também pode ocorrer a conexão entre dois aplicativos de um mesmo computador, como em muitos casos). 

É notável que existe pouca informação prática na internet a respeito de como utilizar soquetes de conexão no Visual Basic, e isso com certeza deixa na dúvida aqueles que querem criar desde um simples programa de bate-papo até aos que querem criar seus próprios sistemas de VNC. Este artigo irá ajudar os dois casos (claro, neste artigo irei tratar apenas do envio e recepção de informações entre soquetes; um sistema de VNC muito provavelmente eu farei, em outro artigo). 

Índice: 

Conceito
Protocolos: TCP e UDP. Qual usar?
Conexão entre dois computadores ou dois aplicativos
Colocando em prática: projeto


Conceito
Primeiramente, vamos saber como importar o controle de soquetes para um projeto do Visual Basic. Com o projeto aberto (não pode ser DLL, somente Standard Exe ou ActiveX EXE), pressione Ctrl+T, ou clique com o botão direito na barra de ferramentas do VB6 (aonde estão a textbox, listbox, entre outros) e selecione Components...
Abrirá a seguinte janela: 



Feito isso, procure por Microsoft Winsock Control:


Caso não estiver com aquele V do lado esquerdo ao Winsock (em outras palavras, caso o Winsock não estiver marcado), marque-o. Feito isso, clique em Aplicar e Ok.

Repare na barra de ferramentas do VB que surgiu um novo controle, de dois monitores, um verde e outro azul, cada um em cima de uma gabinete, e conectados com um fio vermelho:


Com isso, coloque uma instância do novo controle no form (clicando duas vezes no controle). Nomeie-o para "ws". 

Assim, o controle fora totalmente importado para o projeto e estará disponível para uso. Abaixo veremos conceitos de protocolos. 


Protocolos: TCP e UDP. Qual usar?
Com a importação do novo controle, reparamos a propriedade protocol e seus dois valores: TCP e UDP. Nesta seção aprenderemos brevemente sobre cada um e qual é o mais apropriado para o projeto. Para quem não gosta de textos longos e descritivos ou quem gosta de textos práticos, pule para o quarto parágrafo seguinte a esse. 

TCP e UDP são os protocolos mais usados na internet para conexão com outro computador. O TCP (Transmission Control Protocol)
é um protocolo sob o qual se assenta o núcleo da internet (Wikipédia: http://pt.wikipedia.org/wiki/Transmission_Control_Protocol). Nele se há a certeza da integridade dos dados, ou seja, sabe-se que os dados chegarão inteiros ao outro lado, às vezes cortados, mas sempre inteiros. Serve para transmissões HTTP (HyperText Transfer Protocol), FTP (File Transfer Protocol) e vários outros protocolos de comunicação. Uma desvantagem do TCP é a latência, uma vez que primeiro há de se ter uma confirmação da integridade dos dados antes de prosseguir, o que torna o fluxo de dados lento. 

Já o UDP (User Datagram Protocol) é um protocolo largamente utilizado em compartilhadores P2P famosos como o Ares, eMule e outros do gênero (também usam TCP). Com ele não se há a certeza de que os dados irão chegar do outro lado. É usado para casos em que se admite uma perda de dados. A vantagem do UDP é a simplicidade do cabeçalho de comunicação (Porta de origem, porta de destino, mensagem e checksum) e a rapidez com qual os dados são transmitidos (pois não necessita de Acknowledge, ou seja, não precisa de confirmação da chegada e integridade dos dados), entretanto, a desvantagem é que a transmissão de arquivos sem um protocolo de comunicação específico torna-se perigoso, uma vez que um programa, por exemplo, irá chegar corrompido, devido a não-integridade dos dados. 

A maioria das (se não, todas as) placas de rede têm em seu firmware por padrão o protocolo TCP, o que torna possível o processamento dele em modo de hardware, assim sendo mais rápido que o processamento em software. Além do mais, é o protocolo mais confiável para a camada de internet, com qual temos de ter a certeza da integridade dos dados enviados ou recebidos.

No nosso caso do Visual Basic, recomendo usar o protocolo TCP, que já vem por padrão selecionado no Winsock.  Alguns comandos entre TCP e UDP do Winsock são diferentes. Exemplo: o comando Listen do TCP equivale ao Bind do UDP. Enfim, deixe o protocolo TCP selecionado no winsock. 

Na próxima seção veremos sobre o conceito de conexão entre dois computadores ou dois aplicativos de um mesmo computador. Você pode pular para o parágrafo com parte prática, em negrito.


Conexão entre dois computadores ou dois aplicativos
Para uma conexão ocorrer, é necessário abrir portas. Um soquete conectado não consegue receber outro cliente, pois já está conectado a um cliente. Podem existir várias portas locais com um mesmo endereço (exemplo, várias portas com o número 500) desde que as existentes estejam já conectadas (com exceção para o Svchost.exe do Windows, que estranhamente tem duas portas locais com mesmo valor mas ambas estão ouvindo). O porquê disso é: imagine duas instâncias diferentes do Winsock ouvindo na mesma porta. Se um cliente se conecta à essa porta, haveria um conflito: qual deles irá atender aquele cliente, uma vez que os dois estão programados para aceitar um cliente quando este se conectar e uma vez que ambos estão atuando na mesma porta? O mais rápido? 

Agora imagine uma situação diferente: uma instância de soquete está atuando em uma porta. Quando um cliente se conectar àquela porta, além dele aceitar a conexão com o cliente, ele irá criar outra instância, que atuará na mesma porta ouvindo. Pois bem, um cliente se conecta àquela porta, e o soquete irá criar outra porta, com o mesmo número, e ouvirá ali. Assim, se outro cliente fizer o mesmo processo do primeiro cliente, conseguirá se conectar, mas em outra instância do soquete do aplicativo. Esse é basicamente o sistema que um servidor web (Apache, IIS) usa: um site como o Google é amplamente usado e deve receber em média 100 conexões por segundo. Como citado na segunda frase do primeiro parágrafo, um soquete conectado não consegue receber outro cliente, assim, o aplicativo de servidor HTTP do Google (que provavelmente não é terceirizado, ou seja, provavelmente foi desenvolvido pela própria equipe do Google, mas para uso apenas da empresa), a cada conexão nova, cria uma nova instância do soquete web, atuando na mesma porta (80). Quando um cliente desconecta, aquele soquete é destruído, entretanto, sempre há de permanecer um para ouvir e atender aquela porta. Mesmo que por um segundo ninguém (e nada) esteja acessando o Google, ainda sim haverá de ter um soquete atuando na porta 80 do servidor do Google, prestes a atender uma nova conexão. 



Na próxima seção, veremos sobre os dados transmitidos pelo Winsock. 


Tipos de dados no Winsock
Os dados, sejam eles no computador ou na rede, são matrizes de bytes estruturadas.  No Winsock, podemos enviar qualquer tipo de dado, e o outro pode receber em qualquer tipo. Serei mais específico: O soquete pode enviar dados em forma de matriz de bytes, em forma de texto (string), em forma de uma variável de integer, variável de byte, variável de string, variável de string fixa (Dim Mystring As String * 5). Na recepção dos dados, pode-se receber em forma de matriz de bytes, em forma de texto (string), enfim, da mesma forma. Recomendo enviar e receber sempre em forma de matriz de bytes.

Na próxima seção, criaremos um exemplo prático utilizando winsock. Enviarei e receberei dados em forma de string, por ser uma forma de transmissão de texto puro.


Colocando em prática: projeto
Direto ao ponto, criaremos um simples programa de bate-papo de apenas um cliente só, utilizando esses conceitos. 

Você pode baixar o projeto pronto (zipado) em: http://dl.dropbox.com/u/47975575/Tuto_Sockets.zip.

1) Abra o Visual Basic 6. 
2) Inicie um projeto do tipo Standard Exe.
3) Importe o controle Winsock.
4) Crie uma instância dele no form (também ensinado na seção "conceitos").
5) Nomeie o controle winsock para "ws". 
6) Crie no form: 
 Cinco textboxes, 
Cinco labéis, 
Dois options, 
Um botão, 
Um timer. De modo que fiquem posicionados e identificados deste modo:


No código:
'Variáveis:
Dim OtherTyping As Boolean 'indica se o outro está digitando
Dim MeTyping As Boolean 'indica se eu estou digitando
Dim OtherApelido As String 'Apelido do outro

Function STS(ByVal nState As Integer) As String
'Resumo: Dependendo o código do nState, retorna um estado em texto.
'0: fechado
'1: Aberto (UDP)
'2: Ouvindo
'3: Conexão pendente
'4: Resolvendo host
'5: Host resolvido
'6: Conectando
'7: conectado
'8: Fechando (cliente desconectou)
'9: Erro.

    Select Case nState
    Case 0
    STS = "Fechado"
    Case 8
    STS = "Fechando..."
    Case 7
    STS = "Conectado"
    Case 6
    STS = "Conectando..."
    Case 3
    STS = "Conexão Pendente"
    Case 9
    STS = "Erro"
    Case 5
    STS = "Host resolvido"
    Case 2
    STS = "Ouvindo"
    Case 1
    STS = "Aberto"
    Case 4
    STS = "Resolvendo host"
    End Select
End Function

Private Sub bConnect_Click() 'função disparada no clique do botão desconectar.
'Resumo do procedimento: Se o soquete não estiver conectado, ele ordena a desconexão.
'Caso estiver fechado, ele verificará qual modo foi selecionado.
'Se o modo cliente foi selecionado, define o host remoto e a porta remota para se conectar.

'Se o usuário inseriu valores errados, disparará um erro, que será tratado
'na label erro, mostrando uma mensagem de erro.


'Se o modo servidor foi selecionado, define a porta local e ordena que o soquete ouça aquela porta.

On Error GoTo erro
    If ws.State &lt;&gt; sckClosed Then
    ws.Close
    Else
        If Option1.Value Then 'cliente
        ws.RemoteHost = tHost.Text
        ws.RemotePort = tPorta.Text
        ws.Connect
        Else 'servidor
        ws.LocalPort = tPorta.Text
        ws.Listen
        End If
    End If
Exit Sub
erro:
MsgBox "Host ou ip inválido!", vbCritical, "Erro!"
End Sub

Private Sub clientmessage_Change()
'Resumo: quando o usuário digitar, verifica se o soquete está conectado para enviar um comando que
'indicará ao outro aplicativo que o usuário está digitando.

    If ws.State = sckConnected And clientmessage.Text &lt;&gt; "" Then
    ws.SendData "%%%%TYP&amp;&amp;"
    End If
End Sub

Private Sub clientmessage_KeyDown(KeyCode As Integer, Shift As Integer)
'Resumo: se o usuário pressionar enter, a mensagem será enviada (se o soquete estiver conectado) e
'o texto apagado.
    If KeyCode = 13 Then '13 é o código da tecla enter
        If ws.State = sckConnected Then ws.SendData clientmessage.Text
    clientmessage.Text = ""
    End If
End Sub

Private Sub tApelido_KeyDown(KeyCode As Integer, Shift As Integer)
'Resumo: se o usuário "mudar de nick", envia esse novo apelido ao outro.

    If KeyCode = 13 And ws.State = sckConnected Then
    ws.SendData "%%%%COM&amp;&amp; " &amp; tApelido.Text
    End If
End Sub

Private Sub Timer1_Timer()
'Resumo: habilita o controle thost apenas se for cliente e o soquete estiver fechado.
'Habilita o controle tporta apenas se o soquete estiver fechado.
'Habilita os modos se o soquete estiver fechado

'Se o estado do soquete for "fechando", ou "erro", fecha a conexão.
'Se o estado for fechado, então:
'Exibe o status "Desconectado"

'Se for Conectado, então:

'Se o outro estiver digitando, exibe tal informação (Fulano está digitando)
'Senão, exibe "conectado a (ip do cliente ou apelido se tiver)"

'Caso não estiver conectado e nem desconectado (resolvendo host, conectando, etc):
'Exibe o status correspondente.

tHost.Enabled = Option1.Value And (ws.State = sckClosed)
tPorta.Enabled = (ws.State = sckClosed)
Option1.Enabled = tPorta.Enabled
Option2.Enabled = tPorta.Enabled

    If ws.State = sckClosing Or ws.State = sckError Then
    ws.Close
    End If
    
    If ws.State = sckClosed Then
    OtherApelido = ""
    lStatus.Caption = "Desconectado"
    ElseIf ws.State = sckConnected Then
    
        If OtherTyping Then
        lStatus.Caption = IIf(OtherApelido &lt;&gt; "", OtherApelido, ws.RemoteHost) &amp; " está digitando"
        Else
        lStatus.Caption = "Conectado a " &amp; IIf(OtherApelido &lt;&gt; "", OtherApelido, ws.RemoteHost)
        End If
    Else
    lStatus.Caption = STS(ws.State)
    End If

End Sub

Private Sub ws_ConnectionRequest(ByVal requestID As Long)
'Resumo: ao receber a nova conexão, fecha o soquete, aceita a conexão e envia um comando que indicará o apelido.
ws.Close
ws.Accept requestID
ws.SendData "%%%%REQ&amp;&amp;"
End Sub

Private Sub ws_DataArrival(ByVal bytesTotal As Long)
'Resumo: recebe os dados recebidos, e de acordo com o que recebeu executa uma ação.
OtherTyping = False
Dim comandos() As String
Dim receiveddata As String
ws.GetData receiveddata

    If InStr(1, receiveddata, "%%%%") &gt; 0 Then
    comandos = Split(receiveddata, "%%%%")
        For i = 1 To UBound(comandos)
            If comandos(i) = "REQ&amp;&amp;" Then 'requer apelido
            ws.SendData "%%%%COM&amp;&amp; " &amp; tApelido.Text
                If OtherApelido = "" Then
                ws.SendData "%%%%REQ&amp;&amp;"
                End If
            ElseIf InStr(1, comandos(i), "COM&amp;&amp;") &gt; 0 Then 'enviou um apelido
            OtherApelido = Split(comandos(i), " ")(1)
            ElseIf comandos(i) = "TYP&amp;&amp;" Then 'DIGITANDO
            OtherTyping = True
            Else
            chathistory.Text = chathistory.Text &amp; OtherApelido &amp; " (" &amp; CStr(Time) &amp; "): " &amp; comandos(i) &amp; vbCrLf
            chathistory.SelStart = Len(chathistory.Text)
            chathistory.SelLength = 0
            End If
        Next i
    Else
    chathistory.Text = chathistory.Text &amp; OtherApelido &amp; " (" &amp; CStr(Time) &amp; "): " &amp; receiveddata &amp; vbCrLf
    chathistory.SelStart = Len(chathistory.Text)
    chathistory.SelLength = 0
    End If
End Sub


Para executar o processo será necessário compilá-lo e rodá-lo em duas instâncias. Entretanto, se tiver dois ou mais computadores, rode o mesmo projeto nos dois computadores. 
No campo de Host, caso o servidor esteja em outra instância em seu computador, digite 127.0.0.1 (endereço de ip local). Caso o servidor esteja em outro computador de rede local, digite o ip do mesmo (você encontra na lista de DHCP do roteador).

É isso, pessoal. 
Dúvidas, sugestões de artigos, só enviar por email (twitter_diegorw@hotmail.com), twitter (@diegojsRW) ou pelos comentários daqui do blog.
Até o próximo artigo.