首页
社区
课程
招聘
[旧帖] [原创][原创]win32写的聊天室,句句注释 0.00雪花
发表于: 2013-10-24 13:22 1983

[旧帖] [原创][原创]win32写的聊天室,句句注释 0.00雪花

2013-10-24 13:22
1983
来看雪有段时间了,但是还是临时会员,悲催,看到发帖有机率转正,就试试运气吧,好了,闲话不说,直接上代码

下面是客户端源码:

.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期预科班、正式班开始火爆招生报名啦!!!

上传的附件:
收藏
免费 0
支持
分享
最新回复 (6)
雪    币: 2661
活跃值: (2310)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
牛z。。。。。。
2013-10-24 14:38
0
雪    币: 302
活跃值: (246)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
3
额 我都注册一年了 还是临时会员
2013-10-24 15:57
0
雪    币: 39
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
支持楼主
2013-10-24 17:15
0
雪    币: 302
活跃值: (246)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
5
额 当初在鱼C发过,都奖励我鱼币了,今天又重新注释了下客户端,服务端已经注释的很详细了
2013-10-24 17:33
0
雪    币: 5
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
如果是用易语言写 应该不超过百行就能达到你现在的效果偶  亲
2013-10-24 17:45
0
雪    币: 302
活跃值: (246)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
7
额 伟大的易语言。。。。。。。。。
2013-10-24 18:16
0
游客
登录 | 注册 方可回帖
返回