Deixo aqui uma pequena brincadeira desenvolvida em C#, um chat, que serve para mostrar como é fácil trabalhar com sockets e threads. Este artigo pode ser levado mais a sério se pensarmos que este sistema pode ser usado na comunicação entre dois sistemas efectuando, por exemplo, trocas de mensagens. Antes de continuar tenho de referir que este artigo é baseado numa aplicação que não foi desenvolvida por mim, pelo que os créditos desta aplicação podem ser encontrados aqui. A aplicação serve apenas de base para mostrar como se trabalha com sockets e threads em C#.
Antes de falar sobre sockets, comunicação TCP e afins, urge uma explicação teórica de como este tipo de aplicações, cliente-servidor, costuma funcionar. Normalmente existe um servidor que se encontra à escuta num determinado porto. Quando o servidor recebe um pedido de ligação de um cliente, o servidor lança uma thread que passará a efectuar a comunicação com esse cliente, ficando assim o servidor livre para responder a outras solicitações de outros clientes. No final da comunicação, tanto o cliente como a thread do lado do servidor terminam. Esta é apenas uma forma, talvez a mais comum, do modo de funcionamento deste tipo de sistemas. Neste caso, dado que este chat é muito simples e efectua apenas uma ligação ponto-a-ponto, não existe necessidade do servidor lançar uma thread a cada pedido que recebe.
Este artigo deve ser acompanhado vendo o código fonte do chat, que pode ser obtido aqui. O zip deve ser descomptado e aberto o ficheiro <localização_unzip>TcpDeviceSimulatoryListenerTcpDeviceSimulatoryListener.sln que possui ambas as aplicações o cliente e o servidor.
Abram o Form1.cs do TcpDeviceSimulatoryListener, que será o servidor e vejam que esta janela possui apenas o botão de "Start Listener", que servirá para iniciar o listener, ou seja, o servidor que ficará à escuta. A acção deste botão não é muito interessante pois faz pouco mais do que lançar o Form2.cs. Abram o Form2.cs e, este sim com mais interesse, para ver como funciona o servidor.
O método ThreadProcPollOnEthernet é definido da seguinte forma:
private void ThreadProcPollOnEthernet() { for (;;) { Thread.Sleep(100); byte[] msg = new Byte[Constants.maxNoOfBytes]; byte count1 = 0x01; for (int i = 0; i < msg.Length; i++) { msg[i] = count1++; } try { if (formClosing == true) { return; } int readBytes = tcp.GetStream().Read(msg,0,msg.Length); if (readBytes == 8) { StringBuilder shutMessage = new StringBuilder(8); for (int count = 0; count < 8; count++) { char ch = (char)msg[count]; shutMessage = shutMessage.Append(ch); } string shut = "shutdown"; string receivedMessage = shutMessage.ToString(); if (receivedMessage.Equals(shut)) { MessageBox.Show(this,"Shutdown Request has arrived from the nconnected party.nYou cannot send message anymore.nPlease close the window.","Shut Down Request",MessageBoxButtons.OK,MessageBoxIcon.Information); buttonSend.Enabled = false; return; } } StringBuilder str = new StringBuilder(Constants.maxNoOfBytes); for (int count = 0; count < readBytes ; count++) { char ch = (char)msg[count]; str = str.Append(ch); str = str.Append(" "); } textBox1.Text = str.ToString(); } catch (IOException) { return; } } }
Descrito de forma simples, o que este método faz é, num ciclo infinito com intervalos de 100 milisegundos de espera:
byte[] msg = new Byte[Constants.maxNoOfBytes]; byte count1 = 0x01; for (int i = 0; i < msg.Length; i++) { msg[i] = count1++; }
int readBytes = tcp.GetStream().Read(msg,0,msg.Length);
StringBuilder str = new StringBuilder(Constants.maxNoOfBytes); for (int count = 0; count < readBytes ; count++) { char ch = (char)msg[count]; str = str.Append(ch); str = str.Append(" "); } textBox1.Text = str.ToString();
int readBytes = tcp.GetStream().Read(msg,0,msg.Length); if (readBytes == 8) { StringBuilder shutMessage = new StringBuilder(8); for (int count = 0; count < 8; count++) { char ch = (char)msg[count]; shutMessage = shutMessage.Append(ch); } string shut = "shutdown"; string receivedMessage = shutMessage.ToString(); if (receivedMessage.Equals(shut)) { MessageBox.Show(this,"Shutdown Request has arrived from the nconnected party.nYou cannot send message anymore.nPlease close the window.","Shut Down Request",MessageBoxButtons.OK,MessageBoxIcon.Information); buttonSend.Enabled = false; return; } }
O envio de mensagens, que ocorre quando o utilizador pressiona o botão "Send", é tão simples quando a recepção, apenas se efectuam as operações por ordem inversa:
private void buttonSend_Click(object sender, System.EventArgs e) { if (textBox2.Text.Length != 0) { char[] charArray = textBox2.Text.ToCharArray(0,textBox2.Text.Length); dataToSend = new byte[textBox2.Text.Length]; for (int charCount = 0; charCount < textBox2.Text.Length; charCount++) { dataToSend[charCount] = (byte)charArray[charCount]; } } else { dataToSend = new byte[]{(byte)'e',(byte)'m',(byte)'p',(byte)'t',(byte)'y'}; } tcp.GetStream().Write(dataToSend,0,dataToSend.Length); textBox2.Text = ""; }
char[] charArray = textBox2.Text.ToCharArray(0,textBox2.Text.Length); dataToSend = new byte[textBox2.Text.Length]; for (int charCount = 0; charCount < textBox2.Text.Length; charCount++) { dataToSend[charCount] = (byte)charArray[charCount]; }
tcp.GetStream().Write(dataToSend,0,dataToSend.Length);
Quanto ao cliente, a primeira acção a fazer é efectuar uma ligação entre o cliente e o servidor, depois usa-se a mesma técnica explicada no servidor para enviar e receber as mensagens. O código abaixo efectua a comunicação entre o cliente e o servidor:
private void buttonConnect_Click(object sender, System.EventArgs e) { textBoxIPAddress.Enabled = false; IPAddress address = IPAddress.Parse(ipAddress); tcp = new TcpClient((new IPEndPoint(Dns.Resolve(Dns.GetHostName()).AddressList[0],4002))); LingerOption lingerOption = new LingerOption(false, 1); tcp.LingerState = lingerOption; tcp.Connect(new IPEndPoint(Dns.Resolve(ipAddress).AddressList[0],4001)); buttonSend.Enabled = true; ((Button)sender).Enabled = false; receiveThread = new Thread(new ThreadStart(ThreadProcReceive)); receiveThread.Name = "Client's Receive Thread"; receiveThread.ApartmentState = ApartmentState.MTA; receiveThread.Start(); }
IPAddress address = IPAddress.Parse(ipAddress);
O LingerState define o tempo de espera aquando do fecho da ligação.
tcp.Connect(new IPEndPoint(Dns.Resolve(ipAddress).AddressList[0],4001));
receiveThread = new Thread(new ThreadStart(ThreadProcReceive)); receiveThread.Name = "Client's Receive Thread"; receiveThread.ApartmentState = ApartmentState.MTA; receiveThread.Start();
E pronto. Após compilar a solução, podem usar este chat em duas máquinas na mesma rede da seguinte forma:
Consultem a documentação para saberem mais e para exclarecer dúvidas.