2017-01-18 10 views
2

Я реализовал клиент сокета, используя класс TcpClient. Поэтому я могу отправлять и получать данные, все работает хорошо. Но я спрашиваю у некоторых гуру: Есть ли что-то не так с моей реализацией? возможно, есть лучший способ сделать что-то. В частности, как мне обращаться с разъединениями? Есть ли какой-нибудь индикатор (или, может быть, я могу написать его сам), который говорит мне, что сокет отключен?Асинхронный сокет клиент с использованием класса TcpClient C#

Я также изучил функции async, ожидающие функции класса Socket, но не могу обернуть мою голову вокруг «SocketAsyncEventArgs», почему она там в первую очередь. Почему не могу я просто: ждать Client.SendAsync («данные»);?

public class Client 
{ 
    private TcpClient tcpClient; 

    public void Initialize(string ip, int port) 
    { 
     try 
     { 
      tcpClient = new TcpClient(ip, port); 

      if (tcpClient.Connected) 
       Console.WriteLine("Connected to: {0}:{1}", ip, port); 
     } 
     catch (Exception ex) 
     { 
      Console.WriteLine(ex.Message); 
      Initialize(ip, port); 
     } 
    } 

    public void BeginRead() 
    { 
     var buffer = new byte[4096]; 
     var ns = tcpClient.GetStream(); 
     ns.BeginRead(buffer, 0, buffer.Length, EndRead, buffer); 
    } 

    public void EndRead(IAsyncResult result) 
    { 
     var buffer = (byte[])result.AsyncState; 
     var ns = tcpClient.GetStream(); 
     var bytesAvailable = ns.EndRead(result); 

     Console.WriteLine(Encoding.ASCII.GetString(buffer, 0, bytesAvailable)); 
     BeginRead(); 
    } 

    public void BeginSend(string xml) 
    { 
     var bytes = Encoding.ASCII.GetBytes(xml); 
     var ns = tcpClient.GetStream(); 
     ns.BeginWrite(bytes, 0, bytes.Length, EndSend, bytes); 
    } 

    public void EndSend(IAsyncResult result) 
    { 
     var bytes = (byte[])result.AsyncState; 
     Console.WriteLine("Sent {0} bytes to server.", bytes.Length); 
     Console.WriteLine("Sent: {0}", Encoding.ASCII.GetString(bytes)); 
    } 
} 

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

static void Main(string[] args) 
{ 
    var client = new Client(); 
    client.Initialize("127.0.0.1", 8778); 

    client.BeginRead(); 
    client.BeginSend("<Names><Name>John</Name></Names>"); 

    Console.ReadLine(); 
} 

ответ

4

Хорошо, что взял меня за 10 секунд, чтобы найти самую большую проблему вы могли бы сделать:

public void BeginRead() 
{ 
    var buffer = new byte[4096]; 
    var ns = tcpClient.GetStream(); 
    ns.BeginRead(buffer, 0, buffer.Length, EndRead, buffer); 
} 

Но не волнуйтесь, поэтому мы на SO.


Прежде всего позвольте мне объяснить, почему это такой большой вопрос.

Предположим, что вы отправляете сообщение 4097 bytes long. Ваш буфер может принимать только 4096 байт, что означает, что вы не можете упаковать все сообщения в этот буфер. Или предположим, что вы отправляете 12-байтовый длинный пакет, но все же ... вы выделяете 4096 байт в вашей памяти только для хранения 12 байтов.

Как с этим бороться?

Каждый раз, когда вы работаете с сетевыми, вы должны рассмотреть возможность сделать какие-то протокола (некоторые люди называют это сообщение обрамление, но это просто протокол ...), который поможет вам определить весь пакет.

Пример протокола может быть:

[1B = тип сообщения] [4B = длина] [ХВ = сообщение]

- where X == BitConvert.ToInt32(length);

Простой пример:

Приемник:

byte messageType = (byte)netStream.ReadByte(); 
byte[] lengthBuffer = new byte[sizeof(int)]; 
int recv = netStream.Read(lengthBuffer, 0, lengthBuffer.Length); 
if(recv == sizeof(int)) 
{ 
    int messageLen = BitConverter.ToInt32(lengthBuffer, 0); 
    byte[] messageBuffer = new byte[messageLen]; 
    recv = netStream.Read(messageBuffer, 0, messageBuffer.Length); 
    if(recv == messageLen) 
    { 
     // messageBuffer contains your whole message ... 
    } 
} 

Отправитель:

byte messageType = (1 << 3); // assume that 0000 1000 would be XML 
byte[] message = Encoding.ASCII.GetBytes(xml); 
byte[] length = BitConverter.GetBytes(message.Length); 
byte[] buffer = new byte[sizeof(int) + message.Length + 1]; 
buffer[0] = messageType; 
for(int i = 0; i < sizeof(int); i++) 
{ 
    buffer[i + 1] = length[i]; 
} 
for(int i = 0; i < message.Length; i++) 
{ 
    buffer[i + 1 + sizeof(int)] = message[i]; 
} 
netStream.Write(buffer); 

Остальной ваш код выглядит хорошо.Но, по-моему, использование асинхронных операций в вашем случае просто бесполезно. Вы можете сделать то же самое с синхронными вызовами.

2

Трудно ответить, потому что здесь нет точного вопроса, но более какого-то обзора кода. Но все же некоторые подсказки:

  • Ваш механизм подключения кажется неправильным. Я не думаю, что TcpClient.Connected будет блокироваться до тех пор, пока соединение не будет установлено. Поэтому он часто просто терпит неудачу по мере того, как соединение выполняется, и затем вы начинаете все заново. Вы должны переключиться на использование метода блокировки или async Connect.
  • SocketAsyncEventArgs - это механизм высокопроизводительной передачи данных async. В этом редко бывает. Вы должны просто игнорировать его
  • Если вы хотите отправить данные асинхронно, вы должны использовать методы Async, которые возвращают Task, потому что их можно легко комбинировать с асинхронным/ожидающим.
  • Модель APM (BeginXYZ/EndXYZ) является устаревшей, вы больше не должны ее использовать в новом коде. Одна из проблем заключается в том, что иногда метод End называется синхронно внутри метода Begin, что может привести к неожиданному поведению. Если это не так, то обратный вызов завершения будет выполняться из случайного потока ThreadPool. Это также часто не то, что вы хотите. Методы TPL избегают этого.
  • Для вашего простого использования методы блокировки также полностью прекрасны и не сопряжены со сложностью различных асинхронных методов.

Показания сторона коды с TPL метод (непроверенной):

public async Task Initialize(string ip, int port) 
{ 
    tcpClient = new TcpClient; 
    await tcpClient.ConnectAsync(ip, port); 

    Console.WriteLine("Connected to: {0}:{1}", ip, port); 
} 

public async Task Read() 
{ 
    var buffer = new byte[4096]; 
    var ns = tcpClient.GetStream(); 
    while (true) 
    { 
     var bytesRead = await ns.ReadAsync(buffer, 0, buffer.Length); 
     if (bytesRead == 0) return; // Stream was closed 
     Console.WriteLine(Encoding.ASCII.GetString(buffer, 0, bytesRead)); 
    } 
} 

В части инициализации вы могли бы сделать:

await client.Initialize(ip, port); 
// Start reading task 
Task.Run(() => client.Read()); 

Для использования синхронных методов удаления всех Async вхождений и замените задачу на Thread.

+0

'if (bytesRead == 0) return; 'Мне кажется неправильным? – Sir

+0

Все в порядке. 'NetworkStream.ReadAsync' возвращает 0, когда поток закрывается с удаленной стороны. Однако, в зависимости от приложения, может возникнуть больше смысла бросать здесь исключение вместо того, чтобы просто возвращаться. Это был просто пример. Однако, кажется, что 'await' отсутствовал до' ReadAsync' в моем примере. Я это исправил. – Matthias247

+0

Что вы подразумеваете под закрытым с удаленной стороны? Как это соотносится с тем, когда отправляемое сообщение завершено/достигнуто EOF. Но в будущем все еще может быть больше сообщений. Одна вещь, которую я пытаюсь выработать на данный момент, - это то, как продолжать чтение входящих сообщений из потока с соединением, которое никогда не закрывается. В документах MS мало примеров постоянных подключений, они вскоре закрываются после чтения. – Sir