How to send file through socket in c#

后端 未结 3 1867
再見小時候
再見小時候 2021-01-19 12:53

I have server and client console apps which communicate fine as well as sending some string. Here is the code...

Server

public static void Main()
            


        
相关标签:
3条回答
  • 2021-01-19 13:05

    File.ReadAllBytes(string path) will get you a Byte Array of a file.
    File.WriteAllBytes(string path, byte[] bytes) will write it to the disk.
    To help distinguish between file/command/status/etc. content of the message you could add a byte header in front. Enum : byte might come in handy here.

    0 讨论(0)
  • 2021-01-19 13:07

    As requested, you could do the following in the client side - as suggested by this post, except that you may want to consider to change the sync Send part into async Send like this:

    clientSocket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, endSendCallback, clientSocket); //use async
    

    And your callback may look like this:

    private static void endSendCallback(IAsyncResult ar) {
        try {
            SocketError errorCode;
            int result = clientSocket.EndSend(ar, out errorCode);
            Console.WriteLine(errorCode == SocketError.Success ?
                "Successful! The size of the message sent was :" + result.ToString() :
                "Error with error code: " + errorCode.ToString() //you probably want to consider to resend if there is error code, but best practice is to handle the error one by one
            );
        } catch (Exception e) { //exception
            Console.WriteLine("Unhandled EndSend Exception! " + e.ToString());
            //do something like retry or just report that the sending fails
            //But since this is an exception, it probably best NOT to retry
        }
    }
    

    As for how the complete client side code after the above change:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace TcpClientConsoleApplication {
        class Program {
            const int PORT_NO = 2201;
            const string SERVER_IP = "127.0.0.1";
            static Socket clientSocket; //put here
            static void Main(string[] args) {
                //Similarly, start defining your client socket as soon as you start. 
                clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                loopConnect(3, 3); //for failure handling
                string result = "";
                do {
                    result = Console.ReadLine(); //you need to change this part
                    if (result.ToLower().Trim() != "exit") {
                        byte[] bytes = Encoding.ASCII.GetBytes(result); //Again, note that your data is actually of byte[], not string
                        //do something on bytes by using the reference such that you can type in HEX STRING but sending thing in bytes
                        clientSocket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, endSendCallback, clientSocket); //use async
                        //clientSocket.Send(bytes); use this for sync send
                    }
                } while (result.ToLower().Trim() != "exit");
            }
    
            private static void endSendCallback(IAsyncResult ar) {
                try {
                    SocketError errorCode;
                    int result = clientSocket.EndSend(ar, out errorCode);
                    Console.WriteLine(errorCode == SocketError.Success ?
                        "Successful! The size of the message sent was :" + result.ToString() :
                        "Error with error code: " + errorCode.ToString() //you probably want to consider to resend if there is error code, but best practice is to handle the error one by one
                    );
                } catch (Exception e) { //exception
                    Console.WriteLine("Unhandled EndSend Exception! " + e.ToString());
                    //do something like retry or just report that the sending fails
                    //But since this is an exception, it probably best NOT to retry
                }
            }
    
            static void loopConnect(int noOfRetry, int attemptPeriodInSeconds) {
                int attempts = 0;
                while (!clientSocket.Connected && attempts < noOfRetry) {
                    try {
                        ++attempts;
                        IAsyncResult result = clientSocket.BeginConnect(IPAddress.Parse(SERVER_IP), PORT_NO, endConnectCallback, null);
                        result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(attemptPeriodInSeconds));
                        System.Threading.Thread.Sleep(attemptPeriodInSeconds * 1000);
                    } catch (Exception e) {
                        Console.WriteLine("Error: " + e.ToString());
                    }
                }
                if (!clientSocket.Connected) {
                    Console.WriteLine("Connection attempt is unsuccessful!");
                    return;
                }
            }
    
            private const int BUFFER_SIZE = 4096;
            private static byte[] buffer = new byte[BUFFER_SIZE]; //buffer size is limited to BUFFER_SIZE per message
            private static void endConnectCallback(IAsyncResult ar) {
                try {
                    clientSocket.EndConnect(ar);
                    if (clientSocket.Connected) {
                        clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), clientSocket);
                    } else {
                        Console.WriteLine("End of connection attempt, fail to connect...");
                    }
                } catch (Exception e) {
                    Console.WriteLine("End-connection attempt is unsuccessful! " + e.ToString());
                }
            }
    
            const int MAX_RECEIVE_ATTEMPT = 10;
            static int receiveAttempt = 0;
            private static void receiveCallback(IAsyncResult result) {
                System.Net.Sockets.Socket socket = null;
                try {
                    socket = (System.Net.Sockets.Socket)result.AsyncState;
                    if (socket.Connected) {
                        int received = socket.EndReceive(result);
                        if (received > 0) {
                            receiveAttempt = 0;
                            byte[] data = new byte[received];
                            Buffer.BlockCopy(buffer, 0, data, 0, data.Length); //There are several way to do this according to https://stackoverflow.com/questions/5099604/any-faster-way-of-copying-arrays-in-c in general, System.Buffer.memcpyimpl is the fastest
                            //DO ANYTHING THAT YOU WANT WITH data, IT IS THE RECEIVED PACKET!
                            //Notice that your data is not string! It is actually byte[]
                            //For now I will just print it out
                            Console.WriteLine("Server: " + Encoding.UTF8.GetString(data));
                            socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
                        } else if (receiveAttempt < MAX_RECEIVE_ATTEMPT) { //not exceeding the max attempt, try again
                            ++receiveAttempt;
                            socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
                        } else { //completely fails!
                            Console.WriteLine("receiveCallback is failed!");
                            receiveAttempt = 0;
                            clientSocket.Close();
                        }
                    }
                } catch (Exception e) { // this exception will happen when "this" is be disposed...
                    Console.WriteLine("receiveCallback is failed! " + e.ToString());
                }
            }
        }
    }
    

    The complete explanation on how the above pieces of code work (note that point 7 is changed and point 8 is added for async Send):

    Client:

    1. Similarly, put the Socket class in the class context rather than method context and initialize it as soon as you start your program

      const int PORT_NO = 2201;
      const string SERVER_IP = "127.0.0.1";
      static Socket clientSocket; //put here
      static void Main(string[] args) {
          //Similarly, start defining your client socket as soon as you start. 
          clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
      
          //your other main routines
      }
      
    2. Then start to connect by ASync BeginConnect. I would normally go further by LoopConnect just for failure handling like this.

      static void loopConnect(int noOfRetry, int attemptPeriodInSeconds) {
          int attempts = 0;
          while (!clientSocket.Connected && attempts < noOfRetry) {
              try {
                  ++attempts;
                  IAsyncResult result = clientSocket.BeginConnect(IPAddress.Parse(SERVER_IP), PORT_NO, endConnect, null);
                  result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(attemptPeriodInSeconds));
                  System.Threading.Thread.Sleep(attemptPeriodInSeconds * 1000);
              } catch (Exception e) {
                  Console.WriteLine("Error: " + e.ToString());
              }
          }
          if (!clientSocket.Connected) {
              Console.WriteLine("Connection attempt is unsuccessful!");
              return;
          }
      }
      
    3. Similar concept to what you do to the server BeginAccept you need to define endConnectCallback for the ASync BeginConnect you use. But here, unlike server which needs to re-calling BeginAccept, once you are connected, you do not need to do any new BeginConnect since you only need to be connected once.

    4. You may want to declare buffer etc. Then, after you connect, don't forget the next ASync BeginReceive to handle the message retrieval part (similar with the server)

      private const int BUFFER_SIZE = 4096;
      private static byte[] buffer = new byte[BUFFER_SIZE]; //buffer size is limited to BUFFER_SIZE per message
      private static void endConnectCallback(IAsyncResult ar) {
          try {
              clientSocket.EndConnect(ar);
              if (clientSocket.Connected) {
                  clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), clientSocket);
              } else {
                  Console.WriteLine("End of connection attempt, fail to connect...");
              }
          } catch (Exception e) {
              Console.WriteLine("End-connection attempt is unsuccessful! " + e.ToString());
          }
      }
      
    5. Naturally, you need to define your receiveCallback, just like what you did for the server. And yes, it is as you have guessed, it is almost identical to what you did for the server!

    6. You can do anything you want with your data. Note that the data you receive is actually in byte[], not string. So you can do anything with it. But for example's sake, I will just use string to display.

      const int MAX_RECEIVE_ATTEMPT = 10;
      static int receiveAttempt = 0;
      private static void receiveCallback(IAsyncResult result) {
          System.Net.Sockets.Socket socket = null;
          try {
              socket = (System.Net.Sockets.Socket)result.AsyncState;
              if (socket.Connected) {
                  int received = socket.EndReceive(result);
                  if (received > 0) {
                      receiveAttempt = 0;
                      byte[] data = new byte[received];
                      Buffer.BlockCopy(buffer, 0, data, 0, data.Length); //copy the data from your buffer
                      //DO ANYTHING THAT YOU WANT WITH data, IT IS THE RECEIVED PACKET!
                      //Notice that your data is not string! It is actually byte[]
                      //For now I will just print it out
                      Console.WriteLine("Server: " + Encoding.UTF8.GetString(data));
                      socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
                  } else if (receiveAttempt < MAX_RECEIVE_ATTEMPT) { //not exceeding the max attempt, try again
                      ++receiveAttempt;
                      socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
                  } else { //completely fails!
                      Console.WriteLine("receiveCallback is failed!");
                      receiveAttempt = 0;
                      clientSocket.Close();
                  }
              }
          } catch (Exception e) { // this exception will happen when "this" is be disposed...
              Console.WriteLine("receiveCallback is failed! " + e.ToString());
          }
      }
      
    7. And next one (before the very last) -- Yes, again, as you have already guessed, you just need to do something on your main routine - suppose you want to use it to BeginSend data. Because you use Console but you want it to send things as byte[], you need to do the conversion (see the explanation in server 9 of the linked post).

      static void Main(string[] args) {
          //Similarly, start defining your client socket as soon as you start. 
          clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
          loopConnect(3, 3); //for failure handling
          string result = "";
          do {
              result = Console.ReadLine(); //you need to change this part
              if (result.ToLower().Trim() != "exit") {
                  byte[] bytes = Encoding.ASCII.GetBytes(result); //Again, note that your data is actually of byte[], not string
                  //do something on bytes by using the reference such that you can type in HEX STRING but sending thing in bytes
                  clientSocket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, endSendCallback, clientSocket); //use async
                  //clientSocket.Send(bytes); use this for sync send
              }
          } while (result.ToLower().Trim() != "exit");
      }
      
    8. And finally, at the very very last, you only need to declare the async EndSend callback function and you are done!

      private static void endSendCallback(IAsyncResult ar) {
          try {
              SocketError errorCode;
              int result = clientSocket.EndSend(ar, out errorCode);
              Console.WriteLine(errorCode == SocketError.Success ?
                  "Successful! The size of the message sent was :" + result.ToString() :
                  "Error with error code: " + errorCode.ToString() //you probably want to consider to resend if there is error code, but best practice is to handle the error one by one
              );
          } catch (Exception e) { //exception
              Console.WriteLine("Unhandled EndSend Exception! " + e.ToString());
              //do something like retry or just report that the sending fails
              //But since this is an exception, it probably best NOT to retry
          }
      }
      
    0 讨论(0)
  • 2021-01-19 13:11

    You just need to read the file in as a byte array and then send that byte array across the wire

    var bytes = File.ReadAllBytes(path);
    stm.Write(bytes , 0, bytes .Length);
    
    0 讨论(0)
提交回复
热议问题