• 1025阅读
  • 2回复

win32汇编写的聊天室(服务端) [复制链接]

上一主题 下一主题
离线啊冲
 

只看楼主 倒序阅读 使用道具 楼主  发表于: 2016-02-02


win32汇编写的聊天室(服务端)

发表于 2015 年 10 月 29 日

服务端源码:
;在这里先跟大家说下思路 1 创建SOCKET 监听客户端  如果与客户端连接 则创建一个新的SOCKET 并创建一个线程来处理这个新的SOCKET
;2  连接成功后 利用数据接收子程序 接收数据包 检测第一次发来的是不是登陆数据包 如果是 则返回给客户端一个登陆成功数据包 如果不是 则跳转
;3  把消息插入消息队列 并在消息队列中获取此消息的ID   如果想要发送消息给客户端 则指定是哪个ID就行 下面我把消息ID都叫做消息编号了 怕有些人专牛角尖 因为消息队列中的消息 都是我们自定义的消息结构
;消息ID,也都是我们自定义的 然后。。。程序基本成功了
.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
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


善者 慈悲心常在 无怨无恨 以苦为乐
默认压缩密码www.hifyl.com
文件分享密码问题:http://www.hifyl.com/read-htm-tid-4444.html
离线v2680267313

只看该作者 沙发  发表于: 2016-04-30
用户被禁言,该主题自动屏蔽!
离线q1156136807

只看该作者 板凳  发表于: 2017-12-29
感谢分享  看看
7
快速回复
限100 字节
如果您提交过一次失败了,可以用”恢复数据”来恢复帖子内容
 
上一个 下一个