2013-09-02 2 views
0

У меня есть клиентский и серверный класс на C#, который использует связь сокетов. Сервер выглядит следующим образом:Невозможно правильно прочитать данные из .Net-сокета в C#

public class AsyncTcpServer 
    { 
     private Socket _server_socket; 
     private Socket _client_socket; 
     private byte[] _receive_buffer; 
     private byte[] _send_buffer; 
     private NetworkStream _ns; 


     public void Start() 
     { 
      try 
      { 
       _server_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 
       _server_socket.Bind(new IPEndPoint(IPAddress.Any, 17999)); 
       _server_socket.Listen(0); 
       _server_socket.BeginAccept(new AsyncCallback(BeginAccept), null); 
      } 
      catch(Exception e) 
      { 
       Debug.Print(e.Message); 
      } 
     } 

     private void BeginAccept(IAsyncResult ar) 
     { 
      try 
      {   
       _client_socket = _server_socket.EndAccept(ar); 

       _receive_buffer = new byte[_client_socket.ReceiveBufferSize]; 
       _send_buffer = new byte[_client_socket.ReceiveBufferSize]; 
       _ns = new NetworkStream(_client_socket); 

       _client_socket.BeginReceive(_receive_buffer, 0, _receive_buffer.Length, SocketFlags.None, new AsyncCallback(RecieveCallback), null); 
      } 
      catch(Exception e) 
      { 
       Debug.Print(e.Message); 
      } 
     } 

     private void RecieveCallback(IAsyncResult ar) 
     { 
      try 
      { 
       string text = Encoding.ASCII.GetString(_receive_buffer); 
       Debug.Print("Server Received: " + text);     
      } 
      catch (Exception e) 
      { 
       Debug.Print("Unexpected exception: " + e.Message); 
      } 
     } 

     public void Send(byte [] bytes) 
     { 
      try 
      { 
       _ns.Write(bytes, 0, bytes.Length); 
      } 
      catch (Exception e) 
      { 
       Debug.Print("Unexpected exception: " + e.Message); 
      }    
     } 
    } 

И клиент выглядит следующим образом:

public class AsyncTcpClient 
    { 
     private Socket _client_socket; 
     private byte[] _buffer; 
     private const int HEADER_SIZE = sizeof(int); 

     public void Start() 
     { 
      _client_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);   
      _client_socket.BeginConnect(new IPEndPoint(IPAddress.Loopback, 17999), new AsyncCallback(ConnectCallback), null); 
     } 

     private void ConnectCallback(IAsyncResult ar) 
     { 
      try 
      { 
       _client_socket.EndConnect(ar);     
       _buffer = new byte[_client_socket.ReceiveBufferSize]; 
       StartReceive(); 
       byte[] buffer = Encoding.ASCII.GetBytes("Connected!"); 
       _client_socket.Send(buffer); 
      } 
      catch (Exception e) 
      { 
       Debug.Print(e.Message); 
      } 
     } 

     private void StartReceive(int offset = 0) 
     { 
      try 
      { 
       _client_socket.BeginReceive(_buffer, offset, _buffer.Length, SocketFlags.None, new AsyncCallback(RecieveCallback), null); 
      } 
      catch (Exception e) 
      { 
       Debug.Print(e.Message); 
      } 
     } 

     private void RecieveCallback(IAsyncResult ar) 
     { 
      try 
      { 
       int bytes_processed = 0; 
       int bytes_read = _client_socket.EndReceive(ar); 

       if (bytes_read > 0) 
       { 
        NetworkStream ns = new NetworkStream(_client_socket); 

        while (ns.DataAvailable && (bytes_processed < bytes_read)) 
        { 
         byte[] len_bytes = new byte[HEADER_SIZE]; 
         ns.Read(len_bytes, 0, HEADER_SIZE); 
         int current_chunk_size = BitConverter.ToInt32(len_bytes, 0); 

         if (current_chunk_size > 0) 
         { 
          byte[] data_buff = new byte[current_chunk_size]; 
          ns.Read(data_buff, 0, current_chunk_size); 
          string s = Encoding.ASCII.GetString(data_buff); 
          bytes_processed += (HEADER_SIZE + current_chunk_size); 
          Debug.WriteLine(s); 
         } 
        }           
       } 
       StartReceive();   
      } 
      catch (Exception e) 
      { 
       Debug.Print(e.Message); 
      } 

      StartReceive(); 
     } 
    } 

Они работают вместе следующим образом:

  • Сервер начинает
  • Клиент подключается
  • сервера отправляет пользовательские пакеты клиенту для его использования

Я использую следующий «структуры данных», чтобы упаковать мои данные передачи на стороне сервера для отправки клиенту:

{[DATA_LENGTH_IN_BYTES][PAYLOAD_BYTES]} 

На стороне клиента, я анализирую первые 4 байта (SizeOf (INT)), чтобы определить длину полезной нагрузки, а затем проанализировать полезную нагрузку. Это работает в первый раз, когда я делаю это, но после этого член DataAvailable из NetworkStream является ложным, и я не могу разобрать остальную часть полезной нагрузки.

Почему именно DataAvailable не соответствует действительности? Я довольно новичок в том, чтобы делать это на C# - я полностью подхожу к нему?

Спасибо заранее!

+2

Обычно я не использую DataAvailable или NetworkStreams. См. Мой пример ниже. –

ответ

2

Вот решение, я остановился на:

Сервер:

public class Listener 
    { 
     private TcpListener _listener; 
     private TcpClient _client; 

     public void Start() 
     { 
      _listener = new TcpListener(IPAddress.Loopback, 17999); 
      _listener.Start(); 
      _listener.BeginAcceptTcpClient(new AsyncCallback(AcceptTcpClientCallback), _listener); 
     } 

     private void AcceptTcpClientCallback(IAsyncResult ar) 
     { 
      try 
      { 
       Debug.WriteLine("Accepted tcp client callback"); 
       _client = _listener.EndAcceptTcpClient(ar);    
      } 
      catch (Exception e) 
      { 
       Debug.WriteLine(e.Message); 
      }    
     } 

     public void Write(string data) 
     { 
      try 
      { 
       NetworkStream ns = _client.GetStream(); 
       byte[] buffer = Encoding.ASCII.GetBytes(data); 
       ns.Write(buffer, 0, buffer.Length); 
      } 
      catch (Exception e) 
      { 
       Debug.WriteLine(e.Message); 
      } 
     } 

    } 

И клиент:

public class Client 
    { 
     private TcpClient _client; 
     private byte[] _buffer; 

     public void Start() 
     { 
      try 
      { 
       _client = new TcpClient(); 
       _client.BeginConnect(IPAddress.Loopback, 17999, new AsyncCallback(ConnectCallback), _client); 
      } 
      catch (Exception e) 
      { 
       Debug.WriteLine(e.Message); 
      } 
     } 

     private void ConnectCallback(IAsyncResult ar) 
     { 
      try 
      { 
       NetworkStream ns = _client.GetStream(); 
       _buffer = new byte[_client.ReceiveBufferSize]; 
       ns.BeginRead(_buffer, 0, _buffer.Length, new AsyncCallback(ReadCallback), null); 
      } 
      catch (Exception e) 
      { 
       Debug.WriteLine(e.Message); 
      } 
     } 

     private void ReadCallback(IAsyncResult ar) 
     { 
      try 
      { 
       NetworkStream ns = _client.GetStream(); 
       int read = ns.EndRead(ar); 
       string data = Encoding.ASCII.GetString(_buffer, 0, read); 

       var res = data.Split(new [] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); 

       foreach (var r in res) 
       { 
        Debug.WriteLine(r); // process messages 
       } 
       ns.BeginRead(_buffer, 0, _buffer.Length, ReadCallback, _client); 
      } 
      catch (Exception e) 
      { 
       Debug.WriteLine(e.Message); 
      }    
     } 
    } 

Где сообщения от сервера к клиенту формируется следующим образом:

string message = "This is a message" + "\r\n"; 
_listener.Send(message); 

Мне нравится этот вариант для своей простоты. Его намного короче и (по крайней мере, мне) гораздо проще управлять.

HTH

+0

Только «NetworkStream networkStream = _client.GetStream();' в AcceptTcpClientCallback ничего не делает. Он работает сейчас? И проблема в том, что когда сообщение разбивается на серверные пакеты, это происходит неправильно. tip: если вы не удалите \ r \ n, вы можете использовать метод Debug.Write. Это облегчает обработку частичного сообщения. –

+0

'NetworkStream networkStream = _client.GetStream();' не обязательно, вы правы, это было видимым и ничего не делает. Я отредактировал ответ. Я хочу только написать клиенту, как только он подключится. Что касается способа обработки буферов, что бы вы предложили как лучший способ не пропустить данные в отдельных пакетах? –

+0

См. Мой обновленный ответ. –

2

Я думаю, вы забыли EndReceive в RecieveCallback. (код сервера)

private void RecieveCallback(IAsyncResult ar) 
    { 
     try 
     { 
      int bytesReceived = _client_socket.EndReceive(ar); // <--- 
      string text = Encoding.ASCII.GetString(_receive_buffer); 
      Debug.Print("Server Received: " + text);     
     } 
     catch (Exception e) 
     { 
      Debug.Print("Unexpected exception: " + e.Message); 
     } 
    } 

Советуем создать один класс, который читает/записывает в сокеты (реализует протокол). Базовый класс, который обрабатывает чтение/запись в Sockets, клиентский сокет, который является производным от SocketConnection, но сначала подключается к ipendpoint. Сервер с списком SocketConnection. Таким образом, вы сохраняете функциональность клиент/сервер отдельно от обработки сообщений в сокете. Но оба используют один и тот же код для приема/отправки сообщений. Вот пример:

Псевдо:

// base class that handle receive/sent packets 
class SocketConnection 
{ 

    // the reason for a Start method is that event can be bound before the Start is executed. 
    void Start(Socket socket) 
    { 
     StartReceive(); 
    } 

    void StartReceive() 
    { 
     socket.BeginReceive(...); 
    } 

    void EndReceive() 
    { 
     socket.EndReceive(...); 
     // handle received message. 
     // call the ondata event or something 
     StartReceive(); 
    } 
} 

class ClientSocket : SocketConnection 
{ 
    void Connect() 
    { 
     Socket socket = new Socket(...); 
     socket.Connect(..); 

     // start receiving from the client socket. 
     base.Start(socket); 
    } 
} 

class Server 
{ 
    List<SocketConnection> _clients; 

    void Start() 
    { 
     // Create server socket + listening etc.. 

     StartAccept(); 
    } 

    void StartAccept() 
    { 
     serverSocket.BeginAccept(...); 
    } 

    void EndAccept() 
    { 
     Socket serverClientSocket = EndAccept(...); 

     // create a base socket handler..... 
     SocketConnection clientSocket = new SocketConnection(); 

     _clients.Add(clientSocket); 
     // bind the ondata event of the client and pass it to the clientondata event of the server. 
     // Start receiving from the socket. 
     clientSocket.Start(serverClientSocket); 

     // accept new clients. 
     StartAccept(); 
    } 
} 

UPDATE:

«Что касается, как обращаться с буферами, то, что вы могли бы предложить как лучший способ, чтобы не пропустить данные в отдельных пакетах ?»

я пошлю размер первый:

// sending part. 
string message = "This is a message"; 
byte[] buffer = Encoding.ASCII.GetBytes(message); 

_client_socket.Send(BitConverter.GetBytes(buffer.Length)); // sends a int as 4 bytes. 
_client_socket.Send(data); 



// receiving part. 
// try to receive at least 4 bytes. (no more/ no less) 

int length = BitConverter.ToInt32(buffer, 0); 

// try to receive data with `length` size, (no more/ no less) 

Это будет разделять различные пакеты.


Асинхронный Пример:


Вам все еще нужно добавить код обработки исключений.

public static class SocketReader 
{ 
    public static void ReadFromSocket(Socket socket, int count, Action<byte[]> endRead) 
    { 
     // read from socket, construct a new buffer. 
     DoReadFromSocket(socket, 0, count, new byte[count], endRead); 
    } 

    public static void ReadFromSocket(Socket socket, int count, ref byte[] buffer, Action<byte[]> endRead) 
    { 
     // if you do have a buffer available, you can pass that one. (this way you do not construct new buffers for receiving. 
     // the ref is because if the buffer is too small, it will return the newly created buffer. 

     // if the buffer is too small, create a new one. 
     if (buffer.Length < count) 
      buffer = new byte[count]; 

     DoReadFromSocket(socket, 0, count, buffer, endRead); 
    } 

    // This method will continues read until count bytes are read. (or socket is closed) 
    private static void DoReadFromSocket(Socket socket, int bytesRead, int count, byte[] buffer, Action<byte[]> endRead) 
    { 
     // Start a BeginReceive. 
     socket.BeginReceive(buffer, bytesRead, count - bytesRead, SocketFlags.None, (result) => 
     { 
      // Get the bytes read. 
      int read = socket.EndReceive(result); 

      // if zero bytes received, the socket isn't available anymore. 
      if (read == 0) 
      { 
       endRead(new byte[0]); 
       return; 
      } 

      // increase the bytesRead, (index point for the buffer) 
      bytesRead += read; 

      // if all bytes are read, call the endRead with the buffer. 
      if (bytesRead == count) 
       endRead(buffer); 
      else 
       // if not all bytes received, start another BeginReceive. 
       DoReadFromSocket(socket, bytesRead, count, buffer, endRead); 

     }, null); 
    } 

} 

Пример, как использовать его:

Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 


// read header. (length of data) (4 bytes) 
SocketReader.ReadFromSocket(socket, 4, (headerBuffer) => 
    { 
     if (headerBuffer.Length == 0) 
     { 
      // disconnected; 
      return; 
     } 


     int length = BitConverter.ToInt32(headerBuffer, 0); 

     // read bytes specified in length. 
     SocketReader.ReadFromSocket(socket, length, (dataBuffer) => 
      { 
       if (dataBuffer.Length == 0) 
       { 
        // disconnected; 
        return; 
       } 

       // if you want this in a stream, you can do: This way the stream is readonly and only wraps arround the bytearray. 
       using (MemoryStream stream = new MemoryStream(dataBuffer, 0, length)) 
       using (StreamReader reader = new StreamReader(stream)) 
        while (!reader.EndOfStream) 
         Debug.WriteLine(reader.ReadLine()); 

      }); 

    }); 
+0

Привет, Jeroen, спасибо за подробный ответ. К сожалению, вызов end get на стороне сервера не работал, но я передумал и решил пойти с завернутыми сокетами в виде TcpListener и TcpClient. Мое новое решение перечислено ниже ... –