第三章 WINDOWS SOCKETS 1.docx
《第三章 WINDOWS SOCKETS 1.docx》由会员分享,可在线阅读,更多相关《第三章 WINDOWS SOCKETS 1.docx(79页珍藏版)》请在冰豆网上搜索。
第三章WINDOWSSOCKETS1
第三章WindowsSockets1.1应用实例
在本章中,作者的实际工作为背景,给出了一个使用WindowsSockets1.1编程的具体例子。
并对这个例子作了详细的分析。
这个例子在Windows3.1、WindowsSockets1.1和BSDOSforPC2.0(BSDUNIX微机版)环境下调试通过。
3.1套接口网络编程原理
套接口有三种类型:
流式套接口,数据报套接口及原始套接口.
流式套接口定义了一种可靠的面向连接的服务,实现了无差错无重复的顺序数据传输.数据报套接口定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错.原始套接口允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议实现的测试等.
无连接服务器一般都是面向事务处理的,一个请求一个应答就完成了客户程序与服务程序之间的相互作用。
若使用无连接的套接口编程,程序的流程可以用图3-1表示。
面向连接服务器处理的请求往往比较复杂,不是一来一去的请求应答所能解决的,而且往往是并发服务器。
使用面向连接的套接口编程,可以通过图3-1来表示:
其时序。
套接口工作过程如下:
服务器首先启动,通过调用socket()建立一个套接口,然后调用bind()将该套接口和本地网络地址联系在一起,再调用listen()使套接口做好侦听的准备,并规定它的请求队列的长度,之后就调用accept()来接收连接.客户在建立套接口后就可调用connect()和服务器建立连接.连接一旦建立,客户机和服务器之间就可以通过调用read()和write()来发送和接收数据.最后,待数据传送结束后,双方调用close()关闭套接口.
3.2WindowsSockets编程原理
由于Windows的基于消息的特点,WINSOCK和BSD套接口相比,有如下一些新的扩充:
1.异步选择机制
异步选择函数WSAAsyncSelect()允许应用程序提名一个或多个感兴趣的网络事件,如FD_READ,FD_WRITE,FD_CONNECT,FD_ACCEPT等等代表的网络事件.当被提名的网络事件发生时,Windows应用程序的窗口函数将收到一个消息.这样就可以实现事件驱动了.
2.异步请求函数
异步请求函数允许应用程序用异步方式获得请求的信息,如WSAAsyncGetXByY()类函数.这些函数是对BSD标准函数的扩充.函数WSACancelAsyncRequest()允许用户中止一个正在执行的异步请求.
3.阻塞处理方法
WINSOCK提供了"钩子函数"负责处理Windows消息,使Windows的消息循环能够继续.WINSOCK提供了两个函数(WSASetBlockingHook()和WSAUnhookBlockingHook())让应用程序设置或取消自己的"钩子函数".函数WSAIsBlocking()可以检测是否阻塞,函数WSACancelBlockingCall()可以取消一个阻塞的调用.
4.错误处理
WINSOCK提供了两个WSAGetLastError()和WSASetLastError()来获取和设置最近错误号.
5.启动和终止
由于WindowsSockets的服务是以动态连接库WINSOCK.DLL形式实现的,所以必须要先调用WSAStartup()函数对WindowsSocketsDLL进行初始化,协商WINSOCK的版本支持,并分配必要的资源.在应用程序关闭套接口后,还应调用WSACleanup()终止对WindowsSocketsDLL的使用,并释放资源,以备下一次使用.
在这些函数中,实现Windows网络实时通信的关键是异步选择函数WSAAsyncSelect()的使用.用法及详细说明参见第5.3.7.
3.3WindowsSockets与UNIX套接口编程实例
下面是一个简单的基于连接的点对点实时通信程序.它由两部分组成,服务器在主机UNIX下直接运行,客户机在Windows下运行.
3.3.1SERVER介绍
由于SERVER是在UNIX下运行的,它对套接口的使用都是BSD的标准函数,程序也比较简单,只有一段程序,下面简要解释一下.
首先,建立自己的套接口.在互连网的进程通信中,全局标识一个进程需要一个被称为"半相关"的三元组(协议,本地主机地址,本地端口号)来描述,而一个完整的进程通信实例则需要一个被称为"相关"的五元组(协议,本地主机地址,本地端口号,远端主机地址,远端端口号)来描述.
s=socket(AF_INET,SOCK_STREAM,0)
该函数建立指定地址格式,数据类型和协议下的套接口,地址格式为AF_INET(唯一支持的格式),数据类型SOCK_STREAM表示建立流式套接口,参数三为0,即协议缺省.
bind(s,(structsockaddr*)&server,sizeof(server))
该函数将建立服务器本地的半相关,其中,server是sockaddr_in结构,其成员描述了本地端口号和本地主机地址,经过bind()将服务器进程在网上标识出来.
然后,建立连接.先是调用listen()函数表示开始侦听.再通过accept()调用等待接收连接.
listen(s,1)表示连接请求队列长度为1,即只允许有一个请求,若有多个请求,则出现错误,给出错误代码WSAECONNREFUSED.
ns=accept(s,(structsockaddr*)&client,&namelen))
accept()阻塞(缺省)等待请求队列中的请求,一旦有连接请求来,该函数就建立一个和s有相同属性的新的套接口.client也是一个sockaddr_in结构,连接建立时填入请求连接的套接口的半相关信息.
接下来,就可以接收和发送数据了.
recv(ns,buf,1024,0)
send(ns,buf,pktlen,0)
上面两个函数分别负责接收和发送数据,recv从ns(建立连接的套接口)接收数据放入buf中,send则将buf中数据发送给ns.至于第四个参数,表示该函数调用方式,可选择MSG_DONTROUTE和MSG_OOB,0表示缺省.
最后,关闭套接口.
close(ns);
close(s);
3.3.2CLIENT介绍
客户端是在Windows上运行的,使用了一些WindowsSockets的扩展函数,稍微复杂一些.包括了.RC和.C两个文件,其中的主窗口函数ClientProc()是程序的主要部分,下面简单解释一下.
首先,是在WinMain()中建立好窗口后,即向主窗口函数发一条自定义的WM_USER消息,做相关的准备工作.在主窗口函数中,一接收到WM_USER消息,首先调用WSAStartup()函数初始化WindowsSocketsDLL,并检查版本号.如下:
Status=WSAStartup(VersionReqd,lpmyWSAData);
其中,VersionReqd描述了WINSOCK的版本(这里为1.1版),lpmyWSAData指向一个WSADATA结构,该结构描述了WindowsSockets的实现细节.
WSAStartup()之后,进程通过主机名(运行时命令行参数传入)获取主机地址,如下:
hostaddr=gethostbyname(server_address);
hostaddr指向hostent结构,内容参见5.2.1.
然后,进程就不断地消息循环,等待用户通过菜单选择"启动".这时,通过调用Client()来启动套接口.在Client()中,首先也是调用socket()来建立套接口.如下:
if((s=socket(AF_INET,SOCK_STREAM,0))==INVALID_SOCKET)
{
AlertUser(hWnd,"SocketFailed");
return(FALSE);
}
紧接着,调用WSAAsyncSelect()函数提名FD_CONNECT网络事件,如下:
if(!
SetSelect(hWnd,FD_CONNECT))
return(FALSE);
SetSelect()主要就是调用WSAASyncSelect(),让WindowsSocketsDLL在侦测到连接建立时,就发送一条UM_SOCK的自定义消息,使消息循环继续下去.如下:
BOOLSetSelect(HWNDhWnd,longlEvent)
{
if(WSAAsyncSelect(s,hWnd,UM_SOCK,lEvent)==SOCKET_ERROR)
{
AlertUser(hWnd,"WSAAsyncSelectFailure.");
return(FALSE);
}
return(TRUE);
}
为建立连接,必须马上调用connect()如下,由于先调用了WSAASyncSelect(),connect()便是非阻塞调用.进程发出连接请求后就不管了,当连接建立好后,WINSOCKDLL自动发一条消息给主窗口函数,以使程序运行下去.
connect(s,(structsockaddrFAR*)&dst_addr,sizeof(dst_addr));
窗口函数在收到UM_SOCK消息后,判断是由哪个网络事件引起的,第一次,必然是由连接事件引起的,这样,就会执行相应的程序段,同样调用SetSelect()来提名FD_WRITE事件.希望在套接口可发送数据时接到消息.在收到FD_WRITE消息时,先调用send()发送数据,再调用SetSelect()来提名FD_READ事件,希望在套接口可接收数据是接到消息.在收到FD_READ消息时,先调用recv()来接收数据再提名FD_WRITE事件,如此循环下去.直到发生连接关闭的事件FD_CLOSE,这时就调用WSAAsyncSelect(s,hWnd,0,0)来停止异步选择.在窗口函数接到WM_DESTROY消息时(即关闭窗口之前),先调用closesocket()(作用同UNIX中的close())来关闭套接口,再调用WSACleanup()终止WindowsSocketsDLL,并释放资源.
3.3.3源程序清单
程序1:
CLIENT.RC
ClientMenuMENU
BEGIN
POPUP"&Server"
BEGIN
MENUITEM"&Start...",101
MENUITEM"&Exit",102
END
END
程序2:
CLIENT.C
#defineUSERPORT10001
#defineIDM_START101
#defineIDM_EXIT102
#defineUM_SOCKWM_USER+0X100
#include
#include
#include
#include
#defineMAJOR_VERSION1
#defineMINOR_VERSION2
#defineWSA_MAKEWORD(x,y)((y)*256+(x))
HANDLEhInst;
charserver_address[256]={0};
charbuffer[1024];
charFAR*lpBuffer=&buffer[0];
SOCKETs=0;
structsockaddr_indst_addr;
structhostentfar*hostaddr;
structhostenthostnm;
structserventfar*sp;
intcount=0;
BOOLInitApplication(HINSTANCEhInstance);
longFARPASCALClientProc(HWNDhWnd,unsignedmessage,UINTwParam,LONGlParam);
voidAlertUser(HWNDhWnd,char*message);
BOOLClient(HWNDhWnd);
BOOLReceivePacket(HWNDhWnd);
BOOLSetSelect(HWNDhWnd,longlEvent);
BOOLSendPacket(HWNDhWnd,intlen);
intPASCALWinMain(HANDLEhInstance,HANDLEhPrevInstance,LPSTRlpCmdLine,intnCmdShow)
{
HWNDhWnd;
MSGmsg;
lstrcpy((LPSTR)server_address,lpCmdLine);
if(!
hPrevInstance)
if(!
InitApplication(hInstance))
return(FALSE);
hInst=hInstance;
hWnd=CreateWindow("ClientClass","WindowsECHOClient",WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInstance,NULL);
if(!
hWnd)
return(FALSE);
ShowWindow(hWnd,nCmdShow);
UpdateWindow(hWnd);
PostMessage(hWnd,WM_USER,(WPARAM)0,(LPARAM)0);
while(GetMessage(&msg,NULL,NULL,NULL))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return(msg.wParam);
}
BOOLInitApplication(HINSTANCEhInstance)
{
WNDCLASSWndClass;
char*szAppName="ClientClass";
//fillinwindowclassinformation
WndClass.lpszClassName=(LPSTR)szAppName;
WndClass.hInstance=hInstance;
WndClass.lpfnWndProc=ClientProc;
WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
WndClass.hIcon=LoadIcon(hInstance,NULL);
WndClass.lpszMenuName="ClientMenu";
WndClass.hbrBackground=GetStockObject(WHITE_BRUSH);
WndClass.style=CS_HREDRAW|CS_VREDRAW;
WndClass.cbClsExtra=0;
WndClass.cbWndExtra=0;
//registertheclass
if(!
RegisterClass(&WndClass))
return(FALSE);
return(TRUE);
}
longFARPASCALClientProc(HWNDhWnd,unsignedmessage,UINTwParam,LONGlParam)
{
intlength,i;
WSADATAwsaData;
intStatus;
switch(message)
{
caseWM_USER:
{
WORDwMajorVersion,wMinorVersion;
LPWSADATAlpmyWSAData;
WORDVersionReqd;
intret;
wMajorVersion=MAJOR_VERSION;
wMinorVersion=MINOR_VERSION;
VersionReqd=WSA_MAKEWORD(wMajorVersion,wMinorVersion);
lpmyWSAData=(LPWSADATA)malloc(sizeof(WSADATA));
Status=WSAStartup(VersionReqd,lpmyWSAData);
if(Status!
=0)
{
AlertUser(hWnd,"WSAStartup()failed\n");
PostQuitMessage(0);
}
hostaddr=gethostbyname(server_address);
if(hostaddr==NULL)
{
AlertUser(hWnd,"gethostbynameERROR!
\n");
WSACleanup();
PostQuitMessage(0);
}
_fmemcpy(&hostnm,hostaddr,sizeof(structhostent));
}
break;
caseWM_COMMAND:
switch(wParam)
{
caseIDM_START:
if(!
Client(hWnd))
{
closesocket(s);
AlertUser(hWnd,"StartFailed");
}
break;
caseIDM_EXIT:
//WSACleanup();
PostQuitMessage(0);
break;
}
break;
caseUM_SOCK:
switch(lParam)
{
caseFD_CONNECT:
if(!
SetSelect(hWnd,FD_WRITE))
closesocket(s);
break;
caseFD_READ:
if(!
ReceivePacket(hWnd))
{
AlertUser(hWnd,"ReceivePacketFailed.\n");
closesocket(s);
break;
}
if(!
SetSelect(hWnd,FD_WRITE))
closesocket(s);
break;
caseFD_WRITE:
for(i=0;i<1024;i++)
buffer[i]=(char)'A'+i%26;
length=1024;
if(!
SendPacket(hWnd,length))
{
AlertUser(hWnd,"PacketSendFailed!
\n");
closesocket(s);
break;
}
if(!
SetSelect(hWnd,FD_READ))
closesocket(s);
break;
caseFD_CLOSE:
if(WSAAsyncSelect(s,hWnd,0,0)==SOCKET_ERROR)
AlertUser(hWnd,"WSAAsyncSelectFailed.\n");
break;
default:
if(WSAGETSELECTERROR(lParam)!
=0)
{
AlertUser(hWnd,"SocketReportFailure.");
closesocket(s);
break;
}
break;
}
break;
caseWM_DESTROY:
closesocket(s);
WSACleanup();
PostQuitMessage(0);
break;
default:
return(DefWindowProc(hWnd,message,wParam,lParam));
}
return(NULL);
}
voidAlertUser(HWNDhWnd,char*message)
{
MessageBox(hWnd,(LPSTR)message,"Warning",MB_ICONEXCLAMATION);
return;
}
BOOLClient(HWNDhWnd)
{
memset(&dst_addr,'\0',sizeof(structsockaddr_in));
_fmemcpy((charFAR*)&dst_addr.sin_addr,(charFAR*)hostnm.h_addr,hostnm.h_length);
dst_addr.sin_family=hostnm.h_addrtype;
dst_addr.sin_port=htons(USERPORT);
if((s=socket(AF_INET,SOCK_STREAM,0))==INVALID_SOCKET)
{
AlertUser(hWnd,"SocketFailed");
return(FALSE);
}
if(!
SetSelect(hWnd,FD_CONNECT))
return(FALSE);
connect(s,(structsockaddrFAR*)&dst_addr,sizeof(dst_addr));
return(TRUE);
}
BOOLReceivePacket(HWNDhWnd)
{
HDChDc;
intlength;
inti1,i2,i3;
charline1[255],line2[255],line3[255];
count++;
if((length=recv(s,