使用WINSOCK 实现Windows下网络通信
2013-04-29肖季烈肖维
肖季烈 肖维
摘要:本文介绍了Sockets通信原理,从程序员的角度着重讨论了Windows Sockets的功能扩充,并列举了运用 Windows Sockets实现网络实时通信的一个程序实例。
关键词:Windows Sockets 异步通信 阻塞
一、Socket网络编程原理
Socket是BSD UNIX提供的网络应用编程接口,它采用客户——服务器的通讯机制,使网络客户方和服务器方通过Socket实现网络之间的连接和数据交换。Socket提供了一系列的系统调用,可以实现TCP、UDP、ICMP和IP等多种网络协议之间的通讯。
Socket有三种主要类型:Stream sockets、Datagram sockets 和Raw sockets。Stream socket接口定义了一种可靠的面向连接的服务,实现了无差错、无重复的顺序数据传输,它通过内置的流量控制解决了数据的拥堵,应用程序可以发送任意长度的数据;Datagram socket接口定义了一种无连接的服务,数据通过相互独立的包进行传输,包的传输是无序的,但是不能保证包的正确率。包长度是有限的(隐含长度为8,192字节,最大长度可设为32,768字节);Raw socket接口允许对低层协议(如IP和ICMP)的直接存取,它主要用于实现新网络协议的测试等。
下面,我们通过一个面向连接的传输发生的典型情况来说明Socket网络通信的实现。
从图1中我们可以看出,客户和服务器的关系不是对称的,服务器首先启动,然后在某一时间 从
从图1中我们可以看出,客户与服务器的关系不是对称的,服务器首先启动,然后在某一时间启动客户与服务器的连接。服务器和客户开始都必须用调用socket()建立一个套接字(socket),然后服务器调用bind()将套接字与一个本地网络地址捆绑在一起,再用调用listen()使套接字处于一种被动的准备接收状态,同时规定它的请求队列长度,之后服务器就可以调用accept()来接收连接了。客户在建立套接字之后,就可以通过调用connect()和服务器建立连接。建立连接后,客户和服务器之间就可以通过连接发送和接收数据(调用read()和write())。最后,待数据传送结束,双方调用close()关闭套接字。
二、WINSOCK对Socket的扩充
BSD Socket支持阻塞(blocking)和非阻塞(non_blocking)两种工作方式。在阻塞方式下工作,connect()、accept()、read()和recv()等调用在执行时都处于阻塞状态,直到它成功或出错返回;在非阻塞方式下工作,这些调用是立即返回的,但是必须通过查询才能知道它们是否完成。
WINSOCK对BSD Socket的扩充主要基于消息、对网络事件的异步存取接口上。表1列出了WINSOCK扩充的函数功能。
从表1中我们可以看出,WINSOCK的扩充功能可以分为如下几类:
1.异步选择机制
异步选择函数WSAAsyncSelect()允许应用程序提名一个或多个感兴趣的网络事件,所有非阻塞的网络I/O例程(如send()和resv()),不管它是已经使用,还是即将使用,都可作为WSAAsyncSelect()函数选择的候选。当被提名的网络事件发生时,Windows应用程序的窗口函数将收到一个消息,消息附带的参数指示被提名过的某一网络事件。
2.异步请求例程
异步请求例程允许应用程序用异步方式获取请求的信息,如WSAAsyncGetXByY()类函数允许用户请求异步服务,这些功能在使用标准Berkeley函数时是阻塞的。
3.阻塞处理方法
WINSOCK在调用处于阻塞时会进入一个叫“Hook”的例程,它负责处理Windows消息,使得Windows的消息循环能够继续。WINSOCK还提供了两个函数[WSASetBlockingHook()和WSAUnhookBlockingHook()]让用户能够设置和取消自己的阻塞处理例程。另外,函数WSAIsBlocking()可以检测调用是否阻塞,函数WSACancelBlockingCall()可以取消一个阻塞的调用。
4.出错处理
为了和以后的多线索环境(如Windows/NT)兼容,WINSOCK提供了两个出错处理函数WSAGetLastError()和WSASetLastError()来获取和设置本线索的最近错误号。
5.启动与终止
WINSOCK的应用程序在使用上述WINSOCK函数前,必须先调用WSAStartup()函数对Windows Sockets DLL进行初始化,以协商WINSOCK的版本支持,并分配必要的资源。在应用程序退出之前,应该先调用函数WSAClearnup()终止对Windows Sockets DLL的使用,并释放资源,将有利于下一次使用。
三、网络实时通信的实现
我们来设计一个简单的基于连接的点对点网络实时通信程序:服务器首先启动,它建立套接字之后等待客户的连接;客户在启动后建立套接字,然后和服务器建立连接;连接建立后,客户通过连接给服务器发送一段数据,服务器接收后又将它发送回来,客户再发送,如此循环,直至用户命令客户退出或网络出错;客户关闭连接和套接字后退出,服务器在检测到连接关闭后,关闭套接字自动结束。
我们的实例是UNIX下基于BSD Socket的服务器程序和Windows下基于WINSOCK的客户程序之间的通信。我们先来看客户程序,首先定义几个宏、菜单资源和部分全局变量:
程序1:部分Windows程序源代码(宏、菜单和变量)
#define USERPORT3333/* 用户定义端口号 */
#define IDM_START 101/* “启动”菜单项标志 */
#define IDM_EXIT102 /* “退出”菜单项标志 */
#define UM_SOCK WM_USER+0x100/* 用户定义网络消息 */
ClientMenu MENU/* 客户菜单 */
BEGIN
POPUP "&Server"
BEGIN
MENUITEM "&Start...", IDM_START
MENUITEM "S&top", IDM_STOP
END
END
#include
HANDLE hInst;
char server_address[256] = {0}; /* 服务器地址缓冲区 */
char buffer[1024]; /* 接收发送缓冲区 */
char FAR * lpBuffer = &buffer[0];
SOCKET s = 0; /* 套接字 */
struct sockaddr_in dst_addr;/* 目标地址 */
struct hostent *hostaddr; /* 主机地址 */
struct hostent hostnm;
int count = 0;/* 发送接收循环计数器 */
客户程序的窗口主函数很简单,它在注册窗口类、建立窗口后,只是给主窗口函数发送一个用户消息,然后就进入Windows消息处理循环。
程序2:部分Windows程序源代码(窗口主函数)
int PASCAL WinMain( HANDLE hInstance,HANDLE hPrevInstance,LPSTR lpCmdLine, int nCmdShow)
{
HWND hWnd;
MSG msg;
lstrcpy((LPSTR) server_address, lpCmdLine); /* 取主机名字 */
if (!hPrevInstance)
if (!InitApplication(hInstance))
return (FALSE);
hInst = hInstance;
hWnd = CreateWindow("ClientClass","Windows ECHO Client",
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,
CW_USEDEFAULT,CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
if (!hWnd)
return (FALSE);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
/* 给主窗口函数发送WM_USER消息 */
PostMessage(hWnd,WM_USER,(WPARAM) 0, (LPARAM) 0);
while (GetMessage(&msg, NULL, NULL, NULL)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (msg.wParam);
}
我们用最简单的语句编制一个UNIX下基于BSD SOCKET的服务器程序,它在建立连接后,只负责将收到的数据发回去,在连接断开后,服务器关闭套接字返回。
四、结束语
我们可以看出,WINSOCK提供的异步选择机制使Socket强大的网络编程功能能够在Windows下得到应用。相信随着INTERNET的推广,TCP/IP网络协议的广泛使用,使用WINSOCK编制Windows网络实时通信软件将能得到较大的发展。
参考文献:
[1]孙义等,UNIX环境下的网络程序设计[M].北京:希望公司,1991.
[2]梁振军等,新编TCP/IP协议与计算机网络互联技术[M].北京:希望公司,1992.
(作者单位:江西省萍乡市萍乡广播电视大学)