第3章 WinSock 编程 3.1 Windows Sockets 概述 3.2 WinSock 库函数 3.3 网络应用程序的运行环境
3.1 Windows Sockets概述 P53 Microsoft公司以Berkeley Sockets规范为范例,定义了Windows Socktes规范,简称Winsock规范。这是Windows操作系统环境下的套接字网络应用程序编程接口(API)。 Winsock已经成为Windows网络编程事实上的标准。我们可以使用Winsock在Internet上传输数据和交换信息,而不必关心网络连接的细节。
3.1 Windows Sockets概述 图3.1 网络应用进程利用Windock进行通信
3.1 Windows Sockets概述 1.Windows Sockets 1.1版本 Windows Sockets 规范是一套开放的、支持多种协议的Windows下的网络编程接口。从1991年到1995年,从1.0版发展到2.0.8版。Winsock1.1和Winsock2.0是Winsock的两个主要版本。 1.Windows Sockets 1.1版本 在Winsock.h包含文件中,定义了所有WinSock 1.1版本库函数的语法、相关的符号常量和数据结构。库函数的实现在WINSOCK.DLL动态链接库文件中。
Windows Sockets规范 (1)WinSock 1.1 全面继承了Berkeley Sockets规范,见P54表 3-1 (2)数据库函数 表3-1列出了Winsock规范定义的数据库查询例程。其中六个采用getXbyY()的形式,大多要借助网络上的数据库来获得信息 (3)WinSock 1.1 扩充了Berkeley Sockets规范 针对微软 Windows的特点,WinSock 1.1定义了一批新的库函数,提供了对于消息驱动机制的支持,有效地利用Windows多任务多线程的机制。见表3-1 (4)WinSock 1.1只支持TCP/IP协议栈
Windows Sockets规范 2.WinSock 2.0 WinSock 2.0在源码和二进制代码方面与WinSock 1.1兼容,WinSock 2.0增强了许多功能。 (1)支持多种协议 (2)引入了重叠I/O的概念 (3)使用事件对象异步通知 (4)服务的质量(QOS) (5)套接口组 (6)扩展的字节顺序转换例程 (7)分散/聚集方式I/O (8)新增了许多函数。
WinSock1.1和WinSock2.0的头文件 在使用WinSock 1.1时,需要引用头文件winsock.h和库文件wsock32.lib,代码如下: #include<winsock.h> #pragma comment(lib, "wsock32.lib") 如果使用WinSock 2.0实现网络通信的功能,则需要引用头文件winsock2.h和库文件ws2_32.lib,代码如下: #include<winsock2.h> #pragma comment(lib, "ws2_32.lib") C:\Program Files\Windows Kits\8.0\Include\um\WinSocket2.h C:\Program Files\Microsoft SDKs\Windows\v7.0\Include\WinSocket2.h
Windows Sockets规范 3.WinSock 1.1中的阻塞问题 阻塞是在把应用程序从Berkeley套接口环境中移植到Windows环境中的一个主要焦点。阻塞是指唤起一个函数,该函数直到相关操作完成时才返回。 在Berkeley套接口模型中,一个套接字的操作的缺省行为是阻塞方式的,除非程序员显式地请求该操作为非阻塞方式。 在Windows环境下,我们推荐程序员在尽可能的情况下使用非阻塞方式(异步方式)的操作。因为非阻塞方式的操作能够更好地在多进程的Windows环境下工作。
WinSock规范与Berkeley套接字的区别 1.套接口数据类型和该类型的错误返回值 在UNIX中,包括套接口句柄在内的所有句柄,都是非负的短整数。 在WinSock规范中定义了一个新的数据类型,称作SOCKET,用来代表套接字描述符。 typedef u_int SOCKET; SOCKET可以取从0到INVALID_SOCKET-1之间的任意值。 2.select()函数和FD_*宏 在Winsock中,使用select()函数时,应用程序应坚持用FD_XXX宏来设置,初始化,清除和检查fd_set结构。
WinSock规范与Berkeley套接字的区别 3.错误代码的获得 在UNIX 套接字规范中,如果函数执行时发生了错误,会把错误代码放到errno或h_errno变量中。 在Winsock中,错误代码可以使用WSAGetLastError()调用得到。 4.指针 所有应用程序与Windows Sockets使用的指针都必须是FAR指针。
远指针与近指针区别 近指针是16位的指针,它只表示段内的偏移地址,因而只能对64k字节数据段内地址进行存取。 如 char near *p; p=(char near *)0xffff; 远指针是32位指针,它表示段地址:偏移地址,如定义远程指针p指向B500段的2号地址,即B500:0002,则可写作: char far *p; p=(char far *)0xB5000002; 因此,远指针可以进行跨段寻址,可以访问整个内存的地址。
WinSock规范与Berkeley套接字的区别 5.重命名的函数 (1) close()改变为closesocket() (2) ioctl()改变为ioctlsocket() 6.Winsock支持的最大套接字数目 在WINSOCK.H中缺省值是64,在编译时由常量FD_SETSIZE决定。 7.头文件 Berkeley头文件被包含在WINSOCK.H中。一个Windows Sockets应用程序只需包含WinSock.h就够了。 8.Winsock规范对于消息驱动机制的支持 体现在异步选择机制、异步请求函数、阻塞处理方法、错误处理、启动和终止等方面。
WinSock的主要库函数及其简要说明 P54 表3-1 主要函数 socket() ----创建一个套接字,返回套接字标识符。 bind()----把套接字绑定到指定的网络地址上 listen()----启动指定的套接字,监听到来的连接请求。 accept()----接收一个连接请求,并新建一个套接字,原来的套接字返回监听状态。 connect()----请求将本地套接字连接到一个指定的远方套接字上。
WinSock的主要库函数及其简要说明 P54 表3-1 主要函数 send()----向一个已经与对方建立的套接字发送数据。 sendto()----向一个未与对方建立的套接字发送数据,并指定对方的网络地址。 recv()----从一个已经与对方建立的套接字接收数据。 recvfrom()----从一个未与对方建立的套接字接收数据,并返回对方的网络地址。 shutdown()----有选择地关闭套接字的全双工连接。 closesocket()----关闭套接字,释放响应的资源。
WinSock的主要库函数及其简要说明 P54 表3-1 辅助函数 htonl()----把32位无符号的长整形数据从主机字节顺序转换到网络字节顺序。 htons()----把16位无符号的长整形数据从主机字节顺序转换到网络字节顺序。 ntohl()----把32位数据从网络字节顺序转换到主机字节顺序 ntohs()----把16位数据从网络字节顺序转换到主机字节顺序 inet_addr()----把一个点分十进制的IP地址转换成长整形地址 inet_ntoa()----把一个长整形地址转换成点分十进制的IP地址 getpeername()----获得套接字连接上对方的网络地址 getsockname()----获得指定套接字的网络地址
WinSock的主要库函数及其简要说明 P54 控制函数 getsockopt()----获得指定套接字的属性选项 setsockopt()----设置与指定套接字相关的属性选项 ioctlsocket()----为套接字提供控制 slect()----执行同步I/O多路复用
WinSock的主要库函数及其简要说明 P54 数据库查询函数 gethostname()----返回本地计算机的主机名 gethostbyname()----返回对应于给定主机名的主机信息 gethostbyaddr()----根据一个IP地址取回响应的主机信息 getservbyname()----返回对应于给定服务器名和协议名的相关服务信息 getservbyport()----返回对应于给定端口号和协议名的相关服务信息 getportbyname()----返回对应于给定协议名的相关协议信息 getportbynumber()----返回对应于给定协议号名的相关协议信息
WinSock的主要库函数及其简要说明 P54 注册与注销函数 WSAStartup()----初始化底层的Windows Sockets DLL WSACleanup()----从底层的Windows Sockets DLL 撤销注册
WinSock的主要库函数及其简要说明 P54 异步执行的数据库查询函数 WASAsyncGetHostByName()----GetHostByName()的异步版本 WASAsyncGetHostByAddr()----GetHostByAddr()的异步版本 WASAsyncGetServByName()----GetServByName()的异步版本 WASAsyncGetServByPort()----GetServByPort()的异步版本 WASAsyncGetPortByName()----GetPortByName()的异步版本 WASAsyncGetPortByNumber()----GetPortByNumber()的异步版本
WinSock的主要库函数及其简要说明 P54 异步选择机制的相关函数 WSAASyncSelect()----Select()的异步版本 WSACancelAsyncRequest()----取消一个未完成的WSAAsyncGetXByY()函数的实例 WSACancelBlockingCall()----取消未完成的阻塞的API调用 WSAIsBlock()----确定线程是否被一个调用阻塞 错误处理的相关函数 WSAGetLastError()----得到最近一个WinSock调用出错的信息 WSASetLasterror()----设置下一次WSAGetLastError()返回的错误信息
服务器程序在每一步骤中调用的Socket函数 (1)调用WSAStartup( )函数加载Windows Sockets动态库。 (2)然后调用socket( )函数创建一个流式套接字,返回套接字号s。 (3)调用bind( )函数将套接字s绑定到一个已知的地址,通常为本地IP地址。 (4)调用listen( )函数将套接字s设置为监听模式,准备好接收来自各个客户机的连接请求。 (5)调用accept( )函数接受客户端的连接请求。 (6)如果接收到客户端的请求,则accept( )函数返回,得到新的套接字ns。
服务器程序在每一步骤中调用的Socket函数 (7)调用recv( )函数接收来自客户端的数据,调用send( )函数向客户端发送数据。 (8)与客户端的通信结束后,服务器程序可以调用shutdown( )函数通知对方不再发送或接收数据,也可以由客户端程序断开连接。断开连接后,服务器进程调用closesocket( )函数关闭套接字ns。此后服务器程序返回第4步,继续等待客户端进程的连接。 (9)如果要退出服务器程序,则调用closesocket( )函数关闭最初的套接字s。
客户端程序在每一步骤中调用的Socket函数 (1)调用WSAStartup( )函数加载Windows Sockets动态库。 (2)然后调用socket( )函数创建一个流式套接字,返回套接字号s。 (3)调用connect( )函数将套接字s连接到服务器。 (4)调用send( )函数向服务器发送数据,调用recv()函数接收来自服务器的数据。 (5)与服务器的通信结束后,客户端程序可以调用shutdown( )函数通知对方不再发送或接收数据,也可以由服务器程序断开连接。断开连接后,客户端进程调用closesocket( )函数关闭套接字。
服务器与客户端之间的流程 调用WSAStartup( )初始化 调用socket( )创建一个流式套接字,返回套接字号s。 调用bind( )将套接字s绑定到一个IP地址。 调用listen( )将套接字s设置为监听模式。 调用accept( )接受客户端的连接请求。 accept( )函数返回,得到新的套接字ns。 调用recv( )接收客户端的数据 调用send( )向客户端发送数据。 通信结束后调用shutdown( )通知对方不再发送 调用closesocket( )函数关闭套接字ns。 调用WSAStartup( )初始化 调用socket( )函数创建一个流式套接字,返回套接字号s。 调用connect( )函数将套接字s连接到服务器。 调用send( )函数向服务器发送数据 调用recv()函数接收来自服务器的数据。 通信结束后调用shutdown( )通知对方不再发送 调用closesocket( )函数关闭套接字。
3.2.1 Winsock的注册与注销 P55 1.初始化函数WSAStartup() Winsock 应用程序要做的第一件事,就是必须首先调用WSAStartup()函数对Winsock进行初始化,加载Winsock.DLL库。初始化也称为注册。注册成功后,才能调用其他的Winsock API函数。 WSAStartup()函数的调用格式 int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData ); 2.注销函数WSACleanup() 当程序使用完Winsock.DLL提供的服务后,应用程序必须调用WSACleanup()函数,来解除与Winsock.DLL库的绑定,中止使用,释放Winsock实现分配给应用程序的系统资源。
WSAStartup()函数初始化的过程 图3.2 在一台计算机中,使用同一WinSock实现的多个网络应用程序
WSAStartup()函数初始化的过程 P56 首先,检查系统中是否有WinSock实例。 WinSock实例保存在WinSock.dll文件中,如果有此文件,就会发出一个LoadLibrary()调用,建立数据结构。 其次,检查找到的WinSoct实例是否可用,主要是确认WinSock的版本号, WinSock.dll的版本能否满足要求。 再者,建立WinSoct实现与应用程序的联系,将WinSock.dll库绑定到应用程序,应用程序就可以调用Socket库中的其它函数了。 最后,函数成功返回时,在lpWSAData所指向的WSADATA结构中返回许多信息。 wHighVersion成员变量返回WinSock.dll支持的最高版本, wVersion成员变量返回应用程序所需的最小版本。
3.2.1 Winsock 的注册和注销 (3) WSADATA结构在 WinSock.h的定义 P57 #define WSADESCRIPTION_LEN 256 #define WSASYS_STATUS_LEN 128 typedef struct WSAData { WORD wVersion; WORD wHighVersion; char szDescription[WSADESCRIPTION_LEN+1]; char szSystemStatus[WSASYS_STATUS_LEN+1]; unsigned short iMaxSockets; unsigned short iMaxUdpDg; char * lpVendorInfo; } WSADATA;
结构体WSADATA的各字段说明 P57 字段 含义 wVersion Windows Sockets DLL期望调用者使用的Windows Sockets规范的版本,为WORD类型。 wHighVersion Windows Sockets DLL可以支持的Windows Sockets规范 的最高版本 szDescription Windows Sockets DLL将对Windows Sockets实现的开发 商描述复制到该字符串中,最多256个字符 szSystemStatus Windows Sockets DLL将有关状态或配置信息复制到该 字符串 iMaxSockets 单个进程可以打开的最大套接字数量。Windows Sockets可以提供一个全局的套接口,为每个进程分配 套接字资源。 iMaxUdpDg Windows Sockets应用程序能够发送或接收的最大数据 包大小,单位为字节。 lpVendorInfo 指向开发商数据结构的指针
结构体WSADATA在WinSock2.h中的定义 typedef struct WSAData { WORD wVersion; WORD wHighVersion; #ifdef _WIN64 unsigned short iMaxSockets; unsigned short iMaxUdpDg; char FAR * lpVendorInfo; char szDescription[WSADESCRIPTION_LEN+1]; char szSystemStatus[WSASYS_STATUS_LEN+1]; #else unsigned short iMaxSockets; unsigned short iMaxUdpDg; char FAR * lpVendorInfo; #endif } WSADATA, FAR * LPWSADATA; 结构体WSAData用于存储调用WSAStartup()函数后返回的Windows Socket数据,类型 typedef 。在Winsock2.h中定义。
WSAStartup( )函数 WSAStartup( )函数用于初始化Windows Sockets,并返回WSADATA结构体。只有在调用WSAStartup( )之后,才能调用其它Windows Sockets API函数,实现网络通信。 int WSAStartup( IN WORD wVersionRequested, OUT LPWSADATA lpWSAData ); 参数说明如下: wVersionRequested,Windows Sockets DLL规定调用者可以使用的Windows Sockets规范的版本。可以使用MAKEWORD( )函数返回该值,例如MAKEWORD(2,2)。 lpWSAData,指向WSADATA结构体的指针,用于接收Windows Sockets执行的数据。
使用WinSock 2.2实现网络通信的应用程序框架 #include<winsock2.h> #pragma comment(lib, "ws2_32.lib") // 主函数 int _tmain(int argc, _TCHAR* argv[]) { // WSADATA 结构体主要包含了系统 //所支持的Winsock版本信息 WSADATA wsaData; //typedef类型 // 初始化Winsock 2.2 if( WSAStartup( MAKEWORD(2,2), &wsaData) != 0 ) printf( "WSAStartup 无法初始化!"); return 0; } // 使用WinSock实现网络通信 // ...... // 最后应该做一些清除工作 if( WSACleanup() == SOCKET_ERROR ) printf( "WSACleanup 出错!"); return 0; } 函数WSAStartup( )用于 初始化结构体WSAData, 在Winsocket2.h中定义。
【例】 通过一个控制台应用程序实例来演示初始化Windows Sockets并返回结果的方法。假定项目名称为WSAStartup,主程序WSAStartup.cpp的代码如下: #include "stdafx.h" #include<winsock2.h> #pragma comment(lib, "ws2_32.lib") #include <stdlib.h> int _tmain(int argc, _TCHAR* argv[]) { // WSADATA 结构体主要包含了系统所支持的Winsock版本信息 WSADATA wsaData; //typedef类型结构体可以这样用 // 初始化Winsock 2.2 if( WSAStartup( MAKEWORD(2,2), &wsaData) != 0 ) { printf( "WSAStartup 无法初始化!"); return 0; }
【例】 // 显示wsaData中的数据 printf("Version: %d.%d\n", LOBYTE(wsaData.wVersion), HIBYTE(wsaData.wVersion)); printf("High Version: %d.%d\n", LOBYTE(wsaData.wHighVersion), HIBYTE(wsaData.wHighVersion)); printf("Description: %s\n", wsaData.szDescription); printf("System Status: %s", wsaData.szSystemStatus); // 最后应该做一些清除工作 if( WSACleanup() == SOCKET_ERROR ) printf( "WSACleanup 出错!"); printf("\n\n"); system("pause"); return 0; }
【例】
3.2.1 Winsock 的注册和注销 (4) 初始化函数可能返回的错误代码 P57 WSASYSNOTREADY:网络通信依赖的网络子系统没准备好。 WSAVERNOTSUPPORTED:找不到所需的Winsock API相应的动态连接库。 WSAEINVAL:DLL不支持应用程序所需的Winsock版本。 WSAEINPROGRESS:正在执行一个阻塞的Winsock 1.1操作。 WSAEPROCLIM:已经达到Winsock支持的任务数上限。 WSAEFAULT:参数lpWSAData不是合法指针。
3.2.1 Winsock 的注册和注销 (5) 初始化Winsock的示例 P57 #include <winsock.h> // 对于Winsock 2.0,应包括 Winsock2.h文件 aa() { WORD wVersionRequested; // 应用程序所需的Winsock版本号 WSADATA wsaData; // 用来返回Winsock 实现的细节信息 Int err; // 出错代码 wVersionRequested = MAKEWORD(1,1); //生成版本号
3.2.1 Winsock 的注册和注销 // 调用初始化函数。 err = WSAStartup(wVersionRequested, &wsaData ); if (err!=0 ) { return;} // 通知用户找不到合适的DLL文件 // 确认返回的版本号是客户要求的1.1 if ( LOBYTE(wsaData.wVersion )!=1 || HYBYTE(wsaData.wVersion )!=1) { WSACleanup(); return; } // 至此,可以确认初始化成功,Winsock.DLL可用
3.2.1 Winsock 的注册和注销 2.注销函数WSACleanup() P58 当程序使用完Winsock.DLL提供的服务后,应用程序必须调用WSACleanup()函数,来解除与Winsock.DLL库的绑定,释放Winsock实现分配给应用程序的系统资源,中止对Windows Sockets DLL的使用。 (1) WSACleanup ( )函数的调用格式 int WSACleanup ( void ); (2) WSACleanup ( )函数的功能 完成了Windows Socket的使用之后,最后由WSACleanup ( )进行清除工作,终止Windows Socket在所有线程上的操作。
3.2.1 Winsock 的注册和注销 (3) WSACleanup ( )函数可能返回的错误代码 P58 WSANOTINITIALISED: 使用本函数前没进行WSAStartup() WSAENETDOWN: 检测到网络子系统故障 WSAEINPROGRESS: 一个阻塞的Windows Socket操作正在进行
3.2.2 Winsock的错误处理函数 P58 WinSock函数在执行时都有一个返回值,但只是简单的说明函数是否执行成功,并不能从返回值了解出错原因。WinSock专门提供了两个函数来解决这个问题。 1.WSAGetLastError()函数 函数格式: int WSAGetLastError ( void ); 本函数返回本线程进行的上一次Winsock函数调用时的错误代码。一般函数返回值是0表示函数调用成功,否则就要调用WSAGetLastError()函数取得错误代码,给用户以明确的错误提示信息。
3.2.2 Winsock的错误处理函数 2.WinSock规范预定义的错误代码 P59 在WinSock.h中,定义了所有的WinSock规范错误代码,都以WSAE作为前缀,见P59。大概分成几类: 常规的Microsoft C常量的Winsock定义 常规的Berkeley错误的的Winsock定义 扩展的WinSock错误常量定义 用于数据库查询类 主机找不到 不可恢复错误
3.2.2 Winsock的错误处理函数 三个常见的错误代码 P60 1)WSANOTINITIAISED: 在使用此函数前,没有成功的调用 WSAStartup()进行初始化 2)WSAENETDOWN: WinSock检测到网络子系统已经失效。 3)WSAEINPROGRESS: 正在运行一个WinSock调用,系统没时间对本函数处理。
3.2.2 Winsock的错误处理函数 3.WSASetLastError()函数 P60 此函数用于设置可以被WSAGetLastError()接受的错误代码。 void WSASetLastError ( int iError ); 本函数允许应用程序为当前线程设置错误代码,并可由后来的WSAGetLastError()调用返回。
3.2.3 主要的Winsock函数 P61 1.创建套接字socket() P61 (1) socket()函数的调用格式 SOCKET socket (int af, int type, int protocol); 参数 af: 指定套接字的通信域,即指定协议簇,用AF_INET。 参数type : 指定套接字的类型,如SOCKET_STREAM。 参数protocol : 指定套接字使用的协议,一般默认0。 返回值:如果创建成功,就创建了一个新的套接字,并返回其描述符;否则返回SOCKET_ERROR,表示套接字出错。 举例: // 创建一个流式套接字 SOCKET sockfd=socket( AF_INET, SOCK_STREAM, 0); //创建一个数据报套接字 SOCKET sockfd=socket( AF_INET, SOCK_DGRAM, 0);
3.2.3 主要的Winsock函数 P61 (2) socket()函数的功能 P61 根据指定的通信域、套接字类型、协议,创建一个新的套接字,为它分配所需资源,并返回套接字的描述符。创建套接字时,已默认定位在本机的IP地址和一个自动分配的TCP或UDP的自由端口号上,操作系统为此套接字分配了内存,建立了数据结构,各个选项设置为默认值。 套接字描述符是一个整数类型的值。 SOCK_STREAM类型的套接字提供面向连接的可靠的字节流,使用TCP协议。连接成功后使用send()和recv()传送数据 SOCK_DGRAM类型的套接字提供无连接的不可靠的数据报服务,套接字建立成功后使用sendto()和recvfrom()发送和接收数据报。
3.2.3 主要的Winsock函数 P61 (3)socket()函数可能返回的其它错误代码 P61 WSAEAFNOSUPPORT : 不支持所指定的通信域 WSAEMFILE : 没有可使用的套接字描述符 WSAENOBUFS : 没有可用的缓冲区 WSAEPROTNOTSUPPORT : 不支持指定的协议 WSAEPROTOTYPE : 指定的协议类型不适用于本套接字 WSAESOCKNOSUPPORT : 本地址簇不支持该类型套接字
3.2.3 主要的Winsock函数 2.将套接字绑定到指定的网络地址bind() P62 (1)关于套接字地址的概念。 定位一个套接字的三元组称为这个套接字的网络地址。在WinSock API中,把它称为WinSock地址。 (2)bind()函数的调用格式 int bind( SOCKET s, const struct sockaddr * name, int namelen); 参数s : 未经绑定的套接字描述符,由socket()函数返回。 参数name : 是一个指向sockaddr结构变量的指针,指向结构中保存的网络地址,把套接字绑定在这个地址上。 返回值:如果成功实现了函数绑定功能,返回0;如果返回SOCKET_ERROR,表示有错。
3.2.3 主要的Winsock函数 (3)bind函数的功能 P62 将套接字绑定到指定的网络地址上。本函数适用于流式套接字和数据报套接字,一般在connect()和listen()调用之前使用。 其实当用socket()创建套接字后,系统已经自动为它默认定位在 本机的IP地址和一个自由端口上,但是自动分配的端口往往与实际需要不同。 在服务器端需要指定IP地址和端口号,用作侦听客户端的连接请求,因此套接字一定要经过绑定。 客户端使用的套接字一般不绑定。
3.2.3 主要的Winsock函数 (4)bind()函数可能返回的错误代码 P62 WSAEAFNOSUPPORT : 不支持所指定的通信域 WSAEADDRINUSE: 指定了已经在使用中的端地址,有冲突 WSAEFAULT : 入口参数错,namelen太小 WSAEINVAL : 该套接字已经与一个网络地址邦定 WSAENOBUFS : 没有可用的缓冲区 WSATNOTSOCK : 描述字不是一个套接字
3.2.3 主要的Winsock函数 (5)相关的三种Winsock地址结构有许多函数都需要套接字的地址信息,像UNIX 套接字一样,Winsock也定义了三种关于地址的结构,经常使用。 ①通用的Winsock地址结构,针对各种通信域的套接字,存储它们的地址信息。 struct sockaddr { u_short sa_family; /* 地址家族 char sa_data[14]; /* 协议地址 }
3.2.3 主要的Winsock函数 ②专门针对Internet 通信域的Winsock地址结构 Struct sockaddr_in { /* 指定协议家族,一定是AF_INET. short. sin_family; /* 指定将要分配给套接字的传输层端口号 u_short sin_port; /* 指定套接字的主机的IP 地址 Struct in_addr sin_addr; /* 全置为0,是一个填充数。 char sin_zero[8];}
3.2.3 主要的Winsock函数 ③专用于存储IP地址的结构 struct in_addr { union { struct {u_char s_b1,s_b2,s_b3,s_b4;} s_un_b; struct {u_short s_w1,s_w2;} s_un_w; u_long s_addr;} } 这是一个4字节的结构体,每个字节代表IP地址的一个数字。 s_addr是一个4字节的整数,存放IP地址,由inet_addr()把字符形的IP地址转换成u_long型的IP地址,赋给s_addr。 在使用套接字时,这三个数据结构的一般用法是: 1.首先,定义一个Sockaddr_in的结构实例变量,并将它清零。 2.然后,为这个结构的各成员变量赋值, 3.第三步,在调用BIND()绑定函数时,将指向这个结构的指针强制转换为 sockaddr*类型。
3.23 主要的Winsock函数 (6)举例: P63 SOCKET serSock; // 定义了一个SOCKET 类型的变量 // 定义一个Sockaddr_in型的结构实例变量 sockaddr_in my_addr; int err; // 出错码 int slen=sizeof( sockaddr); // sockaddr 结构的长度 // 创建数据报套接字 serSock = SOCKET(AF_INET, SOCK_DGRAM,0 ); // 将Sockaddr_in的结构实例变量清零 memset(my_addr,0); my_addr.sin_family = AF_INET; // 指定通信域是Internet // 指定端口,将端口号转换为网络字节顺序 my_addr.sin_port = htons(21);
3.2.3 主要的Winsock函数 /* 指定IP地址,将IP地址转换为网络字节顺序*/ my_addr.sin_addr.s_addr = htonl( INADDR-ANY); /* 将套接字绑定到指定的网络地址,对&my_addr进行了强制类型转换*/ if (bind(serSock, (LPSOCKADDR )&my_addr, slen) == SOCKET_ERROR ) { /* 调用WSAGetLastError()函数,获取最近一个操作的错误代码*/ err = WSAGetLastError(); /* 以下可以报错,进行错误处理*/ }
3.2.3 主要的Winsock函数 3.启动服务器监听客户端的连接请求listen() P64 (1)listen()函数的调用格式 int listen( SOCKET s, int backlog); 参数s:服务器端的套接字描述符,已经先行绑定到服务器端口,用于侦听来自客户端的连接请求。 参数backlog:等待连接缓冲区队列的最大长度 返回值:正确执行返回0,出错则返回SOCKET_ERROR。
启动服务器监听客户端的连接请求listen() 本函数仅适用于连接的套接字,仅用于流式套接字,并仅用于服务器端。 执行本函数时,首先按照backlog建立连接等待缓冲区,并启动侦听。如果缓冲区队列有空,就接受1个来自客户端的连接请求,放进队列等待被接收,回应客户机确认;如果缓冲区队列已满,就拒绝客户端的连接请求。 对于在等待连接队列中排队的连接请求,要由accept()函数按先进先出的原则来处理。
启动服务器监听客户端的连接请求listen() (3) listen()函数函数可能返回的错误代码 P64 WSAEADDRINUSE : 视图侦听一个正在使用中的地址 WSAEINVAL : 该套接字未被bind()邦定 WSAEISCONN : 套接字已被连接 WSAEMFILE : 无可用文件描述符 WSAENOBUFS : 无可用缓冲区空间 WSAENOTSOCK : 描述字不是一个套接字 WSAEOPNOTSUPP : 该套接字被listen()调用不正常
3.2.3 主要的Winsock函数 4.接收连接请求accept() P65 (1)accept()函数的调用格式 SOCKET accept( SOCKET s, struct sockaddr* addr, int* addrlen); 参数s: 服务器端侦听套接字的描述符。 参数addr: 指向sockaddr结构的指针,该结构用来存放请求连接一方的套接字网络地址。 参数addrlen: 指向整数型的指针,用来返回addr地址的长度。 返回值:如果正确执行,返回一个socket类型的描述符;否则返回INVALID_SOCKET,可以用WSAGetLastError()获得错误代码。
接收连接请求accept() (2)accept()函数的功能。 本函数从侦听套接字s的等待队列中抽取第1个连接请求,创建1个与s同类的新的套接字,来与请求连接的客户机进行连接。如果连接成功,返回新创建的套接字的描述符,之后就用这个新的套接字与客户机套接字交换数据。 如果队列中没有等待的连接请求,则accept()阻塞调用它的进程,直至新的连接请求出现。 如果套接字采用非阻塞方式,如果队列中没有等待的连接, accept()返回一个错误代码,继续侦听后面的连接请求。
接收连接请求accept() (3)accept()函数可能返回的错误代码 P65 WSAEFAULT : addlen参数太小 WSAEINTR : 消除一个阻塞调用 WSAEINVAL : 在accept()前未激活listen() WSAEMFILE : 调用accept()时队列为空,无可用的描述字。 WSAENOBUFS : 无可用缓冲区空间 WSAENOTSOCK : 描述子不是一个套接字 WSAEOPNOTSUPP : 该套接字类型不支持面向连接 WSAEWOULDBLOCK : 该套接字是非阻塞,且无连接请求可供接收。
3.2.3 主要的Winsock函数 5.请求连接connect() P65 (1)connect()函数的调用格式 int connect( SOCKET s, struct sockaddr * name, int namelen); 参数s: SOCKET类型的描述符标识一个客户机端的未连接的套接字。 参数name : 指向sockaddr结构的指针,该结构指定服务器侦听套接字的网络地址。 参数namelen: 网络地址结构的长度。 返回值:若执行正确,返回0;否则返回SOCKET_ERROR。
请求连接connect() (2)connect()函数的功能 P65 本函数用于客户端与服务器端进行连接。 s参数指定1个客户端的未连接的流式或数据报套接字。 name参数必须指定服务器侦听套接字的网络地址。如果网络地址为全零,则返回WSAEADDRNOTAVAIL。 对于SOCK_STREAM流式套接字,建立了与远程主机的连接,可以使用这个连接收发数据。 对于SOCK_DGRAM数据报套接字,仅仅设置了一个默认的目的地址,并用来进行后续的send()和recv()调用。
请求连接connect() (3)connect()函数可能返回的错误代码 P66 WSAEADDRINUSE : 所指地址已经在使用 WSAEADDRNOTAVAIL : 找不到网络地址 WSAENOTSUPPORT : 簇中地址无法与本套接字一起使用 WSAECONNREFUSED : 连接被拒绝 WSAEDESTADDREQ : 需要目的地址 WSAEFAULT : namelen参数不对 WSAEINVAL : 套接字没有与地址邦定 WSAEISCONN : 套接字已经连接 WSAENETUNREACH : 无法从本自己访问网络 WSAETIMEOUT : 超时
3.2.3 主要的Winsock函数 (4)举例: P66 struct sockaddr_in daddr; memset((void *)&daddr,0,sizeof(daddr)); daddr.sin_family=AF_INET; daddr.sin_port=htons(8888); daddr.sin_addr.s_addr=inet_addr("133.197.22.4"); connect(ClientSocket,(struct sockaddr *)&daddr,sizeof(daddr));
3.2.3 主要的Winsock函数 6.向一个已连接的套接字发送数据send() P66 (1)send()函数的调用格式 int send( SOCKET s,char * buf,int len,int flags); 参数s: SOCK描述符,标识发送方已与对方建立连接的套接字,从这个套接字向外发送数据。 参数buf: 指向用户进程的字符缓冲区的指针。 参数len: 用户缓冲区中数据的长度。 参数flags: 执行此调用的方式。 返回值: 如果执行正确,返回实际发送出去的数据;否则返回SOCKET_ERROR。
3.2.3 主要的Winsock函数 (2)send()函数的调用功能 P67 send()函数用于向本地已经建立连接的流式套接字发送数据。 客户端和服务器都可以用send()函数来向TCP连接的另一端发送数据。 s是发送端,即调用函数的一方创建的套接字,它已经与接收端的套接字建立了联系。send()函数将用户进程缓冲区的数据发送到这个本地套接字的数据发送缓冲区中。 注意,真正向对方发送数据的过程,是由下层协议栈自动完成的。成功的完成send()调用并不意味着数据传送到达对方。 如果下层传送缓冲区空间不够,send()将阻塞等待,除非套接字是非阻塞I/O方式。
send()函数的执行流程
3.2.3 主要的Winsock函数 (3)send()函数可能返回的错误代码 P68 WSAEFAULT : buf参数不在用户地址空间中的位置 WSAENETRESET : 套接字放弃了连接 WSAENOBUFS : 套接字报告,有个缓冲区死锁 WSAENOTCONN : 套接字未连接 WSAESHUTDOWN : 套接字已被关闭 WSAEWOULDBLOCK : 套接字是非阻塞模式,但发送操作会产生阻塞 WSAEINVAL : 套接字未用bind()邦定
3.2.3 主要的Winsock函数 7.从一个已连接套接字接收数据recv() P68 (1)recv()函数的调用格 int recv( SOCKET s, char * buf, int len, int flags); 参数s: 套接字描述符,标识一个接收端已经与对方建立连接的套接字。 参数buf: 用于接收数据的字符缓冲区指针,这个缓冲区是用户进程的接收缓冲区。 参数len: 用户缓冲区长度。 参数flags: 指定函数的调用方式,一般设置为0。 返回值: 如果正确执行,返回从套接字s实际读入到buf中的字节数。如果连接已终止,返回0 ;否则返回SOCKET_ERROR,可以通过WSAGetLastError()获取错误代码。
3.2.3 主要的Winsock函数 (2)recv()函数的功能 P69 s是接收端,即调用本函数一方所创建的本地套接字,已经与对方建立了TCP连接。套接字的数据接收缓冲区中存有对方发送来的数据,调用recv()函数就是将本地套接字数据接收缓冲区中的数据接收到用户进程的缓冲区。 如果套接字s的接收缓冲区中没有数据可读,recv() 函数的执行取决于套接字的工作方式。 如果套接字采用阻塞模式,recv()一直等待数据的到来;如果采用非阻塞模式, recv()会立即返回,并返回SOCKET_ERROR。此时再调用WSAGetLastError(),获取的错误代码是WSAEWOULDBLOCK。
3.2.3 主要的Winsock函数 下图说明了send和recv的作用,套接字缓冲区与应用进程缓冲区的关系,以及协议栈所作的传送。
3.2.3 主要的Winsock函数 (3)recv()函数可能返回的错误代码 P70 WSAENOTCONN: 套接字未连接 WSAEINTR: 阻塞进程被取消 WSAENOTSOCK: 描述字不是1个套接字 WSAEOPNOTSUPP: 套接字不是SOCK_STREAM类型 WSAESHUTDOWN: 套接字已经关闭 WSAEWOULDBLOCK: 套接字是非阻塞模式,但接收操作会产生阻塞 WSAEMSGSIZE: 数据太大无法全部装入缓冲区,故被剪切。 WSAEINVAL: 套接字未用bind()绑定 WSAECONNABORTED: 由于超时,虚电路失效。 WSAECONNRESET: 强制终止了虚电路。
3.2.3 主要的Winsock函数 8.按照指定目的地向数据报套接字发送数据 sendto() P70 int sendto( SOCKET s, char * buf, int len, int flags, struct sockaddr * to, int tolen); 参数s : 发送方的数据报套接字描述符,包含发送方的网络地址。数据报通过这个套接字向外发送数据。 参数buf : 指向用户进程发送缓冲区的字符串指针,该缓冲区包含将要发送的数据。 参数len : 用户发送缓冲区中要发送的数据的长度。 参数flag : 指定函数的执行方式,一般置为0。 参数to : 指向sockaddr结构的指针,指定接收数据报的目的套接字的网络地址。 参数tolen : to地址的长度,等于sizeof(struct sockaddr)。 返回值 : 如果发送成功,返回实际发送的字节数。
3.2.3 主要的Winsock函数 (2)sendto()函数的功能 本函数专用于数据报套接字,用来向发送端的本地套接字发送1个数据报。套接字会将数据下交给传输层的UDP,向对方发送。 需要1个五元组信息。通信一端由发送方套接字s指定,通信的另一端由to结构决定。 注意,发送数据长度不应超过通信子网的IP包最大长度。 成功完成sendto()函数,仅仅说明用户缓冲区中的数据已经发送到本地套接字的发送缓冲区中,并不意味数据传送到达对方。 如果套接字缓冲区空间不够, sendto()将等待,并阻塞调用它的进程(如果处于阻塞同步模式)。
3.2.3 主要的Winsock函数 (3) sendto()函数可能返回的错误代码 P71 WSAEACCESS: 要求地址为广播地址,但标志未能正确设置 WSAEINTR: 取消1个阻塞的调用 WSAEFAULE: buf或to参数不是用户地址空间的一部分 WSAENETRESET: 放弃连接,连接被复位 WSAENOBUFS: 报告1个缓冲区死锁 WSAESHUTDOWN: 套接字已经关闭 WSAEMSGSIZE: 套接字为SOCK_DGRAM,数据报大于套接字支持的最大值 WSAEADDRNOTAVAIL: 所指地址无法从本主机获得 WSAEDESADDRREQ: 需要目的地址 WSAENETUNREACH: 当前无法从本主机连上网络
3.2.3 主要的Winsock函数 9.从数据报套接字接收数据并保存源地址 recvfrom() P71 int recvfrom( SOCKET s, char * buf, int len, int flags, struct sockaddr* from, int* fromlen); 参数s : 接收端的数据报套接字描述符,包含接收方的网络地址,从这个套接字接收数据。 参数buf :字符串指针,指向用户进程接收缓冲区,用来接收来自套接字缓冲区的数据。 参数len : 用户接收缓冲区的长度。 参数flags : 接收方式,一般设置为0。 参数from : 指向sockaddr结构的指针,这个结构中返回了发送方的网络地址。 参数fromlen : 返回存在from中的网络地址长度。 返回值 : 如果接收成功,返回实际收到的字节数。
3.2.3 主要的Winsock函数 (2)recvfrom()函数的功能 本函数从s套接字的接收缓冲区队列中,取出第1个数据报,放进用户进程的缓冲区buf。 如果from不是空指针,函数将下层协议栈该数据报的发送方网络地址放到sockaddr结构中,把这个结构的大小放到fromlen中。这两个参数对接收不起作用,仅用来返回数据报源端的网络地址。 如果套接字中没有数据待读,并且套接字工作在阻塞模式,本函数一直等待数据的到来;如果工作在非阻塞模式,则返回SOCKET_ERROR,可以调用WSAGetLastError()获取错误代码。
3.2.3 主要的Winsock函数 (3) recvfrom()函数的错误代码 P72 WSAEFAULT: fromlen参数非法;from缓冲区大小无法装入端地址 WSAENOTSOCK: 描述字不是1个套接字 WSAESHUTDOWN: 套接字已被关闭 WSAEMSGSIZE: 数据报太大无法装入缓冲区 WSAECONNABORTED: 由于超时或其它原因,虚电路失效 WSAECONNRESET: 远端强制终止了虚电路
3.2.3 主要的Winsock函数 10.关闭套接字closesocket() P72 (1)closesocket()函数的调用格式 int closesocket( SOCKET s); 参数s : 一个套接字的描述符 返回值: 如果成功的关闭了套接字,返回0;否则返回SOCKET_ERROR,可以通过WSAGetLastError()获取错误代码。 (2) closesocket()函数的功能 本函数关闭一个套接字,释放套接字描述符s,以后对此套接字的访问都以WSAENOTSOCK错误返回。 可能有多个套接字描述符指向同一个套接字数据结构,套接字数据结构中专门有1个字段存放引用次数,当调用closesocket()函数时,如果该字段大于1,则仅清除s的对应表项,并把引用次数减1。
3.2.3 主要的Winsock函数 closesocket()函数的不同语义 closesocket()函数的语义受SO_LINGER与SO_DOTLINGER选项影响 选项 间隔 关闭方式 等待关闭与否 SO_DONTLINGER 不关心 优雅 否 SO_LINGER 零 强制 非零 是 如果设置了SO_LINGER并设置了零超时间隔,不论套接字中是否有排队数据未发送或未被确认, closesocket()都毫不迟疑的立即执行。这种关闭方式称为“强制”。 如果设置了SO_LINGER并设置了非零超时间隔,则closesocket()等待并阻塞调用它的进程。这种方式称为“优雅”。
3.2.3 主要的Winsock函数 (3) closesocket()函数可能返回的错误代码 WSAENOTSOCK: 描述字不是一个套接字 WSAEINTR:通过一个WSACancelBlocking来取消一个调用 WSAEWOULDBLOCK: 该套接字设置为非阻塞方式且SO_LINGER设置为非零超时间隔。
3.2.3 主要的Winsock函数 11.禁止在一个套接字上进行数据接收与发送shutdown() P74 (1) shutdown()函数的调用格式 int shutdown( SOCKET s, int how); (2) shutdown()的功能 shutdown()函数可以有选择的禁止套接字接收和发送 当参数how为0,该套接字后续接收操作将被禁止。 当参数how为1,禁止后续发送操作。 当参数how为2,同时禁止收和发。 (3) shutdown()函数可能返回的错误代码 WSAEINVAL: how参数非法 WSAENOTCONN: 套接字未连接 WSAENOTSOCK: 描述字不是一个套接字
3.2.4 Winsock的辅助函数 P74 1.Winsock中的字节顺序转换函数 P74 不同的计算机系统存放多字节数据的顺序不同,有的把数据低字节存放在前,高字节在后,即先低后高;也有的相反,把数据的高字节放在前,先高后低。 P74 图3.5 两种本机字节顺序
Winsock中的字节顺序转换函数 多字节数据在网络数据报头中的存储顺序,称为网络字节顺序。在套接字中必须使用网络字节顺序。 在sockaddr_in结构中,sin_addr是IP地址,sin_port是端口号,在指定套接字网络地址时,这两项必须转成网络字节顺序。 相反,如果从网络上接收到对方的网络地址,在本机处理或输出时,应将IP地址和端口号从网络字节顺序转换为本机字节顺序。
Winsock中的字节顺序转换函数 Winsock API特为此设置了四个函数 (1) htonl() 将主机的无符号长整型数本机顺序转换为网络字节顺序 (Host to Network Long),用于IP地址。 u_long PASCAL FAR htonl( u_long hostlong); hostlong是主机字节顺序表达的32位数。htonl()返回一个网络字节顺序的值。 (2) htons() 将主机的无符号短整型数转换成网络字节顺序(Host to Network Short),用于端口号。 u_short PASCAL FAR htons( u_short hostshort); hostshort:主机字节顺序表达的16位数。htons()返回一个网络字节顺序的值。
Winsock中的字节顺序转换函数 (3) ntohl() 将一个无符号长整型数从网络字节顺序转换为主机字节顺序。(Network to Host Long),用于IP地址。 u_long PASCAL FAR ntohl( u_long netlong); netlong是一个以网络字节顺序表达的32位数,ntohl()返回一个以主机字节顺序表达的数。 (4) ntohs() 将一个无符号短整型数从网络字节顺序转换为主机字节顺序。(Network to Host Sort),用于端口号。 u_short PASCAL FAR ntohs( u_short netshort); netshort是一个以网络字节顺序表达的16位数。ntohs()返回一个以主机字节顺序表达的数。
3.2.4 Winsock的辅助函数 2.获取与套接字相连的对方的网络地址getpeername() P75 int getpeername( SOCKET s, struct sockaddr * name, int * namelen); 参数s: 一个已连接套接字的描述字 参数name: 对方网络地址的名字结构(网络地址) 参数namelen: 名字缓冲区的长度 返回值: 若无错误发生, getpeername()返回0;否则返回SOCKET_ERROR,可以用WSAGetLastError()获取其错误代码。 (2) getpeername()函数的功能 getpeername()从s中获取与之绑定的对方的网络地址,并存放在sockaddr 类型的name结构中。
3.2.4 Winsock的辅助函数 3.获取一个套接字的本地名字getsockname() P76 int getsockname( SOCKET s, struct sockaddr * name, int * namelen); 参数s: 一个已连接套接字的描述字 参数name: 本机网络地址的名字结构(网络地址) 参数namelen: 名字缓冲区的长度 (2) getsockname()用于获取一个套接字的名字。用于一个已绑定或已连接的套接字,获取本地网络地址。 当未调用bind()就调用了connect(),这时唯有 getsockname()可以获知系统内定的本地地址。
3.2.4 Winsock的辅助函数 4.将一个点分十进制形式的IP地址转换成一个长整型数inet_addr() P76 unsigned long inet_addr (const char * cp); 参数cp: 字符串,是一个点分十进制的IP地址 返回值: 返回一个无符号长整形数 (2) inet_addr ()函数的功能 本函数将点分十进制的IP地址转换成无符号长整形数,返回值符合网络字节顺序。
3.2.4 Winsock的辅助函数 5.将网络地址转换成点分十进制的字符串格式inet_ntoa() P76 char * inet_ntoa( struct in_addr in); 参数in: 一个in_addr结构变量,包含长整形的IP地址。 返回值: 如果执行正确, inet_ntoa()返回一个字符指针;如果发生错误,返回NULL。 (2) inet_ntoa()函数的功能 本函数将一个包含在in_addr结构变量中的长整形IP地址,转换成点分十进制的字符串形式。
3.2.5 Winsock的信息查询函数 P77 Winsock API提供了一组信息查询函数,让我们能方便地获取套接字所需要的网络地址信息以及其它信息, (1) gethostname() 用来获取 本地计算机的标准主机名。 int gethostname(char* name, int namelen); 参数name : 指向将要存放主机名的缓冲区指针 参数namelen: 缓冲区长度 获取的主机名是一个以NULL结尾的字符串。反回的主机名可以在gethostbyname()和WSAAsyncGetHostByName()中使用。
3.2.5 Winsock的信息查询函数 (2) gethostbyname() 返回对应于给定主机名的主机信息。 struct hostent* gethostbyname(const char* name); 参数name是指向主机名字符串的指针,函数返回的指针指向一个hostent结构,这个结构体在WinSock.h中定义。 struct hostent { char FAR * h_name; /* official name of host */ char FAR * FAR * h_aliases; /* alias list */ short h_addrtype; /* host address type */ short h_length; /* length of address */ char FAR * FAR * h_addr_list; /* list of addresses */ };
3.2.5 Winsock的信息查询函数 (3) gethostbyaddr() 根据一个IP地址取回相应的主机信息。 struct hostent* gethostbyaddr(const char* addr, int len, int type); 参数addr是指向网络地址字节顺序的IP地址的指针;len是地址长度;type 是地址类型。返回的指针指向一个hostent结构,其中包含主机名字和地址信息。
3.2.5 Winsock的信息查询函数 (4) getservbyname() P77 返回对应于给定服务名和协议名的相关服务信息。 struct servent* getservbyname(const char* name, const char* proto); 参数name是一个指向服务器名字的指针;proto是指向协议名的指针。函数返回的指针指向一个servent结构,这个结构体在WinSock.h中定义: struct servent { char FAR * s_name; /* official service name */ char FAR * FAR * s_aliases; /* alias list */ short s_port; /* port # */ char FAR * s_proto; /* protocol to use */ };
3.2.5 Winsock的信息查询函数 (5) getservbyport() P78 返回对应于给定端口号和协议名的相关服务信息。 struct servent * getservbyport(int port,const char *proto); 参数port是给定的端口号,以网络字节顺序排序;proto是指向协议名的指针。返回的指针指向一个servent结构,该结构包含所需要的信息。
3.2.5 Winsock的信息查询函数 (6)getprotobyname() P78 返回对应于给定协议名的相关协议信息。 struct protoent * getprotobyname(const char * name); 参数name是指向一个协议名的指针。函数返回的指针指向一个protoent结构,该结构在WinSock.h中定义: struct protoent { char FAR * p_name; /* official protocol name */ char FAR * FAR * p_aliases; /* alias list */ short p_proto; /* protocol # */ };
3.2.5 Winsock的信息查询函数 (7)getprotobynumber () P78 返回对应于给定协议号的相关协议信息。 Struct protoent * getprotobynumber(int number); 参数number是一个以主机顺序为排列的协议号。函数返回的指针指向一个protoent结构。
3.2.5 Winsock的信息查询函数 除了gethostname()函数以外,其它六个函数有以下共同的特点: ①函数名都采用GetXbyY的形式。 ②如果函数成功地执行,就返回一个指向某种结构的指针,该结构包含所需要的信息。 ③如果函数执行发生错误,就返回一个空指针。应用程序可以立即调用WSAGetLastError()来得到一个特定的错误代码。
3.2.5 Winsock的信息查询函数 ④函数执行时,可能在本地计算机上查询,也可能通过网络向域名服务器发送请求,来获得所需要的信息,这取决于用户网络的配置方式。 ⑤为了能让程序在等待响应时能作其他的事情,Winsock API扩充了一组作用相同的异步查询函数,不会引起进程的阻塞。并且可以使用Windows的消息驱动机制。也是六个函数,与GetXbyY各函数对应,在每个函数名前面加上了WSAAsync前缀,名字采用WSAAsyncGetXByY()的形式。它们的工作机制在后面详述。
3.2.6 WSAAsyncGetXByY类型的扩展函数 P79 WSAAsyncGetXByY类型的扩展函数是GetXByY函数的异步版本,这些函数可以很好地利用Windows的消息驱动机制。 1.WSAAsyncGetHostByName()函数 (1)函数的调用格式 HANDLE WSAAsyncGetHostByName ( HWND hWnd, unsigned int wMsg,const char * name, char * buf, int buflen ); 参数hWnd : 当异步请求完成时,接收消息的窗口句柄。 参数wMsg : 当异步请求完成时,将要接收的消息。 参数name : 指向主机名的指针 参数buf : 接收hostent数据的数据区指针 (2)函数的功能 本函数是GetHostByName()函数的异步版本,用来获取对应于一个主机名的名称和地址信息。
3.2.6 WSAAsyncGetXByY类型的扩展函数 2.WSAAsyncGetHostByAddr()函数 P80 (1)调用格式 HANDLE WSAAsyncGetHostBy Addr ( HWND hWnd, unsigned int wMsg, const char * addr, int len, int type, char * buf, int buflen ); 参数addr : 主机网络地址的指针 参数len : 地址长度 参数type : 地址类型 参数buf : 接收hostent数据的数据区指针 参数buflen : 上述数据区的大小 (2)函数功能。 本函数是GetHostByAddr()函数 的异步版本,用来获取对应一个网络地址的主机名和地址信息。
3.2.6 WSAAsyncGetXByY类型的扩展函数 3.WSAAsyncGetServByName()函数 P81 (1)函数调用格式 HANDLE WSAAsyncGetServByName ( HWND hWnd, unsigned int wMsg,const char * name, const char * proto, char * buf, int buflen ); 参数name : 指向服务器名的指针 参数proto : 指向协议名称的指针 参数buf : 接收protoent数据的数据局指针 参数buflen:上述数据区的大小 (2)函数功能。本函数是GetServByName()函数的异步版本,用来获取对应于一个服务器名的服务器信息。
3.2.6 WSAAsyncGetXByY类型的扩展函数 4.WSAAsyncGetServByPort()函数 P81 (1)函数调用格式 HANDLE WSAAsyncGetServByPort ( HWND hWnd, unsigned int wMsg, int port, const char * proto, char * buf, int buflen ); 参数potr : 服务器的端口,以网络字节顺序 参数proto : 指向协议名称的指针 参数buf : 接收servent数据的数据区指针 参数buflen : 上述数据区的大小 (2)函数功能。 本函数是GetServByPort()函数的异步版本,用来获取对应于一个端口号的服务器信息。
3.2.6 WSAAsyncGetXByY类型的扩展函数 5.WSAAsyncGetProtoByName()函数 P81 (1)函数调用格式 HANDLE WSAAsyncGetProtoByName ( HWND hWnd,unsigned int wMsg, const char * name, char * buf, int buflen ); 参数name : 指向要获得的协议名的指针 参数buf : 接收protoent数据的数据区指针 参数buflen:上述数据区的大小 (2)函数的功能。本函数是GetProtoByName()函数 的异步版本,用来获取对应于一个协议名的协议名称和代号。
3.2.6 WSAAsyncGetXByY类型的扩展函数 6.WSAAsyncGetProtoByNumber()函数 P82 (1)函数调用格式 HANDLE WSAAsyncGetProtoByNumber ( HWND hWnd, unsigned int wMsg,int number, char * buf, int buflen); 参数number : 要获得的协议号,使用主机字节顺序 参数buf : 接收protoent数据的数据区指针 参数buflen : 上述数据区的大小 (2)函数功能。本函数是GetProtoByNumber()函数的异步版本,用来获取对应于一个协议号的协议名称和代号。
3.3 网络应用程序的运行环境 1.开发Windows Sockets网络应用程序的软、硬件环境 3.3 网络应用程序的运行环境 1.开发Windows Sockets网络应用程序的软、硬件环境 采用支持Windows Sockets API的Windows98SE以上的操作系统。 采用可视化和面向对象技术的编程语言,如Microsoft Visual C++ 6.0 采用TCP/IP网络通信协议。
3.3 网络应用程序的运行环境 网络中的所采用的计算机应满足Windows运行的配置要求。 3.3 网络应用程序的运行环境 网络中的所采用的计算机应满足Windows运行的配置要求。 网络中各节点上的计算机需安装网卡,并安装网卡的驱动程序。可以采用以太网交换机将若干台计算机组建成局域网。 在配置网络时,首先实现对等网,使各计算机节点能在“网上邻居”中找到自己和其它各计算机,并能实现文件资源相互共享。 其次,网络配置中,应添加TCP/IP协议,设定相应的IP地址。
3.3 网络应用程序的运行环境 2.进行Windows Sockets通信程序开发的基本步骤 3.3 网络应用程序的运行环境 2.进行Windows Sockets通信程序开发的基本步骤 采用不同套接字的应用程序的调用套接字函数时,应遵循相应的步骤。 3.使用Visual C++ 进行Windows Sockets程序开发的其他技术要点 (1)首先做好初始化处理。 (2)通信双方的程序应采用统一的界面形式。 (3)尽量采用多线程(Multithreaded)编程技术。 (4)应充分利用Windows Sockets的基于消息的网络事件异步选择机制。