首页
论坛
课程
招聘
[旧帖] [原创]罗云彬《32位汇编语言》16章中对TCP聊天室程序的扩展4 0.00雪花
2009-9-20 10:49 1778

[旧帖] [原创]罗云彬《32位汇编语言》16章中对TCP聊天室程序的扩展4 0.00雪花

2009-9-20 10:49
1778
第三步功能实现了,第四步功能就是实现悄悄话的功能。
这个功能比较复杂,因此分两部分实现,第一部分实现用户登录退出时列表框中能同步显示当前用户,第二部分再实现点击列表框中某个用户时实现悄悄话功能
第一部分
1.        在Message.inc中,定义一个SEND_ONLINE_USER结构体,里面包含了当前登录的的所有用户名称,用户个数等信息。

;**************************************************************        ;canmeng萌4
; 发送所有用户名字语句(服务器端->客户端):不等长数据包                        ;canmeng萌4
;***********************************************************                ;canmeng萌4
MSG_ONLINE_USER                struct                                                                                        ;canmeng萌4
  sznumber                dd        ?                ;当前在线用户个数                                                        ;canmeng萌4
  dwlength                dd        ?                ;后面内容字段的长度                                                ;canmeng萌4
  szcontent                db        48 dup (?)        ;所有用户的名字,最多可容纳4个用户        ;canmeng萌4
MSG_ONLINE_USER                ends                                                                                        ;canmeng萌4

增加用于这个数据包的数据包头命令ID
CMD_ONLINE_USER        equ        85h        ; 服务器端 -> 客户端,                        canmeng萌4

在MSG_STRUCT结构体中也增加一个MSG_ONLINE_USER类型的字段
MSG_STRUCT                struct
  MsgHead                MSG_HEAD <>
  union
    Login                MSG_LOGIN <>
    LoginResp                MSG_LOGIN_RESP <>
    MsgUp                MSG_UP <>
    MsgUpResp                MSG_UP_RESP <>                                                                ;canmeng萌2
    MsgDown                MSG_DOWN <>
    MsgOnlineUser        MSG_ONLINE_USER        <>                                                ;canmeng萌4
  ends
MSG_STRUCT                ends
2.        在Server.asm中,增加一个子程序,实现的功能是把所有在线的用户的名字发送到本线程对应的客户端(实际上时发送MSG_ONLINE_USER数据包)。
①        这个子程序我就添加在_LinkCheck子程序之后了。
;**************************************************************        ;canmeng萌4
; 把在线的用户名字发给在线的客户端                                                                        ;canmeng萌4
;**************************************************************        ;canmeng萌4
_SendOnlineUser proc uses esi edi _hSocket                               
        pushad                                                                                        ;canmeng萌4
        invoke        WaitForSingleObject,hEvent,INFINITE                ;canmeng萌4,等待事件对象置位
        mov        [esi].MsgHead.dwCmdId,CMD_ONLINE_USER                                ;命令类型
                mov        ecx,dwThreadCounter                                                        ;canmeng萌4
                mov        eax,12                                                                                        ;canmeng萌4
                mul        ecx                                                                                                        ;canmeng萌4
        inc        eax                                                                                                ;canmeng萌4
        mov        ecx,eax                                ;;canmeng萌4,如果有两个线程,此时ecx应该为25
        push        ecx
       
        mov        edx,offset szusernames                                        ;canmeng萌4
        lea        ebx,[esi].MsgOnlineUser.szcontent                        ;canmeng萌4
        .while        TRUE                                                                        ;canmeng萌4
                sub ecx,1                                                                        ;canmeng萌4
                mov        al,[edx]                                                                ;canmeng萌4       
                mov        [ebx],al                                                                ;canmeng萌4       
                add        edx,1                                                                        ;canmeng萌4
                add        ebx,1                                                                        ;canmeng萌4
                .break        .if ecx==0                                                        ;canmeng萌4
        .endw                                                                                        ;canmeng萌4                所有用户名称
       
        pop        eax                                                       
        mov        [esi].MsgOnlineUser.dwlength,eax                        ;所有用户名称的长度
        add        eax,sizeof MSG_HEAD+MSG_ONLINE_USER.szcontent                ;canmeng萌4
        mov        [esi].MsgHead.dwLength,eax                                                                ;总长度
        mov        eax,dwThreadCounter                                                                ;canmeng萌4
        mov        [esi].MsgOnlineUser.sznumber,eax                                                ;用户个数
        invoke        send,_hSocket,esi,[esi].MsgHead.dwLength,0                        ;canmeng萌4
        invoke        SetEvent,hEvent                                                        ;canmeng萌4,置位事件对象
        popad                                                                                                                        ;canmeng萌4
        ret                                                                                                                                ;canmeng萌4
_SendOnlineUser endp                                                                                                ;canmeng萌4

注意:在填充数据包中所有用户名称即填写[esi].MsgOnlineUser.szcontent时,我是一个一个字节的填写过去的,这是为什么呢?我们知道每一个用户的名字占12个字节,以字符0作为结尾字符。如果直接用lstrcpy函数,只能拷贝一个用户名,所以要一个一个字节的传送进去。
②        在_SerciceThread这个线程中添加一个局部变量dwThreadCounter1,用来记录在线的用户人数。每当有用户登录或者退出时,全局变量dwThreadCounter就会发生变化,在线程的循环处理消息中,如果发现自己的dwThreadCounter1和全局变量dwThreadCounter不一致,那么就会把dwThreadCounter的值赋给本线程内的局部变量dwThreadCounter1并且向对应的客户端发送一个MSG_ONLINE_USER数据包(这是通过调用_SendOnlineUser子程序实现的)。
Local                dwThreadCounter1
③       
;****************************************************************
; 循环处理消息
;****************************************************************
                .while        ! (dwFlag & F_STOP)
                        invoke        _SendMsgQueue,_hSocket,esi,edi
                        .break        .if eax
                        mov        ebx,dwThreadCounter                                                ;canmeng萌4
                        cmp        dwThreadCounter1,ebx                                                ;canmeng萌4
                        jz        @F                                                                                                ;canmeng萌4
                        mov        ebx,dwThreadCounter                                                ;canmeng萌4
                        mov        dwThreadCounter1,ebx                                                ;canmeng萌4
                        invoke _SendOnlineUser,_hSocket                                        ;canmeng萌4
                @@:                                                                                                        ;canmeng萌4
                        invoke        _LinkCheck,_hSocket,esi,edi
                        .break        .if eax
                        .break        .if dwFlag & F_STOP
3.        在Client.rc中进行客户端界面的修改,增加一个列表框,一个复位按钮(用于不选择用户名)等。
#define IDC_COUNT1        2008               
#define IDC_LISTBOX        2009       
#define IDC_RESET        2010               
…………
LTEXT "当前在线人数:", -1, 245, 7, 60, 8                                                ;canmeng萌4       
LTEXT "0", IDC_COUNT1, 300, 7, 37, 8                                                        ;canmeng萌4       
LISTBOX IDC_LISTBOX,250,22,50,120,LBS_STANDARD                                ;canmeng萌4
PUSHBUTTON "复位",IDC_RESET,250,150,30,14                                        ;canmeng萌4
;注意:在define哪一行的后面不能加注释啊,
4.        在Client1.asm中,首先把在Client.rc中定义的常量写出来。

IDC_COUNT1        equ        2008                                                ;canmeng萌4
IDC_LISTBOX        equ        2009                                                ;canmeng萌4
IDC_RESET        equ        2010                                                        ;canmeng萌4
②定义一个全局变量,用来存储所有在线的用户名。
.data?
szUserBuffer        db        48 dup (?)                                        ;canmeng萌4,暂存所有的用户名字
③在循环接收消息中,对接收的消息头部命令ID进行判断,如果为CMD_ONLINE_USER,则说明数据包中包含所有的用户名称,于是把这些用户名称存到全局变量szUserBuffdr中,然后再一个个的显示在列表框中。
;********************************************************************
; 循环接收消息
;********************************************************************
……
.if        @stMsg.MsgHead.dwCmdId == CMD_ONLINE_USER        ;canmeng萌4
        mov        ecx,@stMsg.MsgOnlineUser.dwlength                ;canmeng萌4
        mov        ebx,offset szUserBuffer                                                ;canmeng萌4
        lea        edx,@stMsg.MsgOnlineUser.szcontent                        ;canmeng萌4
        .while        TRUE                                                                                 ;canmeng萌4
                sub        ecx,1                                                                                ;canmeng萌4
                mov        al,[edx]                                                                        ;canmeng萌4
                mov        [ebx],al                                        ;canmeng萌4
                add        edx,1                                                ;canmeng萌4
                add        ebx,1                                                ;canmeng萌4
                .break        .if ecx==0                                ;canmeng萌4
        .endw                                                                ;canmeng萌4
;注意:上面几句代码不能用lstrcpy代替
        invoke        SendDlgItemMessage,hWinMain,IDC_LISTBOX,LB_RESETCONTENT,0,0
        mov        ecx,@stMsg.MsgOnlineUser.sznumber                ;canmeng萌4
        .while        TRUE                                                                                ;canmeng萌4
                sub ecx,1                                                                                ;canmeng萌4
                mov eax,12                                                                                ;canmeng萌4
                mul ecx                                ;注意,这一步会影响edx的值,非常非常重要
                mov edx,offset szUserBuffer                                                ;canmeng萌4
                add edx,eax                                                                                ;canmeng萌4
                push edx                                                                                ;canmeng萌4
                push ecx                                                                                ;canmeng萌4
                invoke SendDlgItemMessage,hWinMain,IDC_LISTBOX,LB_ADDSTRING,0,edx        ;canmeng萌4
                pop  ecx                                                                                ;canmeng萌4               
                pop  edx                                                                                ;canmeng萌4
                .break .if ecx==0                                                                        ;canmeng萌4
        .endw                                                                                                ;canmeng萌4
Invoke SetDlgItemInt,hWinMain,IDC_COUNT1,@stMsg.MsgOnlineUser.sznumber,FALSE        ;canmeng萌4
@@:                                                                                                                ;canmeng萌4
.elseif        @stMsg.MsgHead.dwCmdId == CMD_MSG_DOWN
这样,每当有用户登录或者退出时,每个客户端都能及时的显示当前的用户了。

第二部分
5.        从此步骤开始,就正式实现悄悄话的功能了。在Message.inc中,MSG_UP和MSG_DOWN结构体中都增加爱“本消息的接收对象”字段。
①MSG_UP                        struct
  szreceiver                db        12 dup (?)        ;                        ;canmeng萌4
  dwLength                dd        ?                ;后面内容字段的长度
  szContent                db        256 dup (?)        ;内容,不等长,长度由dwLength指定
MSG_UP                        ends
②MSG_DOWN                struct
  szTime                db        20 dup (?)        ;canmeng萌1
  szSender                db        12 dup (?)        ;消息发送者
  szreceiver                db        12 dup (?)        ;消息接收者                                ;canmeng萌4
  dwLength                dd        ?                ;后面内容字段的长度
  szContent                db        256 dup (?)        ;内容,不等长,长度由dwLength指定
MSG_DOWN                ends
6.        在_MsgQueue.asm中,
①也要改变消息的结构:增加消息的接收对象
MSG_QUEUE_ITEM        struct                        ;队列中单条消息的格式定义
  dwMessageId        dd        ?                ;消息编号
  szSender        db        12 dup (?)        ;发送者
  szreceiver        db        12 dup (?)        ;接收者                                ;canmeng萌4
  szContent        db        256 dup (?)        ;聊天内容
  szTime        db        20 dup (?)        ;canmeng萌1
MSG_QUEUE_ITEM        ends
②在_InsertMsgQueue中,需要增加一个参数_lpszreceiver并添加相应的语句。
_InsertMsgQueue        proc        _lpszSender,_lpszContent,_lpszreceiver        ;canmeng萌4
……
invoke        lstrcpy,addr [esi].szreceiver,_lpszreceiver                                                ;canmeng萌4
③在_GetMsgFromQueue中,需要再增加一个参数_lpszreceiver并添加相应的语句。
_GetMsgFromQueue        proc        _dwMessageId,_lpszSender,_lpszContent,_lpszTime,_lpszreceiver;canmeng萌1,canmeng萌4
……
invoke        lstrcpy,_lpszreceiver,addr [esi].szreceiver                ;canmeng萌4
7.        在服务器端即Server.asm中,当用户发来一个登录数据包或者一个退出数据包,那么此消息的接收者当然是全部的客户端,我把此时的接收对象定义为“Allusers”。所以我在.const段中定义了一个全局变量dwreceiver。
①       
.const
dwreceiver        db        'Allusers',0                        ;canmeng萌4
②        这样当服务器收到客户端的登录数据包时,就把“Allusers”作为消息的接收对象插入到消息队列中去。
;********************************************************************
; 广播:xxx 进入了聊天室
;********************************************************************
                invoke        lstrcpy,esi,addr [edi].szUserName
                invoke        lstrcat,esi,addr szUserLogin
                invoke        _InsertMsgQueue,addr szSysInfo,esi,addr dwreceiver                ;canmeng萌4
③        相似的,当服务器收到客户端的推出数据包时,也把“Allusers”作为消息的接收对象插入到消息队列中。
;********************************************************************
; 广播:xxx 退出了聊天室
;********************************************************************
invoke        lstrcpy,esi,addr [edi].szUserName
invoke        lstrcat,esi,addr szUserLogout
invoke        _InsertMsgQueue,addr szSysInfo,addr @szBuffer,addr dwreceiver        ;canmeng萌4
④        当收到用户的聊天语句数据包时,直接把数据包中所包含的接收对象字段提取出来就行了。
;********************************************************************
; 循环处理消息
;********************************************************************
invoke        _InsertMsgQueue,addr [edi].szUserName,addr [esi].MsgUp.szContent,\
                                                                        addr [esi].MsgUp.szreceiver                ;canmeng萌4
⑤        另外,由于Message.inc中的_GetMsgFromQueue函数已经多了一个参数,所以也要修改_SendMsgFromQueue中的调用语句。
在_SendMsgFromQueue中,
invoke        _GetMsgFromQueue,ecx,addr[esi].MsgDown.szSender,addr [esi].MsgDown.szContent,\
addr [esi].MsgDown.szTime,addr [esi].MsgDown.szreceiver        ;canmeng萌1,canmeng萌4
8.        在Client1.asm中,客户端收到消息即MSG_DOWN数据包后,先进行判断。如果接收对象为“Allusers”,就把聊天语句发送到聊天室;如果接收对象是自己登录时使用的用户名,就显示一个消息框,消息框的内容是聊天语句;如果接受对象是别的,就直接略过。

在.const段中定义一个全局变量dwreceiver
.const
dwreceiver        db        'Allusers',0                                                                ;canmeng萌4

;********************************************************************
; 循环接收消息
;********************************************************************
.elseif        @stMsg.MsgHead.dwCmdId == CMD_MSG_DOWN
        invoke        lstrcmp,addr dwreceiver,addr @stMsg.MsgDown.szreceiver        ;canmeng萌4
        cmp        eax,0                                                                                                                ;canmeng萌4
        jz        @F                                                                                                                                ;canmeng萌4
        invoke        lstrcmp,addr szUserName,addr @stMsg.MsgDown.szreceiver        ;canmeng萌4       
        cmp        eax,0
        jz        @next                                                                                                                        ;canmeng萌4
        jmp        xiayige1                                                                                                                        ;canmeng萌4
@@:                                                                                                                                        ;canmeng萌4
        invoke        lstrcpy,addr @szBuffer,addr @stMsg.MsgDown.szContent        ;canmeng萌1,先显示内容
        invoke        SendDlgItemMessage,hWinMain,IDC_INFO,LB_INSERTSTRING,0,addr @szBuffer

        invoke        lstrcpy,addr @szBuffer,addr @stMsg.MsgDown.szSender
        invoke        lstrcat,addr @szBuffer,addr szSpar

        invoke  lstrcat,addr @szBuffer,addr @stMsg.MsgDown.szTime                ;canmeng萌1
        invoke        SendDlgItemMessage,hWinMain,IDC_INFO,LB_INSERTSTRING,0,addr @szBuffer;canmeng萌1
        jmp        xiayige1                                                        ;canmeng萌4
@next:                                                                        ;canmeng萌4
        invoke        MessageBox,hWinMain,addr @stMsg.MsgDown.szContent,\
                                                addr @stMsg.MsgDown.szSender,MB_OK                ;canmeng萌4
xiayige1:                                                                        ;canmeng萌4
.elseif        @stMsg.MsgHead.dwCmdId == CMD_MSG_UP_RESP                ;canmeng萌2

另外,在列表框中,当点击“发送”按钮时,需要知道哪一个用户被选中了。因此要修改主窗口程序。
.elseif        ax ==        IDOK
invoke        SendDlgItemMessage,hWinMain,IDC_LISTBOX,LB_GETCURSEL,0,0        ;canmeng萌4
mov        ecx,offset szreceiver                                                ;canmeng萌4
invoke        SendDlgItemMessage,hWinMain,IDC_LISTBOX,LB_GETTEXT,eax,ecx        ;canmeng萌4

invoke        lstrlen,addr szreceiver                                                ;canmeng萌4
.if        eax==0                ;canmeng萌4,如果没选择列表框中项目,就把szreceiver设为'Allusers'
invoke        lstrcpy,addr szreceiver,addr dwreceiver                        ;canmeng萌4
.endif                                                                        ;canmeng萌4
invoke        lstrcpy,addr @stMsg.MsgUp.szreceiver,addr szreceiver                ;canmeng萌4
……
其实这里的这个szreceiver是在.data中定义的全局变量。
.data
szreceiver        db 12 dup (?)
④       
添加一个复位按钮,用于不选择列表框中的任何一个项目。
;***************************************************************;canmeng萌4
        .elseif        ax == IDC_RESET                                                                                                ;canmeng萌4
        invoke        SendDlgItemMessage,hWnd,IDC_LISTBOX,LB_SETCURSEL,-1,0        ;canmeng萌4
        invoke        lstrcpy,addr szreceiver,addr dwreceiver                                                ;canmeng萌4
⑤       
再添加一个功能:当用户点击“注销”时,把列表框中的用户名清除掉,而且把在线人数改为0.
.elseif        ax ==        IDC_LOGOUT
@@:
        .if        hSocket
                invoke        closesocket,hSocket
                xor        eax,eax
                mov        hSocket,eax
                invoke        SendDlgItemMessage,hWinMain,IDC_LISTBOX,LB_RESETCONTENT,0,0;canmeng萌4
                invoke        SetDlgItemInt,hWinMain,IDC_COUNT1,0,FALSE                        ;canmeng萌4
        .endif
这样,第四步功能“实现悄悄话”也完成了。
附件是源代码和生成的服务器端程序、客户端程序。

看雪招聘平台创建简历并且简历完整度达到90%及以上可获得500看雪币~

上传的附件:
收藏
点赞0
打赏
分享
最新回复 (8)
雪    币: 99
活跃值: 活跃值 (16)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
townyee 活跃值 2009-9-20 11:25
2
0
学习一下,我的WIN32汇编还没有学到这里,楼主用的RadASM的开发环境嘛?
雪    币: 103
活跃值: 活跃值 (11)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
canmeng萌 活跃值 1 2009-9-20 18:57
3
0
回复二楼:
我用的开发环境不是RadASM,就是书里面介绍的Win32 MASM包,书里面有详细介绍的。呵呵,你还是第一个搭理我的。受鼓舞
雪    币: 0
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
dxs党晨源 活跃值 2009-9-21 22:52
4
0
楼主程序编的真棒,以后有机会我还要向你多多请教
雪    币: 103
活跃值: 活跃值 (11)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
canmeng萌 活跃值 1 2009-9-22 08:25
5
0
回复四楼:
哈哈,不敢当不敢当,共同学习进步呗。
雪    币: 1
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
到处走走 活跃值 2009-9-22 21:16
6
0
我只是个菜鸟~虽然看不懂~但是还是**下~!
雪    币: 1
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
到处走走 活跃值 2009-9-22 21:16
7
0
顶  你  下 刚不好意思连在一起打的~!
雪    币: 35
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wtwt 活跃值 2009-9-23 10:32
8
0
真是太厉害了啊,我要好好学习啊
雪    币: 30
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
khuy 活跃值 2009-9-26 20:01
9
0
厉害~~~~~~~~~~~~~
游客
登录 | 注册 方可回帖
返回