Сервер сокетов TCP — способ обнаружения входящих данных

#c# #sockets #tcp

#c# #сокеты #tcp

Вопрос:

Я пытаюсь написать сервер, который будет прослушивать порт, принимать входящее клиентское соединение, а затем помещать это клиентское соединение в стек для ожидания поступления будущего сообщения. В идеале, когда приходит сообщение, я хотел бы запустить событие, которое затем позволяет серверу выполнить действие.

Я написал почти все это…Я использую AcceptTCPClient() , чтобы фактически установить новое клиентское соединение, и это нормально. Затем я создаю поток, передаю сокет классу, который содержит состояние клиента и некоторые другие данные. Однако единственный способ, который я могу придумать, чтобы заблокировать и дождаться будущего входящего соединения в этом потоке, — это вызвать что-то вроде NetworkStream.Read(), который затем блокируется до поступления байтов.

Итак, вот основная проблема — я использую Protobuff-net, который позволяет мне десериализовать сетевой поток, а не отдельный массив байтов. Итак, прочитав первые пару байтов, я должен сбросить чтение, передать networkstream в метод десериализации protobuff и затем продолжить.

Все, что мне действительно нужно, это метод, который блокирует до тех пор, пока не будут обнаружены некоторые байты, но не требует от меня фактического чтения байтов, пока я не буду готов.

У кого-нибудь есть идеи, как я мог бы этого добиться?

Обновить

Как следует из приведенных ниже комментариев, это, похоже, не поддерживается .Net поэтому самым простым решением, по-видимому, является использование приведенного ниже примера Tsukasa, в котором используется асинхронное чтение / запись.

Я написал его, чтобы использовать все байты в проводе, а затем передать эти байты методу десериализации protobuff.

Не то, что я хотел, но работает нормально. Спасибо всем за помощь.

     private byte[] buffer = new byte[256];
    private Socket socket;
    private NetworkStream networkStream;
    private AsyncCallback callbackRead;
    private AsyncCallback callbackWrite;

    public Socket Socket
    {
        get { return socket; }            
    }

    public ClientProxy(Socket clientSocket)
    {
        socket = clientSocket;
        networkStream = new NetworkStream(clientSocket);
        callbackRead = new AsyncCallback(OnReadComplete);
        callbackWrite = new AsyncCallback(OnWriteComplete);
    }

    public void ReadAsync()
    {
        networkStream.BeginRead(buffer, 0, buffer.Length, callbackRead, null);
    }

    private void OnReadComplete(IAsyncResult ar)
    {
        int bytesRead = networkStream.EndRead(ar);

        if (bytesRead > 0)
        {                
            MemoryStream stream = new MemoryStream(buffer);
            Message data;
            data = Serializer.DeserializeWithLengthPrefix<Message>(stream, PrefixStyle.Fixed32);

            if (data.Type == Chat.Type.User amp;amp; data.Action == Chat.Action.Add)
            {
                Communication.RegisterClient(data.From, socket.Handle.ToString());
            }                
            Communication.readMessage(data);

            ReadAsync();
        }
        else
        {
            networkStream.Close();
            socket.Close();
            networkStream = null;
            socket = null;
        }
    }
  

Комментарии:

1. Попробуйте использовать класс TcpListener.

2. @user1929959 … и как именно это поможет?

3. Вы должны использовать буферизованный поток памяти и читать из сетевого потока через него.

4. @user1929959, это не решает его проблему

5. Можете ли вы быть более конкретным? Есть ли что-то неправильное в том, чтобы просто иметь блок protobuf-net в потоке? Если вас беспокоит количество потоков, я уверен, что protobuf-net поддерживает асинхронный ввод-вывод.

Ответ №1:

Вы можете опросить DataAvailable свойство, но это ненадежно и неэффективно.

Лучший способ — добавить к потоку protobuf префикс длины. Он также позволяет выполнять чтение без чтения сообщения protobuf. Используя префикс, вы можете вызвать асинхронное чтение, которое будет возвращено, как только появится что-то доступное (просто настройте чтение так, чтобы оно считывало только 4 байта, если вы используете заголовок длины).

Если вы не хотите самостоятельно управлять сетевыми операциями, вы можете использовать мою лицензионную библиотеку apache. Вот пример использования protobuf-net: http://blog.gauffin.org/2014/06/easy-and-perfomant-clientserver-communication-with-protobuf-net-griffin-framework/

Комментарии:

1. @usr: afaik нет решения точной проблемы? Так что плохого в предоставлении альтернативных решений?

2. Мы не знаем, в чем его проблема. Вопрос в его нынешнем виде не имеет ответа. Любой ответ на вопрос, на который нет ответа, неверен. Вы знаете, в чем его проблема? Он хочет заблокировать, но не внутри protobuf-net. Понятия не имею, почему.

3. Он определяет это так: All I really want is a method that blocks until some bytes are detected but doesn't require me to actually read the bytes until I'm ready .

4. а) вы не предоставили реалистичного решения для этого и б) это, вероятно, ошибочное требование. Вы не можете предоставить альтернативы, если не понимаете, чего он хочет достичь.

5. вот почему я сказал: «afaik, нет решения точной проблемы? Так что плохого в предоставлении альтернативных решений? » к вашему первому комментарию.

Ответ №2:

BeginRead(), который позволит вам вызывать событие, когда данные будут готовы, таким образом, ваш процесс может находиться в заблокированном состоянии, когда ОС будет пробуждать его только тогда, когда ресурс будет готов

 class Client
{
    private byte[] buffer = new byte[256];
    private Socket socket;
    private NetworkStream networkStream;
    private AsyncCallback callbackRead;
    private AsyncCallback callbackWrite;

    public Client(Socket clientSocket)
    {
        socket = clientSocket;
        networkStream = new NetworkStream(clientSocket);
        callbackRead = new AsyncCallback(OnReadComplete);
        callbackWrite = new AsyncCallback(OnWriteComplete);
    }

    public void StartRead()
    {
        networkStream.BeginRead(buffer, 0, buffer.Length, callbackRead, null);
    }

    private void OnReadComplete(IAsyncResult ar)
    {
        int bytesRead = networkStream.EndRead(ar);

        if (bytesRead > 0)
        {
            string s = System.Text.Encoding.ASCII.GetString(buffer, 0, bytesRead);
            //do something with complete data here
            networkStream.BeginWrite(buffer, 0, bytesRead, callbackWrite, null);
        }
        else
        {
            networkStream.Close();
            socket.Close();
            networkStream = null;
            socket = null;
        }
    }

    private void OnWriteComplete(IAsyncResult ar)
    {
        networkStream.EndWrite(ar);
        networkStream.BeginRead(buffer, 0, buffer.Length, callbackRead, null);
    }
}
  

Использование

 bool running = true;
IPAddress localAddr = IPAddress.Parse("127.0.0.1");
TcpListener tcpListener = new TcpListener(localAddr, 3000);
tcpListener.Start();

while (running)
{
    while (!tcpListener.Pending())
    {
        Thread.Sleep(10);
    }

    Socket socket = tcpListener.AcceptSocket();
    Client client = new Client(socket);
    client.StartRead();
}
  

Комментарии:

1. Это не отвечает на вопрос. Он хочет знать, как заблокировать поток «HandleClientComms» до получения данных без фактического выполнения чтения

2. Добавлен HandleClientComms

3. Это немного скрыто: «Однако единственный способ, который я могу придумать, чтобы заблокировать и дождаться будущего входящего соединения в этом потоке, — это вызвать что-то вроде NetworkStream.Read(), который затем блокируется до поступления байтов». И проблема с этим заключается в следующем абзаце вопроса.

4. Наверное, я немного смущен тем, что он спрашивает. в то время как(myNetworkStream. DataAvailable), а затем ждать, пока что-то не сработает, чтобы затем работать с данными?

5. @Tsukasa на данный момент я не думаю, что на этот вопрос можно ответить. Я жду разъяснений.