• 1054阅读
  • 2回复

win32汇编写的基于SOCKET的聊天室(客户端) [复制链接]

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

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


win32汇编写的基于SOCKET的聊天室(客户端)

发表于 2015 年 10 月 29 日

下面是客户端源码:
.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比较的,所以就是没有                                                                             ;数据,就重新开始循环
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

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

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

只看该作者 板凳  发表于: 2017-08-06
大地飞歌好地方更好地发挥规划规划
快速回复
限100 字节
如果您提交过一次失败了,可以用”恢复数据”来恢复帖子内容
 
上一个 下一个