VB .NET - Programando Sockets com Multithreads
Há muito tempo escrevi um artigo sobre Sockets na plataforma .NET. Foi apenas uma introdução por isso estou voltando ao assunto.
Nota: Uma thread significa uma única linha de execução; múltiplas threads indicam diversas linhas de execução.
Os programadores Visual Basic (versão 6.0, 5.0 e 4.0) não podiam contar com o uso de threads, era preciso criar lançar mão dos famosos ActiveX Exe, o que na verdade não faziam o papel de uma thread e sim criavam um novo processo através de um aplicativo VB. |
Neste artigo vou criar uma aplicação usando programação multithreads com sockets no Visual Basic. Vou fazer um chat multithread.
Thread, ou linha de execução
, é uma forma de um processo
dividir a si mesmo em duas ou mais tarefas que podem ser executadas
simultaneamente. O suporte à thread é fornecido pelo próprio SO, no
caso da Kernel-Level Thread (KLT), ou implementada através de uma
biblioteca de uma determinada linguagem,
no caso de uma User-Level Thread (ULT). Uma linha de execução permite que o usuário de programa, por exemplo, utilize uma funcionalidade do ambiente enquanto outras linhas de execução realizam outros cálculos e operações. Em hardwares equipados com uma única
CPU, cada linha de execução(Thread) é processada de forma
aparentemente simultânea, pois a mudança entre uma linha e outra é feita de
forma tão rápida que para o usuário isso está acontecendo paralelamente. Em
hardwares com múltiplas CPUs ou multi-cores as linhas de
execução(Threads) podem ser realizadas realmente de forma simultânea; |
Se você ainda tem dúvidas sobre sockets e threads sugiro que leia os meus artigos sobre o assunto. (veja as referências)
Um soquete
pode ser usado em ligações de redes de computadores
para um fim de um elo bidirecional de comunicação entre dois programas. A
interface padronizada de soquetes surgiu originalmente no sistema
operacional Unix BSD (Berkeley Software Distribution); portanto, eles
são muitas vezes chamados de Berkeley Sockets.
É também uma abstração computacional que mapeia diretamente a uma porta de transporte (TCP ou UDP) e mais um endereço de rede. Com esse conceito é possível identificar unicamente um aplicativo ou servidor na rede de comunicação IP.(wikipédia) |
Usar a programação multithreads com sockets significa que um servidor que seja multithread tem a capacidade de se comunicar com mais de um cliente ao mesmo tempo.
Dessa forma sempre que o servidor recebe uma requisição de um cliente ele cria uma linha de execução separada e independente; dessa forma cada cliente esta sendo atendido em uma linha de execução separada. Abaixo temos um esquema representando esta situação:
Logo, se desejamos criar uma aplicação chat multithread teremos que criar um servidor multithread e um cliente multithread. Como diria Jack, vamos por partes...
Criando um servidor de chat multithread
Para criar o servidor vou economizar na interface pois o tempo anda escasso, por isso irei criar uma aplicação do tipo console para o nosso servidor que poderá atender diversos clientes ao mesmo tempo. O 'bichinho' é simples mas valente...
O nosso servidor de chat multithread vai permitir que um cliente se comunique com qualquer número de outros clientes que estiverem conectados no servidor conforme mostra o esquema abaixo:
Cada cliente envia uma mensagem
para o servidor e o servidor faz um broadcast da mensagem para todos os
clientes conectados ao servidor.
|
O servidor CHAT será uma aplicação console escutando na porta 8888 na máquina local (127.0.0.1) (Você pode escolher qualquer outra porta disponível)
Quando o servidor recebe uma requisição de comunicação de um cliente, ele inclui o nome do cliente em uma lista (listaClientes) implementada como uma HashTable e cria uma nova thread para a comunicação com o servidor.
A .NET
Framework fornece uma poderosa classe HashTable. Uma HasTable é uma coleção onde os elementos possuem chave/valor , e onde cada elemento é indexado usando uma chave alfanumérica. Ao trabalhar com a classe Hastable tenha em mente que a ordenação dos elementos na coleção é independente da ordem na qual ele entrou na tabela . A classe emprega seu próprio algoritmo hash para ordenar de forma eficiente os pares chave/valor da coleção. |
Quando o servidor obtém uma mensagem de qualquer cliente , ele seleciona todos os clientes a partir da HashTable listaClientes e envia a mensagem para todos os clientes (um broadcast) da lista. De forma que cada cliente pode ver as mensagens uns dos outros e então se comunicar através do servidor de chat.
A lista de clientes é uma HashTable que armazena o nome do cliente e uma instância do socket do Cliente.
Quando um cliente se conecta no servidor , ele cria uma nova thread para comunicação através da classe tratarCliente para tratar o cliente em uma thread separada.
A classe tratarCliente possui uma rotina chamada doChat() que esta tratando a comunicação entre o servidor e o socket Cliente do lado do servidor e um socket Cliente que vem chegando.
Quando o servidor recebe uma mensagem de qualquer cliente conectado, ele faz um broadcast da mensagem para todos os clientes. Isso foi implementado na rotina broadcast().
Após esta explicação vamos implementar o código.
Abra o Visual Basic 2008 Express Edition e crie um novo projeto do tipo console com o nome SuperServidorNet e clique em OK;
Agora vamos incluir o seguinte código no módulo Module1 :
Imports System.Net Imports System.Text Module Module1 Dim listaClientes As New Hashtable Sub Main() Dim enderecoLocal As IPAddress = IPAddress.Parse("127.0.0.1") Dim serverSocket As New TcpListener(enderecoLocal, 8888) Dim clientSocket As TcpClient = Nothing Dim contador As Integer serverSocket.Start() Mensagem("Servidor Chat Iniciado ....") contador = 0 While (True) contador += 1 clientSocket = serverSocket.AcceptTcpClient() Dim bytesFrom(10024) As Byte Dim dadosDoCliente As String Dim networkStream As NetworkStream = clientSocket.GetStream() networkStream.Read(bytesFrom, 0, CInt(clientSocket.ReceiveBufferSize)) dadosDoCliente = Encoding.ASCII.GetString(bytesFrom) dadosDoCliente = dadosDoCliente.Substring(0, dadosDoCliente.IndexOf("$")) listaClientes(dadosDoCliente) = clientSocket broadcast(dadosDoCliente + " Entrou ", dadosDoCliente, False) Mensagem(dadosDoCliente + " Entrou na Sala ") Dim cliente As New tratarCliente cliente.iniciaCliente(clientSocket, dadosDoCliente, listaClientes) End While clientSocket.Close() serverSocket.Stop() Mensagem("sair") Console.ReadLine() End Sub Sub Mensagem(ByVal texto As String) texto.Trim() Console.WriteLine(" >> " + texto) End Sub Private Sub broadcast(ByVal Mensagem As String, ByVal nomeUsuario As String, ByVal flag As Boolean) Dim Item As DictionaryEntry For Each Item In listaClientes Dim broadcastSocket As TcpClient broadcastSocket = CType(Item.Value, TcpClient) Try Dim broadcastStream As NetworkStream = broadcastSocket.GetStream() Dim broadcastBytes As [Byte]() If flag = True Then broadcastBytes = Encoding.ASCII.GetBytes(nomeUsuario + " diz : " + Mensagem) Else broadcastBytes = Encoding.ASCII.GetBytes(Mensagem) End If broadcastStream.Write(broadcastBytes, 0, broadcastBytes.Length) broadcastStream.Flush() Catch ex As Exception MsgBox(ex.Message) End Try Next End Sub Public Class tratarCliente Dim clientSocket As TcpClient Dim clNo As String Dim listaClientes As Hashtable Public Sub iniciaCliente(ByVal inClientSocket As TcpClient, ByVal clineNo As String, ByVal cList As Hashtable) Me.clientSocket = inClientSocket Me.clNo = clineNo Me.listaClientes = cList Dim ctThread As Threading.Thread = New Threading.Thread(AddressOf doChat) ctThread.Start() End Sub Private Sub doChat() Dim contadorRequisicao As Integer Dim bytesFrom(10024) As Byte Dim dadosDoCliente As String Dim rContador As String contadorRequisicao = 0 While (True) Try contadorRequisicao = contadorRequisicao + 1 Dim networkStream As NetworkStream = clientSocket.GetStream() networkStream.Read(bytesFrom, 0, CInt(clientSocket.ReceiveBufferSize)) dadosDoCliente = System.Text.Encoding.ASCII.GetString(bytesFrom) dadosDoCliente = dadosDoCliente.Substring(0, dadosDoCliente.IndexOf("$")) Mensagem("Cliente - " + clNo + " : " + dadosDoCliente) rContador = Convert.ToString(contadorRequisicao) broadcast(dadosDoCliente, clNo, True) Catch ex As Exception MsgBox(ex.ToString) End Try End While End Sub End Class End Module |
Iniciamos importando os namespaces :
Imports System.Net.Sockets
Imports System.Text
que nos dão acesso as classes da plataforma .NET para realizar comunicações com Socket.
Neste código criei um Server Socket a partir da classe TcpListener que esta escutando na porta 8888.(poderia ser outra porta)
A classe
TcpListner é usada para escuta de conexões de clientes de rede TCP. A classe TcpListener fornece métodos simples que escutam e aceitam solicitações de conexão de entrada no mode de bloqueio síncrono. Você pode usar um TcpClient ou um Socket para se conectar com um TcpListener. Use o método Start para começar escutar solicitações de conexão de entrada. Ele irá enfileirar as requisições até você chamar o método Stop ou até atingir o máximo de conexões.(MaxConnections). Você pode usar tanto AcceptSocket como AcceptTcpClient para receber uma requisição de conexão de entrada e colocá-la na fila. Se você quiser evitar o bloqueio, você pode usar o método Pending primeiro, para determinar se as solicitações de conexão estão disponíveis na fila. |
Quando o servidor recebe uma requisição do cliente ele passa a instância desta requisição para uma classe chamada tratarCliente.
A classe tratarCliente é uma thread que faz o tratamento da comunicação entre a instância do cliente no servidor e o cliente.
Para cada requisição no servidor existe uma nova thread que é criada para efetuar a comunicação, de forma que o podemos efetuar a comunicação com mais de um cliente ao mesmo tempo no servidor e efetuar uma comunicação independente.
Executando este programa teremos o servidor inicializado e pronto para atender requisições: Que venham os clientes...
Criando um cliente multithread
Vamos agora criar um cliente Multithread usando sockets como uma aplicação Windows Forms. O cliente irá se conectar com a porta 8888 do servidor. Como o cliente e o servidor estão rodando na minha máquina local possuem o mesmo endereço IP (127.0.0.1). Note que o cliente tem que saber em qual porta o servidor esta escutando.
clientSocket.Connect("127.0.0.1", 8888)
Quando o cliente se conectar com o servidor , o servidor irá criar uma thread para a comunicação com o cliente, em seguida podemos efetuar a conexão com outro cliente para mostrar que a comunicação é simultânea.
A classe
TcpClient fornece conexões de cliente de serviços de rede TCP. A classe TcpClient fornece métodos simples para conectar-se, enviar e de receber um de fluxo de dados em uma rede no modo síncrono de bloqueio. A fim de da classe TcpClient se conectar e trocar dados, um TcpListener ou Socket criado com o ProtocolType TCP deve estar escutando para solicitações de conexão de entrada. Você pode conectar-se com este Listner em uma das seguintes formas:
|
Abra o Visual Basic 2008 Express Edition e crie um novo projeto do tipo Windows Forms Application com o nome superClienteNet e clique em OK;
No formulário padrão form1.vb inclua os seguintes componentes:
A seguir inclua o código abaixo no formulário form1.vb:
Imports System.Net.Sockets Imports System.Text Public Class Form1 Dim clientSocket As New TcpClient() Dim serverStream As NetworkStream Dim lerDados As String Private Sub btnConectar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnConectar.Click lerDados = "Conetado como Servidor ..." Mensagem() Try clientSocket.Connect("127.0.0.1", 8888) serverStream = clientSocket.GetStream() Dim outStream As Byte() = Encoding.ASCII.GetBytes(txtNome.Text + "$") serverStream.Write(outStream, 0, outStream.Length) serverStream.Flush() 'cria uma nova thread para enviar mensagens Dim ctThread As Threading.Thread = New Threading.Thread(AddressOf getMensagem) ctThread.Start() Catch ex As Exception MsgBox(ex.Message) End Try End Sub Private Sub Mensagem() If Me.InvokeRequired Then Me.Invoke(New MethodInvoker(AddressOf Mensagem)) Else txtDados.Text = txtDados.Text + Environment.NewLine + " >> " + lerDados End If End Sub Private Sub btnEnviarMensagem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEnviarMensagem.Click Try Dim outStream As Byte() = Encoding.ASCII.GetBytes(txtMensagem.Text + "$") serverStream.Write(outStream, 0, outStream.Length) serverStream.Flush() Catch ex As Exception MsgBox(ex.Message) End Try End Sub Private Sub getMensagem() 'loop infinito While (True) Try serverStream = clientSocket.GetStream() Dim buffSize As Integer Dim inStream(10024) As Byte buffSize = clientSocket.ReceiveBufferSize serverStream.Read(inStream, 0, buffSize) Dim dadosRetornados As String = Encoding.ASCII.GetString(inStream) lerDados = "" + dadosRetornados Mensagem() Catch ex As Exception MsgBox(ex.Message) Exit Sub End Try End While End Sub End Class |
O cliente se conecta com a porta 8888 em localhost (o servidor e o cliente) estão na mesma máquina local.
Quando o programa Cliente inicia , temos que informar o nome (apelido) do usuário para que o mesmo seja identificado no servidor.
O programa Cliente se conecta com o servidor de Chat e inicia uma thread para receber as mensagens dos clientes.
Implementamos um loop infinito (While(true)) na função getMessage() e chamamos esta função na thread.
Executando o projeto Servidor e o Cliente (em duas instâncias) iremos obter:
E ai esta o nosso chat, simples mas funcional, e, você não precisou de nenhum recurso a não ser as classes da .NET Framework.
O projeto pode ser incrementado com um tratamento de erros mais robusto e outras funcionalidades, meu objetivo foi mostrar como usar as classes do namespace System.NET para criar uma aplicação multithread.
Pegue o projeto completo aqui: servidor : SuperServidorNet.zip e cliente: SuperClienteNet.zip
Eu sei é apenas VB .NET , mas eu gosto...
referências:
José Carlos Macoratti