如何在C#中通过套接字发送文件

4
我有服务器和客户端控制台应用程序,它们可以很好地进行通信并发送一些字符串。以下是代码...
服务器
public static void Main()
    {
        try
        {
            IPAddress ipAd = IPAddress.Parse("127.0.0.1"); 

            /* Initializes the Listener */
            TcpListener myList = new TcpListener(ipAd, 1234);

            /* Start Listeneting at the specified port */
            myList.Start();

            Console.WriteLine("The server is running at port 8001...");
            Console.WriteLine("The local End point is  :" + myList.LocalEndpoint);
            Console.WriteLine("Waiting for a connection.....");

            Socket s = myList.AcceptSocket();
            Console.WriteLine("Connection accepted from " + s.RemoteEndPoint);

            byte[] b = new byte[100];
            int k = s.Receive(b);
            Console.WriteLine("Recieved...");
            for (int i = 0; i < k; i++)
                Console.Write(Convert.ToChar(b[i]));

            ASCIIEncoding asen = new ASCIIEncoding();
            s.Send(asen.GetBytes("The string was recieved by the server."));
            Console.WriteLine("\nSent Acknowledgement");
            /* clean up */
            s.Close();
            myList.Stop();

        }
        catch (Exception e)
        {
            Console.WriteLine("Error..... " + e.StackTrace);
        }
    }

客户
public static void Main()
    {
        try
        {
            TcpClient tcpclnt = new TcpClient();
            Console.WriteLine("Connecting...");

            tcpclnt.Connect("127.0.0.1", 1234);

            Console.WriteLine("Connected");
            Console.Write("Enter the string to be transmitted: ");

            String str = Console.ReadLine();
            Stream stm = tcpclnt.GetStream();

            ASCIIEncoding asen = new ASCIIEncoding();
            byte[] ba = asen.GetBytes(str);
            Console.WriteLine("Transmitting...");

            stm.Write(ba, 0, ba.Length);

            byte[] bb = new byte[100];
            int k = stm.Read(bb, 0, 100);

            for (int i = 0; i < k; i++)
                Console.Write(Convert.ToChar(bb[i]));

            tcpclnt.Close();
        }

        catch (Exception e)
        {
            Console.WriteLine("Error... " + e.StackTrace);
        }
    }

现在,我需要添加发送文件代码算法到相同的应用程序。关键的实现是在客户端中使用Socket.Send。我不确定文件的序列化和反序列化。
任何提示、建议或建议都非常欢迎。谢谢。

你的意思是只为客户端吗?我现在没有电脑,只能通过手机访问。如果你愿意,我可以明天发布它。我可能还会更新一下,改成异步发送(而不是同步发送)。 - Ian
3个回答

7

如您所要求,在客户端可以按照这篇文章中所建议的做以下操作,不过您可能需要考虑将同步的Send部分改为异步的Send,像这样:

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

而您的回调函数可能如下所示:

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
    }
}

针对以上更改后的完整客户端代码:

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://dev59.com/km435IYBdhLWcg3w-1V_ 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());
            }
        }
    }
}

上述代码的完整解释(注意异步Send时进行了更改)如下所示(第7点已更改,第8点已添加): 客户端:
  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
        }
    }
    

1
你只需要将文件读取为字节数组,然后将该字节数组通过网络发送即可。
var bytes = File.ReadAllBytes(path);
stm.Write(bytes , 0, bytes .Length);

如何将字节转换为二进制? - Cloud Cho

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接