Системное программирование

индекс
203,75

IPC: сокеты против именованных каналов

Абсолютные числа большого смысла не имеют, но как сравнение информация представляет некоторую ценность

Условия


Windows 7 x64 с обновлениями
AMD Athlon X2 4600+ (2.41ГГц)
DDR2 2Гб
.Net Framework 3.5, классы из FCL.
Два 64-битных процесса без дополнительных привилегий.
Антивирус Касперского выключен. С ним результаты сокетов существенно хуже.
Сокеты и именованные каналы в асинхронном режиме.
Размеры буферов подбирались оптимальные, эксперементально для каждого вида IPC.

Результаты


Сокеты
Скорость односторонней передачи — 160 мегабайт в секунду (105 с антивирусом)
Загрузка процессора — 90%, из них примерно 2/3 в ядре
Оперативная память — 4-5 мегабайт на процесс, со временем не росла

Именованные каналы
Скорость односторонней передачи — 755 мегабайт в секунду (антивирус не влияет)
Загрузка процессора — 80%, из них примерно 2/3 в ядре
Оперативная память — 4-5 мегабайт на процесс, со временем не росла

Исходный код


Сокеты
using System;
using System.IO.Pipes;
using System.Net;
using System.Net.Sockets;

namespace Server
{
  public static class Program
  {
    private static byte[] buffer = new byte[512 * 1024];

    private static void OnSocketSend(IAsyncResult ar)
    {
      Socket client = (Socket)ar.AsyncState;

      client.EndSend(ar);
      client.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, OnSocketSend, client);
    }

    public static void Main(string[] args)
    {
      Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

      server.Bind(new IPEndPoint(IPAddress.Loopback, 5000));
      server.Listen(10);

      while (true)
      {
        Socket client = server.Accept();

        client.SendBufferSize = 2 * buffer.Length;
        client.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, OnSocketSend, client);

        Console.WriteLine("CONNECTED");
      }
    }
  }
}


* This source code was highlighted with Source Code Highlighter.


using System;
using System.Diagnostics;
using System.IO.Pipes;
using System.Net;
using System.Net.Sockets;

namespace Client
{
  public static class Program
  {
    private static byte[] buffer = new byte[512 * 1024];
    private static Stopwatch watch = new Stopwatch();
    private static long traffic = 0;
    private static int step = 0;

    private static void OnSocketReceive(IAsyncResult ar)
    {
      Socket client = (Socket)ar.AsyncState;

      traffic += client.EndReceive(ar);
      step++;

      if ((step % 1000) == 0)
      {
        watch.Stop();

        Console.WriteLine(
          "{0} MB/s",
          (1000 * (traffic >> 20)) / watch.ElapsedMilliseconds);

        watch.Start();
      }

      client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, OnSocketReceive, client);
    }

    public static void Main(string[] args)
    {
      Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

      client.ReceiveBufferSize = 2 * buffer.Length;
      client.Connect(new IPEndPoint(IPAddress.Loopback, 5000));

      watch.Start();

      client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, OnSocketReceive, client);

      Console.WriteLine("CONNECTED");
      Console.ReadLine();
    }
  }
}


* This source code was highlighted with Source Code Highlighter.

Именованные каналы

using System;
using System.IO.Pipes;
using System.Net;
using System.Net.Sockets;

namespace Server
{
  public static class Program
  {
    private static byte[] buffer = new byte[256 * 1024];

    private static void OnPipeSend(IAsyncResult ar)
    {
      NamedPipeServerStream server = (NamedPipeServerStream)ar.AsyncState;

      server.EndWrite(ar);
      server.BeginWrite(buffer, 0, buffer.Length, OnPipeSend, server);
    }

    public static void Main(string[] args)
    {
      NamedPipeServerStream server = new NamedPipeServerStream("TestPipe", PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 1024 * 1024, 1024 * 1024);

      server.WaitForConnection();
      server.BeginWrite(buffer, 0, buffer.Length, OnPipeSend, server);

      Console.WriteLine("CONNECTED");
      Console.ReadLine();
    }
  }
}


* This source code was highlighted with Source Code Highlighter.


using System;
using System.Diagnostics;
using System.IO.Pipes;
using System.Net;
using System.Net.Sockets;

namespace Client
{
  public static class Program
  {
    private static byte[] buffer = new byte[256 * 1024];
    private static Stopwatch watch = new Stopwatch();
    private static long traffic = 0;
    private static int step = 0;

    private static void OnPipeReceive(IAsyncResult ar)
    {
      NamedPipeClientStream client = (NamedPipeClientStream)ar.AsyncState;

      traffic += client.EndRead(ar);
      step++;

      if ((step % 1000) == 0)
      {
        watch.Stop();

        Console.WriteLine(
          "{0} MB/s",
          (1000 * (traffic >> 20)) / watch.ElapsedMilliseconds);

        watch.Start();
      }

      client.BeginRead(buffer, 0, buffer.Length, OnPipeReceive, client);
    }

    public static void Main(string[] args)
    {
      NamedPipeClientStream client = new NamedPipeClientStream(".", "TestPipe", PipeDirection.InOut, PipeOptions.Asynchronous);

      client.Connect();

      watch.Start();

      client.BeginRead(buffer, 0, buffer.Length, OnPipeReceive, client);

      Console.WriteLine("CONNECTED");
      Console.ReadLine();
    }
  }
}


* This source code was highlighted with Source Code Highlighter.
+22
18 января 2010, 08:03
24

комментарии (74)

0
GiBS0N #
Код в студию можно?
0
adontz #
Добавил
0
GiBS0N #
Подсвети код.
0
adontz #
Чем? Всё что я нашёл на Хабре не работает совсем или глючит.
+3
GiBS0N #
+1
adontz #
Спасибо, раскрасил.
–2
naum #
Использование Socket'ов для IPC под Windows — это зло. Даже обмен виндовыми сообщениями вида WM_COPY и то будет эффективнее и удобнее, в том числе и для пользователей. Антивирусы, брандмауэры — смысл заставлять эту братию волноваться, если вы хотите всего-лишь обменяться данными и вас даже не интересует сеть как таковая?
+2
adontz #
Я не знаю какие у вас задачи, но в моих задачах две программы могут быть установлены как на одном, так и на разных компьютерах. Универсальный вариант, такой как сокеты, не может ни привлекать.

С другой стороны,
server.Bind(new IPEndPoint(IPAddress.Any, 5000));
всполошил брендмауер, а такой
server.Bind(new IPEndPoint(IPAddress.Loopback, 5000));
нет. Хотя, антивирус даже с IPAddress.Loopback что-то проверял.
0
naum #
IPAddress.Loopback = Localhost?

Понятное дело, WM_COPY для IPC между машинами не пойдет. На каждую ситуацию свой вариант, к тому же форма IPC должна быть опциональна, имхо. Юзер должен иметь возможность переключиться с pipes на sockets и наоборот.
0
adontz #
IPAddress.Loopback = 127.0.0.1
0
bems #
Не юзера это дело. Но с сокетами больше шансы что придется его посвятить в свою механику. Типа «настройте брендмауер» и т. д.
Ну а про сообщения как способ IPC вообще давно пора забыть. Вот и в семерке уже всё не так гладко.
+1
SabMakc #
А разве именованные каналы не имеют возможности между разными машинами данные передавать?
(или я коммент неправильно понял?)
0
naum #
Автор не отрицает этого, по-моему. Дают само собой. Но самое важное при удаленке — давать выбор. Так делает тот же MS SQL Server.
0
adontz #
TCP сокеты гораздо эффективнее, у именованных каналов передача информации полностью синхронная.
0
SabMakc #
А можно те же замеры, но на гигабитной сетке (или с виртуальной машиной)?
А выводы, как раз у Вас другие получились: 160 против 755 (сокеты против каналов).
0
adontz #
Ну да, локально сокеты сливают.
+1
amirul #
В каком смысле «полностью синхронная»? Вообще то в NT весь ввод/вывод асинхронный (синхронные операции на самом деле это асинхронные + ожидание окончания). Ну а именованные каналы — это обычная файловая система (\Device\NamedPipe который создается, если не ошибаюсь в npfs.sys) и соответственно с ними работают любые асихронные операции: от обычных GetOverlappedResult и Read/WriteFileEx до IOCP.

Или Вы что-то другое имели в виду?
–2
adontz #
Я имел ввиду что следующий блок данных передаётся только после того, как придёт подтверждение иб успешном приёме предыдущего.
0
vma #
Лично разрабатывал несколько клиент-серверных программ с каналом связи на основе пайпов. Использовал асинхронную передачу.
0
bems #
Про файловую систему это ни о чем не говорит, потому что сокеты тоже «наследуются» от файлов (в том смысле что с ними можно работать через Read/WriteFile)
0
amirul #
Нет, вообще то говорит. NPFS (Named Pipe File System) — это именно что файловая система. Read/WriteFile поддерживает и пр., а вот сокеты — раньше были ПАРОЙ файлов (\Device\Tcp, \Device\Udp и прочих TDI устройств) и вся работа с ними производилась через IOCTL-ы (тоже впрочем асинхронные, как и весь ввод/вывод в NT), а IRP_MJ_READ/IRP_MJ_WRITE этими устройствами даже не поддерживаются. Сейчас, с появлением WSK, все еще интереснее — файлы там не фигурируют вообще (хотя стоит признать, в новой модели я разбираюсь чуть слабее — может и пропустил чего).

В общем я тут умничал только для того, чтобы сказать, что именованые каналы — это обычные файлы с точки зрения как ядерного API, так и Win32 подсистемы, а вот сокеты — это совсем не обычные файлы, я бы даже сказал совсем необычные.
0
iZENfire #
TCP-сокеты изначально тоже синхронны, если не использовать асинхронных «обёрток».
0
adontz #
А это тогда что-за хрень?
en.wikipedia.org/wiki/Sliding_Window_Protocol
0
iZENfire #
Алгоритм скользящего окна.
0
adontz #
Который позволяет мне передавать следующий блок данных до того, как придёт подтверждение об успешном приёме предыдущего. С каналами так нельзя, скользящих окон нет.
0
amirul #
Что значит нельзя? Либо Вы недопонимаете работу каналов, либо я недопонимаю, что Вы имеете в виду. Я могу писать в один конец канала до тех пор, пока не закончится память, отведенная под буферизацию для этого канала — без всяких блокировок. То есть асинхронно.
+1
amirul #
Так и именованные каналы легко пересекают границы машин.
"\\.\pipe\PipeName" versus "\\remote-machine-name\pipe\PipeName"
Для C# нужно использовать версии конструкторов с указанием имени сервера. Например:
public NamedPipeClientStream(
string serverName,
string pipeName
)
0
adontz #
0
Lazin #
WM_COPYDATA(вы же это имели ввиду?), это только синхронный обмен небольшими порциями данных, это очень медленно, не масштабируемо и работает на одной машине.
0
bems #
И требует чтобы окна всегда были с нами. Сейчас это так, но такими темпами сборки винды без user-подсистемы так и останутся мечтой.
0
vma #
Я бы даже сказал, в одной сессии. И то, начиная с Windows Vista могут возникнуть проблемы с уровнем изоляции окон (окно с более низким уровнем привилегий не может передать WM-сообщение окну с более высоким уровнем).
НЛО прилетело и опубликовало эту надпись здесь
0
adontz #
Ну не в 5 раз :-)
–2
Ilya_Smelykh #
Вместо Java — C попробуйте.
0
adontz #
Я Java тут никогда не было :-) Да и 2/3 в ядре, большой разницы не будет.
0
bems #
Вы не поняли. Имелась в виду пропорция
TCP/UDP = Java/C
–1
Ilya_Smelykh #
Ну и ладно) using не увидел, а без него для меня это ява) Насчет того что следует использовать UDP это очень дельный совет, использование TCP тут абсолютно избыточно, на лупбэке не теряются пакеты...) А прирост производительности может быть и в полтора и в два раза.
0
serbod #
Вполне прогнозируемый результат. Локально пайпам не требуется инкапсулироваться в сетевые протоколы и проходить через фильтры. По сети картина должна быть обратной, потому что пайпы помимо своих механизмов управления, инкапсулируют данные в сеть.

Примерно так: [ IP | TCP [ NetBios [ SMB ( pipes data ) ] ] ]
0
Ilya_Smelykh #
Хотелось бы узнать, как на уровне ФС в виндовс эти пайпы ваши поддерживаются, я имею ввиду, отличается ли пайп от обычного файла и чем в рамках ФС.
0
bems #
Например не поддерживают SetFilePointer
0
Ilya_Smelykh #
мне это ниочем не говорит) Я имел ввиду, можно ли пайп файл отличить от непайпа, или пайп это тупо файл в виндовс?
0
serbod #
Пайп это совсем не файл — у него нет ни размера, например. Хендл пайпа может использоваться как хендл файла в функциях ReadFile и WriteFile. Имя именованного пайпа похоже на имя файла.
0
bems #
Можни сказать что «пайп это тупо файл». См. этот комментарий:

habrahabr.ru/blogs/system_programming/81067/#comment_2399951
0
Lazin #
Пайп — stream oriented, а файл поддерживает случайный доступ.
0
bems #
Строго говоря, это не различие. Файл на стримере тоже не поддерживает случайный доступ, но это файл.
И файл на диске, и пайп, и сокет это файловые объекты, потому что имеют одинаковою базовую структуру FILE_OBJECT, на которой основываются общие способы доступа ко всем этим объектам (если более точно — соответствующая структура пайпа начинается с тех же полей что есть в структуре файла и добавляет свои, что принято рассматривать как «наследование» объектов). Среди полей есть указатели на функции чтения и записи, которые отличаются для пайпов и файлов, но фокус в том, что они могут отличаться и у разных файловых объектов.
Поэтому можно сказать что пайп это файл.
И на последок:
msdn.microsoft.com/en-us/library/aa364960%28VS.85%29.aspx
Это описание функции GetFileType, среди возвращаемых начений есть FILE_TYPE_PIPE, что как бы намекает нам.
0
jonie #
внутри локалхоста, наверно, можно использовать SharedMemory эффективнее, нежели пайпы…
0
bems #
Ну это скорее для точно известных объемов данных, не для потока.
0
jonie #
ой да бросьте… на этой базе сделать «поток» не представляет особых сложностей (собственно поток так и работает — буферами, если буферизированный, конечно же)… я даже где-то видел реализацию С++ на основе stl базового basic_ostream и basic_istream…
0
bems #
В общем вы правы, но это уже велосипед. Если «поток так и работает», то давайте его и использовать.
Кроме того для MMF нужно кроме самой памяти иметь какой-нить флажок для синхронизации (event например).
Я бы сказал что это поиск себе проблем на ровном месте. Другое дело, если нужно иметь расшареную структуру фиксированного размера, тут да, MMF рулит.
+1
amirul #
Пайпы как раз и используют shared memory на локалхосте. При этом избавляя от необходимости выполнять кучу рутинной работы по созданию, синхронизации и прочему менеджменту этой памяти. Дополнительным плюсом является возможность легкого разноса клиента с сервером на разные машины практически без изменения кода.
0
Krovosos #
Интересно бы поглядеть на полно-дуплексный вариант. Не пробовали?
0
adontz #
Да, как-то поленился. Может завтра сделаю.
–9
egorinsk #
1) Почему не под Линуксом тесты, мне кажется идусская поделка для запуска игр не очень подходит для теста. Если сербезно — надо наверно тестировать серверные ОС, вряд ли на десктопе что-то понадобится передавать.
2) А почему пайпы не проверили? Которые cmd1 | cmd2
3) Почему на .NET написано, вы бы еще на яве писали, ей богу :) Надо хотя бы Си.
+2
adontz #
Извините, нет желания общаться с красноглазиками. Тестируйтеафриканскую ОС, а мне пока и индусской хватает.
0
bems #
Тоже плохо. Он не прав, но это не значит что Линукс — африканская ось.
0
adontz #
А убунту это что?!
0
bems #
Ну это еще не весь линукс.
+2
amirul #
1. Надо полагать мы сейчас имеем счастье общаться с экспертом в области проектирования ОС? Расскажите, пожалуйста, что Вы думаете о перспективах внедрения асинхронного ввода/вывода в линуксе. Для нас это очень важно.
2. Видно эксперта с мировым именем. Вы правда считаете, что пайпы cmd1 | cmd2 отличаются от тах, что были протестированы?
3. Оба теста на шарпе — имеем «apples to apples». Кроме того, большая часть времени была проведена в ядре, да и в юзермоде что то мне подсказывает, что CLR-слой там достаточно тонкий и все быстро уходит в нейтив.
–1
egorinsk #
1) А что, всякие select (ну да, он несовершенен) kqueue и epoll уже не считаются асинхронным вводам/выводом?
2) А что, именованный канал Windows и Linux pipe — одно и то же?
0
egorinsk #
Ой, kqueue из FreeBSD, но это все равно что Линукс, тоже белые буквы на черном фоне в командной строке :)
+2
amirul #
1. Не считается. Это не асинхронный ввод/вывод, а ожидание момента, когда синхронный ввод/вывод можно произвести без блокировки. Асинхронный это: запустил операцию чтения и вышел — она выполняется в фоне. Я вообще то думал, что Вы вспомните libaio — и уже собирался понасмехаться, но не судьба :-)
По поводу фри — смешно. BSDisALinuxDistro™ ага. В винде тоже белые буквы на черном фоне в командной строке (даже POSIX сертификация есть) — винда тоже линукс? :-)

2. А где Вы видели такое утверждение? В винде «cmd1 | cmd2» использует те же пайпы (только анонимные — создаваемые CreatePipe вместо CreateNamedPipe), что и были протестированы. В линуксе «cmd1 | cmd2» использует пайпы, создаваемые вызовом pipe(2) — именовать их нельзя (для подобия именованых пайпов нужно использовать юникс домейн сокеты — правда очень консистентно?).
–3
egorinsk #
А где нужно это ваше «асинхронное чтение»? Я могу понять, запись: отдал данные в буфер, и пусть их система потихоньку отдает (кстати это в Линуксе есть, см. вызов sendfile() ), а чтение-то зачем?

Например, обработка HTTP-запроса: там можно запускать обработчик после чтения первой строки, содержащей URL (ну и возможно, заголовка Host:, если он используется). Как тут применить асинхронное чтение? «Прочитай первые 2000 байт и возвращайся»? Дык задержка лишняя получается.
0
adontz #
Вообще-то, учитывая что есть такие штуки как DMA, любой ввод-вывод: чтение/запись HDD, приём/передача LAN, по своей природе асинхронен.

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

То есть на современной аппаратуре синхронный ввод-вывод это практически всегда запуск асинхронного и окончание его работы.
0
amirul #
Да-да, раз нет — значит не нужно. Продолжайте читать мантры про «запускалки для игр», ну а когда появится свободная минутка подумайте о том, что когда Вы выставлете асинхронную операцию чтения, вы сразу же предоставляете буфер, который можно залочить в физической памяти и передать в устройство, а устройство будет читать через DMA напрямую в Ваш буфер. Это первое, что пришло в голову. С select-ом все сначала считается во внутренние буферы, а потом скопируется.

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

Вообще select — это «как бы замена» асинхронных операций, но как обычно неполноценная. Подходит только для сокетов (потому что для обычных файлов «ready for reading» или «ready for writing» лишено смысла — они обречены оставаться блокирующими), жутко неудобны в использовании, да и send все так же блокирует.

(кстати это в Линуксе есть, см. вызов sendfile()

Это все та же синхронная операция. Просто комбинация read и write. Даю хинт: асинхронные операции не блокируют вызывающий поток.

Как тут применить асинхронное чтение? «Прочитай первые 2000 байт и возвращайся»?

Вы почитайте про IO Completion Ports — авось перестанете нести чушь в публичных местах.
0
amirul #
На всякий случай (чтобы Вы не воспринимали хинт слишком буквально) отмечу, что O_NONBLOCK не имеет ничего общего с асинхронным IO — просто вместо блокирования будет возвращаться ошибка EWOULDBLOCK (чего люди не придумают, лишь бы не писать на нормальном API).
–1
egorinsk #
Ну меня просто в первую очередь асинхронные операции с сокетами и интересовали, а у вас больше про файлы. А там трюки с DMA не пройдут, так как пакет от сетевой карты разбирается и анализируется по всякому. И опять же, с сокетами нужно сразу обрабатывать полученные данные, как только придет, допустим HTTP запрос, а не ждать, пока там N байт наберется.

0
adontz #
Боже, какую чушь вы несёте с умным видом.

Описание одного из самых примитивных чипов:

wiki.osdev.org/Ne2000
The Ne2000 network card uses two ring buffers for packet handling. These are circular buffers made of 256-byte pages that the chip's DMA logic will use to store received packets or to get received packets.
0
egorinsk #
Вы сырые данные от сетевой карты прямо в userspace грузить будете? Или у них уже аппаратно встроен весь TCP стек?
0
amirul #
А у нас в ядре тоже все асинхронно (в смысле в первую очередь потому что все асинхронно там, все асинхронно и выше). В отличие от. Но вы похоже не улавливаете суть. Если не доверяете «классовым врагам» — почитайте что пишут ваши же (вот прямо с первого раздела Motivation и начинайте).
0
egorinsk #
Да верю, верю :)
+2
amirul #
О, самую мякотку пропустил.
Или у них уже аппаратно встроен весь TCP стек?

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

Ну и когда дойдете до раздела «Lack of support in Linux» остановитесь на секундочку и подумайте, может вместо выкрикивания лозунгов про «запускалки для игр» стоит заняться самообразованием?
0
amirul #
Именно для сетевых приложений тот же IOCP используется наиболее широко (хотя в тех же БД тоже нередко). И да, современные сетевые карты со скоростями передачи 1-10 gbps ТОЛЬКО DMA и используется (плюс куча других техник, типа того же TOE, которого в запускалке желейных окошек нет и не предвидится).
0
amirul #
И, кстати, select — это неудобный и неполноценный КОСТЫЛЬ, а не асинхронный ввод-вывод.
0
andrewsh #
2. Интересно, а давно mkfifo(3) отменили?

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