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)