WIN32汇编语言教程:第16章 TCP/IP和网络通信 · 16.4 UDP协议编程(5)

                         inc    ebx

                  .endw

                 invoke SetDlgItemInt,hWinMain,IDC_COUNT,@dwCount,FALSE

;********************************************************************

                  .endif

                 assume esi:nothing

          .endif

          ret

 

_RecvData     endp

;####################################################################

; 初始化 Socket,绑定到服务UDP端口并监听

;####################################################################

_Init        proc

                  local  @stWsa:WSADATA

                 local  @stSin:sockaddr_in

 

              invoke WSAStartup,101h,addr @stWsa

                  invoke socket,AF_INET,SOCK_DGRAM,0

                 mov    hSocket,eax

                  invoke WSAAsyncSelect,hSocket,hWinMain,WM_SOCKET,FD_READ

 

                  invoke RtlZeroMemory,addr @stSin,sizeof @stSin

                 invoke htons,UDP_PORT

                  mov    @stSin.sin_port,ax

                  mov    @stSin.sin_family,AF_INET

                  mov    @stSin.sin_addr,INADDR_ANY

                  invoke bind,hSocket,addr @stSin,sizeof @stSin

                  .if    eax == SOCKET_ERROR

                         invoke MessageBox,hWinMain,addr szErrBind,NULL,\

                                 MB_OK or MB_ICONWARNING

                         invoke SendMessage,hWinMain,WM_CLOSE,0,0

                  .endif

                 ret

 

_Init         endp

;####################################################################

;      主窗口程序

;####################################################################

_ProcDlgMain      proc      uses ebx edi esi hWnd,wMsg,wParam,lParam

 

              mov    eax,wMsg

              .if    eax == WM_SOCKET

              mov    eax,lParam

                          .if    ax ==  FD_READ

                                 invoke _RecvData,wParam

                         .endif

;********************************************************************

                  .elseif eax == WM_CLOSE

                          invoke closesocket,hSocket

                          invoke WSACleanup

                         invoke EndDialog,hWinMain,NULL

;********************************************************************

                  .elseif eax == WM_INITDIALOG

                          push      hWnd

                          pop    hWinMain

                          call      _Init

;********************************************************************

                  .else

                          mov    eax,FALSE

                          ret

                 .endif

                  mov    eax,TRUE

                  ret

 

_ProcDlgMain      endp

;####################################################################

; 程序开始

;####################################################################

start:

                 invoke GetModuleHandle,NULL

                 invoke  DialogBoxParam,eax,DLG_MAIN,NULL,offset _ProcDlgMain,0

                 invoke ExitProcess,NULL

;####################################################################

                  end    start

当看到这里的时候,读者对于FD_XXX之类的通知码的处理,以及收发数据函数的使用等一定不会陌生了,所以在这里不再对这些内容进行过多的解释。

和UDP聊天室客户端的代码相比,服务器端代码的主要差别有两点:首先是服务器端的数据报套接字必须使用bind函数绑定到一个固定的服务端口上,这样才能接收客户端发送的数据;第二就是必须维护一个客户端ID列表,以便向所有的客户端转发聊天语句。

如果和TCP聊天室的服务器端代码相比,其差别有三点:首先是UDP服务器从开始到结束始终只使用一个数据报套接字来接收所有客户端发过来的数据,也用它来向所有客户端转发数据,而TCP服务器使用一个套接字进行监听,并由accept函数返回很多和客户端相连的新套接字;第二点就是TCP服务器可以用accept函数得到的套接字句柄来惟一确定客户端,而UDP服务器只能通过recvfrom接收数据时得到的IP地址和端口地址来确定客户端;最后一点是对客户端退出的处理上,TCP服务器只要检测到和客户端连接的套接字发出FD_CLOSE通知,就可以将它从列表中删除,而UDP服务器必须规定客户端在收到数据后回发一个确认数据,如果多次得不到确认(次数可以定义),则将客户端从列表中删除,因为UDP数据包可能丢失,得不到一次确认并不意味着客户端就没有发出过确认信息。

根据这些不同点,让我们来看具体在源程序中是怎样处理的。

例子程序为客户端列表中的项目定义了一个CLIENT_ADDR结构,结构定义如下:

CLIENT_ADDR    struct

 

dwClientIP    dd    ?      ;客户端IP地址

wClientPort    dw    ?      ;客户端端口

dwID              dd    ?      ;序号

dwRetryTimes      dd    ?      ;重试次数

 

CLIENT_ADDR    ends

结构中包括客户端的IP地址和端口,这两个字段用于惟一确定客户端,dwID字段仅用做显示,dwRetryTimes用做客户端应答计数器,原始值为最大允许的未应答次数(例子中定义为5次),每次向客户端发送数据后,如果没有得到应答,那么这个计数器减1,如果计数器减到0则视为客户端离线而将它从列表中删除,但是一旦在减到0之前得到回应,那么计数器被恢复到最大的允许值。

每次收到FD_READ通知时,表明有聊天语句或应答信号从客户端发过来,程序调用_RecvData子程序来接收数据,收到数据以后,程序首先按照发送方的地址和端口扫描列表(在_AddSocket子程序中完成),如果这个地址已经在列表中存在,则将对应的应答计数器恢复到最大值,否则找出一个空列表项将它登记进去。

接下来,程序判断收到的数据是否是定义为应答信号的–1,如果不是应答信号,表明这是一个聊天语句,这时程序用一个循环按照列表中的地址向所有客户端转发这个语句,每次向一个客户端转发以后,程序将列表中这个客户端的应答计数器减1,当发现减到0的时候将它从列表中删除。

由于服务器端程序要等待一定的无应答次数以后才将一个客户端视为离线,所以当一个客户端程序关闭的时候,服务器端程序最下面的“当前连线客户端数量”中显示的数值并不会马上变化,直到经过好几句聊天语句以后这个数值才会减少。

上页:第16章 TCP/IP和网络通信 · 16.4 UDP协议编程(4) 下页:第16章 TCP/IP和网络通信 · 16.5 ICMP协议编程(1)

第16章 TCP/IP和网络通信

版权所有 © 云南伯恩科技 证书:粤ICP备09170368号