文章目录
一、计算机网络基础
IP地址
为了使网络上的计算机能够彼此识别对方,每台计算机都需要一个IP地址以标识自己。IP地址由IP协议规定的32位的二进制数表示,最新的IPv6协议将IP地址升为128位,这使得IP地址更加广泛,能够很好地解决目前IP地址紧缺的情况,但是IPv6协议距离实际应用还有一段距离。目前,多数操作系统和应用软件都是以32位的IP地址为基准。
32位的IP地址主要分为两部分:前缀和后缀。前缀表示计算机所属的物理网络,后缀确定该网络上的唯一一台计算机。在互联网上,每一个物理网络都有唯一的网络号,根据网络号的不同,可以将IP地址分为5类,即A类、B类、C类、D类和E类。其中,A类、B类和C类属于基本类,D类用于多播发送,E类属于保留。表16.1描述了各类IP地址的范围。
在上述IP地址中,有几个IP地址是特殊的,有其单独的用途。
- 网络地址:在IP地址中主机地址为0的表示网络地址。例如128.111.0.0
- 广播地址:在网络号后跟所有位全是1的IP地址,表示广播地址
- 回送地址:127.0.0.1表示回送地址,用于测试
OSI七层参考模型
地址解析
所谓地址解析是指将计算机的协议地址解析为物理地址,即MAC(Medium Access Control)地址,又称为媒体访问控制地址。通常,在网络上由地址解析协议(ARP)来实现地址解析。
ARP协议解析地址的过程
假设主机A和主机B处于同一个物理网络上,主机A的IP为192.168.1.21,主机B的IP为192.168.1.23,当主机A与主机B进行通信时,主机B的IP地址192.168.1.23将按如下步骤被解析为物理地址。
(1)主机A从本地ARP缓存中查找IP为192.168.1.23对应的物理地址。用户可以在命令窗口中输入“arp -a”命令查看本地ARP缓存
(2)如果主机A在ARP缓存中没有发现192.168.1.23映射的物理地址,将发送ARP请求帧到本地网络上的所有主机,在ARP请求帧中包含了主机A的物理地址和IP地址。
(3)本地网络上的其他主机接收到ARP请求帧后,检查是否与自己的IP地址匹配,如果不匹配,则丢弃ARP请求帧。如果主机B发现与自己的IP地址匹配,则将主机A的物理地址和IP地址添加到自己的ARP缓存中,然后主机B将自己的物理地址和IP地址发送到主机A,当主机A接收到主机B发来的信息,将以这些信息更新ARP缓存。
(4)当主机B的物理地址确定后,主机A就可以与主机B进行通信了。
域名系统
虽然使用IP地址可以标识网络中的计算机,但是IP地址容易混淆,并且不容易记忆,人们更倾向于使用主机名来标识IP地址。由于在Internet上存在许多的计算机,为了防止主机名相同,Internet管理机构采取了在主机名后加上后缀名的方法标识一台主机,其后缀名被称为域名。例如,www.mingrisoft.com,主机名为www,域名为mingrisoft.com。这里的域名为二级域名,其中com为一级域名,表示商业组织,mingrisoft为本地域名。为了能够利用域名进行不同主机间的通信,需要将域名解析为IP地址,称之为域名解析。域名解析是通过域名服务器来完成的。
TCP/IP协议
TCP/IP(Transmission Control Protocal/Internet Protocal,传输控制协议/网际协议)是互联网上最流行的协议,它能够实现互联网上不同类型操作系统的计算机相互通信。对于网络开发人员,必须了解TCP/IP协议的结构。TCP/IP协议将网络分为4层,分别对应于OSI参考模型的7层结构。下表列出了TCP/IP协议与OSI参考模型的对应关系。
TCP协议
传输控制协议(TCP)是一种提供可靠数据传输的通用协议,它是TCP/IP体系结构中传输层上的协议。在发送数据时,应用层的数据传输到传输层,加上TCP的首部,数据就构成了报文。报文是网际层IP的数据,如果再加上IP首部,就构成了IP数据报。TCP协议C语言数据描述如下:
typedef struct HeadTCP
{
WORD SourcePort; /*16位源端口号*/
WORD DePort; /*16位目的端口*/
DWORD SequenceNo; /*32位序号*/
DWORD ConfirmNo; /*32位确认序号*/
BYTE HeadLen; /*与Flag为一个组成部分, 首部长度,占4位,保留6位,6位标识,共16位*/
BYTE Flag;
WORD WndSize; /*16位窗口大小*/
WORD CheckSum; /*16位校验和*/
WORD UrgPtr; /*16位紧急指针*/
} HEADTCP;
IP协议
IP协议又称为网际协议。它工作在网络层,主要提供无链接数据报传输。IP协议不保证数据报的发送,但可以最大限度地发送数据。IP协议C语言数据描述如下:
typedef struct HeadIP
{
unsigned char headerlen:4; /*首部长度,占4位*/
unsigned char version:4; /*版本,占4位 */
unsigned char servertype; /*服务类型,占8位,即1个字节*/
unsigned short totallen; /*总长度,占16位*/
unsigned short id; /*与idoff构成标识,共占16位,前3位是标识,后13位是片偏移*/
unsigned short idoff;
unsigned char ttl; /*生存时间,占8位*/
unsigned char proto; /*协议,占8位*/
unsigned short checksum; /*首部检验和,占16位*/
unsigned int sourceIP; /*源IP地址,占32位*/
unsigned int destIP; /*目的IP地址,占32位*/
}HEADIP;
ICMP协议
ICMP协议又称为网际控制报文协议。它负责网络上设备状态的发送和报文检查,可以将某个设备的故障信息发送到其他设备上。ICMP协议C语言数据描述如下:
typedef struct HeadICMP
{
BYTE Type; /*8位类型*/
BYTE Code; /*8位代码*/
WORD ChkSum; /*16位校验和*/
} HEADICMP;
UDP协议
用户数据报协议(UDP)是一个面向无连接的协议,采用该协议,两个应用程序不需要先建立连接,它为应用程序提供一次性的数据传输服务。UDP协议不提供差错恢复,不能提供数据重传,因此该协议传输数据安全性略差。UDP协议C语言数据描述如下:
typedef struct HeadUDP
{
WORD SourcePort; /*16位源端口号*/
WORD DePort; /*16位目的端口*/
WORD Len; /*16为UDP长度*/
WORD ChkSum; /*16位UDP校验和*/
} HEADUDP;
端口
在网络上,计算机是通过IP地址来标识自己的,但是当涉及到两台计算机具体通信时,还会出现一个问题。假设主机A中的应用程序A1想与主机B中的应用程序B1通信,如果知道主机A中的是A1应用程序与主机B中的应用程序通信,而不是主机A中的其他应用程序与主机B中的应用程序通信,则当主机B接收到数据时,它如何知道数据是发往应用程序B1的呢?这是因为在主机B中可以同时运行多个应用程序。
为了解决上述问题,TCP/IP协议提出了端口的概念,用于标识通信的应用程序。当应用程序(严格说应该是进程)与某个端口绑定后,系统会将收到的给该端口的数据送往该应用程序。端口是用一个16位的无符号整数值来表示的,范围为0~65535,低于256的端口被作为系统的保留端口,用于系统进程的通信,不在这一范围的端口号被称为自由端口,可以由进程自由使用。
套接字的引入
为了更方便地开发网络应用程序,美国的伯克利大学在UNIX上推出了一种应用程序访问通信协议的操作系统调用套接字(socket)。socket的出现,使得程序员可以很方便地访问TCP/IP,从而开发各种网络应用的程序。后来,套接字被引进到Windows等操作系统,成为开发网络应用程序的有效工具。
套接字存在于通信区域中,通信区域也称为地址族,主要用于将通过套接字通信的进程的公有特性综合在一起。套接字通常只与同一区域的套接字交换数据。Windows Sockets只支持一个通信区域——AF_INET网际域,使用网际协议族通信的进程使用该域。
网络字节顺序
不同的计算机存放多字节值的顺序不同,有的机器在起始地址存放低位字节,有的机器在起始地址存放高位字节。基于Intel CPU的PC机采用低位先存的方式。为了保证数据的正确性,在网络协议中需要指定网络字节顺序,TCP/IP协议使用16位整数和32位整数的高位先存格式。由于不同的计算机存放数据字节的顺序不同,这样发送数据后当接收到该数据时,也有可能无法查看所接收到的数据。因此,在网络中不同主机间进行通信时,要统一采用网络字节顺序。
二、套接字概述
所谓套接字,实际上是一个指向传输提供者的句柄。在WinSock中,就是通过操作该句柄来实现网络通信和管理的。根据性质和作用的不同,套接字可以分为3种,分别为原始套接字、流式套接字和数据包套接字。
- 原始套接字:在原始套接字下接收的数据中包含IP头
- 流式套接字:大家熟悉的TCP协议采用的就是流式套接字
- 数据包套接字:UDP协议采用的就是数据包套接字
TCP的套接字的socket编程
基于TCP面向连接的socket编程的服务器端程序流程如下:
(1)创建套接字socket。
(2)将创建的套接字绑定(bind)到本地的地址和端口上。
(3)设置套接字的状态为监听状态(listen),准备接受客户端的连接请求。
(4)接受请求(accpet),同时返回得到一个用于连接的新套接字。
(5)使用这个新套接字进行通信(通信函数使用send/recv)。
(6)通信完毕,释放套接字资源(closesocket)。
基于TCP面向连接的socket编程的客户端程序流程如下:
(1)创建套接字socket。
(2)向服务器发出连接请求(connect)。
(3)请求连接后与服务器进行通信操作(send/recv)。
(4)释放套接字资源(closesocket)。
注意:在服务器端要建立套接字绑定到指定的主机IP和端口上等待客户的请求,但是对于客户端来说,当发起连接请求并被接受后,在服务器端就保存了该客户端的IP地址和端口号的信息。对于服务器端来说,一旦建立连接之后,实际上它已经保存了客户端的IP地址和端口号的信息了,因此可以利用返回的套接字进行与客户端的通信
UDP的套接字的socket编程
基于UDP面向无连接的socket编程的发送端程序流程如下:
(1)创建套接字socket。
(2)将套接字绑定(bind)到一个本地地址和端口上。
(3)等待接收数据(recvfrom)。
(4)释放套接字资源(closesocket)。
基于UDP面向无连接的socket编程的接收端程序流程如下:
(1)创建套接字socket。
(2)向服务器发送数据(sendto)。
(3)释放套接字资源(closesocket)。
注意:在基于UDP的套接字编程中,还是需要使用bind进行绑定。因为虽然面向无连接的socket编程无须建立连接,但是为了完成通信,首先应该启动接收端来接收发送端发送的数据,这样接收端就必须告诉它的地址和端口,才能接受信息。因此,必须调用bind函数将套接字绑定到一个本地地址和端口上。
三、套接字函数
WSAStartup函数
该函数的功能是初始化套接字库。
该函数的原型如下:
int WSAStartup (WORD wVersionRequested, LPWSADATA lpWSAData );
-
wVersionRequested:表示调用者使用的Windows
Socket的版本,高字节记录修订版本,低字节记录主版本。例如,如果Windows
Socket的版本为2.1,则高字节记录1,低字节记录2。 -
lpWSAData:是一个WSADATA结构指针,该结构详细记录了Windows套接字的相关信息。
socket函数
该函数的功能是创建一个套接字。
该函数的原型如下:
SOCKET socket ( int af,int type, int protocol );
参数说明:
- af:表示一个地址家族,通常为AF_INET。
- tpe:表示套接字类型,如果为SOCK_STREAM,表示创建面向连接的流式套接字;为SOCK_DGRAM,表示创建面向无连接的数据报套接字;为SOCK_RAW,表示创建原始套节字。对于这些值,用户可以在Winsock2.h头文件中找到。
- potocol:表示套接口所用的协议,如果用户不指定,可以设置为0。
- 返回值:创建的套接字句柄。
listen函数
该函数的功能是将套接字设置为监听模式。对于流式套接字,必须处于监听模式才能够接收客户端套接字的连接。
该函数的原型如下:
int listen ( SOCKET s, int backlog);
参数说明:
- s:表示套接字标识。
- backlog:表示等待连接的最大队列长度。例如,如果backlog被设置为2,此时有3个客户端同时发出连接请求,那么前2个客户端连接会放置在等待队列中,第3个客户端会得到错误信息。
accpet函数
该函数的功能是接受客户端的连接。在流式套接字中,只有在套接字处于监听状态,才能接受客户端的连接。
该函数的原型如下:
SOCKET accept ( SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen );
参数说明:
-
s:是一个套接字,它应处于监听状态。
-
addr:是一个sockaddr_in结构指针,包含一组客户端的端口号、IP地址等信息。
-
addrlen:用于接收参数addr的长度。
-
返回值:一个新的套接字,它对应于已经接受的客户端连接,对于该客户端的所有后续操作,都应使用这个新的套接字。
closesocket函数
该函数的功能是关闭套接字。
该函数的原型如下:
int closesocket (SOCKET s);
参数说明:
- s:标识一个套接字。如果参数s设置了SO_DONTLINGER选项,则调用该函数后会立即返回,但此时如果有数据尚未传送完毕,则会继续传递数据,然后才关闭套接字。
connect函数
该函数的功能是发送一个连接请求。
该函数的原型如下:
int connect (SOCKET s,const struct sockaddr FAR* name,int namelen );
参数说明:
- s:表示一个套接字。
- name:表示套接字s要连接的主机地址和端口号。
- namelen:是name缓冲区的长度。
- 返回值:如果函数执行成功,返回值为0,否则为SOCKET_ERROR。用户可以通过WSAGETLASTERROR得到其错误描述。
htons函数
该函数的功能是将一个16位的无符号短整型数据由主机排列方式转换为网络排列方式。
该函数的原型如下:
u_short htons (u_short hostshort );
参数说明:
-
hostshort:是一个主机排列方式的无符号短整型数据。
-
返回值:函数返回值是16位的网络排列方式数据。
htonl函数
该函数的功能是将一个无符号长整型数据由主机排列方式转换为网络排列方式。
该函数的原型如下:
u_long htonl ( u_long hostlong);
参数说明:
- hostlong:表示一个主机排列方式的无符号长整型数据。
- 返回值:32位的网络排列方式数据。
inet_addr函数
该函数的功能是将一个由字符串表示的地址转换为32位的无符号长整型数据。
该函数的原型如下:
unsigned long inet_addr (const char FAR * cp);
参数说明:
- cp:表示一个IP地址的字符串。
- 返回值:32位无符号长整数。
recv函数
该函数的功能是从面向连接的套接字中接收数据。
该函数的原型如下:
int recv (SOCKET s,char FAR* buf,int len,int flags);
参数说明:
- s:表示一个套接字。
- buf:表示接收数据的缓冲区。
- len:表示buf的长度。
- flags:表示函数的调用方式。如果为MSG_PEEK,则表示查看传来的数据,在序列前端的数据会被复制一份到返回缓冲区中,但是这个数据不会从序列中移走;如果为MSG_OOB,则表示用来处理Out-Of-Band数据,也就是外带数据。
send函数
该函数的功能是在面向连接方式的套接字间发送数据。
该函数的原型如下:
int send (SOCKET s,const char FAR * buf, int len,int flags);
参数说明:
- s:表示一个套接字。
- buf:表示存放要发送数据的缓冲区。
- len:表示缓冲区长度。
- flags:表示函数的调用方式。
recvfrom函数
该函数用于接收一个数据报信息并保存源地址。
该函数的原型如下:
int recvfrom (SOCKET s, char FAR* buf, int len, int flags, struct sockaddr FAR* from, int FAR* fromlen );
参数说明:
- s:表示准备接收数据的套接字。
- buf:指向缓冲区的指针,用来接收数据。
- len:表示缓冲区的长度。
- flags:通过设置这个值可以影响函数调用的行为。
- from:是一个指向地址结构的指针,用来接收发送数据方的地址信息
- fromlen:表示缓冲区的长度。
sendto函数
该函数的功能是向一个特定的目的方发送数据。
该函数的原型如下:
int sendto (SOCKET s,const char FAR * buf,int len,int flags,const struct sockaddr FAR * to,int tolen );
- s:表示一个(可能已经建立连接的)套接字的标识符。
- buf:指向缓冲区的指针,该缓冲区包含将要发送的数据。
- len:表示缓冲区的长度。
- flags:通过设置这个值可以影响函数调用的行为。
- to:指定目标套接字的地址。
- tolen:表示缓冲区的长度。
WSACleanup函数
该函数的功能是释放为Ws2_32.dll动态链接库初始化时分配的资源。
该函数的原型如下:
int WSACleanup (void);
使用该函数关闭动态链接库:
WSACleanup(); /*关闭动态链接库*/