Há muito tempo escrevi um artigo sobre Sockets na plataforma .NET. Foi apenas uma introdução, por isso estou voltando ao assunto.
Neste artigo vou criar uma aplicação usando programação multithreads com sockets no Visual Basic. Vou fazer um chat multithread.
Caso não saiba sobre sockets e threads, visite www.macoratti.net para maiores informações.
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 está 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.
Cada cliente envia uma mensagem para o servidor e o servidor faz um broadcast da mensagem para todos os clientes conectados ao servidor.
Broadcast (do Inglês, “transmitir”) ou Radiodifusão é o processo pelo qual se transmite ou difunde determinada informação, tendo como principal característica que a mesma informação está sendo enviada para muitos receptores ao mesmo tempo. Este termo é utilizado em rádio, telecomunicações e em informática.(wikipédia)
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 HashTable é uma coleção onde os elementos possuem chave/valor e onde cada elemento é indexado usando uma chave alfanumérica.
Ao trabalhar com a classe Hashtable 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 está 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 está 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 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 receber um de fluxo de dados em uma rede no modo síncrono de bloqueio.
A fim de a 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:
- Criar um TcpClient e chamar um dos três métodos Connect disponíveis;
- Criar Um TcpClient usando o nome de host e número da porta do host remoto. Esse construtor irá automaticamente tentar uma conexão.
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:
- três caixas de texto, sendo uma com sua propriedade Multiline igual True;
- dois botões de comando btnEnviarMensagem e um botão de comando btnConectar conforme o leiaute a abaixo:
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…