Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > Assembler FAQ > Server/Client Socket


Автор: Дьяволица 19.05.07, 22:17
 
Server/Client Socket

Всем известно, то, что первое появилась это глобальная сеть, а не локальная с приходом протокола TCP/IP для военных целей. Но так как этот способ связи был ненадёжным и опасным для передачи секретных данных, - его начали использовать во всём мире.

Каждый когда-нибудь хотел написать свой клиент сервер, чтобы пообщаться с другом через Интернет или в локальной сети. Сейчас я покажу и подробно прокомментирую, как это делается средствами API – Windows на MASM(ассемблере).

Итак, сервер: главной задачей, которого, является ожидание клиента, обработка пришедшего сообщения, добавление его в LISTBOX – controle и отсылка, что мы написали в EDIT – controle;

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    .386
      .model    flat, stdcall
      option    casemap :none
     
     
      include \masm32\include\windows.inc
      include \masm32\include\user32.inc
      include \masm32\include\kernel32.inc
      include \masm32\include\wsock32.inc
     
      includelib \masm32\lib\wsock32.lib
      includelib \masm32\lib\user32.lib
      includelib \masm32\lib\kernel32.lib
     
     
        _T macro p1, p2 ;макрос обработки строк
          local l
            ifb <p2>; Строка без имени
                .data
                    l   db p1, 0
                .code
                exitm   <addr l>
            else    ; Строка с именем
                .data
                    p1  db p2, 0
                .code
                exitm   <addr p1>
            endif
        endm
     
        DlgProc         proto :HWND, :UINT, :WPARAM, :LPARAM
        EditProc        proto :HWND, :UINT, :WPARAM, :LPARAM
        MESSG           proto :HWND, :DWORD
        ERROR           proto :HWND, :DWORD, :BYTE
     
     
        Port            equ 80
        WM_SOCKET       equ WM_USER+100
        VER_SOCKET      equ 101h        ; требуемая версия WinSock
     
        BUF_LEN         equ 1024        ; размер буфера для приема/передачи
     
        ; контролы из ресурсов
        IDC_OUT         equ 101         ; строка отсылки (контрол EDIT)
        IDC_IN          equ 102         ; приемное "окно" (контрол ListBox)
     
    .data?
        hInstance       HINSTANCE ?
        hDlg            HWND ?          ; хэндл окна диалога
        lpfnEditProc    HWND ?          ; перехваченная оконная функция EDIT
     
        hSocket         dd ?            ; хэндл слушающего сокета
        hClient         dd ?            ; хэндл сокета клиента
        IsSend          dd ?            ; при значении >0 разрешена передача данных клиенту
        wsa             WSADATA <>      ; информация о версии WinSock
        sin             sockaddr_in <>  ;структура узла
     
        ; буфер для операций ввода/вывода
        IOBuff          db BUF_LEN+1 dup(?)
     
     
    .code
     
    start:
        invoke  GetModuleHandle, NULL
        mov     hInstance, eax
     
        ; инициализируем Windows Sockets DLL
        invoke  WSAStartup, VER_SOCKET, addr wsa
        .if (eax == NULL) && (wsa.wVersion == VER_SOCKET)
            ; создаем диалог
            invoke  DialogBoxParam, hInstance, _T("MAINDIALOG"), 0, addr DlgProc, 0
        .else
            invoke  WSACleanup
            invoke  MessageBox, 0, _T("Windows socket not found!"), _T("ERROR"), \
                                MB_OK+MB_ICONERROR
            mov     eax, 90h            ; заканчиваем программу с кодом ошибки
        .endif
        ; уходим в хаОС
        invoke  ExitProcess, eax
     
     
        ; оконная функция диалога
    DlgProc proc    hWnd: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM
        ; определяем пришедшее сообщение
        .if (uMsg == WM_INITDIALOG)
            ; меняем заголовок окна диалога
            invoke  SendMessage, hWnd, WM_SETTEXT, NULL, _T("Server")
            mov     IsSend, FALSE       ; запрещаем отправку данных клиенту
            mov     eax, hWnd
            mov     hDlg, eax           ; хэндл окна нам еще понадобится :)
            ; сабклассируем контрол EDIT
            invoke  GetDlgItem, eax, IDC_OUT
            invoke  SetWindowLong, eax, GWL_WNDPROC, addr EditProc
            mov     lpfnEditProc, eax   ; сохраняем адрес предыдущей функции
            ; создаем сокет для прослушки порта на предмет входящих соединений
            invoke  socket, AF_INET, SOCK_STREAM, 0
            mov     hSocket, eax
            .if (eax != SOCKET_ERROR)
                ; задаем окно для приема сообщений от сокета
                ; нам понадобится только FD_ACCEPT и FD_CLOSE(только для SOCK_STREAM)
                ; автоматически для сокета устанавливается non-block mode.
                invoke  WSAAsyncSelect, hSocket, hWnd, WM_SOCKET, FD_ACCEPT+FD_CLOSE
                .if (eax != SOCKET_ERROR)
                    ; заполням структуру sockaddr_in
                    ; для начала обнуляем, поскольку заполняем не все поля
                    mov     edi, offset sin ;структуру сокета
                    mov     ecx, sizeof sin ;размер структуры
                    xor     eax, eax
                    rep     stosb
                    ; преобразовываем номер порта в сетевой порядок байт
                    invoke  htons, Port
                    mov     sin.sin_port, ax        ; и сохраняем его
                    mov     sin.sin_family, AF_INET ;семейство протоколов
                    mov     sin.sin_addr, INADDR_ANY; принимать с любого адреса
                    ; ассоциируем локальный адрес (Port) с открытым сокетом
                    invoke  bind, hSocket, addr sin, sizeof sin
                    .if (eax != SOCKET_ERROR)
                        ; ставим сокет на прослушку порта
                        invoke  listen, hSocket, 5  ; 5 - размер очереди на входящие запросы
                        .if (eax == SOCKET_ERROR)
                            invoke  ERROR, hWnd, _T("Can't listen socket!"), TRUE
                        .endif
                    .else
                        invoke  ERROR, hWnd, _T("Can't binding socket!"), TRUE
                    .endif
                .else
                    invoke  ERROR, hWnd, _T("Can't select event for socket!"), TRUE
                .endif
            .else
                invoke  ERROR, hWnd, _T("Can't create socket!"), TRUE
            .endif
     
        .elseif (uMsg == WM_CLOSE)
            ; подчищаем за собой и выходим
            invoke  closesocket, hClient    ; закрываем сокеты
            invoke  closesocket, hSocket    ; если уже закрыты, то ничего страшного :)
            invoke  WSACleanup              ; выгружаем WinSock DLL
            invoke  EndDialog, hWnd, NULL
     
        .elseif (uMsg == WM_SOCKET) ;события сокета
            mov     eax, lParam
            mov     edx, eax
            shr     edx, 16         ; dx - код ошибки или ноль
            .if (edx != NULL)       ; если dx <> NULL то получено извещение о ошибке
                invoke  ERROR, hWnd, _T("soket error!"), FALSE
                ; удаляем сокет вызвавший ошибку
                invoke  closesocket, wParam
            .else
                .if (ax == FD_ACCEPT)
                    ; извлекаем из очереди ожидающих подключений первое и
                    ; создаем новый сокет для связи с клиентом
                    invoke  accept, wParam, 0, 0
                    mov     hClient, eax
                    ; от него мы будем получать уведомления на чтение/запись/закрытие
                    invoke  WSAAsyncSelect, eax, hWnd, WM_SOCKET, FD_READ+FD_WRITE+FD_CLOSE
                    invoke  MESSG, hWnd, _T("added client socket!")
     
                .elseif (ax == FD_CLOSE)
                    ; закрываем сокет (в wParam его хэндл)
                    invoke  MESSG, hWnd, _T("close socket!")
                    invoke  closesocket, wParam
     
                .elseif (ax == FD_READ)
                    ; получаем данные с сокета (в wParam его хэндл)
                    invoke  recv, wParam, addr IOBuff, BUF_LEN, 0
                    invoke  MESSG, hWnd, addr IOBuff; отправляем их в ListBox
                .elseif (ax == FD_WRITE)
                    ; извещение о готовности сокета(в wParam его хэндл) к отправке данных
                    mov     IsSend, TRUE            ; устанавливам флаг
                    invoke  MESSG, hWnd, _T("socket is ready for writing!")
                .endif
            .endif  ; WM_SOCKET
     
        .else       ; если сообщение не обрабатывали, то вернем NULL
            xor     eax, eax
            ret
        .endif
            ; сообщение обработано, возвращаем ненулевой результат
            mov     eax, TRUE
            ret
    DlgProc endp
     
     
    ; оконная функция контрола EDIT
    ; просто перехватывает нажатие Enter и отправляет данные клиенту
    EditProc proc   hCtl: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM
        mov     eax, wParam
        .if (uMsg == WM_KEYDOWN) && (al == VK_RETURN) && (IsSend != FALSE)
            ; нажат Enter, отсылаем строку клиенту
            invoke  GetDlgItemText, hDlg, IDC_OUT, addr IOBuff, BUF_LEN
            invoke  send, hClient, addr IOBuff, BUF_LEN, 0
     
            .if (eax==SOCKET_ERROR)
                invoke  ERROR, hDlg, _T("send failed!"), FALSE
            .endif
            ; очищаем строку ввода
            invoke  SetDlgItemText, hDlg, IDC_OUT, NULL
            ret
        .endif
        ; нажата любая другая клавиша, вызываем старую функцию контрола
        invoke  CallWindowProc, lpfnEditProc, hCtl, uMsg, wParam, lParam
        ret
    EditProc endp
     
     
    ; просто добавляет строку в ListBox
    MESSG proc hWnd: HWND, message: DWORD
            invoke  GetDlgItem, hWnd, IDC_IN
            push    eax
            invoke  SendMessage, eax, LB_ADDSTRING, 0, message
            pop     ecx
            invoke  SendMessage, ecx, LB_SETTOPINDEX, eax, 0
            ret
    MESSG endp
     
    ; информирует о последней ошибке, получая ее с помощью WSAGetLastError
    ; при Fatal <> FALSE сообщение выводится в MessageBox, и программа закрывается.
    ; при Fatal == FALSE сообщение просто выводится в ListBox
    ERROR proc hWnd: HWND, message: DWORD, Fatal: BYTE
      local szStr[256]: byte    ; временный буфер для создания строки
        ; получаем код последней ошибки
        invoke  WSAGetLastError
        ; формируем строку с сообщением об ошибке
        invoke  wsprintf, addr szStr, _T("%s Error code: %u"), message, eax
        .if (Fatal != FALSE)
            invoke  MessageBox, hWnd, addr szStr, _T("FATAL ERROR"), MB_OK+MB_ICONERROR
            ; закрываем диалог, все равно смысла нет в дальнейшей работе :)
            invoke  SendMessage, hWnd, WM_CLOSE, 0, 0
        .else
            ; информируем пользователя, засылая строку в ListBox
            invoke  MESSG, hWnd, addr szStr
        .endif
        ret
    ERROR endp
     
    end start
Компилируется:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    ml /c /coff Server.ASM
    Link /SUBSYSTEM:WINDOWS Server.obj Server.RES
    pause

Клиент: главной задачей, которого, является подключение к серверу обработка пришедшего сообщения и отсылка, того, что мы написали в EDIT – controle;

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
     .386
            .model flat, stdcall
            option casemap :none
     
     
      include \masm32\include\windows.inc
      include \masm32\include\user32.inc
      include \masm32\include\kernel32.inc
      include \masm32\include\wsock32.inc
     
      includelib \masm32\lib\wsock32.lib
      includelib \masm32\lib\user32.lib
      includelib \masm32\lib\kernel32.lib
     
        _T macro p1, p2 ;макрос обработки строк
          local l
            ifb <p2>; Строка без имени
                .data
                    l   db p1, 0
                .code
                exitm   <addr l>
            else    ; Строка с именем
                .data
                    p1  db p2, 0
                .code
                exitm   <addr p1>
            endif
        endm
     
        DlgProc         proto :HWND, :UINT, :WPARAM, :LPARAM
        EditProc        proto :HWND, :UINT, :WPARAM, :LPARAM
        MESSG           proto :HWND, :DWORD
        ERROR           proto :HWND, :DWORD, :BYTE
     
        Port            equ 80
        WM_SOCKET       equ WM_USER+100
        VER_SOCKET      equ 101h        ; требуемая версия WinSock
     
        BUF_LEN         equ 1024        ; размер буфера для приема/передачи
     
        ; контролы из ресурсов
        IDC_OUT         equ 101         ; строка отсылки (контрол EDIT)
        IDC_IN          equ 102         ; приемное "окно" (контрол ListBox)
     
     
    .data?
        hInstance       HINSTANCE ?
        hDlg            HWND ?          ; дескриптор окна диалога
        lpfnEditProc    HWND ?          ; перехваченная оконная функция EDIT
     
        hSocket         dd ?            ; дескриптор сокета
        IsSend          dd ?            ; при значении >0 разрешена передача данных серверу
        wsa             WSADATA <>      ; информация о версии WinSock
        sin             sockaddr_in <>
     
        ; буфер для операций ввода/вывода
        IOBuff          db BUF_LEN+1 dup(?)
     
     
    .code
     
    start:
        invoke  GetModuleHandle, NULL ;;получает дескриптор для определенного модуля если
         mov     hInstance, eax       ;файл отображен в пространстве адреса вызова процесса и
                                      ;возвращает дескриптор окна в регистр eax
       ; инициализируем Windows Sockets DLL
        invoke  WSAStartup, VER_SOCKET, addr wsa ;версия и структура сокета
        .if (eax == NULL) && (wsa.wVersion == VER_SOCKET);если всё ок
            ; создаем диалог
            invoke  DialogBoxParam, hInstance, _T("MAINDIALOG"), 0, addr DlgProc, 0
        .else
            invoke  WSACleanup  ;в обратном случае выгружаем библу
            invoke  MessageBox, 0, _T("Windows socket not found!"), _T("ERROR"),\ ;окно ошибки
                                MB_OK+MB_ICONERROR
            mov     eax, 90h            ; заканчиваем программу с кодом ошибки
        .endif
        ; уходим в хаОС
        invoke  ExitProcess, eax ;выход из процесса
     
     
        ; оконная функция диалога
    DlgProc proc    hWnd: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM
        ; определяем пришедшее сообщение окну!
        .if (uMsg == WM_INITDIALOG)
            ; меняем заголовок окна диалога
            invoke  SendMessage, hWnd, WM_SETTEXT, NULL, _T("Client")
            mov     IsSend, FALSE       ; запрещаем отправку данных клиенту
            mov     eax, hWnd           ;дескриптор окна в регистр еах
            mov     hDlg, eax           ; дескриптор окна нам еще понадобится :)
            ; сабклассируем контрол EDIT
            invoke  GetDlgItem, eax, IDC_OUT ;извлекает дескриптор для управления
            invoke  SetWindowLong, eax, GWL_WNDPROC, addr EditProc ;изменяет атрибут определенного окна.
                                                                  ;  Функция также устанавливает 32-
                                                                  ; бит величины  в определенной компенсации
                                                                  ;в дополнительную память окна
            mov     lpfnEditProc, eax   ; сохраняем адрес предыдущей функции
            ; создаем сокет
            invoke  socket, AF_INET, SOCK_STREAM, 0
            mov     hSocket, eax        ;запоминаем дескриптор сокета
            .if (eax != SOCKET_ERROR)   ; если нет ошибки, то продолжаем
                ; задаем окно для приема сообщений от сокета
                invoke  WSAAsyncSelect, hSocket, hWnd, WM_SOCKET, \ ;дескриптор сокета, окна; события сокета, "какие события"
                                        FD_READ+FD_WRITE+FD_CONNECT+FD_CLOSE
                .if (eax != SOCKET_ERROR);если всё ок
                    ; заполням структуру sockaddr_in
                    ; для начала обнуляем, поскольку заполняем не все поля
                    mov     edi, offset sin ;структуру сокета
                    mov     ecx, sizeof sin ;размер структуры
                    xor     eax, eax
                    rep     stosb
                    ; преобразовываем номер порта в сетевой порядок байт
                    invoke  htons, Port
                    mov     sin.sin_port, ax
                    mov     sin.sin_family, AF_INET ;семейство используемых протоколов для интернет  "AF_INET"
                    ; конвертируем строковый формат в IP-адрес
                    invoke  inet_addr, _T("127.0.0.1")
                    mov     sin.sin_addr, eax ;загружаем IP в параметр структуры
                    ; подключаем созданный сокет к указанному в sin IP- адресу
                    invoke  connect, hSocket, addr sin, sizeof sin ;дескриптор сокета, адрес и порт удалённого узла и размер структуры
                    .if (eax == SOCKET_ERROR) ;ошибка небеда :)
                        ; уточняем ошибку :)
                        invoke  WSAGetLastError
                        ; если ошибка не равна нижеследующим, то можно продолжать :)
                        .if (eax != WSAEWOULDBLOCK) && (eax != WSAEINPROGRESS);ресурс временно недоступен, действие  в процессе развития
                            invoke  ERROR, hWnd, _T("Can't connect!"), TRUE ;ошибки :)
                        .endif
                    .endif
                .else
                    invoke  ERROR, hWnd, _T("Can't select event for socket!"), TRUE
                .endif
            .else
                invoke  ERROR, hWnd, _T("Can't create socket!"), TRUE
            .endif
     
        .elseif (uMsg == WM_CLOSE) ;событие закрытия ;)
            ; подчищаем за собой и выходим
            invoke  closesocket, hSocket; закрываем сокет, если он еще не закрыт
            invoke  WSACleanup          ; выгружаем WinSock DLL
            invoke  EndDialog, hWnd, NULL;закрываем диалог
     
        .elseif (uMsg == WM_SOCKET) ;а вот и узнаём какие события сокета
            mov     eax, lParam         ; ax - код события
            mov     edx, eax
            shr     edx, 16             ; dx - код ошибки или ноль
            .if (edx != NULL)           ; если dx <> NULL то получено извещение о ошибке
                invoke  ERROR, hWnd, _T("soket error!"), FALSE
                ; закрываем открытый сокет
                invoke  closesocket, wParam
            .else
                .if (ax == FD_READ) ;событие чтения
                    ; получаем данные с сокета (в wParam его дескриптор)
                    invoke  recv, wParam, addr IOBuff, BUF_LEN,0  ;буфер, размер буфера,флаг
                    invoke  MESSG, hWnd, addr IOBuff    ; отправляем их в ListBox, hWnd - дескриптор окна, IOBuff - буфер
     
                .elseif (ax == FD_WRITE) ;событие отправки
                    ; извещение о готовности сокета(в wParam его дескриптор) к отправке данных
                    mov     IsSend, TRUE    ; выставляем флаг
                    invoke  MESSG, hWnd, _T("socket is ready for writing!")
     
                .elseif (ax == FD_CONNECT) ;событие присоединения
                    ; извещение о успешном соединении с сервером (в wParam дескриптор сокета)
                    invoke  MESSG, hWnd, _T("connected to server!")
     
                .elseif (ax == FD_CLOSE) ;событие закрытия
                    ; закрываем сокет (в wParam его дескриптор)
                    invoke  MESSG, hWnd, _T("close server!")
                    invoke  closesocket, wParam ;закрываем сокет
                .endif
            .endif  ; WM_SOCKET
     
        .else       ; если сообщение не обрабатывали, то вернем NULL
            xor     eax, eax
            ret
        .endif
            ; сообщение обработано, возвращаем ненулевой результат
            mov     eax, TRUE
            ret
    DlgProc endp
     
     
    ; оконная функция контрола EDIT
    ; просто перехватывает нажатие Enter и отправляет данные серверу
    EditProc proc   hCtl: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM
        mov     eax, wParam
        .if (uMsg == WM_KEYDOWN) && (al == VK_RETURN) && (IsSend != FALSE) ;если клавиша энтер и "чистый" флаг
            ; отсылаем строку серверу
            invoke  GetDlgItemText, hDlg, IDC_OUT, addr IOBuff, BUF_LEN ;дескриптор контрола EDIT его идентификатор, буфер и его размер
            invoke  send, hSocket, addr IOBuff, BUF_LEN, 0 ; дескриптор сокета, буфер его размер и флаг
            .if (eax==SOCKET_ERROR) ;эх ошибка ;)
                invoke  ERROR, hDlg, _T("send failed!"), FALSE
            .endif
            ; очищаем строку ввода
            invoke  SetDlgItemText, hDlg, IDC_OUT, NULL
            ret
        .endif
        ; нажата любая другая клавиша, вызываем старую функцию контрола
        invoke  CallWindowProc, lpfnEditProc, hCtl, uMsg, wParam, lParam
        ret
    EditProc endp
     
    ; просто добавляет строку в ListBox
    MESSG proc hWnd: HWND, message: DWORD
            invoke  GetDlgItem, hWnd, IDC_IN
            push    eax
            invoke  SendMessage, eax, LB_ADDSTRING, 0, message
            pop     ecx
            invoke  SendMessage, ecx, LB_SETTOPINDEX, eax, 0
            ret
    MESSG endp
     
    ; информирует о последней ошибке, получая ее с помощью WSAGetLastError
    ; при Fatal <> FALSE сообщение выводится в MessageBox, и программа закрывается.
    ; при Fatal == FALSE сообщение просто выводится в ListBox
    ERROR proc hWnd: HWND, message: DWORD, Fatal: BYTE
      local szStr[256]: byte    ; временный буфер для создания строки
        ; получаем код последней ошибки
        invoke  WSAGetLastError
        ; формируем строку с сообщением об ошибке
        invoke  wsprintf, addr szStr, _T("%s Error code: %u"), message, eax
        .if (Fatal != FALSE)
            invoke  MessageBox, hWnd, addr szStr, _T("FATAL ERROR"), MB_OK+MB_ICONERROR
            ; закрываем диалог, все равно смысла нет в дальнейшей работе :)
            invoke  SendMessage, hWnd, WM_CLOSE, 0, 0
        .else
            ; информируем пользователя, засылая строку в ListBox
            invoke  MESSG, hWnd, addr szStr
        .endif
        ret
    ERROR endp
     
    end start
Компилируется:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    ml /c /coff Client.ASM
    Link /SUBSYSTEM:WINDOWS Client.obj Client.RES
    pause


Ресурс диалога для «общения» с пользователем:

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    MAINDIALOG DIALOG 17, 30, 160, 82
    STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
    CAPTION "MAINDIALOG"
    BEGIN
            CONTROL "", 101, "EDIT", ES_LEFT | ES_MULTILINE | WS_CHILD | WS_VISIBLE | \
    WS_BORDER | WS_TABSTOP, 8, 64, 144, 12
            LISTBOX 102, 7, 7, 145, 49, LBS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_BORDER |\
     WS_VSCROLL
    END
Компилируется борландским компилятором ресурсов brcc32
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    brcc32.exe -32 Client/Server.txt
    pause

В некоторых местах не была вставлена функция обработки ошибок, только в самых необходимых. Это только пример, и в реальных прогах такое не приветствуется ;) Насчет recv и send. Данные функции не гарантируют передачу/прием блока данных за один раз. В данном примере это несущественно. Этот пример относится к WSAAsyncSelect Model. Есть также Select model, WSAEventSelect Model, и Overlapped Model(пожалуй самая интересная ;) ).

Автор: AndNot 28.05.07, 03:38
 
Асинхронный режим, основанный на событиях

В продолжение темы хотелось бы упомянуть о работе с сокетами с помощью системных объектов событий. Этот режим позволяет эффективнее, чем вышеприведенный, обслуживать большое количество клиентов. Но поскольку я ленивый, то представлю только код сервера, а клиентов можно взять из предыдущей статьи. Данный сервер делает простую задачу, принимает сообщения от клиентов и рассылает их всем подключившимся, что то вроде чата, только примитивного. Собственно код:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    ; file: server.asm
      .386
      .model    flat, stdcall
      option    casemap :none
     
     
      include \masm32\include\windows.inc
      include \masm32\include\user32.inc
      include \masm32\include\kernel32.inc
      include \masm32\include\wsock32.inc
      include \masm32\include\ws2_32.inc
     
      includelib \masm32\lib\wsock32.lib
      includelib \masm32\lib\ws2_32.lib
      includelib \masm32\lib\user32.lib
      includelib \masm32\lib\kernel32.lib
     
     
     
      WSA_INFINITE      equ INFINITE
      WSA_WAIT_TIMEOUT  equ WAIT_TIMEOUT
      WSA_WAIT_FAILED   equ WAIT_FAILED
      WSA_WAIT_OBJECT_0 equ WAIT_OBJECT_0
     
        _T macro p1, p2
          local l
            ifb <p2>; Строка без имени
                .data
                    l   db p1, 0
                .code
                exitm   <addr l>
            else    ; Строка с именем
                .data
                    l   db p1, 0
                .code
                exitm   <offset l>
            endif
        endm
     
        DlgProc         proto :HWND, :UINT, :WPARAM, :LPARAM
        EditProc        proto :HWND, :UINT, :WPARAM, :LPARAM
        MESSG           proto :HWND, :DWORD
        ERROR           proto :HWND, :DWORD, :BYTE
     
     
        Port            equ 80
        VER_SOCKET      equ 202h        ; требуемая версия WinSock
        MAX_SOCK        equ 16          ; максимальное количество обслуживаемых
                                        ; в одном потоке клиентов - 1
        BUF_LEN         equ 1024        ; размер буфера для приема/передачи
     
        ; контролы из ресурсов
        IDC_COUNT       equ 101         ; окошко отображения количества клиентов
        IDC_IN          equ 102         ; приемное "окно" (контрол ListBox)
     
    .data?
        hInstance       HINSTANCE ?
        hDlg            HWND ?              ; хэндл окна диалога
     
        hSockets        dd MAX_SOCK dup(?)  ; массив хэндлов сокетов
        hEvents         dd MAX_SOCK dup(?)  ; массив хэндлов объектов событий
        NumSockets      dd ?                ; количество используемых сокетов
     
        NetEvents       WSANETWORKEVENTS <> ; это для получения информации о произошедших событиях
     
        wsa             WSADATA <>          ; информация о версии WinSock
        sin             sockaddr_in <>
     
        ; буфер для операций ввода/вывода
        IOBuff          db BUF_LEN+1 dup(?)
     
    .code
     
     
    start:
        invoke  GetModuleHandle, NULL
        mov     hInstance, eax
     
        ; инициализируем Windows Sockets DLL
        invoke  WSAStartup, VER_SOCKET, addr wsa
        .if (eax == NULL) && (wsa.wVersion == VER_SOCKET)
            ; создаем диалог
            invoke  DialogBoxParam, hInstance, _T("MAINDIALOG"), 0, addr DlgProc, 0
        .else
            invoke  WSACleanup
            invoke  MessageBox, 0, _T("Windows socket not found!"), _T("ERROR"), MB_OK+MB_ICONERROR
            mov     eax, 90h            ; заканчиваем программу с кодом ошибки
        .endif
        ; уходим в хаОС
        invoke  ExitProcess, eax
     
    ; отправляет сообщение сразу всем клиентам
    sendall proc buffptr: DWORD, bufflen: DWORD
        push    esi
        push    ebx
        mov     esi, offset hSockets+4  ; пропускаем слушающий сокет :)
        mov     ebx, 1
        .while (ebx < NumSockets)
            lodsd       ; получаем очередной хэндл
            invoke  send, eax, addr IOBuff, BUF_LEN, 0
            inc     ebx
        .endw
        pop     ebx
        pop     esi
        ret
    sendall endp
     
     
    ; **********************************
    ; поток по обработке событий сокетов
    ; **********************************
    WorkThread proc Param: DWORD
        xor     eax, eax
        mov     NumSockets, eax
        ; создаем сокет для прослушки порта
        ; обнуляем структуру sockaddr_in
        lea     edi, sin
        mov     ecx, sizeof sin
        rep     stosb
        ; заполняем структуру sockaddr_in
        invoke  htons, Port             ; преобразовываем номер порта в сетевой порядок байт
        mov     sin.sin_port, ax        ; и сохраняем его
        mov     sin.sin_family, AF_INET
        invoke  htonl, INADDR_ANY       ; принимать с любого адреса
        mov     sin.sin_addr, eax
        ; создаем сокет
        invoke  socket, AF_INET, SOCK_STREAM, 0
        mov     hSockets, eax           ; первым элементом массива будет слушающий сокет
        .if (eax != SOCKET_ERROR)
            ; создаем объект события для данного сокета
            invoke  WSACreateEvent
            mov     hEvents, eax        ; первым элементом массива будет объект для слушающего сокета
            ; связываем его с событиями FD_ACCEPT и FD_CLOSE
            invoke  WSAEventSelect, hSockets, eax, FD_ACCEPT + FD_CLOSE
            inc     NumSockets
            ; ассоциируем локальный адрес (Port) с открытым сокетом
            invoke  bind, hSockets, addr sin, sizeof sin
            .if (eax != SOCKET_ERROR)
                ; ставим сокет на прослушку порта
                invoke  listen, hSockets, 5  ; 5 - размер очереди на входящие запросы
                .if (eax == SOCKET_ERROR)
                    invoke  ERROR, hDlg, _T("Can't listen socket!"), TRUE
                .endif
            .else
                invoke  ERROR, hDlg, _T("Can't binding socket!"), TRUE
            .endif
        .else
            invoke  ERROR, hDlg, _T("Can't create socket!"), TRUE
        .endif
     
        ; *********************************************************************
        ; "вечный" цикл, в котором мы ждем событий от сокетов и обрабатываем их
        ; *********************************************************************
        .while  TRUE
            ; ждем наступления какого либо события на сокетах
            invoke  WSAWaitForMultipleEvents, NumSockets, addr hEvents, FALSE, WSA_INFINITE, FALSE
            .if (eax == WSA_WAIT_TIMEOUT) || (eax == WSA_WAIT_FAILED)   ; проверка на предмет ошибки
                invoke  ERROR, hDlg, _T("Wait for event failed!"), FALSE
                .continue   ; ошибочка вышла, будем ждать следующих событий
            .endif
            mov     ebx, eax
            sub     ebx, WSA_WAIT_OBJECT_0          ; ebx - индекс объекта в массиве hEvents
            ; теперь уточняем, какие именно события произошли
            mov     esi, [hSockets+ebx*4]           ; esi - сокет
            mov     edx, [hEvents+ebx*4]            ; edx - его объект событий
            ; помимо получения инфы о событиях, мы используем функцию и для сброса событий
            invoke  WSAEnumNetworkEvents, esi, edx, addr NetEvents
            mov     edi, NetEvents.lNetworkEvents
            ; событий может быть несколько, поэтому проверяем все, и обрабатываем произошедшие
     
            .if (edi & FD_ACCEPT)
                mov     eax, FD_ACCEPT_BIT
                ; анализируем код ошибки операции
                .if ([NetEvents.iErrorCode+eax*4] != 0) ; если ноль, то все Ок!
                    invoke  ERROR, hDlg, _T("Accept failed!"), FALSE
                    .continue                       ; продолжаем цикл
                .endif
                ; извлекаем из очереди ожидающих подключений первое и
                ; создаем новый сокет для связи с клиентом
                invoke  accept, esi, NULL, NULL
                push    eax                         ; еще пригодится :)
                .if (NumSockets >= MAX_SOCK)        ; не слишком ли много клиентов?
                    invoke  MESSG, hDlg, _T("Too many connections!")
                    pop     eax
                    invoke  closesocket, eax        ; выкидываем нового клиента
                .else
                    ; создаем объект события для нового сокета
                    invoke  WSACreateEvent
                    pop     edx                     ; edx - хэндл созданного сокета
                    mov     ecx, NumSockets
                    ; добавляем хендлы в массив
                    mov     [hSockets+ecx*4], edx
                    mov     [hEvents+ecx*4], eax
                    push    ecx                     ; пригодится для вывода на IDC_COUNT :)
                    inc     ecx
                    mov     NumSockets, ecx         ; корректируем счетчик клиентов
                    ; связываем сокет с событиями чтения/записи
                    invoke  WSAEventSelect, edx, eax, FD_READ + FD_WRITE + FD_CLOSE
                    invoke  MESSG, hDlg, _T("new client added!")
                    ; обновляем на экране счетчик подключенных клиентов
                    pop     eax
                    invoke  SetDlgItemInt, hDlg, IDC_COUNT, eax, FALSE
                .endif
            .endif  ; FD_ACCEPT
     
            .if (edi & FD_READ)
                mov     eax, FD_READ_BIT
                .if ([NetEvents.iErrorCode+eax*4] != 0)
                    invoke  ERROR, hDlg, _T("read from socket failed!"), FALSE
                    invoke  closesocket, esi        ; закрываем сокет
                    .continue                       ; продолжаем цикл
                .endif
                ; считываем данные с сокета
                invoke  recv, esi, addr IOBuff, BUF_LEN, 0
                ; добавляем сообщение в окно статуса
                invoke  MESSG, hDlg, addr IOBuff
                ; и отсылаем всем остальным
                invoke  sendall, addr IOBuff, BUF_LEN
            .endif  ; FD_READ
     
            .if (edi & FD_WRITE)
                mov     eax, FD_WRITE_BIT
                .if ([NetEvents.iErrorCode+eax*4] != 0)
                    invoke  ERROR, hDlg, _T("write to socket failed!"), FALSE
                    invoke  closesocket, esi        ; закрываем сокет
                    .continue                       ; продолжаем цикл
                .endif
                ; у нас это событие будет возникать только при создании нового сокета
                ; поэтому просто передаем приветствие новому клиенту :)
                mov     edx, _T("Welcom to test server!", TRUE)
                push    edx
                invoke  lstrlen, edx
                pop     edx
                invoke  send, esi, edx, eax, 0      ; отсылаем приветствие новому клиенту
            .endif  ; FD_WRITE
     
            ; поскольку в этом событии происходит исключение сокета из массива,
            ; мы его обрабатываем в последнюю очередь, во избежание лишних проблем
            ; связанных с перестановками в массивах hSockets и hEvents
            .if (edi & FD_CLOSE)
                mov     eax, FD_CLOSE_BIT
                .if ([NetEvents.iErrorCode+eax*4] != 0)
                    invoke  ERROR, hDlg, _T("Close socket failed!"), FALSE
                .else
                    invoke  MESSG, hDlg, _T("Close socket!")
                .endif
                ; нам неважно, была ошибка, или нет
                ; все равно нужно закрывать сокет и корректировать массивы
                mov     eax, [hEvents+ebx*4]
                invoke  WSACloseEvent, eax          ; уничтожаем объект события
                invoke  closesocket, esi            ; закрываем сокет
                ; перестраиваем массивы, исключая из них текущий сокет и объект событий
                lea     esi, [hSockets+ebx*4]
                lea     edi, [hEvents+ebx*4]
                mov     ecx, NumSockets
                sub     ecx, ebx
                dec     ecx                         ; ecx - количество элементов для сдвига
                .while  (ecx != 0)
                    mov     eax, [esi+4]
                    mov     edx, [edi+4]
                    mov     [esi], eax              ; сдвигаем к началу, тем самым исключая
                    mov     [edi], edx              ; элементы из массивов
                    add     esi, 4
                    add     edi, 4
                    dec     ecx
                .endw
                dec     NumSockets                  ; корректируем счетчик сокетов
                ; обновляем на эеране счетчик подключенных клиентов
                mov     eax, NumSockets
                dec     eax
                invoke  SetDlgItemInt, hDlg, IDC_COUNT, eax, FALSE
            .endif  ; FD_CLOSE
        .endw
        ret
    WorkThread endp
     
     
    ; ************************************************
    ; оконная функция диалога, проста до безобразия :)
    ; ************************************************
    DlgProc proc    hWnd: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM
        ; определяем пришедшее сообщение
        .if (uMsg == WM_INITDIALOG)
            ; меняем заголовок окна диалога
            invoke  SendMessage, hWnd, WM_SETTEXT, NULL, _T("Server")
            ; обновляем на экране счетчик подключенных клиентов
            invoke  SetDlgItemInt, hWnd, IDC_COUNT, 0, FALSE
            mov     eax, hWnd
            mov     hDlg, eax                       ; хэндл окна нам еще понадобится :)
            ; запускаем тред обработки событий сокетов
            push    eax
            mov     eax, esp                        ; eax - временный буфер
            invoke  CreateThread, NULL, NULL, offset WorkThread, NULL, NULL, eax
            pop     eax                             ; значение ThreadID нам не нужно
     
        .elseif (uMsg == WM_CLOSE)
            ; подчищаем за собой и выходим
            invoke  WSACleanup                      ; выгружаем WinSock DLL
            invoke  EndDialog, hWnd, NULL
     
        .else       ; если сообщение не обрабатывали, то вернем NULL
            xor     eax, eax
            ret
        .endif
            ; сообщение обработано, возвращаем ненулевой результат
            mov     eax, TRUE
            ret
    DlgProc endp
     
     
    ;***************************************************************
    ; функции вывода информационных сообщений и сообщений об ошибках
    ;***************************************************************
     
    ; просто добавляет строку в ListBox
    MESSG proc hWnd: HWND, message: DWORD
            invoke  GetDlgItem, hWnd, IDC_IN
            push    eax
            invoke  SendMessage, eax, LB_ADDSTRING, 0, message
            pop     ecx
            invoke  SendMessage, ecx, LB_SETTOPINDEX, eax, 0
            ret
    MESSG endp
     
     
    ; информирует о последней ошибке, получая ее с помощью WSAGetLastError
    ; при Fatal <> FALSE сообщение выводится в MessageBox, и программа закрывается.
    ; при Fatal == FALSE сообщение просто выводится в ListBox
    ERROR proc hWnd: HWND, message: DWORD, Fatal: BYTE
      local szStr[256]: byte    ; временный буфер для создания строки
        ; получаем код последней ошибки
        invoke  WSAGetLastError
        ; формируем строку с сообщением об ошибке
        invoke  wsprintf, addr szStr, _T("%s Error code: %u"), message, eax
        .if (Fatal != FALSE)
            invoke  MessageBox, hWnd, addr szStr, _T("FATAL ERROR"), MB_OK+MB_ICONERROR
            ; закрываем диалог, все равно смысла нет в дальнейшей работе :)
            invoke  SendMessage, hWnd, WM_CLOSE, 0, 0
        .else
            ; информируем пользователя, засылая строку в ListBox
            invoke  MESSG, hWnd, addr szStr
        .endif
        ret
    ERROR endp
     
    end start

Файл ресурса:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    ; file: server.rc
    MAINDIALOG DIALOG 22, 46, 190, 93
    STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
    CAPTION "Server"
    BEGIN
      CONTROL 0, 101, "STATIC", SS_CENTER | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_GROUP, 154, 21, 28, 12
      LTEXT "Clients", -1, 155, 9, 24, 8, WS_CHILD | WS_VISIBLE | WS_GROUP
      LISTBOX 102, 7, 7, 141, 81, LBS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL
    END

Для сборки программы можно применить следующий bat-файл:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    :: file: makeit.bat
    brcc32.exe server.rc
    c:\masm32\bin\ml /c /coff /nologo server.asm
    c:\masm32\bin\Link /SUBSYSTEM:WINDOWS server.obj server.res
    pause

И в заключение хотелось бы упомянуть о нескольких неочевидных вещах. Дело в том, что WSAWaitForMultipleEvents может обслуживать не более 64-х объектов событий (константа WSA_MAXIMUM_WAIT_EVENTS), поэтому, для поддержки большего количества клиентов, создаются дополнительные треды, со своими наборами сокетов и их объектов событий. Что касается применения данной модели к созданию клиентов, то это неплохой вариант для клиента, который может независимо работать с несколькими серверами, достаточно для каждого соединения создать отдельный тред. Ну и, разумеется, в целях наглядности, обработка ошибок сведена к минимуму.

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)