Socket vs Socket, или использование сокетов MS Windows в ассемблерных программах — Архив WASM.RU

Все статьи

Socket vs Socket, или использование сокетов MS Windows в ассемблерных программах — Архив WASM.RU

Теория применения сокетов совсем недавно была описана в статье “Сокеты M$ Windows”, которую вы можете легко найти в разделе “Статьи/Сеть”. Не будем повторяться, но вкратце скажем, что сокеты определяют логику для программирования сети аналогичную работе с файлами. В разделе “Исходники/Сеть и коммуникации” уже давно (со времен wasm.zite.ru) имеется рабочая небольшая программа “ledilog.zip\connect.asm”, в которой на основе использования сокетов реализован обмен текстовыми сообщениями между двумя компьютерами в сети. Таким образом, накопилась критическая масса, состоящая из некоторого минимума теории и кое-каких практических материалов. Все это и подтолкнуло автора к использованию в своем проекте (детали в данном случае не важны) связи по локальной сети между компьютерами на основе сокетов MS Windows. Однако все оказалось не так просто, как хотелось. Впрочем, как и всегда...

Куски кода, связанные с созданием и использованием сокетов из программы-оригинала легко переносились и компилировались, но все работало не так, как надо, либо вообще не работало. Обнаружились значительные провалы между кажущимся пониманием теории и практикой, к тому же комментарии у источника были на итальянском языке, что не добавляло ясности. Пришлось отказаться от первоначальной идеи и писать отдельную программку с минимумом интерфейса специально для проверки, что ж это за зверь такой: сокеты Windows. В данной статье приведена попытка описать полученный опыт на примере реальной, хотя и учебной программы. Тут не будет много теории, только необходимый для понимания кода минимум. Также предполагается, что читатель уже знаком с тем, как создавать приложение под Windows, имеющее основное окно и процедуру этого окна, как вызываются диалоговые окна... И последнее, так уж получилось, что моя программа внешне вышла очень похожей на программу-оригинал. Не судите меня строго...

Для впервые заинтересовавшихся сетевым программированием есть смысл пояснить, что же из себя представляют сокеты. Сокет можно рассматривать, как конечный пункт передачи данных по сети. Сетевое соединение - это процесс передачи данных по сети между двумя компьютерами или процессами. Тогда сокет - конечный пункт такой передачи данных. Другими словами, когда программы используют сокет, для них он является абстракцией, представляющий одно из окончаний сетевого соединения. Для установления соединения в абстрактной модели сокетов необходимо, чтобы каждая из сетевых программ имела свой собственный сокет. Недаром слово socket переводится с английского как гнездо или разъем!

Связь между двумя сокетами может быть ориентирована на соединение, а может быть и нет. С чем это едят? Все дело в том, в сетевом протоколе TCP/IP (а на сегодня это “родной” протокол интернета, да и большинства локальных сетей) предусмотрено два режима: ориентированный и не ориентированный на соединение. В ориентированных на соединение протоколах данные перемещаются как единый, последовательный поток байт без какого либо деления на блоки. Конечно, имеется в виду логика процесса, а не то, что физически происходит в среде передачи. В не ориентированных на соединение протоколах сетевые данные перемещаются в виде отдельных пакетов, называемых датаграммами. Как мы уже говорили, сокеты могут работать как с одними, так и с другими. В дальнейшем при создании сокета мы с этим столкнемся. А сейчас достаточно запомнить, что датаграммы могут приходить к получателю не подряд, а в непpедсказуемой последовательности. Так что датаграммы мы использовать в данном примере не будем, а только режим, ориентированный на соединение!

С аналогией между сокетами и файлами тоже не все просто. Конечно, для начала тоже нужно создать сокет, получить его дескриптор, а потом, используя этот дескриптор, писать или читать… Но отличие в том, что дескриптор файла указывает на уже существующий или только что созданный файл или на устройство, дескриптор сокета не содержит каких-либо определенных адресов или пунктов назначения сетевого соединения. Этот факт существенно отличает его от любого другого дескриптора стандартной системы ввода-вывода. Программа, работающая с сокетами, сначала образует сокет и только потом соединяет его с точкой назначения на другом конце сетевого соединения. Если бы файловый ввод-вывод состоял из таких же шагов, приложение сначала получало бы дескриптор файла, а затем привязывало бы его к имени определенного файла на диске.

Несмотря на то, что первоначально сокеты появились в системе UNIX (т.н. сокеты Беркли), на данный момент разработчики Windows давно модифицировали и расширили интерфейс для работы сокетов. Таким образом, сейчас мы имеем многочисленные функции API, так или иначе связанные с сокетами:

WSAStartup()* инициализирует Windows Socket dll
WSACleanup()* прекращает использование этой dll
socket()* функция создает сокет с заданными параметрами
WSAAsyncSelect()* функция указывает посылать сообщение от
сокета заданному окну при любом из
заданных сетевых событий
bind()* ассоциирует локальный адрес с сокетом
listen()* устанавливает сокет в состояние, в котором он
слушает порт на предмет входящих соединений
accept()* функция извлекает из очереди ожидающих
подключений первое, создает новый сокет и
возвращает его дескриптор
connect()* функция подключает созданный сокет к
указанному адресу
select() функция определяет статус одного или более
сокетов
shutdown() функция запрещает посылать и/или принимать
данные от сокета
ioctlsocket() функция управляет режимом сокета
getsockopt() функция возвращает установки сокета
recv()* функция получает данные от сокета
send()* функция посылает данные в ранее
подключенный сокет
sendto() функция посылает данные по указанному адресу
recvfrom() функция получает датаграммы от сокета

Звездочками отмечены те функции, которые будут встречаться в тестовой программе. Учтите, что кроме перечисленных существуют и многие другие… А теперь приступим к более подробному рассмотрению того, как этим хозяйством пользоваться. Скачайте пример здесь. Программа начинается как обычно с подключения необходимых библиотек, имеет секцию инициализированных и неинициализированных данных (.data и .data?). Обратите внимание на задание констант. Все константы (определение использованных ресурсов и др.) вынесены в отдельный файл SocSoc.inc и они нам в этот раз не интересны. Другое дело строка:

WM_SOCKET equ WM_USER + 100

Это задание численного значения сообщения WM_SOCKET, того самого сообщения, которое нам в дальнейшем будет посылать Windows при работе с сокетами. Дело в том, что Windows не использует для своих стандартных сообщений значения выше WM_USER, поэтому мы легко можем использовать этот диапазон для нужд своего приложения. Отметим этот важный момент, в дальнейшем мы еще раз вернемся к обсуждению сообщений типа WM_USER+...

Секция кода начинается с определений макросов, используемых при анализе сообщений нашим окном. Конечно, надо бы вынести макросы в отдельный файл .inc – файл и подключить его с помощью include. Но в данном случае их всего два и хотелось максимально упростить для понимания начинающими этот проект... Только поэтому макросы помещены в начало секции .code. Ну не данные же это в самом деле! Так что не делайте, как я.

Cначала опишем вкратце общий алгоритм работы нашей программы, а затем приступим к подробному анализу кода:

1) при запуске получаем и сохраняем хэндл программы, регистрируем у Windows текстовую строку в качестве сообщения и сохраняем возвращенный нам код сообщения для дальнейшего использования (об этом позже);

2) инициализируем dll, ответственную за использование сокетов Windows;

3) если получена ошибка инициализации, то делать дальше нечего – выходим с ошибкой из программы! Если же вызов dll прошел успешно, то продолжим работу: получаем адрес командной строки, (если таковая была при запуске – в данном примере не используется). Вызываем функцию WinMain – которая, собственно и определяет логику работы приложения и которая будет подробно рассмотрена ниже;

4) после выхода из WinMain возвращаемся в Windows.

А теперь разберем, что происходит в третьем пункте. В объявлении функции WinMain все достаточно традиционно и подробный анализ пропускаем. Для нас интерес составляет процедура WndProc главного окна программы, которая получает и реагирует на сообщения Windows. Сразу же после создания нашего окна процедура WndProc получит соответствующее сообщение: WM_CREATE. Это удобный момент для приведения в исходное состояние всего нашего хозяйства. Раз мы собираемся обмениваться с другими компьютерами, хорошо бы узнать кое-что и о себе, вернее о том компьютере, где запущен экземпляр нашей программы. Можно получить имя компьютера в виде текстовой строки в buffer соответствующей функцией, а затем оттуда скопировать в выходной буфер:

invoke gethostname, addr buffer, sizeof buffer 
invoke wsprintf, addr buf_out, addr szName, addr buffer

Аналогично получаем IP адрес компьютера по имени:

invoke gethostbyname, addr buffer

Для этого есть в API специальная функции gethostbyname, которая возвращает информацию о компьютере по его имени, заполняя специальную структуру hostent. Приведем ее для лучшего понимания дальнейших действий:

hostent STRUCT
h_name DWORD ? ; char FAR *
h_alias DWORD ?
h_addr WORD ?
h_len WORD ?
h_list DWORD ? ; char FAR * FAR *
hostent ENDS

Элемент структуры h_name – не что иное, как указатель на строку с именем нашего компьютера. А вот элемент h_list более интересен! Это указатель на список IP-адресов компьютера. Их может быть несколько, по числу сетевых интерфейсных карт, установленных в компьютере. Причем адреса представлены в сетевом порядке байт. Нас интересует первый из них. Вот как добраться до этого адреса:

mov eax, [eax+12] ; получаем указатель на элемент h_list в HOSTENT
mov eax, [eax]     ; получаем указатель на указатель на IP
mov eax, [eax] ; получаем указатель на строку IP в сетевом порядке байт

После преобразования функцией inet_ntoa, сохраняем полученный строковый формат IP адреса (вида 127.0.0.1) в еще один выходной буфер. Во всем этом есть один тонкий момент, непонимание которого может привести к дальнейшему использованию неправильных данных! Те функции Windows Sockets API, которые возвращают указатели на различные данные (а это часто так и есть) гарантирует их (данных) сохранность только до следующего вызова функций Sockets API. Поэтому необходимо сразу же копировать все необходимые нам значения в отведенные нашей программой для этого переменные! Это в полной мере относится и к функции gethostbyname.

Собственно, и имя компьютера, и его IP-адрес в данном случае используются программой только для информации, и их можно было и не получать. На работе программы это не скажется. Другое дело, что пример-то учебный, а в реальном проекте при обмене данными скорее всего нам понадобится указывать от кого, собственно, посылочка...

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

Итак. Сначала нам нужно открыть сокет:

invoke socket, AF_INET, SOCK_STREAM, 0;

Параметры следующие:

AF_INET – семейство, в версии 1.1 только AF_INET;

SOCK_STREAM - тип сокета, который вы желаете использовать.

Помните, мы выбираем связь, ориентированную на соединение, и говорим НЕТ датаграммам (SOCK_DGRAM);

0 – протокол, не устанавливать никакого протокола.

И если нет ошибки, сохранить его дескриптор для дальнейшего использования:

mov hSocket2, eax

Далее нужно указать Windows, какому окну надо посылать сообщения об определенных событиях, связанных с открытым сокетом. Это очень важный момент:

invoke WSAAsyncSelect, hSocket2, hWnd, WM_SOCKET, \
FD_ACCEPT+FD_READ

где hSocket2 - дескриптор сокета (вот сразу и пригодился)

hWnd - дескриптор главного окна приложения

WM_SOCKET - сообщение, нами же определенное

FD_ACCEPT+FD_READ – маска, задающая интересующие нас сообщения от этого сокета (в данном случае мы хотим получать уведомление о попытке подключения и уведомление о готовности данных для чтения).

Затем приступаем к уточнению деталей. Для этого сначала надо преобразовать номер порта в так называемый сетевой порядок байт. Хорошо, что для этого есть специальная API-функция. Воспользуемся ей и заполняем структуру <sin> другими необходимыми параметрами:

invoke htons, Port
mov sin.sin_port, ax
mov sin.sin_family, AF_INET
mov sin.sin_addr, INADDR_ANY

Далее необходимо сопоставить локальный адрес, представленный в структуре <sin> с ранее открытым сокетом:

invoke bind, hSocket2, addr sin, sizeof sin

И последнее, теперь надо заставить сокет слушать указанный порт на предмет входящих сообщений:

invoke listen, hSocket2, 5 

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

Затем, если пользователь отважится и выберет пункт меню “Подключить”, то создаем диалоговое окно выбора IP адреса подключения:

invoke DialogBoxParam, hInstance, addr DlgNameZ, hWnd, addr DlgProcZ, 0

После выхода из которого (если адрес был указан) приходит пора создавать сокет на передачу:

invoke socket, AF_INET, SOCK_STREAM, 0
 .if eax != INVALID_SOCKET 
  mov hSocket, eax 
 .else
  invoke ERROR, addr ErrorCrSocket, 1
 .endif
invoke WSAAsyncSelect, hSocket, hWnd, WM_SOCKET,\
                                     FD_CONNECT+FD_CLOSE
      

Здесь пока все тоже, что и ранее с первым сокетом, только теперь нас интересуют сообщение о состоявшемся подключении и уведомление о закрытии сокета (FD_CONNECTи FD_CLOSE).

Далее по аналогии формируем структуру <sin>, содержащую адрес подключения:


invoke htons, Port
mov sin.sin_port, ax
mov sin.sin_family, AF_INET
 
invoke inet_addr, addr AdresIP
mov sin.sin_addr, eax

      

Тут немного пояснений. После выхода из диалогового окна выбора адреса в буфере AdresIP содержится IP-адрес в привычном для нас виде a.b.c.d. Для удобства отладки приложения указан и адрес “самого себя”. Это - 127.0.0.1. Дело в том, что согласно соглашениям TCP гарантируется, что все пакеты на все адреса, начинающиеся со 127.ххх в физическую линию передаваться не будут. Таким образом, это сильно облегчает жизнь тем людям, у которых нет возможности все время сидеть на двух компьютерах в сети сразу. Пример полностью работает и на ОДНОМ компьютере. Только адрес надо предварительно преобразовать функцией inet_addr из строкового формата с точками, а потом уже заносить в <sin>.

И апофеоз, надо подключить созданный сокет к указанному в <sin> IP- адресу:

invoke connect, hSocket, addr sin, sizeof sin

Вот после этих строк процедура главного окна получит сообщение от Windows: WM_SOCKET с lParam = FD_CONNECT. Тут самое время сообщить о состоявшемся (или нет) соединении с помощью простого MessageBox-а. Ничего дополнительно делать не надо.

Что же произойдет, когда слушающий сокет обнаруживает попытку соединения? Не забывайте, что мы можем коннектиться к самим себе, но это существа дела не меняет. Все просто. При наступлении какого-либо события, связанного с сокетом, Windows шлет сообщение процедуре главного окна, в данном случае пошлет тот же WM_SOCKET, теперь уже с параметром FD_ACCEPT. Делать нечего, нужно вызвать специальную функцию API, чтобы разрешить входящее соединение:

 invoke  accept, hSocket2, 0, 0
mov hClient, eax ; сохранить дескриптор сокета

Будьте бдительны! Это еще один важный момент, который в свое время попортил мне немало нервов. Конечно, входящее соединение надо сначала разрешить, а если мы не прореагируем на попытку соединения, то следующего такого сообщения от сокета больше не получим. Но более того, возвращаемое функцией accept значение представляет из себя дескриптор НОВОГО сокета, который мы должны будем далее использовать для приема данных. Его, конечно, надо сохранить. И именно его использовать в функции приема данных! Первоначальный слушающий сокет остается открытым.

Теперь о том, как передать данные, если уж связь налажена. Специально в программе есть два места, откуда можно передавать. Это и главное окно, пункт меню “Передать…”, и диалоговое окно приема/передачи. Рассмотрим второе. Это участок процедуры DlgProcZ1 диалогового окна:

.if eax == IDC_BUTT6; нажата кнопка "отослать"
  .if Connected == 1
  invoke GetDlgItemText, hDlg, IDC_EDIT02, addr BytSend, 64
  invoke lstrlen, addr BytSend
    .if eax < 64
    invoke send, hSocket, addr BytSend, 64, 0
     .if eax == SOCKET_ERROR
    invoke ERROR, addr ErrorReseau, 0
     .endif
    .endif
  .endif
      

При нажатии кнопки “Отослать” проверяем, установлено ли уже соединение и если да, то получаем с элемента редактирования текста IDC_EDIT02 собственно текст (до 64 байт) в буфер BytSend. Проверяем длину строки. И передаем весь буфер в сокет. Функция send имеет следующие параметры:

hSocket – дескриптор (раннее подключенного функцией connect) сокета

addr BytSend – указатель на передаваемый буфер

64 – длина буфера

0 - флаг, определяющий поведение функции (для нас только такой).

Сразу же не забудем проверить признак ошибки при передаче. Мало ли чего!

А что же прием? Если мы (или какой-нибудь другой компьютер) прислали данные на слушающий сокет, для которого была выполнена функция accept (помните, мы сохраняли дескриптор в hClient), опять процедуре главного окна летит сообщение WM_SOCKET уже c параметром FD_READ. Вот кусок соответствующего кода:

.elseif ax == FD_READ
  HIWORD lParam
  .if ax == NULL  ; отсутствует ошибка
   invoke recv, hClient, addr BytRecu, 64, 0
  mov eax, 1      ;  установить признак, что в буфер чтения получено...
 invoke SendMessage, HWND_BROADCAST, MessageIPC, eax, 0
      

Ну, с функцией собственно приема recv, надеюсь ясно. Параметры аналогичны функции send:


hClient – дескриптор сокета (ранее полученный функцией accept!)
addr BytRecu – указатель на приемный буфер
64 – длина этого буфера
0 - флаг (для нас такой).

Далее наступает ключевой момент. Теперь, когда принятые данные уже в буфере BytRecu, пришла пора еще одного трюка.

Картинка у меня долго не складывалась, пока не появилась статья от CyberManiac (см. “Статьи/Секреты Win32/IPC”). Посвящена она механизму обмена данными между приложениями (Interprocess communication, сокращенно – IPC). Почитайте. Но у нас ситуация несколько иная. Приложение у нас одно и в данном случае не надо пересылать данные. Пусть они себе лежат в буфере. Надо всего лишь подать сигнал процедуре диалогового окна приема/передачи о факте приема от сокета. А как? Сокет–то шлет сообщения главному окну, а индицируем принятые данные в другом, диалоговом, которое ничего не знает... Да и самого диалогового окна приема/передачи в момент приема данных может не быть на экране. Но нас это не сильно беспокоит, не хотите – не надо. Наше дело сообщить. Посылаем широковещательное сообщение всем окнам функцией SendMessage. Параметры следующие:

HWND_BROADCAST – идентификатор окна, процедура которого получит
               сообщение, или  HWND_BROADCAST,
               тогда сообщение посылается всем окнам верхнего уровня в системе,
               в том числе и невидимым,…но сообщение не посылается дочерним окнам;
MessageIPC – номер зарегистрированного ранее сообщения
eaxwParam, дополнительный параметр сообщения (DWORD)
0lParam, аналогично.

А вот и часть кода в процедуре окна DlgProcZ1, ответственная за прием такого сообщения:

; если получено зарегистрированное нами сообщение
 .elseif eax == MessageIPC        
; вывести полученные данные из буфера приема в контрол IDC_EDIT03
  invoke SetDlgItemText, hDlg, IDC_EDIT03, addr BytRecu
      

Можно заметить, что не анализируется wParam и lParam. Нам достаточно только факта самого сообщения. А теперь рассмотрим, как все-таки регистрировалось сообщение MessageIPC при входе в программу. Для этого надо было придумать уникальную текстовую строку:

MsgString  db "MessageSocDirectOr", 0

Это своего рода пароль для разных приложений, по которому они могут узнавать “свое” сообщение. Такой себе “у вас продается славянский шкаф?”. Далее надо провести собственно регистрацию:

invoke RegisterWindowMessage, addr MsgString

И на выходе, если eax не равно нулю, значит ошибки нет и там содержится код сообщения. Какой он? Но мы же не хотим, знать больше, чем положено. Надо просто запомнить его и использовать по мере надобности:

mov MessageIPC, eax ; сохранить присвоенный сообщению номер

Идея в том, что при первом вызове этой функции Windows резервирует и отдает в наше полное распоряжение первое попавшееся ей на глаза свободное сообщение. А затем при повторном вызове этой функции с такой же точно текстовой строкой в качестве параметра, приложение (и наше, и любые другие) в качестве результата получат именно этот номер, привязанный теперь к этой текстовой строке. Другое дело, что механизма освобождения сообщения нет и оно наше до конца сеанса.

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

; закрываем сокеты и сообщаем о том, что dll нам больше уже не нужна
invoke closesocket, hSocket
invoke closesocket, hSocket2
invoke WSACleanup       ;  dll больше не нужна
invoke PostQuitMessage, NULL 

      

Менее очевидный момент заключается в том, что надо закрыть сокет на передачу, если получено сообщение о разрыве связи с приемного конца. Вот часть разбора сообщения WM_SOCKET в процедуре главного окна:

.elseif ax == FD_CLOSE
   HIWORD lParam
   .if ax == NULL ; отсутствует ошибка
       invoke closesocket, hSocket
       mov hSocket, 0
      

И еще. Возвращаемся к обсуждению сообщений типа WM_USER+… Цитата из CyberManiac-а: “…в некоторых программах для обмена информацией используются сообщения WM_USER+N, в частности, именно так реализован механизм IPC в WinAmp. Однако Microsoft имеет по этому поводу свое особое мнение - согласно MSDN, сообщения от WM_USER+0 до WM_USER+3FFFh включительно используются только для передачи данных внутри или между окнами одного класса.” Так что одно дело получать сообщения в процедуре окна от сокетов Windows, и совсем другое, слать IPC широковещательные сообщения с WM_USER+… в качестве параметра.

Все происходящее при открытии/закрытии сокетов очень удобно наблюдать при помощи утилиты Tcpview.exe (Mark Russinovich - www.sysinternals.com). Рекомендую! Хорошо видны порты, а также протоколы, их использующие… В частности, хорошо отслеживается эффект “расщепления” сокета на два после выполнения функции accept. Кстати, появляется еще один (кроме первоначального 3030) используемый порт, что-нибудь типа 30хх, в зависимости от наличия свободных.

Подведем итог. Для того, чтобы организовать обмен информацией между двумя компьютерами в сети, в программе создается два сокета, используемые раздельно для приема и для передачи. Имеется главное окно приложения и в разных местах программы могут создаваться два диалоговых окна. Сокеты создаются в процедуре главного окна, но это не принципиально. Существенно важно то, что оба они при наступлении соответствующих событий посылают предопределенное нами самими сообщение WM_SOCKET именно главному окну. Где в цикле разбора сообщений от Windows уже анализируются и обрабатываются… Таким образом, ВСЯ работа с сокетами сосредоточена в процедуре главного окна. Существует затруднение, состоящее в том, что данные, полученные с сокета на прием, могут быть нужны нам в диалоговом окне приема/передачи, которое у нас не получает соответствующего сообщения от сокетов Windows. Вопрос решается с помощью посылки главным окном широковещательного сообщения о факте приема данных всем другим окнам. Диалоговое окно (или другое приложение, если захотите) может легко получить такое сообщение и обработать… Конечно, это не единственный способ взаимодействия между главным окном и диалоговым. Но соль в данном случае в том, что при поступлении данных от сокета диалогового окна-то может и не быть. Поэтому был выбран путь, когда программа просто сигнализирует о факте приема данных от сокета, а далее уже не важно, нужны они кому-нибудь, или нет.

В описываемом примере было рассмотрено использование только некоторых основных функции API, ответственных за работу с сокетами Windows, в основном разобраны их параметры, кроме тех, значение которых не критично. Но! Возникает (у меня, по крайней мере) несколько вопросов. Ну, во-первых, номер порта. Какой он может быть? А должен? Какие правила на этот счет. Кроме тех, которые широко известны: 80 порт для HTTP и др. Конечно, Windows при попытке создать сокет с уже используемым портом вернет код ошибки. Попробуйте запустить два экземпляра тестовой программы! Но разве от этого легче? Можно, конечно, в программе перебирать номера, но как тот, другой компьютер узнает, чем сердце успокоилось?
Во-вторых, понятно, почему используется два раздельных сокета. Чтобы можно было независимо передавать данные и читать их тогда, когда они поступают извне. А как можно обойтись одним сокетом? Если уж действительно сокеты задают логику работы с сетью, аналогичную работе с файлами, то хорошо было бы и писать, и читать, работая с одним “файлом”. И более того. Что делать, когда надо посылать сообщения нескольким компьютерам, например, десяти… Создавать десять сокетов или перебирать один с разными IP–адресами по очереди? Что-нибудь типа широковещательного сообщения? Вообще, тот же пример, расширенный на n-компьютеров, где каждый может обмениваться с каждым, выглядит пока туманно. Более или менее ясно, как на один слушающий сокет принимать входящие с разных адресов. Не зря же мы писали: invoke listen, hSocket2, 5. 5! Хотя программа все равно могла сохранить только один дескриптор сокета после invoke accept…

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

2002-2013 (c) wasm.ru