.NET

13 jan, 2009

VB .NET – Programando Sockets com Multithreads

Publicidade

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…