c#中如何通过socket发送文件

Fer*_*vić 4 c# sockets console tcp

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

服务器

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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

客户

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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

现在,我需要添加将通过相同应用程序发送文件的代码算法。实现的关键是在客户端使用Socket.Send。我不确定文件的序列化和反序列化。

任何提示、建议、建议都更受欢迎。谢谢。

Ian*_*Ian 7

根据要求,您可以在客户端执行以下操作 - 正如这篇文章所建议的那样,除了您可能需要考虑将同步Send部分更改为这样的异步Send

clientSocket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, endSendCallback, clientSocket); //use async
Run Code Online (Sandbox Code Playgroud)

您的回调可能如下所示:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

至于上面改动后的完整客户端代码如何:

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 /sf/ask/356972311/ 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());
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

客户:

  1. 类似地,将Socket类放在类上下文中而不是方法上下文中,并在启动程序后立即对其进行初始化

    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
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 然后开始连接ASync BeginConnect。我通常会更进一步,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;
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. BeginAccept您需要endConnectCallbackASync BeginConnect您使用的服务器定义的服务器类似的概念。但是在这里,不像需要重新调用的服务器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());
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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());
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  7. 下一个(在最后一个之前)-- 是的,同样,正如您已经猜到的,您只需要在主例程中执行某些操作- 假设您想将其用于BeginSend数据。因为您使用Console但您希望它以 as 发送内容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");
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)