计算机网络实验指导书
11、熊桂喜 等译。计算机网络。清华大学出版社2002年7月。
附录1 实验原理
1. 基本术语
客户机:Client,也称为工作站端或客户端,它是通信的发起端。 服务器:Server,它是通信的等待方。
图4.1显示了客户端和服务器端工作方式的不同。软件运行的界面如图4.2所示。
图4.1 TCP C/S模型
图4.2 软件运行界面
2. 聊天室客户端的实现
客户端程序包括程序文件client.asm和资源文件client.rc。资源文件确定了客户端的界面形式。程序文件的流程图如图图4.3所示。
①它先调用WSAStartup函数初始化WinSock库。
②当用户输入IP并点击连接按钮后,使用Socket函数创建流套接字,使用WSAAsyncSelect函数将通知消息自定义的ID(即:hSocket)绑定到窗口过程中,
第 31 页 共 64 页
计算机网络实验指导书
再使用connect函数去连接服务器。
③连接成功时,系统的FD_CONNECT会通知消息给客户端窗口。 ④连接成功后就可以传输数据了。
A. 系统原语recv收到数据后,发送系统通知消息FD_READ,客户端将从缓冲区szReadBuffer中读取数据。
B. 客户端需要发送数据时,从对话框中获取数据,写入缓冲区@szBuffer,调用系统原语send函数发送出数据,在收到系统FD_WRITE通知消息后,继续发送数据。
C. 在缓冲区满或发送失败时,客户端将重发并等待FD_WRITE通知消息的到来。
⑤连接失败则调用closesocket函数断开连接。
⑥用户在数据传输完毕,调用closesocket函数断开连接。 ⑦客户端收到FD_CLOSE通知消息时,连接已经断开。
⑧在客户端关闭时,调用WSACleanup函数卸载WinSock库。
图4.3 客户端程序结构
图4.4 服务器端程序结构
3. 聊天室服务器端的实现
服务器端程序包括程序文件server.asm和资源文件server.rc。资源文件确定了服务器端的界面形式。程序文件的流程图如图图4.4所示。
在WinSock库的加载和卸载,何时去读取套接字,发送数据时的流量控制等方面,服务器端和客户端的工作原理是一样的。
第 32 页 共 64 页
计算机网络实验指导书
当服务器端准备在端口9999提供服务时,需要创建流套接字,并设置为非阻塞模式,再使用bind函数将套接字hSocket和端口@stSin绑定。接着调用函数listen让套接字进入监听状态,并指定监听队列允许保持的尚未处理的最大连接数为5。
这样,主程序收到系统FD_ACCEPT通知消息后,调用accept接受客户机的连接请求,调用_AddClient将其加入到客户端列表中,并统计当前的用户数dwCount。收到系统FD_READ通知消息后,服务器会调用_RecvData处理接收到的TCP包。收到系统FD_CLOSE通知消息后,服务器会调用_RemoveClient从客户端列表中删除当前用户的sochet。
附录2 实验源程序
1、服务器端
; Server.asm
; 使用 TCP 协议的聊天室例子程序 —— 服务器端 ; 使用 nmake 或下列命令进行编译和链接: ; ml /c /coff Server.asm ; rc Server.rc
; Link /subsystem:windows Server.obj Server.res .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 ; equ 数据 DLG_MAIN equ 2000 IDC_INFO equ 2001 IDC_COUNT equ 2002
WM_SOCKET equ WM_USER + 100 TCP_PORT equ 9999
MAX_SOCKET equ 100 ;聊天室最大容量 ; 数据段 .data? hWinMain dd ? hSocket dd ? dwCount dd ?
第 33 页 共 64 页
计算机网络实验指导书
szReadBuffer db 32768 dup (?) szBuffer db 32768 dup (?) stTable dd MAX_SOCKET dup (?) .const
szErrBind db ‘无法绑定到TCP端口9999,请检查是否有其它程序在使用!’,0
szFormat db ‘【客户端#x】- %s’,0dh,0ah,0 ; 代码段 .code
; 在客户端列表中加上一个 socket _AddClient proc _hSocket invoke
WSAAsyncSelect,_hSocket,hWinMain,WM_SOCKET,FD_READ FD_CLOSE
xor ebx,ebx mov esi,offset stTable .while ebx < MAX_SOCKET .if ! dword ptr [esi] push _hSocket pop [esi] inc dwCount invoke
SetDlgItemInt,hWinMain,IDC_COUNT,dwCount,FALSE
ret .endif inc ebx add esi,4 .endw invoke closesocket,_hSocket ret
_AddClient endp
; 从客户端列表中去掉一个 socket _RemoveClient proc _hSocket xor ebx,ebx mov esi,offset stTable mov edi,_hSocket .while ebx < MAX_SOCKET .if [esi] == edi invoke closesocket,[esi] mov dword ptr [esi],0 dec dwCount
第 34 页 共 64 页
or 计算机网络实验指导书
invoke
SetDlgItemInt,hWinMain,IDC_COUNT,dwCount,FALSE
ret .endif inc ebx add esi,4 .endw ret
_RemoveClient endp ; 处理接收到的TCP包
_RecvData proc _hSocket local @dwRecv invoke RtlZeroMemory,addr szReadBuffer,sizeof szReadBuffer invoke recv,_hSocket,addr szReadBuffer,sizeof szReadBuffer,NULL .if eax != SOCKET_ERROR mov @dwRecv,eax invoke wsprintf,addr szBuffer,addr szFormat,\\ _hSocket,addr szReadBuffer ; 按照客户端列表逐一发送 invoke GetDlgItem,hWinMain,IDC_INFO mov ebx,eax invoke GetWindowTextLength,ebx invoke SendMessage,ebx,EM_SETSEL,eax,eax invoke SendMessage,ebx,EM_REPLACESEL,FALSE,addr szBuffer
mov esi,offset stTable xor ebx,ebx .while ebx < MAX_SOCKET mov edi,[esi] .if edi invoke lstrlen,addr szBuffer invoke send,edi,addr szBuffer,eax,0 .endif add esi,4 inc ebx .endw .endif ret
_RecvData endp
; 初始化 Socket,绑定到服务TCP端口并监听 _Init proc
第 35 页 共 64 页