【问题标题】:Sending a value from server to client with sockets使用套接字将值从服务器发送到客户端
【发布时间】:2016-01-04 07:48:12
【问题描述】:

我正在使用以下项目来创建serverclient 套接字之间的异步通信。当我运行这些项目时,我正在从客户端向服务器发送一条消息,因此我收到了这条消息:

数据:记录EOF,发送14字节给客户端。

我想要实现的是使用套接字将布尔变量从服务器发送到客户端。这样做是否可行,我想知道因为在代码中我有等待和监听的服务器以及发送数据的客户端,我可以做相反的事情吗?一般来说,我想要的是向几个客户发送一个布尔值。为什么我需要文件结尾才能发送字符串?是否有必要将所有内容都转换为字符串?

编辑:一般来说,我想要将一个变量从一台计算机发送到另外两台计算机,以便在所有计算机中同时开始一个进程。实际上是创建一个切换器,它发出信号以同时在 2-3 台机器中开始一个进程。

我尝试将以下代码用于服务器

class Program
{
    const int PORT_NO = 2201;
    const string SERVER_IP = "127.0.0.1";
    static void Main(string[] args)
    {
        //---listen at the specified IP and port no.---
        IPAddress localAdd = IPAddress.Parse(SERVER_IP);
        TcpListener listener = new TcpListener(localAdd, PORT_NO);
        Console.WriteLine("Listening...");
        listener.Start();
        //---incoming client connected---
        TcpClient client = listener.AcceptTcpClient();
        //---get the incoming data through a network stream---
        NetworkStream nwStream = client.GetStream();
        byte[] buffer = new byte[client.ReceiveBufferSize];
        //---read incoming stream---
        int bytesRead = nwStream.Read(buffer, 0, client.ReceiveBufferSize);
        //---convert the data received into a string---
        string dataReceived = Encoding.ASCII.GetString(buffer, 0, bytesRead);
        Console.WriteLine("Received : " + dataReceived);
        //---write back the text to the client---
        Console.WriteLine("Sending back : " + dataReceived);
        nwStream.Write(buffer, 0, bytesRead);
        client.Close();
        listener.Stop();
        Console.ReadLine();
    }
}

对于客户

class Program
{
    const int PORT_NO = 2201;
    const string SERVER_IP = "127.0.0.1";
    static void Main(string[] args)
    {
        //---data to send to the server---
        string textToSend = DateTime.Now.ToString();
        //---create a TCPClient object at the IP and port no.---
        TcpClient client = new TcpClient(SERVER_IP, PORT_NO);
        NetworkStream nwStream = client.GetStream();
        byte[] bytesToSend = ASCIIEncoding.ASCII.GetBytes(textToSend);
        //---send the text---
        Console.WriteLine("Sending : " + textToSend);
        nwStream.Write(bytesToSend, 0, bytesToSend.Length);
        //---read back the text---
        byte[] bytesToRead = new byte[client.ReceiveBufferSize];
        int bytesRead = nwStream.Read(bytesToRead, 0, client.ReceiveBufferSize);
        Console.WriteLine("Received : " + Encoding.ASCII.GetString(bytesToRead, 0, bytesRead));
        Console.ReadLine();
        client.Close();
    }
}

以防万一我在同一台机器上工作。我总共将拥有 4 台机器,我希望其中一台向其余的机器发出信号以开始录制 rgb 流。因此,服务器应该向客户端发送信号以开始记录。我应该怎么做才能改变服务器发送数据而不是监听的行为。是否可以让多台机器监听并等待发出信号?

编辑:

private void mouseClick1(object sender, MouseEventArgs e)
    {

        Thread thread = new Thread(() => StartServer());
        thread.Start();

        if (e.Button == MouseButtons.Left)
        {
            button5.Enabled = false;
            button3.Enabled = true;

            try
            {
                obj = new Capturer();
            }
            catch (Exception e1)
            {
                Console.WriteLine("The process failed: {0}", e1.ToString());
            }
        }
    }

    private void mouseClick2(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Right)
        {
            obj.flag2 = true;
        }
    }

我的代码现在是左键调用 startServer() 函数和一个新线程,这是@Ians 实现中的主要代码,然后我调用我的对象。当我单击右键时,我更改了一个标志并且捕获器停止了。我怎样才能停止服务器或暂停以便通过左键再次打开它?

【问题讨论】:

  • 据我所知,你可以,但方法我不知道,看看这个。 p.s.我记得是将要发送的数据转换为字节[] - codeproject.com/Articles/2477/…
  • 抱歉,刚刚发现这是c++,只需要在codeproject的网站上搜索c#的客户端服务器网络传输,我已经使用了这个网站的一个,希望对您有帮助
  • @Ian 感谢您的超级分析回答。我会尝试你所有的步骤。 :P
  • @JoseRamon 太棒了! :D 请注意,最后一步(服务器中的第 9 步和客户端中的第 7 步)是供您更改的。这就是为什么我提供的代码与那些步骤完全不同的原因。但是,我提供的代码是一个工作版本,没有您需要更改任何内容。此代码的想法是与您分享如何定义 Async 并将其工作。然后您只需更改最后一步以使其适应您的需要。=)

标签: c# sockets


【解决方案1】:

先回答问题:

问:是否有必要将所有内容都转换为字符串?...一般来说我 想要的是将变量从一台计算机发送到另外两台计算机,以便 进程在所有计算机中同时开始。

A:不,发送时不需要将所有内容都转换为字符串 使用Socket。您可以发送您最可能想要的byte[]

问:我想要实现的是从服务器发送一个布尔变量 使用套接字发送给客户端

A:你的意思是boolean 还是byte?因为你的基本变量类型 将从Socket 得到byte。你总是可以改变byte 从发送方/接收方发送到bool

bool val = byteToCheck > 0;

A2:而且由于你的服务器是Console应用程序,我建议采取 看看十六进制 stringbyte[] 的转换。这样,你可以 在string 中写一些东西,但将其解释为byte[]。查看 this。这里的整个想法非常简单。那就是:你输入 string,但它会以byte[] 发送。因为它是byte[] 你可以有任何价值。

在这里我提出我的解决方案来处理您的 (1) 多个客户端,(2) Async 连接 & 接受 & 接收,但有 (3) 发送同步,以及 (4) 从 hex string 转换为byte[](结构和想法),最后但并非最不重要的(5)带有用户输入的工作代码(供您更改此部分)用于测试!

我会使用简单的Socket 类来解决此类问题,因为这是我最熟悉的解决方案。但是如果你使用TcpListener.Server(这是Socket 类的底层网络),你总是可以做类似的事情。而且,如您所愿,我会使用 ASync 来做到这一点。

需要几个步骤才能在服务器和客户端中实现您想要的:


服务器

  1. 将您的Socket 设为类字段而不是方法字段,因为您将在任何地方使用 if 并且您需要多种方法来实现您想要的。并在您启动主程序后立即对其进行初始化。

    const int PORT_NO = 2201;
    const string SERVER_IP = "127.0.0.1";
    static Socket serverSocket; //put here as static
    static void Main(string[] args) {
        //---listen at the specified IP and port no.---
        Console.WriteLine("Listening...");
        serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT_NO));
        serverSocket.Listen(4); //the maximum pending client, define as you wish
        //your next main routine
    }
    
  2. 由于服务器将为许多客户端提供服务,因此我建议您使用ASync 而不是Sync 进行处理。使用 BeginAccept 而不是使用 Accept 初始化您的 Socket,将 acceptCallback 放入您的 BeginAccept

    static void Main(string[] args) {
        //---listen at the specified IP and port no.---
        Console.WriteLine("Listening...");
        serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT_NO));
        serverSocket.Listen(4); //the maximum pending client, define as you wish
        serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null);      
        //other stuffs
    }
    
  3. 定义acceptCallback,这是当您接受Socket 时您将去的地方。把EndAccept放在那里。

    private void acceptCallback(IAsyncResult result) { //if the buffer is old, then there might already be something there...
        System.Net.Sockets.Socket socket = null;
        try {
            socket = serverSocket.EndAccept(result); // To get your client socket
            //do something later
        } catch (Exception e) { // this exception will happen when "this" is be disposed...        
            //do something later
        }
    }
    
  4. 我通常会列出我的客户端套接字,并在客户端处置时做一些事情(即未列出) - 但这取决于需要。在这种情况下,您似乎需要它。并且不要忘记创建缓冲区等...这是用于缓冲传入数据。

  5. 开始接受从客户端收到的东西,在客户端Socket 上使用另一个ASync BeginReceive(现在你需要receiveCallback)。然后,非常重要,重复您的BeginAccept 以接受其他客户!

    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 List<Socket> clientSockets = new List<Socket>(); //may be needed by you
    private static void acceptCallback(IAsyncResult result) { //if the buffer is old, then there might already be something there...
        Socket socket = null;
        try {
            socket = serverSocket.EndAccept(result); // The objectDisposedException will come here... thus, it is to be expected!
            //Do something as you see it needs on client acceptance such as listing
            clientSockets.Add(socket); //may be needed later
            socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
            serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null); //to receive another client
        } catch (Exception e) { // this exception will happen when "this" is be disposed...        
            //Do something here
            Console.WriteLine(e.ToString());
        }
    }
    
  6. 定义您的receiveCallback,即当您从客户那里收到东西时。由于失败,这部分可能非常棘手!但基本上,您现在需要的只是 EndReceive 并且再次非常重要,从同一个客户端重复 BeginReceive,以便您可以收到它的下一条消息!

    const int MAX_RECEIVE_ATTEMPT = 10;
    static int receiveAttempt = 0; //this is not fool proof, obviously, since actually you must have multiple of this for multiple clients, but for the sake of simplicity I put this
    private static void receiveCallback(IAsyncResult result) {
        Socket socket = null;
        try {
            socket = (Socket)result.AsyncState; //this is to get the sender
            if (socket.Connected) { //simple checking
                int received = socket.EndReceive(result);
                if (received > 0) {
                    byte[] data = new byte[received]; //the data is in the byte[] format, not string!
                    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 SOMETHING ON THE DATA IN byte[] data!! Yihaa!!
                    Console.WriteLine(Encoding.UTF8.GetString(data)); //Here I just print it, but you need to do something else
                    receiveAttempt = 0; //reset receive attempt
                    socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); //repeat beginReceive
                } else if (receiveAttempt < MAX_RECEIVE_ATTEMPT) { //fail but not exceeding max attempt, repeats
                    ++receiveAttempt; //increase receive attempt;
                    socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); //repeat beginReceive
                } else { //completely fails!
                    Console.WriteLine("receiveCallback fails!"); //don't repeat beginReceive
                    receiveAttempt = 0; //reset this for the next connection
                }
            }
        } catch (Exception e) { // this exception will happen when "this" is be disposed...
            Console.WriteLine("receiveCallback fails with exception! " + e.ToString());
        }
    }
    
  7. 假设您想在收到消息后回复您的发件人,只需在if (received &gt; 0) 部分执行此操作:

    if (received > 0) {
        byte[] data = new byte[received]; //the data is in the byte[] format, not string!
        //DO SOMETHING ON THE DATA int byte[]!! Yihaa!!
        Console.WriteLine(Encoding.UTF8.GetString(data)); //Here I just print it, but you need to do something else                     
    
        //Message retrieval part
        //Suppose you only want to declare that you receive data from a client to that client
        string msg = "I receive your message on: " + DateTime.Now;
        socket.Send(Encoding.ASCII.GetBytes(msg)); //Note that you actually send data in byte[]
        Console.WriteLine("I sent this message to the client: " + msg);
    
        receiveAttempt = 0; //reset receive attempt
        socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); //repeat beginReceive
    }
    
  8. 在你的主程序中添加更多内容后,你就完成了(!) - 如果你不要求以byte[]发送给客户端

  9. 现在,如果您想以byte[] 的身份向所有您的客户发送内容,您只需列出您的所有客户(参见步骤 4-5)。请参阅this 并将上面的result string(记得根据需要以十六进制string 格式输入)为byte[],然后使用您的客户端套接字列表将其发送给所有客户端(这里是它的位置需要!):

    static void Main(string[] args) {
        //---listen at the specified IP and port no.---
        Console.WriteLine("Listening...");
        serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT_NO));
        serverSocket.Listen(4); //the maximum pending client, define as you wish
        serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null);      
    
        //normally, there isn't anything else needed here
        string result = "";
        do {
            result = Console.ReadLine();
            if (result.ToLower().Trim() != "exit") {
                byte[] bytes = null;
                //you can use `result` and change it to `bytes` by any mechanism which you want
                //the mechanism which suits you is probably the hex string to byte[]
                //this is the reason why you may want to list the client sockets
                foreach(Socket socket in clientSockets)
                    socket.Send(bytes); //send everything to all clients as bytes
            }
        } while (result.ToLower().Trim() != "exit");
    }
    

在这里,您或多或少地完成了您的服务器。接下来是你的客户


客户:

  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
    }
    
  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等。然后,在你连接之后,不要忘记下一个ASyncBeginReceive来处理消息检索部分(与服务器类似)

    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. 最后...是的,再次,正如您已经猜到的那样,您只需要在您的主程序上做一些事情 - 假设您想使用它来发送数据。因为您使用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.Send(bytes);
            }
        } while (result.ToLower().Trim() != "exit");
    }
    

结果:

给你!我通过发送string 进行显示测试,但是当您想将其更改为byte[] 时,我已经提出了所需的内容


测试代码:

服务器

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace TcpListenerConsoleApplication {
    class Program {
        const int PORT_NO = 2201;
        const string SERVER_IP = "127.0.0.1";
        static Socket serverSocket;
        static void Main(string[] args) {
            //---listen at the specified IP and port no.---
            Console.WriteLine("Listening...");
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT_NO));
            serverSocket.Listen(4); //the maximum pending client, define as you wish
            serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null);      
            string result = "";
            do {
                result = Console.ReadLine();
            } while (result.ToLower().Trim() != "exit");
        }

        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 acceptCallback(IAsyncResult result) { //if the buffer is old, then there might already be something there...
            Socket socket = null;
            try {
                socket = serverSocket.EndAccept(result); // The objectDisposedException will come here... thus, it is to be expected!
                //Do something as you see it needs on client acceptance
                socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
                serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null); //to receive another client
            } catch (Exception e) { // this exception will happen when "this" is be disposed...        
                //Do something here             
                Console.WriteLine(e.ToString());
            }
        }

        const int MAX_RECEIVE_ATTEMPT = 10;
        static int receiveAttempt = 0; //this is not fool proof, obviously, since actually you must have multiple of this for multiple clients, but for the sake of simplicity I put this
        private static void receiveCallback(IAsyncResult result) {
            Socket socket = null;
            try {
                socket = (Socket)result.AsyncState; //this is to get the sender
                if (socket.Connected) { //simple checking
                    int received = socket.EndReceive(result);
                    if (received > 0) {
                        byte[] data = new byte[received]; //the data is in the byte[] format, not string!
                        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 SOMETHING ON THE DATA int byte[]!! Yihaa!!
                        Console.WriteLine(Encoding.UTF8.GetString(data)); //Here I just print it, but you need to do something else                     

                        //Message retrieval part
                        //Suppose you only want to declare that you receive data from a client to that client
                        string msg = "I receive your message on: " + DateTime.Now;                      
                        socket.Send(Encoding.ASCII.GetBytes(msg)); //Note that you actually send data in byte[]
                        Console.WriteLine("I sent this message to the client: " + msg);

                        receiveAttempt = 0; //reset receive attempt
                        socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); //repeat beginReceive
                    } else if (receiveAttempt < MAX_RECEIVE_ATTEMPT) { //fail but not exceeding max attempt, repeats
                        ++receiveAttempt; //increase receive attempt;
                        socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); //repeat beginReceive
                    } else { //completely fails!
                        Console.WriteLine("receiveCallback fails!"); //don't repeat beginReceive
                        receiveAttempt = 0; //reset this for the next connection
                    }
                }
            } catch (Exception e) { // this exception will happen when "this" is be disposed...
                Console.WriteLine("receiveCallback fails with exception! " + e.ToString());
            }
        }

    }
}

客户

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.Send(bytes);
                }
            } while (result.ToLower().Trim() != "exit");
        }

        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());
            }
        }
    }
}

最后的备注(编辑)

由于上面的代码使用Console Application 运行,它必须使用static main void 关键字运行。因此上面定义的客户端Socketstatic 类型。这可能会阻止客户端Socket 在每次“定义”时被多次定义,因为它与名为Programclass 相同,它将引用相同的Socket(尽管这可能不是总是这样,至少根据OP的实验:他可以在同一台计算机上成功运行多个客户端)。

不过,克服这一点并不难。只需将客户端应用程序移植到非以static 类启动的平台(例如WinForms),以上所有代码仍将正常运行。或者,如果它必须使用Console Applications 运行,并且出现问题,只需复制客户端应用程序并使用不同的namespace 或不同的class 名称重新定义它,以避免由于相同的@ 而定义相同的Socket 987654428@或class

但是解决这个问题最重要的部分是明智地使用AsyncSync 来解决给定的问题。


此话题的继续可以找到here

【讨论】:

  • @JoseRamon 是的!我帮你查了。这真的是因为static 类。当我创建另一个复制项目时,完全相同,但我将namespace 更改为其他内容,它运行得非常好......
  • 在客户端代码中,我怎样才能只监听服务器?我试图删除实际发送数据的 do while 。但是我无法打印我刚刚从服务器端发送的数据。
  • 实际上我正在尝试在服务器的主要功能中找到我应该添加的内容,以便将数据发送到客户端。在客户端的情况下,在 loopConect 之后创建一个套接字,然后是实际将数据发送到服务器的发送。我应该在服务器端主要功能中添加哪些?发送是如何工作的?它发送到所有侦听数据的端口?客户端是否可以监听和等待数据?我应该添加绑定并监听客户端代码吗?
  • 嘿,伊恩,抱歉这个愚蠢的问题,我对网络一点也不熟悉。如何从服务器站点检索 clientSockets 列表?监听功能仅适用于服务器端?为了从客户端读取数据,我必须使用 receiveCallback?
【解决方案2】:

您为什么不使用 SignalR 让您的生活更轻松?
下面你可以看到一个简单的例子,其中服务器和客户端都是控制台应用程序

客户端(多次运行此 .exe)

using System;
using Microsoft.AspNet.SignalR.Client;

namespace SignalRClient
{
    class Program
    {
        private static IHubProxy HubProxy { get; set; }
        const string ServerURI = "http://localhost:1234/signalr";
        private static HubConnection Connection { get; set; }

        static void Main(string[] args)
        {
            Connection = new HubConnection(ServerURI);
            HubProxy = Connection.CreateHubProxy("MyHub");

            HubProxy.On<string, string>("SendMessage", (name, message) => Console.WriteLine(name + ":" + message));
            Connection.Start().Wait();

            Console.WriteLine("Press Enter to stop client");
            Console.ReadLine();
        }
    }
}

服务器(在运行客户端之前运行此 .exe)

using System;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Client;
using Microsoft.Owin.Hosting;
using Owin;

namespace SignalRServer
{
    class Program
    {
        static private IDisposable SignalR { get; set; }
        const string ServerURI = "http://localhost:1234";
        private static IHubProxy HubProxy { get; set; }
        private static HubConnection Connection { get; set; }
        static void Main(string[] args)
        {
            SignalR = WebApp.Start(ServerURI);
            Console.WriteLine("Server running at " + ServerURI);

            Connection = new HubConnection(ServerURI);
            HubProxy = Connection.CreateHubProxy("MyHub");
            HubProxy.On<string, string>("SendMessage", (name, message) => Console.WriteLine(name + ":" + message));
            Connection.Start().Wait();

            string messageToSentToClients;
            do
            {
                Console.WriteLine("Type someting to send to clients and press enter");
                messageToSentToClients = Console.ReadLine();
               HubProxy.Invoke("Send", "Server", messageToSentToClients);
           } while (messageToSentToClients != "exit");

       }
    }

    public class MyHub : Hub
    {
        public void Send(string name, string message) { Clients.All.sendMessage(name, message); }
    }

    class Startup
    {
        public void Configuration(IAppBuilder app) { app.MapSignalR(); }
    }
}

为了使上述内容正常工作,您需要以下 NuGet 包:

客户:
Microsoft.AspNet.SignalR.Client
服务器
Microsoft.AspNet.SignalR
Microsoft.AspNet.SignalR.Client
Microsoft.AspNet.SignalR.SelfHost
Microsoft.Owin.Host.HttpListener

如果您希望服务器/客户端位于不同的机器上,您只需更改两个项目中的 ServeURI 属性:

//Clients
const string ServerURI = "http://SERVER_IP:PORT/signalr";
//Server
const string ServerURI = "http://SERVER_IP:PORT"; 

您可以在此处在 WinForms 中找到另一个类似的示例:
https://code.msdn.microsoft.com/windowsdesktop/Using-SignalR-in-WinForms-f1ec847b

【讨论】:

  • 我尝试从一开始就创建表单。它实际上工作正常,但是当我尝试添加我的库时遇到问题: mscorlib.dll 中发生“System.IO.FileLoadException”类型的异常,但未在用户代码中处理附加信息:无法加载文件或程序集 'FileHelpers, Version=2.0.0.0, Culture=neutral, PublicKeyToken=3e0c08d59cc3d657' 或其依赖项之一。不支持操作。 (HRESULT 异常:0x80131515)
  • 我尝试添加的库可能有什么冲突?
  • 事实上 FileHelpers 是我的库。当我评论一堆signalR代码时工作正常。但是,当我添加 startServer SignalR = WebApp.Start(ServerURI);我收到异常的行。由于 signalR 引用,我似乎无法正确加载我的库。
  • 我喜欢这个答案,因为它使用了一个用于此类工作的库。为什么要维护自定义多播代码并处理网络问题等 - 您的核心问题是保持客户端同步,而不是编写网络库。
【解决方案3】:

您可以向客户端发送数据,其实现方式与客户端 -> 服务器类似(除非您的套接字仅用于接收,如果是,则将它们切换为发送和接收)。发送布尔值需要转换为字节,您可以通过 BitConverter 类来实现。

【讨论】:

  • 我可以为多个客户这样做吗?
  • 是的。每个客户端都应该有一个套接字。如果您将每个客户端存储在某个集合中,那么每当您需要向所有客户端(或仅特定客户端)发送相同的数据时,您都可以遍历该集合并发送所需的数据。
  • 目前我正在返回一个字符串,它是 content = "false" 如何删除 EOF 字符以便将字符串解析为布尔变量?
【解决方案4】:

在页面https://msdn.microsoft.com/en-us/library/fx6588te%28v=vs.110%29.aspx 参见方法AcceptCallback(它在客户端连接时调用)。有行Socket handler = listener.EndAccept(ar);。 每个客户端连接时都会发生这种情况,因此请将这些 Socket 实例保留在某个列表中。 当您想向客户端发送数据时,请对列表中的每个套接字使用同一示例中的 Send 方法(或者如果您只想发送给某些客户端,则可以选择性地使用)。

private static void Send(Socket handler, String data) {
    // Convert the string data to byte data using ASCII encoding.
    byte[] byteData = Encoding.ASCII.GetBytes(data);

    // Begin sending the data to the remote device.
    handler.BeginSend(byteData, 0, byteData.Length, 0,
        new AsyncCallback(SendCallback), handler);
}

【讨论】:

  • 我必须将所有 Socket 侦听器保存到一个列表中吗?我有点困惑。
  • 总的来说,我对客户端和侦听器功能感到困惑。我可以有一个只发送数据的服务器和从服务器监听的客户端吗?
  • 不是套接字侦听器,而是套接字(Socket 类的实例)。但是,是的,您需要它们,它们代表您的客户端,因此发送到套接字就是发送到客户端。侦听器和客户端的区别在于您有一个侦听器等待来自客户端的连接,但您有许多客户端,每个客户端都由 Socket 实例表示。
猜你喜欢
  • 2013-04-01
  • 2021-03-15
  • 1970-01-01
  • 2016-07-17
  • 1970-01-01
  • 2020-06-01
  • 2017-05-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多