На главную
ПРАВИЛА FAQ Помощь Участники Календарь Избранное DigiMania RSS
msm.ru
! Перед отправкой сообщения внимательно прочтите правила раздела!!!
1. Все статьи должны быть оформлены согласно Правил оформления статей.
2. Любые обсуждения должны происходить в специальной теме, обсуждение в любых других темах раздела запрещены.
3. Запрещается писать статьи о создании и распространении вирусов, троянов и других вредоносных программ!
4. За грамотно написанные и правильно оформленные статьи авторы награждаются DigiMoney.

Дополнительные ссылки:
Желаю творческих успехов! ;)
Модераторы: Jin X
  
    > Server/Client Socket, Windows, MASM, Сеть
       
      Server/Client Socket

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

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

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

      ExpandedWrap disabled
        .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
      Компилируется:
      ExpandedWrap disabled
        ml /c /coff Server.ASM
        Link /SUBSYSTEM:WINDOWS Server.obj Server.RES
        pause

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

      ExpandedWrap disabled
         .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
      Компилируется:
      ExpandedWrap disabled
        ml /c /coff Client.ASM
        Link /SUBSYSTEM:WINDOWS Client.obj Client.RES
        pause


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

      ExpandedWrap disabled
        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
      ExpandedWrap disabled
        brcc32.exe -32 Client/Server.txt
        pause

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

        В продолжение темы хотелось бы упомянуть о работе с сокетами с помощью системных объектов событий. Этот режим позволяет эффективнее, чем вышеприведенный, обслуживать большое количество клиентов. Но поскольку я ленивый, то представлю только код сервера, а клиентов можно взять из предыдущей статьи. Данный сервер делает простую задачу, принимает сообщения от клиентов и рассылает их всем подключившимся, что то вроде чата, только примитивного. Собственно код:
        ExpandedWrap disabled
          ; 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

        Файл ресурса:
        ExpandedWrap disabled
          ; 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-файл:
        ExpandedWrap disabled
          :: 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), поэтому, для поддержки большего количества клиентов, создаются дополнительные треды, со своими наборами сокетов и их объектов событий. Что касается применения данной модели к созданию клиентов, то это неплохой вариант для клиента, который может независимо работать с несколькими серверами, достаточно для каждого соединения создать отдельный тред. Ну и, разумеется, в целях наглядности, обработка ошибок сведена к минимуму.
        0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
        0 пользователей:


        Рейтинг@Mail.ru
        [ Script Execution time: 0,1480 ]   [ 17 queries used ]   [ Generated: 22.10.18, 21:41 GMT ]