Начало работы с программированием сокета в C# - Лучшие практики

Преимущество добавления пути к sys.path (по использованию импорта) состоит в том, что это упрощает вещи при импорте больше чем одного модуля из единственного пакета. Например:

import sys
# the mock-0.3.1 dir contains testcase.py, testutils.py & mock.py
sys.path.append('/foo/bar/mock-0.3.1')

from testcase import TestCase
from testutils import RunTests
from mock import Mock, sentinel, patch
33
задан Navaneeth K N 22 July 2009 в 03:38
поделиться

3 ответа

Рассмотрите возможность использования асинхронных сокетов. Дополнительную информацию по этому вопросу можно найти в

2
ответ дан 27 November 2019 в 18:36
поделиться

1 - Привязка и прослушивание сокета

Мне нравится. Однако ваш код привяжет сокет только к одному IP-адресу. Если вы просто хотите прослушивать любой IP-адрес / сетевой интерфейс, используйте IPAddress.Any :

serverSocket.Bind(new IPEndPoint(IPAddress.Any, 4444));

На будущее, возможно, вы захотите поддержать IPv6. Чтобы прослушивать любой IPv6-адрес, используйте IPAddress.IPv6Any вместо IPAddress.Any .

Обратите внимание, что вы не можете прослушивать любой IPv4 и любой IPv6-адрес одновременно, за исключением случаев, когда вы используете разъем для двух стеков . Для этого вам потребуется отключить параметр сокета IPV6_V6ONLY :

serverSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, 0);

Чтобы включить Teredo с вашим сокетом, вам необходимо установить параметр сокета PROTECTION_LEVEL_UNRESTRICTED :

serverSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)23, 10);

2 - Получение данных

I ' d рекомендую использовать NetworkStream , который обертывает сокет в поток , вместо того, чтобы читать фрагменты вручную.

Чтение фиксированного числа байтов немного неудобно:

using (var stream = new NetworkStream(serverSocket)) {
   var buffer = new byte[MaxMessageLength];
   while (true) {
      int type = stream.ReadByte();
      if (type == BYE) break;
      int length = stream.ReadByte();
      int offset = 0;
      do
         offset += stream.Read(buffer, offset, length - offset);
      while (offset < length);
      ProcessMessage(type, buffer, 0, length);
   }
}

Где NetworkStream действительно хорош тем, что вы можете использовать его, как любой другой Stream . Если важна безопасность, просто оберните NetworkStream в SslStream для аутентификации сервера и (необязательно) клиентов с помощью сертификатов X.509. Сжатие работает таким же образом.

var sslStream = new SslStream(stream, false);
sslStream.AuthenticateAsServer(serverCertificate, false, SslProtocols.Tls, true);
// receive/send data SSL secured

3 - Отправка данных и указание длины данных

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

13
ответ дан 27 November 2019 в 18:36
поделиться

Вот реализация, использующая string :: find и string :: insert, не уверен, что она быстрее, вам придется это выяснить! Вот оно:

std::string src = "hey there i have \" all \" over the f\"in pla\"ce\"";
size_t n = 0;
while ( (n=src.find("\"",n)) != std::string::npos )
{
    src.insert(n,"\\");
    n+=2;
}   
std::cout << src << std::endl;

Что напечатано:

привет, у меня \ "все \"

2 - Получение данных
Я бы сделал буфер немного длиннее 255 байт, если только вы не можете ожидать, что все сообщения вашего сервера будут иметь размер не более 255 байт. Я думаю, вам нужен буфер, который, вероятно, будет больше, чем размер пакета TCP, чтобы вы могли избежать многократного чтения для получения одного блока данных.

Я бы сказал, что выбор 1500 байтов должен быть нормальным, а может быть даже 2048 для хорошего круглого числа.

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

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

В качестве альтернативы, если ваш формат сообщения (порядок его компонентов) разработан таким образом, чтобы в любой момент клиент мог иметь возможность определить, должны ли быть следующие данные (например, код 0x01 означает, что следующим будет int и строка, код 0x02 означает, что следующим будет 16 байтов и т. д., и т. д.). В сочетании с подходом NetworkStream на стороне клиента это может быть очень эффективным подходом.

На всякий случай вы можете добавить проверку полученных компонентов, чтобы убедиться, что вы только обрабатываете вменяемые ценности. Например, если вы получили указание на строку длиной 1 ТБ, возможно, где-то произошло повреждение пакета, и может быть безопаснее закрыть соединение и заставить клиента повторно подключиться и «начать заново». Этот подход дает вам очень хорошее универсальное поведение в случае неожиданных сбоев.

4/5 - Закрытие клиента и сервера
Лично я бы выбрал только Close без дополнительных сообщений; когда соединение закрыто, вы получите исключение при любой блокировке чтения / записи на другом конце соединения, которое вам придется обслуживать.

Так как вам все равно придется обслуживать «неизвестные разъединения», чтобы получить надежное решение , усложнять отключение, как правило, бессмысленно.

6 - Неизвестные отключения
Я бы не доверял даже статусу сокета ... соединение может умереть где-нибудь на пути между клиентом и сервером без клиент или сервер замечают.

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

В результате, единственный надежный способ обнаружить все неожиданные подключения - реализовать механизм «ping», где в идеале клиент и сервер будут периодически отправлять сообщение на другой конец, которое приводит только к ответному сообщению, указывающему, что «пинг» был получен.

Чтобы оптимизировать ненужные эхо-запросы, вы можете захотеть иметь механизм «тайм-аута» который отправляет эхо-запрос только в том случае, если другой трафик не был получен с другого конца в течение установленного периода времени (например, если последнее сообщение от сервера старше x секунд, клиент отправляет эхо-запрос, чтобы убедиться, что соединение не прервано без уведомления).

Более продвинутый
Если вам нужна высокая масштабируемость, вам нужно будет изучить асинхронные методы для всех операций с сокетами (Accept / Send / Receive) . Это варианты «Начало / Конец», но они намного сложнее в использовании.

Я не рекомендую пробовать это, пока у вас не будет работать простая версия.

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

Я, вероятно, забыл целую кучу других важных предложений, но этого должно быть достаточно, чтобы получить достаточно устойчивую и надежную реализацию для начала с

26
ответ дан 27 November 2019 в 18:36
поделиться
Другие вопросы по тегам:

Похожие вопросы: