来看雪有段时间了,但是还是临时会员,悲催,看到发帖有机率转正,就试试运气吧,好了,闲话不说,直接上代码
下面是客户端源码:
.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
;*****************************************************************************
;命令代码定义
;*********************************************************************
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
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
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;*********************************************************************
ICO_MAIN equ 1000h
DLG_MAIN equ 2000h
IDC_IP equ 2001h
IDC_NAME equ 2002h
IDC_PASS equ 2003h
IDC_Login equ 2004h
IDC_LOGOUT equ 2005h
IDC_INFO equ 2006h
IDC_TEXT equ 2007h
TCP_PORT equ 521
;*********************************************************************
.data?
hInstance dd ? ;模块句柄
hWinMain dd ? ;窗口句柄
hSocket dd ? ;SOCKET句柄
dwLastTime dd ? ;程序最后一次接收聊天的时间 用语在客户端内显示
szip db 16 dup (?) ;保存IP的缓冲区
szuser db 12 dup (?) ;用户名
szmima db 12 dup (?) ;密码
sztext db 256 dup (?) ;要发送的内容
;*********************************************************************
.const
szErrIP db '无效的服务器IP地址!',0
szErrConnect db '无法连接到服务器!',0
szErrLogin db '无法登录到服务器,请检查用户名密码!',0
szSpar db ' 说: ',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;规定时间内等待数据到达 如果有数据到达,采取接收数据,因为我们采用的是阻塞模式,阻塞模式就是 必须一方有返回值,才进行进入下一步,不然就一直等待
;举个例子,因为我们这个程序 就是send revc组合的,如果另一方没有发过来数据,我们的客户端就会处于一直等待状态,如果对方按下关闭退出了,我们也检测不到他,就会一直等待
;所以这里就用select函数来检查数据,如果到达了,才让recv去接收,这个检测是在接受前面的
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_WaitData proc _hSocket,_dwTime ;传过来的两个参数 第一个是句柄,第二个是要等待的时间
local @fd:fd_set,@time:timeval ;第一个结构指定套接字是否可读可写可执行 第二个结构是用来指定超时的时间
mov @fd.fd_count,1 ;套接字句柄数量
push _hSocket
pop @fd.fd_array ;套接字句柄
push _dwTime ;对于select函数 ;第一个参数是为UNIX设计的,windwos可以忽略,因为socket是适合各个平台的 但是微软弄出了一些列只能在windows上才能用的socket函数,就是我们刚刚见到的WSA系列(Windows Socket Api),
pop @time.tv_usec ;这里解释下刚刚的fd_set和select select的第二个参数是可读,第三个是可写,第四个是可执行,如果把fd_set放到第二个参数,就是数据是否可读
mov @time.tv_sec,0 ;第五个参数,如果规定时间内,没有数据,就返回0 因为我们在下面程序时.if eax比较的,所以就是没有数据,就重新开始循环 啊哈 不知道你们听懂了没 我感觉够清楚了啦 O(∩_∩)O哈哈~
invoke select,0,addr @fd,NULL,NULL,addr @time
ret
_WaitData endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_RecvData proc _hSocket,_lpData,_dwSize ;接受数据
local @dwStartTime
mov esi,_lpData
mov ebx,_dwSize
invoke GetTickCount ;调用获得时间函数
mov @dwStartTime,eax
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
@@:
invoke GetTickCount
sub eax,@dwStartTime ;此时的时间-上次的时间
cmp eax,10*100
jge err1 ;如果超时 就退出
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
invoke _WaitData,_hSocket,100*1000 ;等待数据
cmp eax,SOCKET_ERROR
jz err1 ;出错则跳转
or eax,eax
jz @B ;规定时间内,继续接收
invoke recv,_hSocket,esi,ebx,0 ;句柄,接收数据头到缓冲区,数据头大小,0
.if (eax==SOCKET_ERROR) || ! eax
err1:
xor eax,eax
inc eax
ret
.endif
.if eax < ebx
add esi,eax
sub ebx,eax
jmp @B ;接收的数据头如果不够,也继续接收,这里就不详细注释了
.endif
xor eax,eax ;这里eax是0
ret
_RecvData endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_RecvPacket proc _hSocket,szbuffer,dwsize
local @dwReturn
pushad
mov @dwReturn,TRUE
mov esi,szbuffer
assume esi:ptr MSG_STRUCT
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;接收数据头
invoke _RecvData,_hSocket,esi,sizeof MSG_HEAD ;仅接收数据头
or eax,eax
jnz _Ret ;出错则跳转
mov ecx,[esi].MsgHead.dwLength ;接收的数据包大小
cmp ecx,sizeof MSG_HEAD
jb _Ret ;小于数据头 则跳转
cmp ecx,dwsize
jz _Ret ;大于数据包 则跳转
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;接收数据体
sub ecx,sizeof MSG_HEAD ;总的大小,减去数据头的大小,就是内容的大小了
add esi,sizeof MSG_HEAD ;缓冲区地址+到数据头后面
.if ecx
invoke _RecvData,_hSocket,esi,ecx ;接收数据体
.else
xor eax,eax
.endif
mov @dwReturn,eax ;把接收的返回值放到局部变量 返回值都是0
_Ret:
popad
assume esi:nothing
mov eax,@dwReturn ;局部变量的值 再放回寄存器 eax是0
ret
_RecvPacket endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
workthread proc _lParam
local @stSin:sockaddr_in ;这个结构就是保护我们IP和端口的结构了
local @mst:MSG_STRUCT ;我们刚刚定义的消息结构,发送的时候,就发送它
local @buffer[512]:byte
pushad
invoke GetDlgItem,hWinMain,IDC_IP
invoke EnableWindow,eax,FALSE
invoke GetDlgItem,hWinMain,IDC_NAME
invoke EnableWindow,eax,FALSE
invoke GetDlgItem,hWinMain,IDC_PASS
invoke EnableWindow,eax,FALSE
invoke GetDlgItem,hWinMain,IDC_Login
invoke EnableWindow,eax,FALSE ;点击了登陆之后 就不能输入IP 用户名 密码 和登录了
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;创建SOCKET
invoke RtlZeroMemory,addr @stSin,sizeof @stSin ;先将次结构清零
invoke inet_addr,addr szip ;这个是把IP字符串转换成32位二进制数表示的IP地址
.if eax == INADDR_NONE
invoke MessageBox,hWinMain,addr szErrIP,NULL,MB_OK or MB_ICONSTOP
.endif
;invoke inet_addr,addr TCP_IP
mov @stSin.sin_addr,eax ;然后把转换后的IP地址,填充这个结构
mov @stSin.sin_family,AF_INET ;地址格式,不同平台下,其值也不同,因为我实在windows上进行通讯的,所以就指定这个格式
invoke htons,TCP_PORT ;很多朋友都知道,我们的处理器分位大端和小端,而TCP/UDP是默认使用大端方式进行传输的,但是很不幸运,我的就是小端方式,所以为了兼容其他CPU 我只好把端口转换成大端方式
mov @stSin.sin_port,ax ;返回值是一个16位的值,但是调用的时候 默认扩展32位的
invoke socket,AF_INET,SOCK_STREAM,0 ;指定了地址格式,套接字类型,大体上类型分为三种,流套接字,数据报套接字和原始套接字 指定了套接字类型,也就默认指定了协议,流套接字是TCP协议,数据报套接字是UDP,至于原始套接字嘛,就程序默认处理,不使用特定的协议
mov hSocket,eax ;接口建立好了 要进行连接了
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;连接服务器
invoke connect,hSocket,addr @stSin,sizeof @stSin;第一个参数是TCP套接字的句柄,第二个是咱们刚刚的IP 端口结构,第三个是结构大小
.if eax == SOCKET_ERROR
invoke MessageBox,hWinMain,addr szErrConnect,NULL,MB_OK or MB_ICONSTOP
jmp _Ret
.endif
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;登陆服务器
invoke lstrcpy,addr @mst.Login.szUserName,addr szuser ;得到输入的用户名
invoke lstrcpy,addr @mst.Login.szPassword,addr szmima ;得到输入的密码
mov @mst.MsgHead.dwLength,sizeof MSG_HEAD + sizeof MSG_LOGIN ;结构数据报大小
mov @mst.MsgHead.dwCmdId,CMD_LOGIN ;发送一个登陆命令过去
invoke send,hSocket,addr @mst,@mst.MsgHead.dwLength,0 ;发送登陆数据包过去
cmp eax,SOCKET_ERROR
jz @F ;发送出错 跳转
invoke _RecvPacket,hSocket,addr @mst,sizeof @mst ;接收数据 为了程序好看 就专门写了个子程序接收数据
or eax,eax
jnz @F ;接收登陆回应出错 跳转
cmp @mst.MsgHead.dwCmdId,CMD_LOGIN_RESP ;由于上面调用了接收数据包,所以服务器会回应一个数据包来显示是否成功
jnz @F
.if @mst.LoginResp.dbResult ;回应结果出错 跳转
@@: invoke MessageBox,hWinMain,addr szErrLogin,NULL,MB_OK or MB_ICONSTOP
jmp _Ret
.endif
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;使输入 注销 控件可用
invoke GetDlgItem,hWinMain,IDC_LOGOUT
invoke EnableWindow,eax,TRUE
invoke GetDlgItem,hWinMain,IDC_TEXT
invoke EnableWindow,eax,TRUE
invoke GetTickCount
mov dwLastTime,eax
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;聊天语句的循环
.while hSocket
invoke GetTickCount ;得到这次的时间 因为虽然他给服务器发送数据,但是他自己也会收到他发的
sub eax,dwLastTime ;这次得到的时间减去上次发送的时间
.break .if eax >=60 * 1000 ;如果超时,就退出循环
invoke _WaitData,hSocket,200 * 100
.break .if eax == SOCKET_ERROR ;等待数据,如果出错,也退出
.if eax ;如果等待到了数据
invoke _RecvPacket,hSocket,addr @mst,sizeof @mst ;接收数据
.break .if eax ;如果返回值不为0 那么就是接受出错了 退出if循环
.if @mst.MsgHead.dwCmdId==CMD_MSG_DOWN ;如果发送过来的是内容
invoke lstrcpy,addr @buffer,addr @mst.MsgDown.szSender ;用户名拷贝到缓冲区
invoke lstrcat,addr @buffer,addr szSpar ;“说”这个字 添加到用户名后面
invoke lstrcat,addr @buffer,addr @mst.MsgDown.szContent ;总共就是 用户名说:内容
invoke SendDlgItemMessage,hWinMain,IDC_INFO,LB_INSERTSTRING,0,addr @buffer ;信息显示出来
.endif
invoke GetTickCount
mov dwLastTime,eax ;程序接收最后一次聊天语句的时间
.endif
.endw
invoke GetDlgItem,hWinMain,IDOK
invoke EnableWindow,eax,FALSE
invoke GetDlgItem,hWinMain,IDC_TEXT
invoke EnableWindow,eax,FALSE
invoke GetDlgItem,hWinMain,IDC_LOGOUT
invoke EnableWindow,eax,FALSE ;这些按钮灰化
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Ret: .if hSocket
invoke closesocket,hSocket ;如果句柄还是存在,就关闭,并且 让IP NAME PASS LOGIN这些按钮恢复正常
xor eax,eax
mov hSocket,eax
.endif
invoke GetDlgItem,hWinMain,IDC_IP
invoke EnableWindow,eax,TRUE
invoke GetDlgItem,hWinMain,IDC_NAME
invoke EnableWindow,eax,TRUE
invoke GetDlgItem,hWinMain,IDC_PASS
invoke EnableWindow,eax,TRUE
invoke GetDlgItem,hWinMain,IDC_Login
invoke EnableWindow,eax,TRUE
popad
ret
workthread endp
;====================================================================
main proc uses ebx esi edi hWnd,msg,wParam,lParam
local @wsa:WSADATA
local @mst:MSG_STRUCT
;====================================================================
mov eax,msg
mov eax,msg
.if eax == WM_INITDIALOG ;对话框初始化
push hWnd
pop hWinMain
invoke LoadIcon,hInstance,ICO_MAIN
invoke SendMessage,hWnd,WM_SETICON,ICON_BIG,eax ;换图标
invoke WSAStartup,101h,addr @wsa ;在使用windows socket之前,要先初始化,不然对其他WinSock函数调用不会成功的
invoke SendDlgItemMessage,hWinMain,IDC_IP,EM_SETLIMITTEXT,15,0
invoke SendDlgItemMessage,hWinMain,IDC_NAME,EM_SETLIMITTEXT,11,0
invoke SendDlgItemMessage,hWinMain,IDC_PASS,EM_SETLIMITTEXT,11,0
invoke SendDlgItemMessage,hWinMain,IDC_TEXT,EM_SETLIMITTEXT,250,0 ;对IP 用户名,密码和内容的长度限制
;********************************************************************
; 全部输入IP地址,用户名和密码后则激活"登录"按钮
;********************************************************************
.elseif eax ==WM_COMMAND
mov eax,wParam
.if (ax==IDC_IP) || (ax==IDC_NAME) || (ax==IDC_PASS)
invoke GetDlgItemText,hWinMain,IDC_IP,addr szip,sizeof szip ;得到输入的IP
invoke GetDlgItemText,hWinMain,IDC_NAME,addr szuser,sizeof szuser ;得到用户名
invoke GetDlgItemText,hWinMain,IDC_PASS,addr szmima,sizeof szmima ;得到密码
invoke GetDlgItem,hWinMain,IDC_Login ;得到登陆按钮的具备
.if szip && szuser && szmima ;如果IP 用户名 密码编辑框都输入信息了,登陆按钮才可使用
invoke EnableWindow,eax,TRUE
.else ;否则,一直是灰化的
invoke EnableWindow,eax,FALSE
.endif
.elseif ax == IDC_Login
push ecx
invoke CreateThread,NULL,0,offset workthread,0,NULL,esp ;当点击了一个登陆按钮会,会创建一个新的线程,
pop ecx ;push ecx,pop ecx,主要是得到线程的ID 但是我们要这个ID没用,所以就不用单独找个变量保存起来了
invoke CloseHandle,eax
.elseif ax == IDC_TEXT ;当点击了内容编辑框后
invoke GetDlgItemText,hWinMain,IDC_TEXT,addr sztext,sizeof sztext ;得到输入的内容
invoke GetDlgItem,hWinMain,IDOK
.if sztext && hSocket ;hSocket是刚刚Socket句柄,其实也就是点击了登陆按钮,再点击内容编辑框,才可以使用
invoke EnableWindow,eax,TRUE
.else
invoke EnableWindow,eax,FALSE
.endif
.elseif ax == IDOK ;发送按钮
invoke lstrcpy,addr @mst.MsgUp.szContent,addr sztext ;把得到的内容,放到消息结构
invoke lstrlen,addr @mst.MsgUp.szContent ;得到要发送的内容的长度,返回值保存到eax
inc eax ;以0结尾,所以长度+1
mov @mst.MsgUp.dwLength,eax ;把内容长度,给消息结构
add eax,sizeof MSG_HEAD+MSG_UP.szContent ;长度变量+消息结构头+内容长度 就是总消息结构的大小
mov @mst.MsgHead.dwLength,eax ;把总结构大小给 头消息结构
mov @mst.MsgHead.dwCmdId,CMD_MSG_UP ;命令ID,此处发送的是MSG_UP命令,也就是咱们客户端发送给服务端内容的结构
invoke send,hSocket,addr @mst,@mst.MsgHead.dwLength,0 ;此时就会把消息结构发送给服务端,然后服务端会根据消息队列发送给各个客户端
cmp eax,SOCKET_ERROR
jz @F ;发送中,如果出错了,就退出
invoke GetTickCount
mov dwLastTime,eax ;得到时间
invoke SetDlgItemText,hWinMain,IDC_TEXT,NULL ;主要是把内容编辑框清灵
invoke GetDlgItem,hWinMain,IDC_TEXT
invoke SetFocus,eax
.elseif ax == IDC_LOGOUT ;点击了注销按钮,就会关闭SOCKET
@@:
.if hSocket
invoke closesocket,hSocket
xor eax,eax
mov hSocket,eax
.endif
.endif
;====================================================================
.elseif eax == WM_CLOSE ;只有当注销了以后,对话框才能关闭
.if ! hSocket
invoke WSACleanup
invoke EndDialog,hWinMain,NULL
.endif
;====================================================================
.else ;不是以上的这些 就返回错误
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
main endp
;====================================================================
start:
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke DialogBoxParam,hInstance,DLG_MAIN,NULL,offset main,0 ;对话框
invoke ExitProcess,NULL
end start
服务端源码:
;在这里先跟大家说下思路 1 创建SOCKET 监听客户端 如果与客户端连接 则创建一个新的SOCKET 并创建一个线程来处理这个新的SOCKET
;2 连接成功后 利用数据接收子程序 接收数据包 检测第一次发来的是不是登陆数据包 如果是 则返回给客户端一个登陆成功数据包 如果不是 则跳转
;3 把消息插入消息队列 并在消息队列中获取此消息的ID 如果想要发送消息给客户端 则指定是哪个ID就行 下面我把消息ID都叫做消息编号了 怕有些人专牛角尖 因为消息队列中的消息 都是我们自定义的消息结构
;消息ID,也都是我们自定义的 然后。。。程序基本成功了
; 使用 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 1000h
DLG_MAIN equ 2000h
IDC_COUNT equ 2001h
TCP_PORT equ 521
;********************************************************************
; 客户端会话信息
;********************************************************************
SESSION struct
szUserName db 12 dup (?) ; 用户名
dwMessageId dd ? ; 已经下发的消息编号 通俗点讲 也就是咱们要发送的消息编号
dwLastTime dd ? ; 链路最近一次活动的时间
SESSION ends
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
QUEUE_SIZE equ 100 ;消息队列的长度
MSG_QUEUE_ITEM struct ;队列中单条消息的格式定义
dwMessageId dd ? ;消息编号
szSender db 12 dup (?) ;发送者
szContent db 256 dup (?) ;聊天内容
MSG_QUEUE_ITEM ends
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
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
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hInstance dd ?
hWinMain dd ?
hListenSocket dd ?
dwThreadCounter dd ?
dwFlag dd ?
F_STOP equ 0001h
stCS CRITICAL_SECTION <?>;临界区对象
stMsgQueue MSG_QUEUE_ITEM QUEUE_SIZE dup (<?>)
dwMsgCount dd ? ;队列中当前消息数量
.const
szErrBind db '无法绑定到TCP端口9999,请检查是否有其它程序在使用!',0
szSysInfo db '系统消息',0
szUserLogin db ' 进入了聊天室!',0
szUserLogout db ' 退出了聊天室!',0
szuser db "聊天室",0
.data
dwSequence dd 1 ;消息序号,从1开始
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;_WaitData _RecvData _RecvPacket 这三个跟客户端的一样 都是接受数据的,具体注释 大家可以看下客户端源码
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_WaitData proc _hSocket,_dwTime
local @stFdSet:fd_set
local @stTimeval:timeval
mov @stFdSet.fd_count,1
push _hSocket
pop @stFdSet.fd_array
push _dwTime
pop @stTimeval.tv_usec
mov @stTimeval.tv_sec,0
invoke select,0,addr @stFdSet,NULL,NULL,addr @stTimeval
ret
_WaitData endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 接收规定字节的数据,如果缓冲区中的数据不够则等待
; 返回:eax = TRUE,连接中断或发生错误
; eax = FALSE,成功
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_RecvData proc _hSocket,_lpData,_dwSize
local @dwStartTime
mov esi,_lpData
mov ebx,_dwSize
invoke GetTickCount
mov @dwStartTime,eax
;********************************************************************
@@:
invoke GetTickCount ;查看是否超时
sub eax,@dwStartTime
cmp eax,10 * 1000
jge _Err
;********************************************************************
invoke _WaitData,_hSocket,100*1000 ;等待数据100ms
cmp eax,SOCKET_ERROR
jz _Err
or eax,eax
jz @B
invoke recv,_hSocket,esi,ebx,0
.if (eax == SOCKET_ERROR) || ! eax
_Err:
xor eax,eax
inc eax
ret
.endif
.if eax < ebx
add esi,eax
sub ebx,eax
jmp @B
.endif
xor eax,eax
ret
_RecvData endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 接收一个符合规范的数据包
; 返回:eax = TRUE (失败)
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_RecvPacket proc _hSocket,_lpBuffer,_dwSize
local @dwReturn
pushad
mov @dwReturn,TRUE
mov esi,_lpBuffer
assume esi:ptr MSG_STRUCT
;********************************************************************
; 接收数据包头部并检测数据是否正常
;********************************************************************
invoke _RecvData,_hSocket,esi,sizeof MSG_HEAD
or eax,eax
jnz _Ret
mov ecx,[esi].MsgHead.dwLength
cmp ecx,sizeof MSG_HEAD
jb _Ret
cmp ecx,_dwSize
ja _Ret
;********************************************************************
; 接收余下的数据
;********************************************************************
sub ecx,sizeof MSG_HEAD
add esi,sizeof MSG_HEAD
.if ecx
invoke _RecvData,_hSocket,esi,ecx
.else
xor eax,eax
.endif
mov @dwReturn,eax
_Ret:
popad
assume esi:nothing
mov eax,@dwReturn
ret
_RecvPacket endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;当初没学过消息队列,真是难住了我好几天,还好,最后终于从牛角尖专了出来.........这里我要感谢党,感谢政府。。。。。。。。。。。。。。。。。
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_InsertMsgQueue proc _lpszSender,_lpszContent ;这里定义了两个参数 分别是用户名和内容
pushad
invoke EnterCriticalSection,addr stCS ;进入临界区
mov eax,dwMsgCount ;消息队列的数量
.if eax >= QUEUE_SIZE ;如果消息队列超过或等于100个 则将99个消息 连续的移动一位 此时 就能在第99个后面空出一个
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 ;movsb则是将DS:ESI(SI)中的一个字节复制到ES:EDI(DI)中,然后自动将ESI,EDI加一,rep前缀则是和loop检查ECX机制一样,ECX-1,!=0,继续,=0结束。 哈哈 没想到注释时 把这个弄懂了
rep movsb ;rep是循环的意思 循环多少次 要看ecx的值 movsb是字符串传送指令
.else ;如果消息队列不到100
inc dwMsgCount ;消息数量加1
mov ecx,sizeof MSG_QUEUE_ITEM ;获得一个结构的大小 因为我们定义的一个消息就是一个结构
mul ecx ;结构与数量相乘
.endif
lea esi,[stMsgQueue+eax] ;获得将要插入消息的指针地址
assume esi:ptr MSG_QUEUE_ITEM ;伪指令 定义esi为消息结构的指针
invoke lstrcpy,addr [esi].szSender,_lpszSender ;把用户名复制到自定义的消息结构中
invoke lstrcpy,addr [esi].szContent,_lpszContent ;内容也复制过去 这里其实不是复制字符串 好像是指针什么的 反正 意思一样就行
inc dwSequence ;编号加1
mov eax,dwSequence ;其实这个dwSequence就是所谓的消息编号 发送消息给客户端时 就是用的它
mov [esi].dwMessageId,eax ;消息ID 下面的两个子程序 发送子程序调用得到消息子程序 得到消息子程序就是有了消息ID 才能获得内容和用户名 然后放到数据包中 发送给客户端
assume esi:nothing
invoke LeaveCriticalSection,addr stCS ;离开临界区
popad
ret
_InsertMsgQueue endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_GetMsgFromQueue proc _dwMessageId,_lpszSender,_lpszContent ;得到消息 其实也就是给他ID 就能知道用户名和内容
local @dwReturn
pushad
invoke EnterCriticalSection,addr stCS ;进入临界区
xor eax,eax
mov @dwReturn,eax
cmp dwMsgCount,eax ;如果消息队列的消息为0 则直接退出
jz _Ret
mov esi,offset stMsgQueue ;把消息队列的指针 给esi
assume esi:ptr MSG_QUEUE_ITEM
;********************************************************************
; esi 指向队列头部,所以这条消息的编号就是最小编号
; 最大编号=最小编号+队列长度-1
;********************************************************************
mov ecx,[esi].dwMessageId ;ecx=队列中的最小消息编号
mov edx,dwMsgCount
lea edx,[edx+ecx-1] ;edx=队列中的最大消息编号 至于为什么减1 大家应该不知道 如果实在不知道 那就口算下
;********************************************************************
; 如果指定编号 < 最小编号,则返回最小编号的消息
; 如果指定编号 > 最大编号,则不返回任何消息
;********************************************************************
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 ;将消息编号给eax
ret
_GetMsgFromQueue endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 循环取消息队列中的聊天语句并发送到客户端,直到全部消息发送完毕
assume esi:ptr MSG_STRUCT,edi:ptr SESSION
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_SendMsgQueue proc uses esi edi _hSocket,_lpBuffer,_lpSession ;定义了三个参数,句柄,缓冲区,和自定义 与客户端保存会话的结构 就是那个刚开始保存的SESSION结构
local @stMsg:MSG_STRUCT
mov esi,_lpBuffer ;缓冲区指针
mov edi,_lpSession ;结构指针
.while ! (dwFlag & F_STOP)
mov ecx,[edi].dwMessageId ;把消息编号给寄存器
inc ecx ;加1 大家有没有想到为什么加1 嘿嘿 因为插入消息队列的子程序那里就加1了
invoke _GetMsgFromQueue,ecx,addr [esi].MsgDown.szSender,addr [esi].MsgDown.szContent ;这里就是得到消息了 把用户名和内容都保存到数据包中
.break .if ! eax
mov [edi].dwMessageId,eax ;此时的消息编号给SESSION结构中 因为不一定想取的那个就在 也许由于网络慢 已经被挤出队列队列 只能取最小的那个呢
invoke lstrlen,addr [esi].MsgDown.szContent ;获取内容长度
inc eax ;这里加1 因为还包括0 就是那个以0结尾的0
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 ;获取最后一次活动时间 把时间放到SESSION结构中
;********************************************************************
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 ;这里的SESSION结构式自己定义的 分别是用户名,消息编号,链路最后一次的活动时间 服务端与每个客户端连接 一般 都会创建一个这样的结构 用来保存会话消息
local @szBuffer[512]:byte
pushad
inc dwThreadCounter
invoke SetDlgItemInt,hWinMain,IDC_COUNT,dwThreadCounter,FALSE ;在服务端上显示与几个客户端连接
lea esi,@szBuffer ;获取缓冲区的指针
lea edi,@stSession ;获取结构的指针
invoke RtlZeroMemory,edi,sizeof @stSession ;将自定义的结构填0
mov eax,dwSequence ;此时的消息编号是1
mov [edi].dwMessageId,eax ;放在自定义的结构中
;********************************************************************
; 用户名和密码检测,为了简化程序,现在可以使用任意用户名和密码
;********************************************************************
invoke _RecvPacket,_hSocket,esi,sizeof @szBuffer ;接收数据包
or eax,eax
jnz _Ret
.if [esi].MsgHead.dwCmdId != CMD_LOGIN ;如果数据头的命令ID不是登陆命令,因为客户端第一次与服务端连接 肯定是登陆数据包(其实这些都是自己定义的)
jmp _Ret
.else ;如果是
invoke lstrcpy,addr [edi].szUserName,addr [esi].Login.szUserName ;把登陆数据包中 用户名负责到自己定义的结构中 此时结构中的两个参数都被赋值了吧
mov [esi].LoginResp.dbResult,0 ;因为客户端发送登陆命令后 中间万一出现什么错误 它也不知道 所以我们要给客户端一个登陆回应包 登陆成功 我们就回应给他0 是1是0可以自己定义 客户端设置为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 ;XX进入聊天室
invoke _InsertMsgQueue,addr szSysInfo,esi ;插入到消息队列 XX进入了聊天室
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 ;关闭SOCKET
dec dwThreadCounter ;显示的连接减1
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 ;转换端口为INTER顺序
mov @stSin.sin_port,ax
mov @stSin.sin_family,AF_INET ;格式是windows的
mov @stSin.sin_addr,INADDR_ANY ;IP地址是本机
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 ;开始监听 5是最大连接数
.while TRUE
invoke accept,hListenSocket,NULL,0 ;接受与客户端的连接 创建一个新的套接字句柄 如果成功 返回值放在eax中
.break .if eax == INVALID_SOCKET
push ecx
invoke CreateThread,NULL,0,offset _ServiceThread,eax,NULL,esp ; 专为客户端通讯使用 新的套接字句柄在eax中
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 ;初始化SOCKET数据
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
网上的源码确实很多 但是重要的地方 基本就打个哈欠过去了,害我当时理解了好几天,还有,不得不说,汇编写的程序就是小,发布后 不知道能不能转正
[培训]科锐软件逆向54期预科班、正式班开始火爆招生报名啦!!!
上传的附件: