⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 cwsbook.txt

📁 介绍window下socket编程的一本好书
💻 TXT
📖 第 1 页 / 共 5 页
字号:
			&& WSAGetLastError() == EWOULDBLOCK)
		   {...} 

	虽然为了兼容性原因,错误常量与4.3BSD所提供的一致;应用程序应该尽可能
地使用“WSA”系列错误代码定义。例如,一个更准确的上面程序片断的版本应该
是:
		r = recv(...);
		if (r == -1                                 /* 但请见下文 */
			&& WSAGetLastError() == WSAEWOULDBLOCK)
		   {...}

2.6.4 指针
	所有应用程序与Windows Sockets使用的指针都必须是FAR指针,为了方便应
用程序开发者使用,Windows Sockets规范定义了数据类型LPHOSTENT。

2.6.5 重命名的函数
	有两种原因Berkeley套接口中的函数必须重命名以避免与其他的API冲突:

2.6.5.1 close()和closesocket()
	在Berkeley套接口中,套接口出现的形式与标准文件描述字相同,所以close()
函数可以用来和关闭正规文件一样来关闭套接口。虽然在Windows Sockets API中,
没有任何规定阻碍Windows Sockets实现用文件句柄来标识套接口,但是也没有任何
规定要求这么做。套接口描述字并不认为是和正常文件句柄对应的,而且并不能认为
文件操作,例如read(),write()和close()在应用于套接口后不能保证正确工作。套接
口必须使用closesocket()例程来关闭,用close()例程来关闭套接口是不正确的,这样
做的效果对于Windows Sockets规范说来也是未知的。

2.6.5.2 ioctl()和iooctlsocket()
	许多C语言的运行时系统出于与Windows Sockets无关的目的使用ioctl()例程,
所以Windows Sockets定义ioctlsocket()例程。它被用于实现BSD中用ioctl()和fcntl()
实现的功能。

2.6.6 阻塞例程和EINPROGRESS宏
	虽然Windows Sockets支持关于套接口的阻塞操作,但是这种应用是被强烈反对
的.如果程序员被迫使用阻塞模式(例如一个准备移植的已有的程序),那么他应该
清楚地知道Windows Sockets中阻塞操作的语义。有关细节请参见4.1.1

2.6.7 Windows Sockets支持的最大套接口数目
	一个特定的Windows Sockets提供者所支持的套接口的最大数目是由实现确定
的。任何一个应用程序都不应假设某个待定数目的套接口可用。这一点在4.3.15 
WSAStartup()中会被重申。而且一个应用程序可以真正使用的套接口的数目和某一
特定的实现所支持的数目是完全无关的。
	一个Windows Sockets应用程序可以使用的套接口的最大数目是在编译时由常量
FD_SETSIZE决定的。这个常量在select()函数(参见4.1.18)中被用来组建fd_set
结构。在WINSOCK.H中缺省值是64。如果一个应用程序希望能够使用超过64个
套接口,则编程人员必须在每一个源文件包含WINSOCK.H前定义确切的FD_SET
值。有一种方法可以完成这项工作,就是在工程项目文件中的编译器选项上加入这
一定义。例如在使用Microsoft C时加入-D FD_SETSIZE=128作为编译命令的一个
命令行参数.要强调的是:FD_SET定义的值对Windows Sockets实现所支持的套接
口的数目并无任何影响。

2.6.8 头文件
为了方便基于Berkeley套接口的已有的源代码的移植,Windows Sockets支持许多
Berkeley头文件。这些Berkeley头文件被包含在WINSOCK.H中。所以一个Windows 
Sockets应用程序只需简单的包含WINSOCK.H就足够了(这也是一种被推荐使用的
方法)。

2.6.9 API调用失败时的返回值
	常量SOCKET_ERROR是被用来检查API调用失败的。虽然对这一常量的使用
并不是强制性的,但这是推荐的。如下的例子表明了如何使用SOCKET_ERROR常

		典型的BSD风格:
		r = recv(...);
		if (r == -1                 /* or r < 0 */
			&& errno == EWOULDBLOCK)
		   {...}

		更优良的风格:
		r = recv(...);
		if (r == SOCKET_ERROR
			&& WSAGetLastError == WSAEWOULDBLOCK)
		   {...}

2.6.10 原始套接口
	Windows Sockets规范并没有规定Windows Sockets DLL必须支持原始套接口-
用SOCK_RAW打开的套接口。然而Windows Sockets规范鼓励Windows Sockets DLL
提供原始套接口支持。一个Windows Sockets兼容的应用程序在希望使用原始套接口
时应该试图用socket()调用(参见5.1.23节)来打开套接口。如果这么做失败了,应
用程序则应该使用其他类型的套接口或向用户报告错误。

2.7 在多线程Windows版本中的Windows Sockets
	Windows Sockets接口被设计成既能够在单线程的Windows版本(例如Windows 
3.1)又能够在占先的多线程Windows版本(例如Windows NT)中使用,在多线程
环境中,套接口接口基本上是不变的。但多线程应用程序的作者必须知道,在线程
之间同步对套接口的使用是应用程序的责任,而不是Windows Sockets实现的责任。
这一点在其他形式的I/O中管理,例如文件I/O中是一样的。没有对套接口调用进行
同步将导致不可预测的结果。例如,如果有两个线程同时调用同一套接口进行
send(),那么数据发送的先后顺序就无法保证了。
	在一个线程中关闭一个未完成的阻塞的套接口将会导致另一个线程使用同一套
接口的阻塞调用出错(WSAEINTER)返回,就象操作被取消一样。这也同样适用
于某一个select()调用未完成时,应用程序关闭了其中的一个被选择的套接口。
	在占先的多线程Windows版本中,并没有缺省的阻塞钩子函数。这是因为如果
一个单一的应用程序在等待某一操作结束时并不会调用PeekMessage()或
GetMessage()这些会使应用程序产生一个非占先窗口的函数。因此机器在这种情况下
不会被阻塞。然而,为了向后的兼容性,在多线程Windows版本中,
WSASetBlockingHook()函数也被实现了。任何使用缺省阻塞钩子的应用程序可以安
装它们自己的阻塞钩子函数来覆盖缺省的阻塞钩子函数。


第三章 Windows Sockets 1.1应用实例
    在本章中,作者的实际工作为背景,给出了一个使用Windows Sockets 1.1编程
的具体例子。并对这个例子作了详细的分析。这个例子在Windows 3.1、Windows 
Sockets 1.1和BSD OS for PC 2.0(BSD UNIX微机版)环境下调试通过。

3.1 套接口网络编程原理
	套接口有三种类型:流式套接口,数据报套接口及原始套接口.
    流式套接口定义了一种可靠的面向连接的服务,实现了无差错无重复的顺序数
据传输.数据报套接口定义了一种无连接的服务,数据通过相互独立的报文进行传输,
是无序的,并且不保证可靠,无差错.原始套接口允许对低层协议如IP或ICMP直接访
问,主要用于新的网络协议实现的测试等.
    无连接服务器一般都是面向事务处理的,一个请求一个应答就完成了客户程序
与服务程序之间的相互作用。若使用无连接的套接口编程,程序的流程可以用图3-
1表示。
 

    面向连接服务器处理的请求往往比较复杂,不是一来一去的请求应答所能解决
的,而且往往是并发服务器。使用面向连接的套接口编程,可以通过图3-1来表示:其
时序。
                      
 

	套接口工作过程如下:服务器首先启动,通过调用socket()建立一个套接口,然后
调用bind()将该套接口和本地网络地址联系在一起,再调用listen()使套接口做好侦听
的准备,并规定它的请求队列的长度,之后就调用accept()来接收连接.客户在建立套
接口后就可调用connect()和服务器建立连接.连接一旦建立,客户机和服务器之间就
可以通过调用read()和write()来发送和接收数据.最后,待数据传送结束后,双方调用
close()关闭套接口.

3.2 Windows Sockets编程原理
	由于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.启动和终止
		由于Windows Sockets的服务是以动态连接库WINSOCK.DLL形式实现的,
所以必须要先调用WSAStartup()函数对Windows Sockets DLL进行初始化,协商
WINSOCK的版本支持,并分配必要的资源.在应用程序关闭套接口后,还应调用
WSACleanup()终止对Windows Sockets DLL的使用,并释放资源,以备下一次使用.
	在这些函数中,实现Windows网络实时通信的关键是异步选择函数
WSAAsyncSelect()的使用. 用法及详细说明参见第5.3.7.

3.3 Windows Sockets与UNIX套接口编程实例
	下面是一个简单的基于连接的点对点实时通信程序.它由两部分组成,服务器在
主机UNIX下直接运行, 客户机在Windows下运行.

3.3.1 SERVER介绍
	由于SERVER是在UNIX下运行的,它对套接口的使用都是BSD的标准函数,程序
也比较简单, 只有一段程序,下面简要解释一下.
	首先,建立自己的套接口.在互连网的进程通信中,全局标识一个进程需要一个被
称为"半相关"的三元组(协议,本地主机地址,本地端口号)来描述,而一个完整的进程
通信实例则需要一个被称为"相关"的五元组(协议, 本地主机地址,本地端口号,远端
主机地址,远端端口号)来描述.
	s=socket(AF_INET, SOCK_STREAM, 0)
	该函数建立指定地址格式,数据类型和协议下的套接口,地址格式为AF_INET(唯
一支持的格式),数据类型SOCK_STREAM表示建立流式套接口,参数三为0,即协议
缺省.
	bind(s, (struct sockaddr *)&server, sizeof(server))
	该函数将建立服务器本地的半相关,其中,server是sockaddr_in结构,其成员描述
了本地端口号和本地主机地址,经过bind()将服务器进程在网上标识出来.
	然后,建立连接.先是调用listen()函数表示开始侦听.再通过accept()调用等待接
收连接.
	listen(s,1)表示连接请求队列长度为1,即只允许有一个请求,若有多个请求,则出
现错误,给出错误代码WSAECONNREFUSED.
	ns = accept(s, (struct sockaddr *)&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.2 CLIENT介绍
	客户端是在Windows上运行的,使用了一些Windows Sockets的扩展函数,稍微
复杂一些.包括了.RC和.C两个文件,其中的主窗口函数ClientProc()是程序的主要部
分,下面简单解释一下.
	首先,是在WinMain()中建立好窗口后,即向主窗口函数发一条自定义的
WM_USER消息, 做相关的准备工作.在主窗口函数中,一接收到WM_USER消息,首
先调用WSAStartup()函数初始化Windows Sockets DLL,并检查版本号.如下:
	Status = WSAStartup(VersionReqd, lpmyWSAData);
	其中,VersionReqd描述了WINSOCK的版本(这里为1.1版),lpmyWSAData指向
一个WSADATA结构,该结构描述了Windows Sockets的实现细节.
	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, "Socket Failed");
		return (FALSE);
	}
	紧接着,调用WSAAsyncSelect()函数提名FD_CONNECT网络事件,如下:		
	if (!SetSelect(hWnd, FD_CONNECT))
		return (FALSE);
	SetSelect()主要就是调用WSAASyncSelect(),让Windows Sockets DLL在侦测
到连接建立时,就发送一条UM_SOCK的自定义消息,使消息循环继续下去.如下:
	BOOL SetSelect(HWND hWnd, long lEvent)
	{
		if (WSAAsyncSelect(s, hWnd, UM_SOCK, lEvent) == SOCKET_ERROR)
		{
			AlertUser(hWnd, "WSAAsyncSelect Failure.");
			return (FALSE);
		}
		return (TRUE);
	}
	为建立连接,必须马上调用connect()如下,由于先调用了
WSAASyncSelect(),connect()便是非阻塞调用.进程发出连接请求后就不管了,当连
接建立好后,WINSOCK DLL自动发一条消息给主窗口函数,以使程序运行下去.
	connect(s, (struct sockaddr FAR *)&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()终止Windows Sockets DLL,并释放资源.
3.3.3 源程序清单
程序1:CLIENT.RC
ClientMenu MENU
BEGIN
	POPUP "&Server"
	BEGIN
		MENUITEM "&Start...", 101
		MENUITEM "&Exit",  102
	END
END

程序2:CLIENT.C
#define USERPORT 10001
#define IDM_START 101
#define IDM_EXIT  102
#define UM_SOCK WM_USER + 0X100

#include <alloc.h>
#include <mem.h>
#include <windows.h>
#include <winsock.h>
#define MAJOR_VERSION 1
#define MINOR_VERSION 2
#define WSA_MAKEWORD(x,y)  ((y)*256+(x))

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 far *hostaddr;
struct hostent hostnm;
struct servent far *sp;
int count = 0;

BOOL InitApplication(HINSTANCE hInstance);
long FAR PASCAL ClientProc(HWND hWnd, unsigned message, UINT wParam, LONG 
lParam);
void AlertUser(HWND hWnd, char *message);

BOOL Client(HWND hWnd);
BOOL ReceivePacket(HWND hWnd);
BOOL SetSelect(HWND hWnd, long lEvent);
BOOL SendPacket(HWND hWnd, int len);

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);

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -