wtorek, 15 sierpnia 2017

C# - Aplikacje Klient, Serwer

W tym poście chciałbym przedstawić dwie aplikacje, jedna działa jako klient druga jako serwer. Każda z nich działa na jako aplikacja konsolowa.

Aplikacja Serwer konsola:


Procedurę uruchomienia należy rozpocząć od postawienia serwera. W funkcji tworzy się nowe połączenie o podanych parametrach. Następnie ustanawia się maksymalną liczbę akceptowalnych połączeń.Ostatnim elementem jest utworzenie tzw. callbacka, który będzie wywoływany w momencie otrzymania danych.

  1. private static void SetupServer()
  2. {
  3.     IPAddress setIp = IPAddress.Any;                        /* Pass any Ip adress into variable */
  4.     Console.WriteLine(@"Start setting up server...");
  5.     serverSocket.ExclusiveAddressUse = true;                 /* No other socket can't bing to selected port */
  6.     serverSocket.LingerState = new LingerOption(true10);   /* The socket will linger for 10 seconds after calling                                                             Socket.Close */
  7.     serverSocket.NoDelay = true;                             /* Disable the Nagle Algorithm for this tcp socket */
  8.     serverSocket.ReceiveBufferSize = 8192;                   /* Set the receive buffer size to 8k */
  9.     serverSocket.ReceiveTimeout = 1000;
  10.     serverSocket.Ttl = 42;                                   /* Set the Time To Live (TTL) to 42 router hops */
  11.     serverSocket.Bind(new IPEndPoint(setIp, port));         /* Connect with define port and set IP */
  12.     serverSocket.Listen(15);                                 /* Max connection that can wait in queue */
  13.     serverSocket.BeginAccept(AcceptCallback, null);         /* Begins an asynchronous operation to accept
  14.                                                                        an incoming connection attempt */
  15.     Console.WriteLine(@"Server setup complete");            
  16. }

Zamknięcie serwera odbywa się za pomocą tej funkcji. Sprawdza ona wszystkie uruchomione połączenia.

  1. private static void CloseAllSockets()
  2. {
  3.      foreach (Socket socket in clientSockets)
  4.      {
  5.         socket.Shutdown(SocketShutdown.Both);   /* Block sending and receiving */
  6.         /*
  7.            or
  8.            socket.Close();
  9.         */
  10.       }
  11.       Console.WriteLine(@"All sockets has been closed");
  12. }

Akceptowanie połączenia oraz odbieranie wiadomości wykonane jest na callbackach.

Akceptacja połączenia:

  1. private static void AcceptCallback(IAsyncResult AR)
  2. {
  3.     Socket socket;
  4.     try{
  5.         socket = serverSocket.EndAccept(AR);                    /* Asynchronously accepts an incoming connection attempt
  6.                                                                    and creates a new Socket to handle remote host communication. */
  7.     }
  8.     catch (ObjectDisposedException ex){
  9.           Console.WriteLine(@"Caught: {0}", ex.Message);  
  10.           return;
  11.     }
  12.     clientSockets.Add(socket);                                  /* Add new connection*/
  13.     /* Begins to asynchnous receive data */
  14.     socket.BeginReceive(buffer, 0, bufferSize, SocketFlags.None, ReceiveCallback, socket);   /* Get data */
  15.     Console.WriteLine(@"Client connected...");
  16.     serverSocket.BeginAccept(AcceptCallback, null);             /* Begins asonchronyous operation for
  17.                                                                    accepting try for connection */
  18. }

Odebranie oraz dekodowanie wiadomości:

  1. private static void ReceiveCallback(IAsyncResult AR)
  2. {
  3.     Socket recDataCurrentConnection = (Socket)AR.AsyncState;
  4.     int received;
  5.     try{
  6.       received = recDataCurrentConnection.EndReceive(AR);
  7.     }
  8.     catch (SocketException ex){
  9.         Console.WriteLine(@"Client disconnected" + ex);
  10.         recDataCurrentConnection.Close();  /* Only close connection don't need to shutdown communication */
  11.         clientSockets.Remove(recDataCurrentConnection);   /* Remove client from socket list */
  12.         return;      /* Exit from function */
  13.      }
  14.      byte[] recBuf = new byte[received];
  15.      Array.Copy(buffer, recBuf, received);
  16.      string text = Encoding.ASCII.GetString(recBuf);
  17.      Console.WriteLine(@"Received Message: " + text);
  18.      if (text.ToLower() == "time"){
  19.         Console.WriteLine("Send time:");
  20.         byte[] data = Encoding.ASCII.GetBytes(DateTime.Now.ToLongTimeString());
  21.         recDataCurrentConnection.Send(data);
  22.         Console.WriteLine("Message send to client:");
  23.      }
  24.      else if (text.ToLower() == "date") {
  25.         Console.WriteLine("Send date:");
  26.         byte[] data = Encoding.ASCII.GetBytes(DateTime.Now.ToLongDateString());
  27.         recDataCurrentConnection.Send(data);
  28.         Console.WriteLine("Message send to client:");
  29.      }
  30.      else if (text.ToLower() == "ip local"){
  31.         Console.WriteLine("Send local ip:");
  32.         byte[] data = Encoding.ASCII.GetBytes(GetLocalIPAddress());
  33.         recDataCurrentConnection.Send(data);
  34.         Console.WriteLine("Message send to client:");
  35.      }
  36.      else if (text.ToLower() == "close"){
  37.         recDataCurrentConnection.Shutdown(SocketShutdown.Both);
  38.         recDataCurrentConnection.Close();
  39.         clientSockets.Remove(recDataCurrentConnection);
  40.         Console.WriteLine("Client disconnected");
  41.         return;
  42.      }
  43.      else{
  44.         Console.WriteLine("Text is an invalid request");
  45.         byte[] data = Encoding.ASCII.GetBytes("Invalid request");
  46.         recDataCurrentConnection.Send(data);
  47.         Console.WriteLine("Warning Sent");
  48.      }
  49.      recDataCurrentConnection.BeginReceive(buffer, 0, bufferSize,
  50.      SocketFlags.None, ReceiveCallback, recDataCurrentConnection);
  51. }

Jedna z akceptowalnych połączeń pozwala na wyświetlenie w odpowiedzi ustawionego adresu IP:

  1. private static string GetLocalIPAddress()
  2. {
  3.     var host = Dns.GetHostEntry(Dns.GetHostName());
  4.     foreach (var ip in host.AddressList)
  5.     {
  6.        if (ip.AddressFamily == AddressFamily.InterNetwork)
  7.        {
  8.             return ip.ToString();
  9.        }
  10.     }
  11.     throw new Exception("Local IP Address Not Found!");
  12. }

Funkcja główna wygląda następująco:

Aplikacja Klient konsola:


W tym przypadku wykonywane jest połączenie z serwerem przy użyciu następującej funkcji:

  1. private static byte ConnectToServer()
  2. {
  3.     int connectAttempt = 0;             /* Number of connection attempts before good connection */
  4.     while (!ClientSocket.Connected)         /* Wait for establish connection, block before connection will be set */
  5.     {
  6.        try{
  7.           connectAttempt++;           /* Add variable for next connection */
  8.           Console.WriteLine(@"Connection attempts: " + connectAttempt);
  9.           ClientSocket.Connect(IPAddress.Loopback, port);     /* Set IPLoopback (127.0.0.1) to remote IP */
  10.        }
  11.        catch (SocketException ex){
  12.           Console.Clear();
  13.           Console.WriteLine("Error: {0}", ex.Message);    /* Console clear and display error message */
  14.        }
  15.        if(connectAttempt == 100) {
  16.           Console.Clear();
  17.           Console.WriteLine(@"Can't connect to server: " + connectAttempt);
  18.           CloseSocket();
  19.           return 0;
  20.        }
  21.      }
  22.      if(ClientSocket.Connected){
  23.         Console.Clear();
  24.         Console.WriteLine(@"Connected");
  25.      }
  26.      return 1;
  27. }

Funkcja wykonuje 100 prób połączenia. Jeśli uda się wykonać połączenie to funkcja zwraca jeden i przechodzi do dalszej części programu. Inaczej zwraca zero i program kończy działanie.

Odłączenie klienta od serwera wygląda następująco:

  1. private static void CloseSocket()
  2. {
  3.     SendString("close");                         /* Send close dat to server*/
  4.     ClientSocket.Shutdown(SocketShutdown.Both);  /* Block communication */
  5.     ClientSocket.Close();                        /* Close client */
  6.     Environment.Exit(0);                            
  7. }

Funkcje obsługujące dane wyglądają następująco:

  1. private static void RequestLoop()
  2. {
  3.     Console.WriteLine(@"Close command disconnect client from server");
  4.     while (true){
  5.       SendRequest();          /* Wait for sending command to server */
  6.       ReceiveResponse();
  7.     }
  8. }
  9. private static void SendRequest()
  10. {
  11.     Console.Write("Send a command: ");
  12.     string request = Console.ReadLine();    /* Read command */
  13.     SendString(request);                    /* Send data to server */
  14.     if (request.ToLower() == "close"){
  15.        CloseSocket();                      /* Close socket */
  16.     }
  17. }
  18. private static void SendString(string text)
  19. {
  20.     byte[] buffer = Encoding.ASCII.GetBytes(text);
  21.     /* Send data to server, no flags is use to establish communication */
  22.     ClientSocket.Send(buffer, 0, buffer.Length, SocketFlags.None);
  23. }
  24. private static void ReceiveResponse()
  25. {
  26.     var buffer = new byte[8196];
  27.     int received = ClientSocket.Receive(buffer, SocketFlags.None);  /* Receive data */
  28.     if (received == 0){
  29.        Console.WriteLine(@"No data was received");
  30.     }
  31.     else{
  32.         var data = new byte[received];
  33.         Array.Copy(buffer, data, received);
  34.         string text = Encoding.ASCII.GetString(data);
  35.         Console.WriteLine(text);
  36.     }                                      
  37. }

W pętli while przygotowywane są wiadomości do wysłania, następnie w kolejnej funkcji są one odbierane i przetwarzane tak aby je wyświetlić w konsoli. Do wysyłania użyta jest metodasocketu send, odbieranie obsługiwane jest przez receive. Wysłanie przez klienta wiadomości close powoduje odłączenie klienta od serwera oraz jego wyłączenie.

Main prezentuje się następująco:

  1. private const int port = 1001;      /* Port for connection with server */
  2. /*
  3. *   InterNetwork: Address for IP in version 4
  4. *   Stream: Support two way connection-based byte wihout duplication of data
  5. *   ProtocolType: Transmission Control Protocol
  6. */
  7. private static readonly Socket ClientSocket = new Socket
  8.     (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  9. static void Main()
  10. {
  11.   Console.Title = @"Client TCP";          /* Console window title */
  12.   /* Connect client to server */
  13.   if (1 == ConnectToServer()){
  14.      RequestLoop();      /* Loop for establish communication */
  15.      CloseSocket();      /* Close communication */
  16.   }
  17. }