网络通信编程技术2_第1页
网络通信编程技术2_第2页
网络通信编程技术2_第3页
网络通信编程技术2_第4页
网络通信编程技术2_第5页
已阅读5页,还剩31页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第二章Winsock编程接口Winsock是Windows下网络编程的标准接口,它允许两个或多个应用程序在相同机器上,或者是通过网络相互交流。Winsock是真正的协议无关的接口,本章主要讲述如何使用它来编写应用层的网络应用程序。2.1Winsock库Winsock库有两个版本,Winsock1和Winsock2。现在开发网络应用程序都使用Winsock2,需要在程序中包含头文件winsock2.h,它包含了绝大部分socket函数和相关结构类型的声明和定义。同时要添加的还有到WS2_32.lib库的链接。包含必要的头文件,设置好链接环境之后,便可进行下面的编码工作了。2.1Winsock库Winsock库的装入和释放每个Winsock应用程序必须加载相应版本的WinsockDLL。如果在调用Winsock函数前没有加载Winsock库,函数返回SOCKET_ERROR,出错代码将是WSANOTINITIALISED。加载Winsock库的函数是WSAStartup,其定义如下。intWSAStartup(WORDwVersionRequested,//指定想要加载的Winsock库的版本,高字节为次版本号,低字节为主版本号LPWSADATAlpWSAData//一个指向WSADATA结构的指针,用来返回DLL库的详细信息);wVersionRequested参数用来指定想要加载的Winsock库的版本。为了建立此参数的值,可以使用宏MAKEWORD(x,y),其中x是高字节,y是低字节。lpWSAData是一个指向LPWSADATA结构的指针,WSAStartup使用所加载库的版本信息填充它。typedefstructWSAData{WORDwVersion;//库文件建议应用程序使用的版本WORDwHighVersion;//库文件支持的最高版本charszDescription[WSADESCRIPTION_LEN+1];//库描述字符串charszSystemStatus[WSASYS_STATUS_LEN+1];//系统状态字符串unsignedshortiMaxSockets;//同时支持的最大套接字的数量unsignedshortiMaxUdpDg;//2.0版中已废弃的参数charFAR*lpVendorInfo;//2.0版中已废弃的参数}WSADATA,FAR*LPWSADATA;函数调用成功返回0。否则要调用WSAGetLastError函数查看出错的原因。此函数的作用相当于API函数GetLastError,它取得最后发生错误的代码。每一个对WSAStartup的调用必须对应一个对WSACleanup的调用,这个函数释放Winsock库。intWSACleanup(void);所有的Winsock函数都是从WS2_32.DLL导出的,VC++在默认情况下并没有链接到该库,如果想使用WinsockAPI,就必须包含相应的库文件。#pragmacomment(lib,"WS2_32")2.1.2封装CInitSock类每次写网络程序都必须编写代码载入和释放Winsock库,为了今后讨论方便,这里封装一个CInitSock类来管理Winsock库,类的使用方法见下一小节。#include<winsock2.h>//initsock.h文件#pragmacomment(lib,"WS2_32")//链接到WS2_32.libclassCInitSock{public:CInitSock(BYTEminorVer=2,BYTEmajorVer=2){//初始化WS2_32.dllWSADATAwsaData;WORDsockVersion=MAKEWORD(minorVer,majorVer);if(::WSAStartup(sockVersion,&wsaData)!=0){exit(0);}}~CInitSock(){::WSACleanup();}};2.2Winsock的寻址方式和字节顺序因为Winsock要兼容多个协议,所以必须使用通用的寻址方式。TCP/IP使用IP地址和端口号来指定一个地址,但是其他协议也许采用不同的形式。如果Winsock强迫使用特定的寻址方式,添加其他协议就不大可能了。Winsock的第一个版本使用sockaddr结构来解决此问题。structsockaddr

{

u_shortsa_family;charsa_data[14];};2.2Winsock的寻址方式和字节顺序在这个结构中,第一个成员sa_family指定了这个地址使用的地址家族。sa_data成员存储的数据在不同的地址家族中可能不同。本书仅仅使用Internet地址家族(TCP/IP),Winsock已经定义了sockaddr结构的TCP/IP版本——sockaddr_in结构。它们本质上是相同的结构,但是第2个更容易操作。在Winsock中,应用程序通过SOCKADDR_IN结构来指定IP地址和端口号,定义如下。structsockaddr_in{shortsin_family;//地址家族(即指定地址格式),应为AF_INETu_shortsin_port;//端口号structin_addrsin_addr;//IP地址charsin_zero[8];//空字节,要设为0};(1)sin_family域必须设为AF_INET,它告诉Winsock程序使用的是IP地址家族。(2)sin_port域指定了TCP或UDP通信服务的端口号。应用程序在选择端口号时必须小心,因为有一些端口号是保留给公共服务使用的,如FTP和HTTP。基本上,端口号可分成如下3个范围:公共的、注册的、动态的(或私有的)。z0~1023由IANA(InternetAssignedNumbersAuthority)管理,保留为公共的服务使用。z1024~49151是普通用户注册的端口号,由IANA列出。z49152~65535是动态和/或私有的端口号。2.2Winsock的寻址方式和字节顺序普通用户应用程序应该选择1024~49151之间的注册了的端口号,以避免使用了一个其他应用程序或者系统服务已经使用的端口号。在49152~65535之间的端口号也可以自由地使用,因为没有服务注册这些端口号。(3)sin_addr域用来存储IP地址(32位),它被定义为一个联合来处理整个32位的值,两个16位部分或者每个字节单独分开。描述32位IP地址的in_addr结构定义如下。structin_addr{union{struct{u_chars_b1,s_b2,s_b3,s_b4;}S_un_b;//以4个u_char来描述struct{u_shorts_w1,s_w2;}S_un_w;//以2个u_short来描述u_longS_addr;//以1个u_long来描述}S_un;};2.2Winsock的寻址方式和字节顺序用字符串“aa.bb.cc.dd”表示IP地址时,字符串中由点分开的4个域是以字符串的形式对in_addr结构中的4个u_char值的描述。由于每个字节的数值范围是0~255,所以各域的值都不可超过255。(4)最后一个域sin_zero没有使用,是为了与SOCKADDR结构大小相同才设置的。应用程序可以使用inet_addr函数将一个由小数点分隔的十进制IP地址字符串转化成由32位二进制数表示的IP地址。inet_ntoa是inet_addr函数的逆函数,它将一个网络字节顺序的32位IP地址转化成字符串。unsignedlonginet_addr(constchar*cp);//将一个"aa.bb.cc.dd"类型的IP地址字符串转化为32位的二进制数char*inet_ntoa(structin_addrin);//将32位的二进制数转化为字符串注意,inet_addr返回的32位二进制数是用网络顺序存储的,下一小节详细讲述字节顺序。2.2Winsock的寻址方式和字节顺序2.2.2字节顺序字节顺序是长度跨越多个字节的数据被存储的顺序。例如,一个32位的长整型0x12345678跨越4个字节(每个字节8位)。Intelx86机器使用小尾顺序(little-endian),意思是最不重要的字节首先存储。因此,数据0x12345678在内存中的存放顺序是0x78、0x56、0x34、0x12。大多数不使用小尾顺序的机器使用大尾顺序(big-endian),即最重要的字节首先存储。同样的值在内存中的存放顺序将是0x12、0x34、0x56、0x78。因为协议数据要在这些机器间传输,所以就必须选定其中的一种方式做为标准,否则会引起混淆。TCP/IP统一规定使用大尾方式传输数据,也称为网络字节顺序。例如,端口号(它是一个16位的数字)12345(0x3039)的存储顺序是0x30、0x39。32位的IP地址也是以这种方式存储的,IP地址的4部分存储在4个字节中,第一部分存储在第一个字节中。上述sockaddr和sockaddr_in结构中,除了sin_family成员(它不是协议的一部分)外,其他所有值必须以网络字节顺序存储。Winsock提供了一些函数来处理本地机器的字节顺2.2Winsock的寻址方式和字节顺序2.2.2字节顺序u_shorthtons(u_shorthostshort);//将u_short类型变量从主机字节顺序转化到TCP/IP网络字节顺序u_longhtonl(u_longhostlong);//将u_long类型变量从主机字节顺序转化到TCP/IP网络字节顺序u_shortntohs(u_shortnetshort);//将u_short类型变量从TCP/IP网络字节顺序转化到主机字节顺序u_longntohl(u_longnetlong);//将u_long类型变量从TCP/IP网络字节顺序转化到主机字节顺序2.2Winsock的寻址方式和字节顺序初始化sockaddr_in结构sockaddr_insockAddr;//设置地址家族sockAddr.sin_family=AF_INET;//转化端口号6789到网络字节顺序,并安排它到正确的成员sockAddr.sin_port=htons(6789);//inet_addr函数转化一个"aa.bb.cc.dd"类型的IP地址字符串到长整型,//它是以网络字节顺序记录的IP地址sockAddr.sin_addr.S_un.S_addr=inet_addr("");//也可以用下面的代码设置IP地址(通过设置4个字节部分,设置sockAddr的地址)/*sockAddr.sin_addr.S_un.S_un_b.s_b1=127;sockAddr.sin_addr.S_un.S_un_b.s_b2=0;sockAddr.sin_addr.S_un.S_un_b.s_b3=0;sockAddr.sin_addr.S_un.S_un_b.s_b4=1;*/2.2Winsock的寻址方式和字节顺序2.2.3获取地址信息通常,主机上的接口被静态地指定一个IP地址,或者是由配置协议来分配,如动态主机配置协议(DHCP)。如果DHCP服务器不能到达,系统会使用AutomaticPrivateIPAddressing(APIPA)自动分配/16范围内的地址。2.2Winsock的寻址方式和字节顺序2.2.3获取地址信息1.获取本机IP地址获取本机的IP地址比较简单,下面的GetAllIps例子打印出了本机使用的所有IP(一个适配器一个IP地址),程序代码如下。#include"../common/InitSock.h"//GetAllIps工程#include<stdio.h>CInitSockinitSock;//初始化Winsock库voidmain(){charszHost[256];

//取得本地主机名称

::gethostname(szHost,256);

//通过主机名得到地址信息

hostent*pHost=::gethostbyname(szHost);

//打印出所有IP地址

in_addraddr;

for(inti=0;;i++)

{char*p=pHost->h_addr_list[i];//p指向一个32位的IP地址

if(p==NULL)break;

memcpy(&addr.S_un.S_addr,p,pHost->h_length);

char*szIp=::inet_ntoa(addr);

printf("本机IP地址:%s\n",szIp);}}GetAllIps先调用gethostname取得本地主机的名称,然后通过主机名得到其地址信息。2.2Winsock的寻址方式和字节顺序2.2.3获取地址信息有时为了检测网络,或者为了一些其他特殊的目的,需要自己来直接操作原始数据帧(第9章再具体讲述),这就需要获取自己和LAN中其他主机的MAC地址。获取本地机器的MAC地址很容易,使用帮助函数GetAdaptersInfo即可。此函数的作用是获取本地机器的适配器信息,用法如下。DWORDGetAdaptersInfo(PIP_ADAPTER_INFOpAdapterInfo,//指向一个缓冲区,用来取得IP_ADAPTER_INFO结构的列表–PULONGpOutBufLen//用来指定上面缓冲区的大小。如果大小不够,此参数返回所需大小);//函数调用成功返回ERROR_SUCCESSIP_ADAPTER_INFO结构包含了本地计算机上网络适配器的信息,定义如下。typedefstruct_IP_ADAPTER_INFO{struct_IP_ADAPTER_INFO*Next;//指向适配器列表中的下一个适配器(计算机可能有多个适配器)2.2Winsock的寻址方式和字节顺序2.2.3获取地址信息程序见LocalhostInfo2.3Winsock编程详解使用TCP创建网络应用程序稍微复杂一些,因为TCP是面向连接的协议,需要通信双方首先建立一个连接。本节先以建立简单的TCP客户端和服务器端应用程序为例,详细说明Winsock的编程流程,然后再介绍较为简单的UDP编程。2.3Winsock编程详解2.3.1Winsock编程流程1.套接字的创建和关闭使用套接字之前,必须调用socket函数创建一个套接字对象,此函数调用成功将返回套接字句柄。SOCKETsocket(intaf,//用来指定套接示使用的地址格式,WinSock中只支持AF_INETinttype,//用来指定套接字的类型intprotocol//配合type参数使用,用来指定使用的协议类型。可以是IPPROTO_TCP等);2.3Winsock编程详解2.3.1Winsock编程流程1.套接字的创建和关闭当type参数指定为SOCK_STREAM和SOCK_DGRAM时,系统已经明确使用TCP和UDP来工作,所以protocol参数可以指定为0。函数执行失败返回INVALID_SOCKET(即-1),可以通过调用WSAGetLastError取得错误代码。也可以使用Winsock2的新函数WSASocket来创建套接字,与socket相比,它提供了更多的参数,如可以自己选择下层服务提供者、设置重叠标志等,后面再具体讨论它。当不使用socket创建的套接字时,应该调用closesocket函数将它关闭。如果没有错误发生,函数返回0,否则返回SOCKET_ERROR。函数用法如下。intclosesocket(SOCKETs);//函数惟一的参数就是要关闭的套接字的句柄2.3Winsock编程详解2.3.12.绑定套接字到指定的IP地址和端口号bind函数用在没有建立连接的套接字上,它的作用是绑定面向连接的或者无连接的套接字。套接字被socket函数创建以后,存在于指定的地址家族里,但它是未命名的。bind函数通过安排一个本地名称到未命名的socket而建立此socket的本地关联。本地名称包含3部分:主机地址、协议号(分别为UDP或TCP)和端口号。本节的TCPServer程序使用以下代码绑定套接字s到本地地址。2.3Winsock编程详解2.3.12.绑定套接字到指定的IP地址和端口号//填充sockaddr_in结构sockaddr_insin;sin.sin_family=AF_INET;sin.sin_port=htons(4567);sin.sin_addr.S_un.S_addr=INADDR_ANY;//绑定这个套接字到一个本地地址if(::bind(sListen,(LPSOCKADDR)&sin,sizeof(sin))==SOCKET_ERROR){printf("Failedbind()\n");return0;}sockaddr_in结构中的sin_familly字段用来指定地址家族,该字段和socket函数中的af参数的含义相同,所以惟一可以使用的值就是AF_INET。sin_port字段和sin_addr字段分别指定套接字需要绑定的端口号和IP地址。放入这两个字段的数据的字节顺序必须是网络字节顺序。因为网络字节顺序和IntelCPU的字节顺序刚好相反,所以必须首先使用htons函数进行转换。如果应用程序不关心所使用的地址,可以指定Internet地址为INADDR_ANY,指定端口号为0。如果Internet地址等于INADDR_ANY,系统会自动使用当前主机配置的所有IP地址,简化了程序设计;如果端口号等于0,程序执行时系统会为这个应用程序分配惟一的端口号,其值在1024~5000之间。应用程序可以在bind之后使用getsockname来知道为它分配的地址。但是要注意,直到套接字连接上之后getsockname才可能填写Internet地址,因为对一个主机来说可能有多个地址是可用的。TCP客户端程序也可以在不显式绑定地址和端口号的情况下发送数据或者连接。在这种情况下,系统也会默认地为套接字绑定一个本地端口(1024~5000之间)。2.3Winsock编程详解2.3.12.绑定套接字到指定的IP地址和端口号3.设置套接字进入监听状态listen函数设置套接字进入监听状态。intlisten(SOCKETs,//套接字句柄intbacklog//监听队列中允许保持的尚未处理的最大连接数量);为了接受连接,首先使用socket函数创建套接字,然后使用bind函数将它绑定到本地地址,再用listen函数为到达的连接指定backlog,最后使用accept接受请求的连接。listen仅应用在支持连接的套接字上,如SOCK_STREAM类型的套接字。函数执行成功后,套接字s进入了被动模式,到来的连接会被通知要排队等候接受处理。在同一时间处理多个连接请求的服务器通常使用listen函数,如果一个连接请求到达,并且排队已满,客户端将接收到WSAECONNREFUSED错误。2.3Winsock编程详解2.3.12.绑定套接字到指定的IP地址和端口号3.设置套接字进入监听状态4.接受连接请求accept函数用于接受到来的连接。SOCKETaccept(SOCKETs,//套接字句柄structsockaddr*addr,//一个指向sockaddr_in结构的指针,用于取得对方的地址信息int*addrlen//一个指向地址长度的指针);2.3Winsock编程详解2.3.12.绑定套接字到指定的IP地址和端口号3.设置套接字进入监听状态4.接受连接请求该函数在s上取出未处理连接中的第一个连接,然后为这个连接创建新的套接字,返回它的句柄。新创建的套接字是处理实际连接的套接字,它与s有相同的属性。程序默认工作在阻塞模式下,这种方式下如果没有未处理的连接存在,accept函数会一直等待下去,直到有新的连接发生才返回。addrlen参数用于指定addr所指空间的大小,也用于返回地址的实际长度。如果addr或者addrlen是NULL,则没有关于远程地址的信息返回。客户端程序在创建套接字之后,要使用connect函数请求与服务器连接,2.3Winsock编程详解2.3.12.绑定套接字到指定的IP地址和端口号3.设置套接字进入监听状态4.接受连接请求5.收发数据对流套接字来说,一般使用send和recv函数来收发数据。intsend(SOCKETs,//套接字句柄constcharFAR*buf,//要发送数据的缓冲区地址intlen,//缓冲区长度intflags//指定了调用方式,通常设位0);intrecv(SOCKETs,charFAR*buf,intlen,int);send函数在一个连接的套接字上发送缓冲区内的数据,返回发送数据的实际字节数。recv函数从对方接收数据,并将其存储到指定的缓冲区。flags参数在这两函数中通常设为0。在阻塞模式下,send将会阻塞线程的执行直到所有的数据发送完毕(或者发生错误),而recv函数将返回尽可能多的当前可用信息,直到达到缓冲区指定的大小。2.3Winsock编程详解2.3.12.绑定套接字到指定的IP地址和端口号2.3.2典型过程图TCP服务器程序和客户程序的创建过程如图2.2所示。服务器端创建监听套接字,并为它关联一个本地地址(指定IP地址和端口号),然后进入监听状态准备接受客户的连接请求。为了接受客户端的连接请求,服务器端必须调用accept函数。客户端创建套接字后即可调用connect函数去试图连接服务器监听套接字。当服务器端的accept函数返回后,connect函数也返回。此时客户端使用socket函数创建的套接字,服务器端使用accept函数创建的套接字,双方就可以通信了。2.3Winsock编程详解2.3Winsock编程详解2.3.3TCP服务器和客户端程序举例下面是最简单的TCP服务器程序和TCP客户端程序的例子。这两个程序都是控制台界面的Win32应用程序,分别在配套光盘的TCPServer和TCPClient工程下。运行服务器程序TCPServer,如果没有错误发生,将在本地机器上的4567端口上等待客户端的连接。如果没有连接请求,服务器会一直处于休眠状态。运行服务器之后,再运行客户端程序TCPClient,其最终效果如图2.3所示。客户端连接到了服务器,双方套接字可以通信了。程序见TCPServer2.3Winsock编程详解2.3.4UDP编程TCP由于可靠、稳定的特点而被用在大部分场合,但它对系统资源要求比较高。UDP是一个简单的面向数据报的传输层协议,又叫用户数据报协议。它提供了无连接的、不可靠的数据传输服务。无连接是指它不像TCP那样在通信前先与对方建立连接以确定对方的状态。不可靠是指它直接按照指定IP地址和端口号将数据包发出去,如果对方不在线的话数据就可能丢失。1.UDP编程流程(1)服务器端程序设计流程如下。①创建套接字(socket)。②绑定IP地址和端口(bind)。③收发数据(sendto/recvfrom)。④关闭连接(closesocket)。(2)客户端程序设计流程如下。①创建套接字(socket)。②收发数据(sendto/recvfrom)。③关闭连接(closesocket)。UDP用于发送和接收数据的函数是sendto和recvfrom,它们的用法如下。同样,UDP接收数据时也需要知道通信对端的地址信息。intrecvfrom(SOCKETs,charFAR*buf,intlen,intflags,structsockaddrFAR*from,intFAR*fromlen);这个函数比recv函数多出

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论