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

How to send file through socket in c#

我有服务器和客户端控制台应用程序,它们可以正常通信并发送一些字符串。这是代码...

服务器

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 。我不确定文件的序列化和反序列化。

欢迎任何提示、意见和建议。谢谢

您只需将文件作为字节数组读入,然后通过网络发送该字节数组

var bytes = File.ReadAllBytes(path);
stm.Write(bytes , 0, bytes .Length);

File.ReadAllBytes(string path) will get you a Byte Array of a file.
File.WriteAllBytes(string path, byte[] bytes) 将其写入磁盘。
帮助区分file/command/status/etc。消息内容可以在前面加一个字节header。 Enum : byte 在这里可能会派上用场。

根据要求,您可以在客户端执行以下操作 - 按照 的建议,除了您可能需要考虑将同步 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  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());
            }
        }
    }
}

关于上述代码段如何工作的完整解释(注意第 7 点已更改,并为异步添加第 8 点 Send):

客户:

  1. 同样,将Socket class放在class上下文而不是方法上下文中,并在启动程序后立即初始化它

    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. 然后ASyncBeginConnect开始连接。我通常会更进一步 LoopConnect 只是为了这样的故障处理。

    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. 类似于您对服务器所做的操作 BeginAccept 您需要为您使用的 ASync BeginConnect 定义 endConnectCallback。但是这里,不像服务器需要重新调用BeginAccept,一旦你连接上,你不需要做任何新的BeginConnect因为你只需要连接一次.

  4. 你可能想要声明buffer等。然后,在你连接之后,不要忘记接下来的ASync BeginReceive来处理消息检索部分(与服务器类似)

    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. 当然,您需要定义您的receiveCallback,就像您为服务器所做的一样。是的,正如您所猜到的,它与您为服务器所做的几乎相同!

  6. 您可以对数据做任何您想做的事情。请注意,您收到的数据实际上是 byte[],而不是 string。所以你可以用它做任何事情。但是为了举例,我就直接用string来显示吧。

    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. 下一个(在最后一个之前)——是的,再一次,正如你已经猜到的,你只需要在你的主例程上做一些事情——假设你想用它来 BeginSend数据。因为您使用 Console 但您希望它以 byte[] 发送内容,所以您需要进行转换(请参阅 的服务器 9 中的解释)。

    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. 最后,在最后,你只需要声明异步EndSend回调函数就大功告成了!

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