czwartek, 14 września 2017

C# - Aplikacje okienkowe klient oraz serwer

Ten post chciałbym poświęcić na zaprezentowanie dwóch programów odpowiedzialnych za komunikację pomiędzy klientem a serwerem. Wysyłane będzie żądanie z odpowiednią komendą, dzięki której nastąpi wykonanie podanej akcji.

[Źródło: https://docs.microsoft.com/en-us/dotnet/]

Programy są modyfikacją wersji konsolowych, umieszczonych w jednym z poprzednich postów.

Aplikacja serwer:




Do aplikacji definiuje się adres IP oraz Port. Po tej operacji należy uruchomić serwer. Dane odebrane oraz komunikaty są wyświetlane w oknie receive data. Dane wysłane wraz z krótkim komentarzem są wyświetlone w oknie Sended data. 

Blok tekstowy Data to send będzie zawierał komendą jaką chce się wysłać do klientów. Należy w tym celu wprowadzić ją w polu i kliknąć Send data. Odłączenie klientów od serwera następuje po kliknięciu przycisku Server Stop.

Poniżej wklejam cały kod projektu serwera wraz z komentarzem. Cały projekt można pobrać w linku na dole strony.

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Drawing;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Net;
  9. using System.Net.Sockets;
  10. using System.Threading.Tasks;
  11. using System.Windows.Forms;
  12. namespace CSharp_Window_ServerTCP
  13. {
  14.     public partial class Server : Form
  15.     {
  16.         private const int bufferSize = 8192;         /* Data buffer size */
  17.         private const int port = 1001;               /* Port to establish communication */
  18.         /*
  19.          *   InterNetwork: Address for IP in version 4
  20.          *   Stream: Support two way connection-based byte wihout duplication of data
  21.          *   ProtocolType: Transmission Control Protocol
  22.         */
  23.         private static readonly Socket serverSocket = new Socket
  24.                 (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  25.         private static readonly List<Socket> clientSockets = new List<Socket>();    /* List of connected sockets*/
  26.         private static readonly byte[] buffer = new byte[bufferSize];               /* buffer with data to send */
  27.         //-----------------------------------------------------------------------------------------
  28.         public Server()
  29.         {
  30.             InitializeComponent();
  31.         }
  32.         //-----------------------------------------------------------------------------------------
  33.         /*
  34.          * Close all connected clients, blocks sending and receiving data from client, it is more
  35.          * flexible way then close who destoy the connection
  36.         */
  37.         void CloseAllSockets()
  38.         {
  39.             foreach (Socket socket in clientSockets)
  40.             {
  41.                 socket.Shutdown(SocketShutdown.Both);   /* Block sending and receiving */
  42.                 /*
  43.                     or
  44.                     socket.Close();
  45.                 */
  46.             }
  47.             dataListBox.Items.Add("All sockets has been closed\r\n");
  48.         }
  49.         //-------------------------------------------------------------------------------------------
  50.         /*
  51.          * Accept Callback function
  52.         */
  53.         void AcceptCallback(IAsyncResult AR)
  54.         {
  55.             Socket socket;
  56.             try{
  57.                 socket = serverSocket.EndAccept(AR);                    /* Asynchronously accepts an incoming connection attempt
  58.                                                                         and creates a new Socket to handle remote host communication. */
  59.             }
  60.             catch (ObjectDisposedException ex){
  61.                 dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Caught:" + ex.Message + "\r\n"); }));
  62.                 return;
  63.             }
  64.             clientSockets.Add(socket);                                  /* Add new connection*/
  65.             /* Begins to asynchnous receive data */
  66.             socket.BeginReceive(buffer, 0, bufferSize, SocketFlags.None, ReceiveCallback, socket);     /* Get data */
  67.                                                                                                        // dataListBox.Items.Add("Client connected...");
  68.             dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Client connected...\r\n"); }));
  69.             serverSocket.BeginAccept(AcceptCallback, null);             /* Begins asonchronyous operation for
  70.                                                                             accepting try for connection */
  71.         }
  72.         //-------------------------------------------------------------------------------------------
  73.         /*
  74.             * Receive callback function, checks data that was send from client to server
  75.         */
  76.         void ReceiveCallback(IAsyncResult AR)
  77.         {
  78.             Socket recDataCurrentConnection = (Socket)AR.AsyncState;
  79.             int received;
  80.             try{
  81.                 received = recDataCurrentConnection.EndReceive(AR);
  82.             }
  83.             catch (SocketException ex){
  84.                 dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Client disconnected" + ex + "\r\n"); }));
  85.                 recDataCurrentConnection.Close();                       /* Only close connection don't need to shutdown communication */
  86.                 clientSockets.Remove(recDataCurrentConnection);         /* Remove client from socket list */
  87.                 return;                                                 /* Exit function */
  88.             }
  89.             byte[] recBuf = new byte[received];
  90.             Array.Copy(buffer, recBuf, received);
  91.             string text = Encoding.ASCII.GetString(recBuf);
  92.             dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Received Message: " + text + "\r\n"); }));
  93.             if (text.ToLower() == "time"){
  94.                 byte[] data = Encoding.ASCII.GetBytes(DateTime.Now.ToLongTimeString());
  95.                 recDataCurrentConnection.Send(data);
  96.                 sendListBox.Invoke(new Action(delegate () { sendListBox.Items.Add("Send time:" + System.Text.Encoding.UTF8.GetString(data) +"\r\n"); }));
  97.                 dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Message send to client" + "\r\n"); }));
  98.             }
  99.             else if (text.ToLower() == "date") {
  100.                 byte[] data = Encoding.ASCII.GetBytes(DateTime.Now.ToLongDateString());
  101.                 recDataCurrentConnection.Send(data);
  102.                 sendListBox.Invoke(new Action(delegate () { sendListBox.Items.Add("Send data:" + System.Text.Encoding.UTF8.GetString(data) +"\r\n"); }));
  103.                 dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Message send to client.\r\n"); }));
  104.             }
  105.             else if (text.ToLower() == "ip local"){
  106.                 byte[] data = Encoding.ASCII.GetBytes(GetLocalIPAddress());
  107.                 recDataCurrentConnection.Send(data);
  108.                 sendListBox.Invoke(new Action(delegate () { sendListBox.Items.Add("Send local ip: " +System.Text.Encoding.UTF8.GetString(data) + "\r\n"); }));
  109.                 dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Message send to client: \r\n"); }));
  110.             }
  111.             else if (text.ToLower() == "close"){
  112.                 recDataCurrentConnection.Shutdown(SocketShutdown.Both);
  113.                 recDataCurrentConnection.Close();
  114.                 clientSockets.Remove(recDataCurrentConnection);
  115.                 dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Client disconnected.\r\n"); }));
  116.                 return;
  117.             }
  118.             else{
  119.                 dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Text is an invalid reques!\n\r"); }));
  120.                 byte[] data = Encoding.ASCII.GetBytes("Invalid request");
  121.                 recDataCurrentConnection.Send(data);
  122.                 dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Warning sent\r\n"); }));
  123.             }
  124.             try
  125.             {
  126.                 recDataCurrentConnection.BeginReceive(buffer, 0, bufferSize, SocketFlags.None, ReceiveCallback, recDataCurrentConnection);
  127.             }
  128.             catch(Exception e){
  129.                 MessageBox.Show("Client disconnected from app\r\n" + e.Message + "\r\n");
  130.             }
  131.            
  132.         }
  133.         //-------------------------------------------------------------------------------------------
  134.         /*
  135.             * This function is use to get local ip adres of used computer
  136.         */
  137.         private static string GetLocalIPAddress()
  138.         {
  139.             var host = Dns.GetHostEntry(Dns.GetHostName());
  140.             foreach (var ip in host.AddressList)
  141.             {
  142.                 if (ip.AddressFamily == AddressFamily.InterNetwork)
  143.                 {
  144.                     return ip.ToString();
  145.                 }
  146.             }
  147.             throw new Exception("Local IP Address Not Found!");
  148.         }
  149.         //-----------------------------------------------------------------------------------------
  150.         /*
  151.             * Start server application
  152.         */
  153.         private void startBtn_Click(object sender, EventArgs e)
  154.         {
  155.             IPAddress setIp = null;                                  /* Pass any Ip adress into variable */
  156.             int port = System.Convert.ToInt16(portNumericUpDown.Value);
  157.             dataListBox.Items.Add("Start setting serwer\r\n");
  158.             try
  159.             {
  160.                 setIp = IPAddress.Parse(ipTextBox.Text);
  161.             }
  162.             catch (Exception ex)
  163.             {
  164.                 MessageBox.Show("Wrong IP format: " + ex.ToString(), "ERROR");
  165.                 ipTextBox.Text = String.Empty;
  166.                 return;
  167.             }
  168.             serverSocket.ExclusiveAddressUse = true;                 /* No other socket can't bing to selected port */
  169.             serverSocket.LingerState = new LingerOption(true, 10);   /* The socket will linger for 10 seconds after calling Socket.Close */
  170.             serverSocket.NoDelay = true;                             /* Disable the Nagle Algorithm for this tcp socket */
  171.             serverSocket.ReceiveBufferSize = 8192;                   /* Set the receive buffer size to 8k */
  172.             serverSocket.ReceiveTimeout = 1000;
  173.             serverSocket.Ttl = 42;                                   /* Set the Time To Live (TTL) to 42 router hops */
  174.             serverSocket.Bind(new IPEndPoint(setIp, 1001));         /* Connect with define port and set IP */
  175.             serverSocket.Listen(15);                                 /* Max connection that can wait in queue */
  176.             serverSocket.BeginAccept(AcceptCallback, null);         /* Begins an asynchronous operation to accept
  177.                                                                        an incoming connection attempt */
  178.             dataListBox.Items.Add("Start set up\r\n");
  179.             startBtn.Enabled = false;
  180.             serverStopBtn.Enabled = true;
  181.         }
  182.         //-----------------------------------------------------------------------------------------
  183.         /*
  184.             * Send entered data to client
  185.         */
  186.         private void sendDataBtn_Click(object sender, EventArgs e)
  187.         {
  188.             byte[] data = null;
  189.             if (sendMsgTextBox.Text.Length > 0){
  190.                 data = Encoding.ASCII.GetBytes(sendMsgTextBox.Text);
  191.             }
  192.             else{
  193.                 return;
  194.             }
  195.             if(startBtn.Enabled == false)
  196.             {
  197.                 sendListBox.Invoke(new Action(delegate () { sendListBox.Items.Add("Send command: " +System.Text.Encoding.UTF8.GetString(data) + "\r\n"); }));
  198.                 foreach (Socket socket in clientSockets)
  199.                 {
  200.                     socket.Send(data);
  201.                 }
  202.             }
  203.         }
  204.         //-----------------------------------------------------------------------------------------
  205.         private void sendMsgTextBox_KeyDown(object sender, KeyEventArgs e)
  206.         {
  207.             if(e.KeyCode == Keys.Enter)
  208.             {
  209.                 sendDataBtn_Click(this, new EventArgs());
  210.             }
  211.         }
  212.         //-----------------------------------------------------------------------------------------
  213.         /*
  214.             * Close all sockets
  215.         */
  216.         private void serverStopBtn_Click(object sender, EventArgs e)
  217.         {
  218.             CloseAllSockets();
  219.             dataListBox.Items.Add("Server close, client disconnected\r\n");
  220.             startBtn.Enabled = true;
  221.             serverStopBtn.Enabled = false;
  222.         }
  223.         //-----------------------------------------------------------------------------------------
  224.     }
  225. }

Aplikacja klient:

Okno aplikacji klienta prezentuje się właściwie tak samo jak serwera:


Aplikacja po wysłaniu odpowiedniej komendy pobiera odpowiedź od serwera i wyświetla ją użytkownikowi na ekranie. Jeśli serwer prześle informacje dla klienta to ten je odbierze i wyświetli w zakładce Receive Data.

Dane odbierana są za pomocą klasy backgroud worker. Wykorzystane są dwie funkcje jedna sprawdza czy zostały dane odebrane, druga po odebraniu danych ponownie uruchamia pierwszą.

Kod aplikacji z komentarzem:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Drawing;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Threading.Tasks;
  9. using System.Windows.Forms;
  10. using System.Net;
  11. using System.Net.Sockets;
  12. using System.IO;
  13. namespace TCP_Client_Window_App
  14. {
  15.     public partial class Form1 : Form
  16.     {
  17.         BackgroundWorker backgroundWorker1;
  18.         /* Port for connection with server */
  19.         private const int port = 1001;    
  20.         /*
  21.         *   InterNetwork: Address for IP in version 4
  22.         *   Stream: Support two way connection-based byte wihout duplication of data
  23.         *   ProtocolType: Transmission Control Protocol
  24.         */
  25.         private static readonly Socket ClientSocket = new Socket
  26.             (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  27.         private static byte[] buffer = new byte[8196];
  28.        
  29.         //-----------------------------------------------------------------------------------------
  30.         public Form1()
  31.         {
  32.             InitializeComponent();
  33.         }
  34.         //-----------------------------------------------------------------------------------------
  35.         private void InitializeBackgroundWorker()
  36.         {
  37.             backgroundWorker1 = new BackgroundWorker();
  38.             backgroundWorker1.DoWork += backgroundWorker1_DoWork;
  39.             backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
  40.         }
  41.         //-----------------------------------------------------------------------------------------
  42.         private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
  43.         {
  44.             BackgroundWorker worker = sender as BackgroundWorker;
  45.             e.Result = ReceiveResponse();
  46.         }
  47.         private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
  48.         {
  49.             backgroundWorker1.RunWorkerAsync();
  50.         }
  51.         //-----------------------------------------------------------------------------------------
  52.         /* Close socket and disconnect client from server */
  53.         private void CloseSocket()
  54.         {
  55.             SendString("close");                         /* Send close dat to server*/
  56.             ClientSocket.Shutdown(SocketShutdown.Both);  /* Block communication */
  57.             ClientSocket.Close();                        /* Close client */
  58.             Environment.Exit(0);
  59.         }
  60.         //-----------------------------------------------------------------------------------------
  61.         private byte ConnectToServer()
  62.         {
  63.             int connectAttempt = 0;                 /* Number of connection attempts before good connection */
  64.             while (!ClientSocket.Connected)         /* Wait for establish connection, block before connection will be set */
  65.             {
  66.                 try
  67.                 {
  68.                     connectAttempt++;                                   /* Add variable for next connection */
  69.                     dataListBox.Items.Add("Connection attempts: " + connectAttempt + \r\n");
  70.                     ClientSocket.Connect(IPAddress.Loopback, port);     /* Set IPLoopback (127.0.0.1) to remote IP */
  71.                 }
  72.                 catch (SocketException ex)
  73.                 {
  74.                                                                         /* Console clear and display error message */
  75.                     dataListBox.Items.Clear();
  76.                     dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Error: " + ex.Message + \r\n"); }));
  77.                 }
  78.                 if (connectAttempt == 100)
  79.                 {
  80.                     dataListBox.Items.Clear();
  81.                     dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Can't connect to server: " + connectAttempt + \r\n"); }));
  82.                     CloseSocket();
  83.                     return 0;
  84.                 }
  85.             }
  86.             if (ClientSocket.Connected)
  87.             {
  88.             }
  89.             return 1;
  90.         }
  91.         //-----------------------------------------------------------------------------------------
  92.         /*
  93.             * @brief: enable client, connect to server
  94.         */
  95.         private void startBtn_Click(object sender, EventArgs e)
  96.         {
  97.             if(ConnectToServer() == 1)
  98.             {
  99.                 /* Clear list box */
  100.                 dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Connected to server" + "\r\n"); }));
  101.                 InitializeBackgroundWorker();
  102.                 backgroundWorker1.RunWorkerAsync();
  103.                 clientStopBtn.Enabled = true;
  104.                 sendDataBtn.Enabled = true;
  105.                 startBtn.Enabled = false;
  106.             }
  107.         }
  108.         //-----------------------------------------------------------------------------------------
  109.         /*
  110.          *  @brief: Send string using ASCII encoding
  111.          *  @param: string text: data to send to server
  112.          *  @ret:   none
  113.          */
  114.         private static void SendString(string text)
  115.         {
  116.             byte[] buffer = Encoding.ASCII.GetBytes(text);
  117.             /* Send data to server, no flags is use to establish communication */
  118.             ClientSocket.Send(buffer, 0, buffer.Length, SocketFlags.None);
  119.         }
  120.         //-----------------------------------------------------------------------------------------
  121.         private int ReceiveResponse()
  122.         {
  123.             var buffer = new byte[8196];
  124.             int received = 0;
  125.             try{
  126.                 received = ClientSocket.Receive(buffer, SocketFlags.None);  /* Receive data */
  127.             }
  128.             catch(Exception e){
  129.                 MessageBox.Show("Server disconnected from app\n" + e.Message);
  130.                 Environment.Exit(0);
  131.             }
  132.              if (received == 0)
  133.              {
  134.                 sendListBox.Invoke(new Action(delegate () { sendListBox.Items.Add("No data was received \r\n"); }));
  135.              }
  136.              else {
  137.                  var data = new byte[received];
  138.                  Array.Copy(buffer, data, received);
  139.                  string text = Encoding.ASCII.GetString(data);
  140.                  dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add(text + "\r\n"); }));
  141.              }
  142.              return received;
  143.         }
  144.         //-----------------------------------------------------------------------------------------
  145.         /*
  146.             * @brief: send data to server when click on button
  147.         */
  148.         private void sendDataBtn_Click(object sender, EventArgs e)
  149.         {
  150.             byte[] data = null;
  151.             if (sendMsgTextBox.Text.Length > 0)
  152.             {
  153.                 data = Encoding.ASCII.GetBytes(sendMsgTextBox.Text);
  154.             }
  155.             else {
  156.                 return;
  157.             }
  158.             if (startBtn.Enabled == false)
  159.             {
  160.                 sendListBox.Invoke(new Action(delegate () { sendListBox.Items.Add("Send command: " +System.Text.Encoding.UTF8.GetString(data) + "\r\n"); }));
  161.                 SendString(sendMsgTextBox.Text);
  162.             }
  163.         }
  164.         //-----------------------------------------------------------------------------------------
  165.         /*
  166.             * @brief: stop client
  167.         */
  168.         private void clientStopBtn_Click(object sender, EventArgs e)
  169.         {
  170.             ClientSocket.Shutdown(SocketShutdown.Both);   /* Block sending and receiving */
  171.             clientStopBtn.Enabled = false;
  172.             sendDataBtn.Enabled = false;
  173.             startBtn.Enabled = true;
  174.         }
  175.         //-----------------------------------------------------------------------------------------
  176.         /*
  177.             * @brief: send message to server, when click enter button on sendMsgTextBox
  178.         */
  179.         private void sendMsgTextBox_KeyDown(object sender, KeyEventArgs e)
  180.         {
  181.             if (e.KeyCode == Keys.Enter)
  182.             {
  183.                 sendDataBtn_Click(this, new EventArgs());
  184.             }
  185.         }
  186.         //-----------------------------------------------------------------------------------------
  187.     }
  188. }

W programie wykorzystano łączenie z serwerem na podstawie komendy:

  1.  ClientSocket.Connect(IPAddress.Loopback, port);

IPAddress.Loopback odpowiada wartości 127.0.0.1. Aby połączyć się z określonym adresem w polu tekstowym należy go podać do komendy jako string:


  1.  ClientSocket.Connect(ipTextBox.Text, port);

Pliki z projektem można znaleźć na dysku Google pod tym linkiem.

Wymaga ona jeszcze trochę poprawek, ale jak na pierwszą wersję to działa bardzo dobrze.