Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.137.192.3] |
|
Сообщ.
#1
,
|
|
|
Server/Client Socket Всем известно, то, что первое появилась это глобальная сеть, а не локальная с приходом протокола TCP/IP для военных целей. Но так как этот способ связи был ненадёжным и опасным для передачи секретных данных, - его начали использовать во всём мире. Каждый когда-нибудь хотел написать свой клиент сервер, чтобы пообщаться с другом через Интернет или в локальной сети. Сейчас я покажу и подробно прокомментирую, как это делается средствами API – Windows на MASM(ассемблере). Итак, сервер: главной задачей, которого, является ожидание клиента, обработка пришедшего сообщения, добавление его в LISTBOX – controle и отсылка, что мы написали в EDIT – controle; .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 ml /c /coff Server.ASM Link /SUBSYSTEM:WINDOWS Server.obj Server.RES pause Клиент: главной задачей, которого, является подключение к серверу обработка пришедшего сообщения и отсылка, того, что мы написали в EDIT – controle; .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 ml /c /coff Client.ASM Link /SUBSYSTEM:WINDOWS Client.obj Client.RES pause Ресурс диалога для «общения» с пользователем: 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.exe -32 Client/Server.txt pause В некоторых местах не была вставлена функция обработки ошибок, только в самых необходимых. Это только пример, и в реальных прогах такое не приветствуется Насчет recv и send. Данные функции не гарантируют передачу/прием блока данных за один раз. В данном примере это несущественно. Этот пример относится к WSAAsyncSelect Model. Есть также Select model, WSAEventSelect Model, и Overlapped Model(пожалуй самая интересная ). |
Сообщ.
#2
,
|
|
|
Асинхронный режим, основанный на событиях В продолжение темы хотелось бы упомянуть о работе с сокетами с помощью системных объектов событий. Этот режим позволяет эффективнее, чем вышеприведенный, обслуживать большое количество клиентов. Но поскольку я ленивый, то представлю только код сервера, а клиентов можно взять из предыдущей статьи. Данный сервер делает простую задачу, принимает сообщения от клиентов и рассылает их всем подключившимся, что то вроде чата, только примитивного. Собственно код: ; 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 Файл ресурса: ; 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-файл: :: 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), поэтому, для поддержки большего количества клиентов, создаются дополнительные треды, со своими наборами сокетов и их объектов событий. Что касается применения данной модели к созданию клиентов, то это неплохой вариант для клиента, который может независимо работать с несколькими серверами, достаточно для каждого соединения создать отдельный тред. Ну и, разумеется, в целях наглядности, обработка ошибок сведена к минимуму. |