WIN32汇编语言教程:第16章 TCP/IP和网络通信 · 16.3 TCP协议编程(6)
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
.else
invoke listen,hSocket,5
.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_ACCEPT
invoke accept,wParam,0,0
invoke _AddClient,eax
.elseif ax == FD_READ
invoke _RecvData,wParam
.elseif ax == FD_CLOSE
invoke _RemoveClient,wParam
.endif
;********************************************************************
; 退出时关闭全部连接
;********************************************************************
.elseif eax == WM_CLOSE
invoke closesocket,hSocket
xor ebx,ebx
mov esi,offset stTable
cld
.while ebx < MAX_SOCKET
lodsd
.if eax
invoke closesocket,eax
.endif
inc ebx
.endw
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
服务器端程序的结构如图16.11所示,对于WinSock库的装入和清除,何时去读取套接字,以及发送数据时的流量控制等方面,服务器端程序和工作站端程序的用法是相同的,所以WSAStartup,WSACleanup,recv和send等函数的使用方法和客户端程序大同小异。服务器端程序的特殊之处就在于如何监听和接受连接。
图16.11 TCP服务器端程序的常见结构
当服务器端程序准备在某个端口提供服务时(如图16.11中的②),程序首先使用socket函数创建一个用于监听的流套接字并将它设置为非阻塞模式,例子中对应的代码是这样的:
invoke socket,AF_INET,SOCK_STREAM,0
mov hSocket,eax
invoke WSAAsyncSelect,hSocket,hWinMain,WM_SOCKET,FD_ACCEPT
... ...
invoke bind,hSocket,addr @stSin,sizeof @stSin
由于用来监听的套接字以后并不会用来收发数据,也不会用它去主动连接到其他地方,所以不需要为它设置FD_CONNECT,FD_READ,FD_WRITE和FD_CLOSE等通知码,惟一需要设置的就是FD_ACCEPT通知码。然后,必须使用bind函数将套接字绑定到一个固定的端口上,例子程序中使用9 999号端口来当做服务端口。接下来,需要让套接字进入监听方式。
1. 监听进入的连接
使用listen可以让一个流套接字进入监听状态:
invoke listen,s,backlog
参数s指定套接字句柄,backlog参数是监听队列中允许保持的尚未处理的最大连接数量。
注意不要将backlog参数理解为最多能和多少个客户端相连接,它的真正含义是:当套接字监听到有客户端连接进来后,还需要调用accept函数来接受这个连接后,连接才真正被建立。在调用accept函数之前,连接请求将暂时被保留在队列中,如果这时有另一个客户端也发起连接的话,这个连接也将被保留在队列中,backlog参数指的就是这个队列的长度。实际上,如果程序能够在足够短的时间内响应进入的连接,那么即使将backlog参数指定为1,仍然不会丢失任何连接请求。
如果listen函数执行失败将返回SOCKET_ERROR。执行成功函数返回0,这时套接字就处于等待连接进入的状态了(如图16.11中的③)。
2. 接受客户端的连接请求
当有客户端向监听的套接字发起连接后,必须对监听的套接字调用accept函数以后,连接才最后被确定:
invoke accept,s,lpsockaddr,lpaddrlen
.if eax != INVALID_SOCKET
mov hNewSocket,eax
.endif
参数s指定监听中的套接字句柄,lpsockaddr指向一个缓冲区,函数会在这里返回一个sockaddr_in结构,结构中存放有连接请求方的IP地址和端口,lpaddrlen则指向一个双字变量,函数在这里放入返回到缓冲区中的结构长度。如果不需要得到对方的地址信息,可以将lpsockaddr和lpaddrlen参数都设置为NULL。
如果执行成功,函数将新建一个套接字,这个套接字和客户端相连接,程序可以使用它来和客户端之间收发数据,而原来在监听状态的套接字仍然保持监听状态,以便接受下一个连接的进入。
上页:第16章 TCP/IP和网络通信 · 16.3 TCP协议编程(5) 下页:第16章 TCP/IP和网络通信 · 16.3 TCP协议编程(7)