第2章Socket套接字编程•套接字是由美国伯克利大学提出并设计的一种在网络中不同主机之间进行数据交换的通信桥梁。在实际生活中,人们所使用的网络通信软件功能均是基于Socket套接字作为通信桥梁实现。所以,套接字在网络编程中,有着非常重要的作用。本章将向用户介绍使用Socket套接字编程的相关概念以及实现方法。2.1寻址方式和字节顺序•在讲解套接字编程前,用户需要首先了解一下什么是寻址方式和字节顺序。在Socket套接字编程中,为了准确定位通信双方和数据传输的有效性、完整性,编程时必须使用统一的寻址方式和字节排列顺序。2.1.1寻址方式•因为套接字需要在各种网络协议中使用,所以为了区分程序所使用的网络协议必须使用统一的寻址方式。例如,在TCP/IP协议通信中,用户使用IP地址和端口号进行确定通信双方。而在其他的协议中不一定也使用该方式确定通信双方。•在Winsock(SocketAPI)中,用户可以使用TCP/IP地址家族中统一的套接字地址结构解决TCP/IP寻址中可能出现的问题。•该套接字地址结构定义如下:•structsockaddr_in•{•shortsin_family;//指定地址家族即地址格式•unsignedshortsin_port;//端口号码•structin_addrsin_addr;//IP地址•charsin_zero[8];//需要指定为0•};•在这个结构中,成员sin_family指定使用该套接字地址的地址家族。在这里必须设置为AF_INET,表示程序所使用的地址家族是TCP/IP。•注意:该结构的最后一个成员并未实际使用,主要是为了与第一个版本的套接字地址结构大小相同而设置。在实际使用时,将这8个字节直接设为0即可。•该结构成员变量sin_addr表示32位的IP地址结构。其结构定义如下:•structin_addr•{•union•{•struct•{•unsignedchars_b1,s_b2,s_b3,s_b4;•}S_un_b;//用4个u_char字符描述IP地址•struct•{•unsignedshorts_w1,s_w2;•}S_un_w;//用2个u_short类型描述IP地址•unsignedlongS_addr;//用1个u_long类型描述IP地址•}S_un;•};•通常,用户在网络编程中使用1个u_long类型的字符进行描述IP地址即可。例如,使用IP地址结构in_addr进行描述IP地址“218.6.132.5”。代码如下:•sockaddr_inaddr;•addr.sin_addr.S_un.S_addr=inet_addr(218.6.132.5);•在程序中,首先定义sockaddr_in结构对象addr,然后为IP地址结构in_addr中的成员S_addr赋值。因为结构成员S_addr所描述的IP地址均为网络字节顺序,所以程序调用inet_addr()函数将字符串IP转换为以网络字节顺序排列的IP地址。2.1.2字节顺序•在Socket套接字编程中,传输数据的排列顺序以网络字节顺序和主机字节顺序为主。通常情况下,如果用户将数据通过网络发送时,需要将数据转换成以网络字节顺序排列,否则可能造成数据损坏。如果用户是将网络中接收到的数据存储在本地计算机上,那么需要将数据转换成以主机字节顺序排列。从数据存储的角度来讲,网络字节顺序即将数据中最重要的字节首先进行存储,而主机字节顺序则将不重要的字节首先存储。•注意:IP地址结构in_addr中的成员S_addr的值均是以网络字节顺序排列。1.字节顺序转换函数•在Winsock中提供了几个关于网络字节顺序与主机字节顺序之间的转换函数。函数定义如下:•//将一个u_short类型的IP地址从主机字节顺序转换到网络字节顺序•u_shorthtons(u_shorthostshort);•//将一个u_long类型的IP地址从主机字节顺序转换到网络字节顺序•u_longhtonl(u_longhostlong);•//将一个u_long类型的IP地址从网络字节顺序转换到主机字节顺序•u_longntohl(u_longnetlong);•//将一个u_short类型的IP地址从网络字节顺序转换到主机字节顺序•u_shortntohs(u_shortnetshort);•//将一个字符串IP转换到以网络字节顺序排列的IP地址•unsignedlonginet_addr(constcharFAR*cp);•//将一个以网络字节顺序排列的IP地址转换为一个字符串IP•charFAR*inet_ntoa(structin_addrin);•以上函数的使用均与操作系统平台无关。因此,用户使用这些函数编写的程序能在所有操作系统平台中运行。2.实例程序•在本节中,将编写实例程序向用户讲解字节顺序转换函数的用法。代码如下:•...//省略部分代码•sockaddr_inaddr;//定义套接字地址结构变量•addr.sin_family=AF_INET;//指定地址家族为TCP/IP•addr.sin_port=htons(80);//指定端口号•//将字符串IP转换为网络字节顺序排列的IP•addr.sin_addr.S_un.S_addr=inet_addr(127.0.0.1);•//将网络字节顺序排列的IP转换为字符串IP•charaddres[]=inet_ntoa(addr.sin_addr.S_un.S_addr);•在程序中,用户首先使用函数inet_addr()将字符串IP“127.0.0.1”转换为以网络字节顺序排列的IP并保存在IP地址结构成员S_addr中。然后,再使用函数inet_ntoa()则将该成员所表示的IP值转换成字符串IP。2.1.3Socket相关函数•由于Windows网络程序开发均是基于Windows套接字实现,所以本节将重点介绍MFC中的CSocket类以及使用CSocket类编程的基本流程。1.创建套接字•使用CSocket类创建套接字对象是通过该类的构造函数创建的。其原型如下:•CSocket::CSocket();•例如,用户创建CSocket类对象,代码如下:•CSocketsock;•如果用户需要创建套接字对象指针,则应该使用关键字new进行创建。代码如下:•CSocket*sock;//定义套接字指针对象•sock=newCSocket;//使用new关键字创建套接字2.绑定地址信息•如果用户创建服务器套接字,那么用户应该调用该类的函数Bind()将套接字对象与服务器地址信息绑定在一起。其原型如下:•BOOLBind(constSOCKADDR*lpSockAddr,intnSockAddrLen);•该函数的作用是将套接字对象与服务器地址结构绑定在一起。如果函数调用成功,则返回true。否则,返回false。参数lpSockAddr指定将要绑定的服务器地址结构,参数nSockAddrLen表示地址结构的长度。•例如,用户将上面创建的套接字对象与地址结构绑定。代码如下:•CSocketsock;•//创建套接字对象•sockaddr_inaddr;•//定义套接字地址结构变量•addr.sin_family=AF_INET;•//指定地址家族为TCP/IP•addr.sin_port=htons(80);•//指定端口号•//将字符串IP转换为网络字节顺序排列的IP•addr.sin_addr.S_un.S_addr=inet_addr(127.0.0.1);•sock.Bind((SOCKADDR*)addr,sizeof(addr));•//绑定套接字与地址结构•...//省略部分代码•在服务器端,当地址信息绑定套接字成功后,还需要调用函数Listen()在指定端口监听客户端的连接请求。函数Listen()的原型如下:•BOOLListen(intnConnectionBacklog=5);•参数nConnectionBacklog表示套接字监听客户端请求的最大数目。该参数的有效范围是1~5。默认为5,表示该套接字只能监听5个客户端所发送的连接请求。•例如,套接字监听5个客户端的连接请求,代码如下:•CSocketsock;•//创建套接字对象•sockaddr_inaddr;•//定义套接字地址结构变量•addr.sin_family=AF_INET;•//指定地址家族为TCP/IP•addr.sin_port=htons(80);•//指定端口号•//将字符串IP转换为网络字节顺序排列的IP•addr.sin_addr.S_un.S_addr=inet_addr(127.0.0.1);•sock.Bind((SOCKADDR*)addr,sizeof(addr));•//绑定套接字与地址结构•sock.Listen(5);•//监听端口3.连接服务器•客户端创建套接字成功以后,可以调用函数Connect()向服务器发送连接请求。函数原型如下:•BOOLConnect(constSOCKADDR*lpSockAddr,intnSockAddrLen);•该函数调用成功,则返回true。否则,将返回false。参数lpSockAddr表示将连接的服务器地址结构。参数nSockAddrLen表示地址结构的长度大小。•例如,服务器IP地址为“127.0.0.1”,端口为80,客户端连接服务器,代码如下:•CSocketsock;•//创建套接字对象•sockaddr_inaddr;•//定义套接字地址结构变量•addr.sin_family=AF_INET;•//指定地址家族为TCP/IP•addr.sin_port=htons(80);•//指定端口号•//将字符串IP转换为网络字节顺序排列的IP•addr.sin_addr.S_un.S_addr=inet_addr(127.0.0.1);•sock.Connect((SOCKADDR*)addr,sizeof(addr));•//连接服务器4.数据交换•无论是服务器,还是客户端都是通过函数Send()和Receive()进行数据交换。函数原型如下:•virtualintSend(constvoid*lpBuf,intnBufLen,intnFlags=0);•virtualintReceive(void*lpBuf,intnBufLen,intnFlags=0);•其中,函数Send()用于发送指定缓冲区的数据,函数Receive()用于接收对方发送的数据,并将数据存放在指定缓冲区中。参数lpBuf表示数据缓冲区地址。参数nBufLen表示数据缓冲区的大小。参数nFlags表示数据发送或接收的标志,一般情况下,该参数均设置为0。•例如,使用这两个函数进行数据的发送和接收。代码如下:•...•//省略部分代码•charbuff[]='a';•//定义并初始化数据缓冲区•sock.Send(&buff,sizeof(buff),0);•//发送数据缓冲区中的数据•sock.Receive(&buff,sizeof(buff),0);•//接收数据并将数据存放在数据缓冲区中5.关闭套接字对象•当服务器和客户端的通信完成以后,用户还必须调用函数Close()将套接字对象关闭。否则,程序可能在退出时发生错误。该函数原型如下:•virtualvoidClose();•例如,客户端关闭套接字对象,代码如下:•...•//省略部分代码•sock.Close();•//关闭套接字对象•套接字关闭的同时,也将服务器与客户端之间连接关闭了。•本节主要向用户介绍了CSocket类的常用函数以及用法。当用户创建VC应用程序时,如果没有为应用程序指定支持WindowsSocket,那么用户必须手动添加该类的头文件afxsock.h。否则,程序将不能使用CSocket类。2.2Winsock网络程序开发流程•本节将向用户讲述基于Wind