能力值:
( LV2,RANK:10 )
2 楼
Server.asm:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Sample code for < Win32ASM Programming 2nd Edition>
; by 罗云彬, http://asm.yeah.net
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Server.asm
; 使用 TCP 协议的聊天室例子程序 —— 服务器端
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 使用 nmake 或下列命令进行编译和链接:
; ml /c /coff Server.asm
; rc Server.rc
; Link /subsystem:windows Server.obj Server.res
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat, stdcall
option casemap :none ; case sensitive
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 数据
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
include wsock32.inc
includelib wsock32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; equ 数据
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN equ 1000
DLG_MAIN equ 2000
IDC_COUNT equ 2001
TCP_PORT equ 9999
;********************************************************************
; 客户端会话信息
;********************************************************************
SESSION struct
szUserName db 12 dup (?) ; 用户名
dwMessageId dd ? ; 已经下发的消息编号
dwLastTime dd ? ; 链路最近一次活动的时间
SESSION ends
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hInstance dd ?
hWinMain dd ?
hListenSocket dd ?
dwThreadCounter dd ?
dwFlag dd ?
F_STOP equ 0001h
.const
szErrBind db '无法绑定到TCP端口9999,请检查是否有其它程序在使用!',0
szSysInfo db '系统消息',0
szUserLogin db ' 进入了聊天室!',0
szUserLogout db ' 退出了聊天室!',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
include _Message.inc
include _SocketRoute.asm
include _MsgQueue.asm
assume esi:ptr MSG_STRUCT,edi:ptr SESSION
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 循环取消息队列中的聊天语句并发送到客户端,直到全部消息发送完毕
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_SendMsgQueue proc uses esi edi _hSocket,_lpBuffer,_lpSession
local @stMsg:MSG_STRUCT
mov esi,_lpBuffer
mov edi,_lpSession
.while ! (dwFlag & F_STOP)
mov ecx,[edi].dwMessageId
inc ecx
invoke _GetMsgFromQueue,ecx,addr [esi].MsgDown.szSender,addr [esi].MsgDown.szContent
.break .if ! eax
mov [edi].dwMessageId,eax
invoke lstrlen,addr [esi].MsgDown.szContent
inc eax
mov [esi].MsgDown.dwLength,eax
add eax,sizeof MSG_HEAD+MSG_DOWN.szContent
mov [esi].MsgHead.dwLength,eax
mov [esi].MsgHead.dwCmdId,CMD_MSG_DOWN
invoke send,_hSocket,esi,[esi].MsgHead.dwLength,0
.break .if eax == SOCKET_ERROR
invoke GetTickCount
mov [edi].dwLastTime,eax
;********************************************************************
invoke _WaitData,_hSocket,0
.break .if eax == SOCKET_ERROR
.if eax
xor eax,eax
.break
.endif
;********************************************************************
.endw
ret
_SendMsgQueue endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 检测链路的最后一次活动时间
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_LinkCheck proc uses esi edi _hSocket,_lpBuffer,_lpSession
;********************************************************************
; 查看是否需要检测链路(30秒内没有数据通信则发送链路检测包)
;********************************************************************
invoke GetTickCount
push eax
sub eax,[edi].dwLastTime
cmp eax,30 * 1000
pop eax
jb _Ret
@@:
mov [edi].dwLastTime,eax
mov [esi].MsgHead.dwCmdId,CMD_CHECK_LINK
mov [esi].MsgHead.dwLength,sizeof MSG_HEAD
invoke send,_hSocket,esi,[esi].MsgHead.dwLength,0
cmp eax,SOCKET_ERROR
jnz _Ret
ret
_Ret:
xor eax,eax
ret
_LinkCheck endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 通讯服务线程:每个客户端登录的连接将产生一个线程
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ServiceThread proc _hSocket
local @stSession:SESSION,@szBuffer[512]:byte
pushad
inc dwThreadCounter
invoke SetDlgItemInt,hWinMain,IDC_COUNT,dwThreadCounter,FALSE
lea esi,@szBuffer
lea edi,@stSession
invoke RtlZeroMemory,edi,sizeof @stSession
mov eax,dwSequence
mov [edi].dwMessageId,eax
;********************************************************************
; 用户名和密码检测,为了简化程序,现在可以使用任意用户名和密码
;********************************************************************
invoke _RecvPacket,_hSocket,esi,sizeof @szBuffer
or eax,eax
jnz _Ret
.if [esi].MsgHead.dwCmdId != CMD_LOGIN
jmp _Ret
.else
invoke lstrcpy,addr [edi].szUserName,addr [esi].Login.szUserName
mov [esi].LoginResp.dbResult,0
.endif
mov [esi].MsgHead.dwCmdId,CMD_LOGIN_RESP
mov [esi].MsgHead.dwLength,sizeof MSG_HEAD+sizeof MSG_LOGIN_RESP
invoke send,_hSocket,esi,[esi].MsgHead.dwLength,0
cmp eax,SOCKET_ERROR
jz _Ret
cmp [esi].LoginResp.dbResult,0
jnz _Ret
;********************************************************************
; 广播:xxx 进入了聊天室
;********************************************************************
invoke lstrcpy,esi,addr [edi].szUserName
invoke lstrcat,esi,addr szUserLogin
invoke _InsertMsgQueue,addr szSysInfo,esi
invoke GetTickCount
mov [edi].dwLastTime,eax
;********************************************************************
; 循环处理消息
;********************************************************************
.while ! (dwFlag & F_STOP)
invoke _SendMsgQueue,_hSocket,esi,edi
.break .if eax
invoke _LinkCheck,_hSocket,esi,edi
.break .if eax
.break .if dwFlag & F_STOP
;********************************************************************
; 使用 select 函数等待 200ms,如果没有接收到数据包则循环
;********************************************************************
invoke _WaitData,_hSocket,200 * 1000
.break .if eax == SOCKET_ERROR
.if eax
invoke _RecvPacket,_hSocket,esi,sizeof @szBuffer
.break .if eax
invoke GetTickCount
mov [edi].dwLastTime,eax
.if [esi].MsgHead.dwCmdId == CMD_MSG_UP
invoke _InsertMsgQueue,addr [edi].szUserName,addr [esi].MsgUp.szContent
.endif
.endif
.endw
;********************************************************************
; 广播:xxx 退出了聊天室
;********************************************************************
invoke lstrcpy,esi,addr [edi].szUserName
invoke lstrcat,esi,addr szUserLogout
invoke _InsertMsgQueue,addr szSysInfo,addr @szBuffer
;********************************************************************
; 关闭 socket
;********************************************************************
_Ret:
invoke closesocket,_hSocket
dec dwThreadCounter
invoke SetDlgItemInt,hWinMain,IDC_COUNT,dwThreadCounter,FALSE
popad
ret
_ServiceThread endp
assume esi:nothing,edi:nothing
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 监听线程
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ListenThread proc _lParam
local @stSin:sockaddr_in
;********************************************************************
; 创建 socket
;********************************************************************
invoke socket,AF_INET,SOCK_STREAM,0
mov hListenSocket,eax
invoke RtlZeroMemory,addr @stSin,sizeof @stSin
invoke htons,TCP_PORT
mov @stSin.sin_port,ax
mov @stSin.sin_family,AF_INET
mov @stSin.sin_addr,INADDR_ANY
invoke bind,hListenSocket,addr @stSin,sizeof @stSin
.if eax
invoke MessageBox,hWinMain,addr szErrBind,\
NULL,MB_OK or MB_ICONSTOP
invoke ExitProcess,NULL
ret
.endif
;********************************************************************
; 开始监听,等待连接进入并为每个连接创建一个线程
;********************************************************************
invoke listen,hListenSocket,5
.while TRUE
invoke accept,hListenSocket,NULL,0
.break .if eax == INVALID_SOCKET
push ecx
invoke CreateThread,NULL,0,offset _ServiceThread,eax,NULL,esp
pop ecx
invoke CloseHandle,eax
.endw
invoke closesocket,hListenSocket
ret
_ListenThread endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 主窗口程序
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcDlgMain proc uses ebx edi esi hWnd,wMsg,wParam,lParam
local @stWsa:WSADATA
mov eax,wMsg
;********************************************************************
.if eax == WM_INITDIALOG
push hWnd
pop hWinMain
invoke LoadIcon,hInstance,ICO_MAIN
invoke SendMessage,hWnd,WM_SETICON,ICON_BIG,eax
invoke InitializeCriticalSection,addr stCS
invoke WSAStartup,101h,addr @stWsa
push ecx
invoke CreateThread,NULL,0,offset _ListenThread,0,NULL,esp
pop ecx
invoke CloseHandle,eax
;********************************************************************
.elseif eax == WM_CLOSE
invoke closesocket,hListenSocket
or dwFlag,F_STOP
.while dwThreadCounter
.endw
invoke WSACleanup
invoke DeleteCriticalSection,addr stCS
invoke EndDialog,hWinMain,NULL
;********************************************************************
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
_ProcDlgMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 程序开始
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke DialogBoxParam,eax,DLG_MAIN,NULL,offset _ProcDlgMain,0
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
能力值:
( LV2,RANK:10 )
6 楼
_MsgQueue.inc:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Sample code for < Win32ASM Programming 2nd Edition>
; by 罗云彬, http://asm.yeah.net
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 使用 TCP 协议的聊天室例子程序
; 通讯链路传输的数据结构定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;
;
;
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
CMD_LOGIN equ 01h ; 客户端 ->服务器端,登录
CMD_LOGIN_RESP equ 81h ; 服务器端 -> 客户端,登录回应
CMD_MSG_UP equ 02h ; 客户端 -> 服务器端,聊天语句
CMD_MSG_DOWN equ 82h ; 服务器端 -> 客户端,聊天语句
CMD_CHECK_LINK equ 83h ; 服务器端 -> 客户端,链路检测
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据包定义方式
; 每个数据包以 MSG_HEAD + MSG_xxx 组成,整个长度填入 MSG_HEAD.dwLength
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;********************************************************************
; 数据包头部,所有的数据包都以 MSG_HEAD 开头
;********************************************************************
MSG_HEAD struct
dwCmdId dw ? ;命令ID
dwLength dd ? ;整个数据包长度=数据包头部+数据包体
MSG_HEAD ends
;********************************************************************
; 登录数据包(客户端->服务器端)
;********************************************************************
MSG_LOGIN struct
szUserName db 12 dup (?) ;用户登录ID
szPassword db 12 dup (?) ;登录密码
MSG_LOGIN ends
;********************************************************************
; 登录回应数据包(服务器端->客户端)
;********************************************************************
MSG_LOGIN_RESP struct
dbResult db ? ;登录结果:0=成功,1=用户名或密码错
MSG_LOGIN_RESP ends
;********************************************************************
; 聊天语句(客户端->服务器端):不等长数据包
;********************************************************************
MSG_UP struct
dwLength dd ? ;后面内容字段的长度
szContent db 256 dup (?) ;内容,不等长,长度由dwLength指定
MSG_UP ends
;********************************************************************
; 聊天语句(服务器端->客户端):不等长数据包
;********************************************************************
MSG_DOWN struct
szSender db 12 dup (?) ;消息发送者
dwLength dd ? ;后面内容字段的长度
szContent db 256 dup (?) ;内容,不等长,长度由dwLength指定
MSG_DOWN ends
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
MSG_STRUCT struct
MsgHead MSG_HEAD <>
union
Login MSG_LOGIN <>
LoginResp MSG_LOGIN_RESP <>
MsgUp MSG_UP <>
MsgDown MSG_DOWN <>
ends
MSG_STRUCT ends
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_MsgQueue.asm:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Sample code for < Win32ASM Programming 2nd Edition>
; by 罗云彬, http://asm.yeah.net
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; FIFO(First in, first out)消息队列的实现
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;
;
;
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
QUEUE_SIZE equ 100 ;消息队列的长度
MSG_QUEUE_ITEM struct ;队列中单条消息的格式定义
dwMessageId dd ? ;消息编号
szSender db 12 dup (?) ;发送者
szContent db 256 dup (?) ;聊天内容
MSG_QUEUE_ITEM ends
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
stCS CRITICAL_SECTION <?>
stMsgQueue MSG_QUEUE_ITEM QUEUE_SIZE dup (<?>)
dwMsgCount dd ? ;队列中当前消息数量
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
dwSequence dd 1 ;消息序号,从1开始
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 在队列中加入一条消息
; -- 如果队列已经满了,则将整个队列前移一个位置,相当于最早的消息被覆盖
; 然后在队列尾部空出的位置加入新消息
; -- 如果队列未满,则在队列的最后加入新消息
; -- 消息编号从1开始递增,这样保证队列中各消息的编号是连续的
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 入口:_lpszSender = 指向发送者字符串的指针
; _lpszContent = 指向聊天语句内容字符串的指针
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_InsertMsgQueue proc _lpszSender,_lpszContent
pushad
invoke EnterCriticalSection,addr stCS
mov eax,dwMsgCount
;********************************************************************
; 如果队列满,则移动队列,并在队列尾部添加新消息
;********************************************************************
.if eax >= QUEUE_SIZE
mov edi,offset stMsgQueue
mov esi,offset stMsgQueue + sizeof MSG_QUEUE_ITEM
mov ecx,(QUEUE_SIZE-1) * sizeof MSG_QUEUE_ITEM
mov eax,ecx
cld
rep movsb
;********************************************************************
; 否则直接添加到队列尾部
;********************************************************************
.else
inc dwMsgCount
mov ecx,sizeof MSG_QUEUE_ITEM
mul ecx
.endif
;********************************************************************
; 前面的语句执行完毕后,eax指向队列中空位置的偏移量
;********************************************************************
lea esi,[stMsgQueue+eax]
assume esi:ptr MSG_QUEUE_ITEM
invoke lstrcpy,addr [esi].szSender,_lpszSender
invoke lstrcpy,addr [esi].szContent,_lpszContent
inc dwSequence
mov eax,dwSequence
mov [esi].dwMessageId,eax
assume esi:nothing
invoke LeaveCriticalSection,addr stCS
popad
ret
_InsertMsgQueue endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 从队列获取指定编号的消息
; -- 如果指定编号的消息已经被清除出消息队列,则返回编号最小的一条消息
; 当向连接速度过慢的客户端发消息的速度比不上消息被清除的速度,则中间
; 的消息等于被忽略,这样可以保证慢速链路不会影响快速链路
; -- 如果队列中的所有消息的编号都比指定编号小(意味着这些消息以前都被获取过)
; 那么不返回任何消息
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 入口:_dwMessageId = 需要获取的消息编号
; _lpszSender = 用于返回消息中发送者字符串的缓冲区指针
; _lpszSender = 用于返回消息中聊天内容字符串的缓冲区指针
; 返回:eax = 0(队列为空,或者队列中没有小于等于指定编号的消息)
; eax <> 0(已经获取了一条消息,获取的消息编号返回到eax中)
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_GetMsgFromQueue proc _dwMessageId,_lpszSender,_lpszContent
local @dwReturn
pushad
invoke EnterCriticalSection,addr stCS
xor eax,eax
mov @dwReturn,eax
cmp dwMsgCount,eax
jz _Ret
mov esi,offset stMsgQueue
assume esi:ptr MSG_QUEUE_ITEM
;********************************************************************
; esi 指向队列头部,所以这条消息的编号就是最小编号
; 最大编号=最小编号+队列长度-1
;********************************************************************
mov ecx,[esi].dwMessageId ;ecx=队列中的最小消息编号
mov edx,dwMsgCount
lea edx,[edx+ecx-1] ;edx=队列中的最大消息编号
;********************************************************************
; 如果指定编号 < 最小编号,则返回最小编号的消息
; 如果指定编号 > 最大编号,则不返回任何消息
;********************************************************************
mov eax,_dwMessageId
cmp eax,ecx
jae @F
mov eax,ecx
@@:
cmp eax,edx
ja _Ret
mov @dwReturn,eax
;********************************************************************
; 要获取的消息在队列中的位置 = 指定消息编号-最小消息编号
;********************************************************************
sub eax,ecx
mov ecx,sizeof MSG_QUEUE_ITEM
mul ecx
lea esi,[esi+eax]
invoke lstrcpy,_lpszSender,addr [esi].szSender
invoke lstrcpy,_lpszContent,addr [esi].szContent
;********************************************************************
assume esi:nothing
_Ret:
invoke LeaveCriticalSection,addr stCS
popad
mov eax,@dwReturn
ret
_GetMsgFromQueue endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>