Conheça o Plugfeed | » Início » Programação » Visual Basic » Sockets II -->
 
Avaliação: Não avaliado | Publicado em: 18/02/2008
Sockets II
Fred Jader Desenvolvedor web desde os 12 anos, Frederico Jader hoje em dia atua como diretor de arte e administrador de portais em geral, tendo como mais novo projeto o site www.gamesnahora.com
Sockets II

Continuando nosso tutorial sobre sockets, vamos agora ver algo mais prático e não tão trivial. A idéia é transmitir um arquivo através da conexão. Como já vimos anteriormente, só é possível transmitir bytes através de um socket, sem qualquer classificação. Isso implica que o socket não sabe (nem se importa) se você quer passar uma "mensagem", um "arquivo" ou um "registro", por exemplo. Você não pode meramente fazer: winsock1.SendData "c:autoexec.bat". Ele não enviará o conteúdo do arquivo "autoexec.bat" que está na raíz da unidade "c:", e sim a string "c:autoexec.bat". Esses "tipos de dados" são abstrações que nós possuímos. Portanto, deveremos criar "regras" a fim de que possamos enviar e receber, corretamente, esses "tipos de dados". A primeira vista isso pode até parecer ruim, no entanto, a meu ver, dá uma grande flexibilidade ao programador permitindo-lhe tratar as informações como bem lhe convir.
Pois bem, como definir um "arquivo" (nesse caso)? Existem centenas maneiras diferentes de se fazer isso, e aqui estarei demonstrando apenas uma delas. Essa maneira a qual me refiro é colocar uma determinada informação no começo e no fim da transmissão do arquivo. A idéia disso é simples e vem a seguir: a informação do começo dirá para o outro computador que um arquivo será enviado, a fim de que este se prepare para recebê-lo (abrindo um arquivo no disco para gravação, por exemplo); a informação do fim da transmissão dirá para o outro computador que a transmissão do arquivo acabou (para que ele descarregue os dados do buffer para o arquivo no disco, por exemplo). Quanto ao que seriam essas "informações", por exemplo, *poderíamos* colocar as strings "início" e "fim" para indicá-las. Mas, por que eu disse "poderíamos"? É que aí entra um problema grave. Essas informações ("início" e "fim") trafegarão junto com o conteúdo do arquivo. Provavelmente você já deve ter imaginado o que isso acarreta e que pode ser traduzido pela pergunta a seguir: "e, se no conteúdo do arquivo existirem as palavras 'início' e 'fim'?". Baseando-se somente nessa perspectiva, talvez não haja problema se a string "início" estiver no conteúdo do arquivo, dependendo de como a rotina foi estruturada. Porém, a string "fim", quase com certeza, gerará problemas (já que a rotina está esperando por ela para finalizar o arquivo). Agora que já pensamos em um empeçilho existente nessa maneira, cabe a nós desenvolvermos uma solução para o mesmo. Aqui também haverá uma diversidade muito grande de opções, no entanto, a idéia principal é usar strings que dificilmente serão achadas dispostas de uma tal maneira. Digo "dificilmente" porque é possível achá-las, mas é extremamente improvável. A fim de especificarmos também o nome do arquivo, podemos usar "", onde em "arquivo.ext" estará o nome do arquivo. Para dificultar ainda mais a possibilidade de uma string assim ser encontrada no conteúdo do arquivo podemos acrescentar outros caracteres ao começo e fim da mesma, como chr(0) e chr(255), por exemplo. Para o caso do fim, é interessante fazer algo semelhante, colocando o tamanho do arquivo no lugar do nome, ficando "", a fim de podermos validar se o arquivo foi enviado corretamente (pelo menos o número de bytes do mesmo). É claro que "arquivo.ext" e "32442" irão variar de acordo com o arquivo que está sendo enviado. Por isso, deveremos procurar por "" que fecha a sequência a fim de que saibamos o nome do arquivo. O mesmo vale para o fim, ou seja, procuraremos por "" que fecha a sequência. Para ficar melhor ainda, adicionaremos também os caracteres chr(0) e chr(255) numa ordem arbitrária. Essa ordem será "chr(0) & chr(255) & chr(0)" e estará antes de cada "<" e depois de cada ">". Bem, chega de teoria e vamos à prática.

Abra os mesmos projetos do tutorial anterior (prjServidor e prjCliente). A parte de conexão já está toda pronta, por isso não precisaremos fazer novamente. Focaremo-nos apenas no envio e recepção de arquivos. Coloque uma textbox em cada form e nomei-a "txtArquivo". Vá na opção Components do menu Project e marque o Microsoft Common Dialog Control. Em seguida, insira o mesmo em cada form, e para encurtar o nome, nomei-o como "CD1". Repare que tudo que é feito no projeto Servidor deverá ser feito no projeto Cliente também. É claro que poderia ser apenas um e servir aos dois propósitos, mas aí fica a seu critério e você deverá tomar as medidas necessárias para que o projeto funcione dos dois modos. Insira dois botões, nomeando-os "cmdProcurar" e "cmdEnviarArquivo". Não estou explicitando, mas a propriedade Caption de cada botão fica óbvia pelos nomes.
O código a seguir ficará no evento Click do botão cmdProcurar (para ambos os projetos):

CÓDIGO

CD1.CancelError = True
On Error Resume Next
CD1.ShowOpen
If Not Err.Number = cdlCancel Then
txtArquivo.Text = CD1.FileName
End If
On Error GoTo 0


Essa rotina permitirá que escolhamos o arquivo e o caminho do mesmo será mostrado na textbox txtArquivo.
O código a seguir ficará no evento Click do botão cmdEnviarArquivo (para ambos os projetos, observe apenas a linha antes do "End If", que deverá ser diferente de acordo com o projeto):

CÓDIGO

Dim início As String, delim As String, fim As String
Dim tamanho As Double
Dim n As Byte
Dim nome As String, paraEnviar As String
início = Chr(0) & Chr(255) & Chr(0) & "<início:"
fim = Chr(0) & Chr(255) & Chr(0) & "<fim:"
delim = ">" & Chr(0) & Chr(255) & Chr(0)
If Dir(txtArquivo.Text) <> "" And trim(txtArquivo.Text) <> "" Then
n = FreeFile()
Open txtArquivo.Text For Binary As #n
tamanho = LOF(n)
conteúdo = Input(tamanho, n)
Close #n
nome = Right(txtArquivo.Text, Len(txtArquivo.Text) - InStrRev(txtArquivo.Text, ""))
paraEnviar = início & nome & delim & conteúdo & fim & tamanho & delim
sckServidor.SendData paraEnviar '** sckCliente.SendData paraEnviar
End If


Essa rotina formatará os dados da forma como já foi mostrada anteriormente e enviará para o outro computador através do socket. Falta agora apenas a parte da recepção, que se dará no evento DataArrival do socket (lembra-se do DataArrival no primeiro tutorial, certo?). O código que deverá ficar nesse evento será (note que deixei o código do projeto anterior também, apesar de não ser muito útil mostrar os dados na listbox):

CÓDIGO

Static buffer As String
Static n As Byte
Static tamanho As Double
Static nome As String

Dim dados As String
sckServidor.GetData dados '** sckCliente.GetData dados
lbStatusSvr.AddItem "Recebido: " & dados '** lbStatusClt.AddItem "Recebido: " & dados
buffer = buffer & dados

Dim início As String, delim As String, fim As String
Dim iniciar As Boolean, finalizar As Boolean
início = Chr(0) & Chr(255) & Chr(0) & "<início:"
fim = Chr(0) & Chr(255) & Chr(0) & "<fim:"
delim = ">" & Chr(0) & Chr(255) & Chr(0)
pos = InStr(1, buffer, início)

iniciar = False
If pos > 0 Then
pos2 = InStr(pos, buffer, delim)
If pos2 > 0 Then
nome = Mid(buffer, pos + Len(início), pos2 - pos - Len(início))
buffer = Mid(buffer, pos2 + Len(delim), Len(buffer) - pos2 - Len(delim) + 1)
iniciar = True
End If
End If
finalizar = False
pos = InStr(1, buffer, fim)
If pos > 0 Then
pos2 = InStr(pos, buffer, delim)
If pos2 > 0 Then
tamanho = CDbl(Mid(buffer, pos + Len(fim), pos2 - pos - Len(fim)))
buffer = Mid(buffer, 1, pos - 1)
finalizar = True
End If
End If
If iniciar Then
n = FreeFile()
Open "c:" & nome For Binary As #n
End If
If finalizar Then
If Len(buffer) = tamanho Then
Put #n, , buffer
Else
MsgBox "Tamanho original do arquivo difere da quantidade recebida de bytes!"
End If
Close #n
MsgBox "Concluído!"
End If


A rotina acima faz o seguinte: a cada vez que os dados são recebidos, os mesmos são acumulados na variável buffer e também é verificado se trata-se do envio de um arquivo (quando começa) ou se trata-se do fim do arquivo (quando termina). Ao término ele verifica se a quantidade recebida de bytes está correta e grava o arquivo no disco (nesse caso, grava na raíz da unidade C: com o nome original do arquivo).
Para quem não está muito familiarizado com algumas funções do VB, seguem agora alguns esclarecimentos. InStr() procura uma substring, ou seja, a posição de uma string em outra (sempre começando a partir de 1, e retorna 0 caso não encontre). Len() retorna o tamanho de uma string. Mid() retorna uma substring, ou seja, um pedaço de uma string.
Agora execute ambos os projetos, clique no botão Aguardar no projeto Servidor e clique no botão Conectar do projeto Cliente. Escolha um arquivo em qualquer um dos dois pelo botão "Procurar" e clique no botão "Enviar arquivo".

Devo lembrar que é essa só uma das possíveis maneiras de fazer isso, e nem mesmo se trata da melhor delas. Para ter uma idéia, o exemplo desse tutorial não é o ideal se você quer passar arquivos maiores do que 3,5MB; outro fator negativo é que a leitura de arquivos desse tamanho são demorados (se lidos de uma vez só, como é o caso), além de consumir muita memória; não há qualquer resposta do receptor para o transmissor indicando-lhe que o mesmo pode enviar mais dados. Em suma, carece de diversos cuidados técnicos que devem ser imagininados pelo programador. Portanto, trata-se apenas de uma experiência deveras simplificada com o único propósito de fixar a idéia de como funciona a comunicação através de sockets.
Espero que tenham gostado desse segundo tutorial sobre sockets em VB. Caso haja algo errado (sempre há!) ou você queira fazer sugestões ou críticas (positivas e/ou negativas!) ou mesmo tirar dúvidas, você pode contactar o autor através do endereço: washingtonj@openlink.com.br.
Happy socketing!
Este artigo é a parte 2 de 2 da seguinte série:
  1. Sockets
  2. Sockets II








Um produto Detetive.net