#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 на данный момент я не думаю, что на этот вопрос можно ответить. Я жду разъяснений.