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.
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.
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Data;
- using System.Drawing;
- using System.Linq;
- using System.Text;
- using System.Net;
- using System.Net.Sockets;
- using System.Threading.Tasks;
- using System.Windows.Forms;
- namespace CSharp_Window_ServerTCP
- {
- public partial class Server : Form
- {
- private const int bufferSize = 8192; /* Data buffer size */
- private const int port = 1001; /* Port to establish communication */
- /*
- * InterNetwork: Address for IP in version 4
- * Stream: Support two way connection-based byte wihout duplication of data
- * ProtocolType: Transmission Control Protocol
- */
- private static readonly Socket serverSocket = new Socket
- (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- private static readonly List<Socket> clientSockets = new List<Socket>(); /* List of connected sockets*/
- private static readonly byte[] buffer = new byte[bufferSize]; /* buffer with data to send */
- //-----------------------------------------------------------------------------------------
- public Server()
- {
- InitializeComponent();
- }
- //-----------------------------------------------------------------------------------------
- /*
- * Close all connected clients, blocks sending and receiving data from client, it is more
- * flexible way then close who destoy the connection
- */
- void CloseAllSockets()
- {
- foreach (Socket socket in clientSockets)
- {
- socket.Shutdown(SocketShutdown.Both); /* Block sending and receiving */
- /*
- or
- socket.Close();
- */
- }
- dataListBox.Items.Add("All sockets has been closed\r\n");
- }
- //-------------------------------------------------------------------------------------------
- /*
- * Accept Callback function
- */
- void AcceptCallback(IAsyncResult AR)
- {
- Socket socket;
- try{
- socket = serverSocket.EndAccept(AR); /* Asynchronously accepts an incoming connection attempt
- and creates a new Socket to handle remote host communication. */
- }
- catch (ObjectDisposedException ex){
- dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Caught:" + ex.Message + "\r\n"); }));
- return;
- }
- clientSockets.Add(socket); /* Add new connection*/
- /* Begins to asynchnous receive data */
- socket.BeginReceive(buffer, 0, bufferSize, SocketFlags.None, ReceiveCallback, socket); /* Get data */
- // dataListBox.Items.Add("Client connected...");
- dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Client connected...\r\n"); }));
- serverSocket.BeginAccept(AcceptCallback, null); /* Begins asonchronyous operation for
- accepting try for connection */
- }
- //-------------------------------------------------------------------------------------------
- /*
- * Receive callback function, checks data that was send from client to server
- */
- void ReceiveCallback(IAsyncResult AR)
- {
- Socket recDataCurrentConnection = (Socket)AR.AsyncState;
- int received;
- try{
- received = recDataCurrentConnection.EndReceive(AR);
- }
- catch (SocketException ex){
- dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Client disconnected" + ex + "\r\n"); }));
- recDataCurrentConnection.Close(); /* Only close connection don't need to shutdown communication */
- clientSockets.Remove(recDataCurrentConnection); /* Remove client from socket list */
- return; /* Exit function */
- }
- byte[] recBuf = new byte[received];
- Array.Copy(buffer, recBuf, received);
- string text = Encoding.ASCII.GetString(recBuf);
- dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Received Message: " + text + "\r\n"); }));
- if (text.ToLower() == "time"){
- byte[] data = Encoding.ASCII.GetBytes(DateTime.Now.ToLongTimeString());
- recDataCurrentConnection.Send(data);
- sendListBox.Invoke(new Action(delegate () { sendListBox.Items.Add("Send time:" + System.Text.Encoding.UTF8.GetString(data) +"\r\n"); }));
- dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Message send to client" + "\r\n"); }));
- }
- else if (text.ToLower() == "date") {
- byte[] data = Encoding.ASCII.GetBytes(DateTime.Now.ToLongDateString());
- recDataCurrentConnection.Send(data);
- sendListBox.Invoke(new Action(delegate () { sendListBox.Items.Add("Send data:" + System.Text.Encoding.UTF8.GetString(data) +"\r\n"); }));
- dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Message send to client.\r\n"); }));
- }
- else if (text.ToLower() == "ip local"){
- byte[] data = Encoding.ASCII.GetBytes(GetLocalIPAddress());
- recDataCurrentConnection.Send(data);
- sendListBox.Invoke(new Action(delegate () { sendListBox.Items.Add("Send local ip: " +System.Text.Encoding.UTF8.GetString(data) + "\r\n"); }));
- dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Message send to client: \r\n"); }));
- }
- else if (text.ToLower() == "close"){
- recDataCurrentConnection.Shutdown(SocketShutdown.Both);
- recDataCurrentConnection.Close();
- clientSockets.Remove(recDataCurrentConnection);
- dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Client disconnected.\r\n"); }));
- return;
- }
- else{
- dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Text is an invalid reques!\n\r"); }));
- byte[] data = Encoding.ASCII.GetBytes("Invalid request");
- recDataCurrentConnection.Send(data);
- dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Warning sent\r\n"); }));
- }
- try
- {
- recDataCurrentConnection.BeginReceive(buffer, 0, bufferSize, SocketFlags.None, ReceiveCallback, recDataCurrentConnection);
- }
- catch(Exception e){
- MessageBox.Show("Client disconnected from app\r\n" + e.Message + "\r\n");
- }
- }
- //-------------------------------------------------------------------------------------------
- /*
- * This function is use to get local ip adres of used computer
- */
- private static string GetLocalIPAddress()
- {
- var host = Dns.GetHostEntry(Dns.GetHostName());
- foreach (var ip in host.AddressList)
- {
- if (ip.AddressFamily == AddressFamily.InterNetwork)
- {
- return ip.ToString();
- }
- }
- throw new Exception("Local IP Address Not Found!");
- }
- //-----------------------------------------------------------------------------------------
- /*
- * Start server application
- */
- private void startBtn_Click(object sender, EventArgs e)
- {
- IPAddress setIp = null; /* Pass any Ip adress into variable */
- int port = System.Convert.ToInt16(portNumericUpDown.Value);
- dataListBox.Items.Add("Start setting serwer\r\n");
- try
- {
- setIp = IPAddress.Parse(ipTextBox.Text);
- }
- catch (Exception ex)
- {
- MessageBox.Show("Wrong IP format: " + ex.ToString(), "ERROR");
- ipTextBox.Text = String.Empty;
- return;
- }
- serverSocket.ExclusiveAddressUse = true; /* No other socket can't bing to selected port */
- serverSocket.LingerState = new LingerOption(true, 10); /* The socket will linger for 10 seconds after calling Socket.Close */
- serverSocket.NoDelay = true; /* Disable the Nagle Algorithm for this tcp socket */
- serverSocket.ReceiveBufferSize = 8192; /* Set the receive buffer size to 8k */
- serverSocket.ReceiveTimeout = 1000;
- serverSocket.Ttl = 42; /* Set the Time To Live (TTL) to 42 router hops */
- serverSocket.Bind(new IPEndPoint(setIp, 1001)); /* Connect with define port and set IP */
- serverSocket.Listen(15); /* Max connection that can wait in queue */
- serverSocket.BeginAccept(AcceptCallback, null); /* Begins an asynchronous operation to accept
- an incoming connection attempt */
- dataListBox.Items.Add("Start set up\r\n");
- startBtn.Enabled = false;
- serverStopBtn.Enabled = true;
- }
- //-----------------------------------------------------------------------------------------
- /*
- * Send entered data to client
- */
- private void sendDataBtn_Click(object sender, EventArgs e)
- {
- byte[] data = null;
- if (sendMsgTextBox.Text.Length > 0){
- data = Encoding.ASCII.GetBytes(sendMsgTextBox.Text);
- }
- else{
- return;
- }
- if(startBtn.Enabled == false)
- {
- sendListBox.Invoke(new Action(delegate () { sendListBox.Items.Add("Send command: " +System.Text.Encoding.UTF8.GetString(data) + "\r\n"); }));
- foreach (Socket socket in clientSockets)
- {
- socket.Send(data);
- }
- }
- }
- //-----------------------------------------------------------------------------------------
- private void sendMsgTextBox_KeyDown(object sender, KeyEventArgs e)
- {
- if(e.KeyCode == Keys.Enter)
- {
- sendDataBtn_Click(this, new EventArgs());
- }
- }
- //-----------------------------------------------------------------------------------------
- /*
- * Close all sockets
- */
- private void serverStopBtn_Click(object sender, EventArgs e)
- {
- CloseAllSockets();
- dataListBox.Items.Add("Server close, client disconnected\r\n");
- startBtn.Enabled = true;
- serverStopBtn.Enabled = false;
- }
- //-----------------------------------------------------------------------------------------
- }
- }
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:
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:
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Data;
- using System.Drawing;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using System.Windows.Forms;
- using System.Net;
- using System.Net.Sockets;
- using System.IO;
- namespace TCP_Client_Window_App
- {
- public partial class Form1 : Form
- {
- BackgroundWorker backgroundWorker1;
- /* Port for connection with server */
- private const int port = 1001;
- /*
- * InterNetwork: Address for IP in version 4
- * Stream: Support two way connection-based byte wihout duplication of data
- * ProtocolType: Transmission Control Protocol
- */
- private static readonly Socket ClientSocket = new Socket
- (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- private static byte[] buffer = new byte[8196];
- //-----------------------------------------------------------------------------------------
- public Form1()
- {
- InitializeComponent();
- }
- //-----------------------------------------------------------------------------------------
- private void InitializeBackgroundWorker()
- {
- backgroundWorker1 = new BackgroundWorker();
- backgroundWorker1.DoWork += backgroundWorker1_DoWork;
- backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
- }
- //-----------------------------------------------------------------------------------------
- private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
- {
- BackgroundWorker worker = sender as BackgroundWorker;
- e.Result = ReceiveResponse();
- }
- private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
- {
- backgroundWorker1.RunWorkerAsync();
- }
- //-----------------------------------------------------------------------------------------
- /* Close socket and disconnect client from server */
- private void CloseSocket()
- {
- SendString("close"); /* Send close dat to server*/
- ClientSocket.Shutdown(SocketShutdown.Both); /* Block communication */
- ClientSocket.Close(); /* Close client */
- Environment.Exit(0);
- }
- //-----------------------------------------------------------------------------------------
- private byte ConnectToServer()
- {
- int connectAttempt = 0; /* Number of connection attempts before good connection */
- while (!ClientSocket.Connected) /* Wait for establish connection, block before connection will be set */
- {
- try
- {
- connectAttempt++; /* Add variable for next connection */
- dataListBox.Items.Add("Connection attempts: " + connectAttempt + " \r\n");
- ClientSocket.Connect(IPAddress.Loopback, port); /* Set IPLoopback (127.0.0.1) to remote IP */
- }
- catch (SocketException ex)
- {
- /* Console clear and display error message */
- dataListBox.Items.Clear();
- dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Error: " + ex.Message + " \r\n"); }));
- }
- if (connectAttempt == 100)
- {
- dataListBox.Items.Clear();
- dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Can't connect to server: " + connectAttempt + " \r\n"); }));
- CloseSocket();
- return 0;
- }
- }
- if (ClientSocket.Connected)
- {
- }
- return 1;
- }
- //-----------------------------------------------------------------------------------------
- /*
- * @brief: enable client, connect to server
- */
- private void startBtn_Click(object sender, EventArgs e)
- {
- if(ConnectToServer() == 1)
- {
- /* Clear list box */
- dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add("Connected to server" + "\r\n"); }));
- InitializeBackgroundWorker();
- backgroundWorker1.RunWorkerAsync();
- clientStopBtn.Enabled = true;
- sendDataBtn.Enabled = true;
- startBtn.Enabled = false;
- }
- }
- //-----------------------------------------------------------------------------------------
- /*
- * @brief: Send string using ASCII encoding
- * @param: string text: data to send to server
- * @ret: none
- */
- private static void SendString(string text)
- {
- byte[] buffer = Encoding.ASCII.GetBytes(text);
- /* Send data to server, no flags is use to establish communication */
- ClientSocket.Send(buffer, 0, buffer.Length, SocketFlags.None);
- }
- //-----------------------------------------------------------------------------------------
- private int ReceiveResponse()
- {
- var buffer = new byte[8196];
- int received = 0;
- try{
- received = ClientSocket.Receive(buffer, SocketFlags.None); /* Receive data */
- }
- catch(Exception e){
- MessageBox.Show("Server disconnected from app\n" + e.Message);
- Environment.Exit(0);
- }
- if (received == 0)
- {
- sendListBox.Invoke(new Action(delegate () { sendListBox.Items.Add("No data was received \r\n"); }));
- }
- else {
- var data = new byte[received];
- Array.Copy(buffer, data, received);
- string text = Encoding.ASCII.GetString(data);
- dataListBox.Invoke(new Action(delegate () { dataListBox.Items.Add(text + "\r\n"); }));
- }
- return received;
- }
- //-----------------------------------------------------------------------------------------
- /*
- * @brief: send data to server when click on button
- */
- private void sendDataBtn_Click(object sender, EventArgs e)
- {
- byte[] data = null;
- if (sendMsgTextBox.Text.Length > 0)
- {
- data = Encoding.ASCII.GetBytes(sendMsgTextBox.Text);
- }
- else {
- return;
- }
- if (startBtn.Enabled == false)
- {
- sendListBox.Invoke(new Action(delegate () { sendListBox.Items.Add("Send command: " +System.Text.Encoding.UTF8.GetString(data) + "\r\n"); }));
- SendString(sendMsgTextBox.Text);
- }
- }
- //-----------------------------------------------------------------------------------------
- /*
- * @brief: stop client
- */
- private void clientStopBtn_Click(object sender, EventArgs e)
- {
- ClientSocket.Shutdown(SocketShutdown.Both); /* Block sending and receiving */
- clientStopBtn.Enabled = false;
- sendDataBtn.Enabled = false;
- startBtn.Enabled = true;
- }
- //-----------------------------------------------------------------------------------------
- /*
- * @brief: send message to server, when click enter button on sendMsgTextBox
- */
- private void sendMsgTextBox_KeyDown(object sender, KeyEventArgs e)
- {
- if (e.KeyCode == Keys.Enter)
- {
- sendDataBtn_Click(this, new EventArgs());
- }
- }
- //-----------------------------------------------------------------------------------------
- }
- }
W programie wykorzystano łączenie z serwerem na podstawie komendy:
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:
Pliki z projektem można znaleźć na dysku Google pod tym linkiem.
- 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:
- 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.