使用套接字将一个值从服务器发送到客户端

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

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

我想要实现的是通过套接字从服务器向客户端发送一个布尔变量。我在想是否可以这样做,因为在代码中我有等待并侦听的服务器和发送数据的客户端,我能否反过来做?总的来说,我想要向多个客户端发送一个布尔值。为什么我需要EOF才能发送字符串?将所有内容转换为字符串是必要的吗?

编辑: 总体来说,我想要做的是从一台计算机向另外两台计算机发送一个变量,以便在所有计算机上同时开始一个进程。实际上,创建一个开关器,在同一时间内给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实现中的主要代码,之后我调用我的对象。当我右键单击时,我会改变一个标志位并停止捕获。如何停止服务器或暂停以便再次使用左键打开?


2
据我所知,你可以这样做,但是具体的方法我不清楚,请看一下这个链接。顺便说一句,我记得的是将要发送的数据转换为byte[]类型 - http://www.codeproject.com/Articles/2477/Multi-threaded-Client-Server-Socket-Class - CybeX
1
抱歉,刚意识到这是C++的内容,请在CodeProject网站上搜索C#的客户端服务器网络传输,我曾经使用过这个网站上的一个,希望能帮到你。 - CybeX
@Ian 谢谢你的超级分析性答案。我会尝试你所有的步骤。 :P - Jose Ramon
2
@JoseRamon 太好了! :D 只是请注意最后一步(服务器中的第9步和客户端中的第7步)需要您自己更改。这就是为什么我给出的代码与这些步骤非常不同的原因。然而,我给出的代码是一个可工作的版本,您无需更改任何内容。这段代码的想法是与您分享异步如何定义和工作的。然后,您只需要更改最后一步以使其适应您的需求。 =) - Ian
4个回答

31

首先回答问题:

问:在使用Socket发送数据时,是否需要将所有内容转换为字符串?我想做的是从一台计算机向另外两台计算机发送一个变量,以便在所有计算机上同时开始一个进程。

答:不需要将所有内容转换为字符串。您可以发送byte[],这可能是您想要的。

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

答:您是指boolean还是byte?因为您从Socket获得的基本变量类型是byte。您始终可以通过以下方式从发送方/接收方将byte更改为bool

bool val = byteToCheck > 0;

答2:由于您的服务器是Console应用程序,我建议查看十六进制stringbyte[]转换。这样,您可以编写string,但将其解释为byte[]。检查this。整个思路非常简单。即:您键入string,但它将作为byte[]发送。由于它是byte[],因此可以在其中具有任何值。

这里我提供了解决方案来处理您的(1)多个客户端、(2)Async连接&接受&接收,但有(3)同步发送,以及(4)从十六进制字符串转换为byte[](结构和思路),最后但并非最不重要的是(5)带有用户输入的工作代码(供您更改此部分)进行测试!

我会使用简单的Socket类来解决这个问题,因为这是我最熟悉的解决方案。但如果您使用TcpListener.Server(它是Socket类的底层网络),您也可以采用类似的方法。并且,如您所需,我会使用ASync来完成。

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


服务器
  1. Make your Socket as class field rather than method field, since you will use if everywhere and you need multiple methods to achieve what you want. And initialize it as soon as you started your main routine.

    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. Since the server will serve many clients, I will recommend you to use ASync rather than Sync for the process. Initialize your Socket by using BeginAccept rather than using Accept, put acceptCallback in your 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. Define acceptCallback, which is where you will go when you accept a Socket. Put EndAccept there.

    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. I would typically list my client sockets, and do something on client disposal (that is unlisted it) - but this depends on the need. In this case, you seem to need it. And don't forget to create buffers, etc... This is for buffering the incoming data.

  5. Start to accept something received from the client, using another ASync BeginReceive on the client Socket (and now you need receiveCallback). Then, very important, repeat your BeginAccept to accept other clients!

    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. Define your receiveCallback, that is, when you receive something from your client. This part could be quite tricky because of failures! But basically, what you need for now is simply EndReceive and again, very important, to repeat the BeginReceive from the same client such that you can receive its next message!

    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://dev59.com/km435IYBdhLWcg3w-1V_ 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. And suppose you want to reply your sender after you receive the message, simply do this in the if (received > 0) part:

    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. And after putting a little more things in your main routine, you are done(!) - IF you do not ask for sending to client as byte[]

  9. And now, if you want to send something to all your clients as byte[] you simply need to list all your client (see step 4-5). See this and convert the result string above (remember to type it in hex string format as required) to byte[] then send it to all the clients using your client socket list (here is where it is needed!):

    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. 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 at the very 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 send 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.). And afterwards you are completely done!!

    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[]时所需的内容。

enter image description here

enter image description here


您的测试代码:

服务器

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

最终说明(编辑)

由于上述代码是使用控制台应用程序运行的,因此必须使用static main void关键字运行。因此,上面定义的客户端Socket静态类型。这可能会防止客户端Socket被定义多次,因为每次它被“定义”,由于它是同一个名为Programclass,它将引用同一Socket(尽管根据OP的实验,这并不总是如此:他可以在同一台计算机上成功运行多个客户端)。

然而,要克服这个问题并不难。只需将客户端应用程序移植到非初始化为静态类的平台(例如WinForms),所有上述代码仍将按正常方式运行。或者,如果必须使用控制台应用程序,并且出现问题,请复制客户端应用程序并重新定义它,使用不同的命名空间或不同的class名称来避免由于相同的命名空间class而定义相同的Socket

但在解决这个问题时,最重要的部分是明智地使用AsyncSync来解决所给出的问题。

此话题的续篇可以在这里找到。


1
@JoseRamon 是的!我已经为您检查过了。确实是因为static类的原因。当我创建了另一个完全相同的副本项目,但是我将namespace改成其他名称时,它可以正常运行... - Ian
在客户端代码中,我如何只监听服务器?我尝试删除 do while 循环,该循环实际上发送数据。但是我无法打印从服务器端刚刚发送的数据。 - Jose Ramon
实际上,我正在尝试找出在服务器的主函数中应该添加什么以便向客户端发送数据。对于客户端,创建一个套接字后,执行loopConnect,然后是send,它实际上将数据发送到服务器。在服务器端的主函数中应该添加哪些内容?send如何工作?它会发送到所有监听数据的端口吗?客户端是否可以监听并等待数据?我应该在客户端代码中添加bind和listen吗? - Jose Ramon
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/100532/discussion-between-ian-and-jose-ramon。 - Ian
嗨Ian,对于那个愚蠢的问题很抱歉,我对网络一点也不熟悉。我该如何从服务器端检索clientSockets列表?listen功能只适用于服务器端吗?为了从客户端读取数据,我必须使用receiveCallback吗? - Jose Ramon
实际上,现在我的代码位于 onMouseRight 和 onMouseLeft 点击事件中。通过右键单击,我会在新线程中调用 StartServer 函数,然后进行录制操作。在右键单击时,我需要执行某些操作来停止服务器和录制功能。 - Jose Ramon

5
为什么不使用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的异常) - Jose Ramon
我尝试添加的库可能存在冲突,有什么可能的原因? - Jose Ramon
你正在尝试添加哪个库?你是否已经查看了以下链接:http://stackoverflow.com/questions/1869239/error-when-using-filehelpers-on-iis-sometimes-s-policyexception-required-p http://stackoverflow.com/questions/14483550/replacing-dll-to-a-newer-version http://serverfault.com/questions/93342/error-when-using-filehelpers-on-iis-sometimes-policyexception-required-perm - George Vovos
事实上,FileHelpers是我的库。当我注释掉一堆SignalR代码时,它可以正常工作。但是,当我添加了startServer SignalR = WebApp.Start(ServerURI);这行代码后,我收到了异常。看起来由于SignalR的引用,我无法正确加载我的库。 - Jose Ramon
问题不在于FileHelpers本身,而是它的一个依赖项。逐个检查其引用并确保SignalR依赖于相同的版本。 - George Vovos
1
我喜欢这个答案,因为它使用了一个专门用于这种工作的库。为什么要维护自定义的多播代码和处理网络问题等等?你的核心问题是保持客户端同步,而不是编写一个网络库。 - ripvlan

2

您可以向客户端发送数据,类似于您向服务器发送数据的方式(除非您的套接字是只接收的,如果是这样,请将它们切换到发送和接收)。发送布尔值需要将其转换为字节,您可以通过BitConverter类实现此目的。


我可以为几个客户做到这一点吗? - Jose Ramon
1
是的。每个客户端应该有一个套接字。如果您在某个位置存储了每个客户端,那么每当您需要将相同的数据发送给所有客户端(或仅特定客户端),您可以遍历此集合并发送所需数据。 - Jaxter
目前我正在返回一个字符串,内容为 "false<EOF>"。我该如何去除 EOF 字符以便将该字符串解析为布尔变量? - Jose Ramon

2
在页面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监听器都保存到一个列表中吗?我有点困惑。 - Jose Ramon
总的来说,我对客户端和监听器功能感到困惑。我能否拥有一个仅发送数据的服务器和从服务器接收数据的客户端? - Jose Ramon
不是套接字监听器,而是套接字(Socket类的实例)。但是,是的,你需要它们,它们代表你的客户端,因此向套接字发送数据就是向客户端发送数据。监听器和客户端之间的区别在于,你只有一个监听器等待来自客户端的连接,但你有许多客户端,每个客户端都由一个套接字实例表示。 - Shadowed

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