2.2 网络编程基础知识

上面对两台计算机之间如何进行通信有了一个大概的了解,现在开始真正学习Windows下网络编程的相关知识。

2.2.1 通信模型

既然是通信,必然要遵循一个通信模型,常见的通信模型有C/S模型(即客户端/服务端模型)、B/S模型(即浏览器/服务端模型)。常见的木马是C/S模式的,不过也有木马是B/S模式的,只不过这种模式相对少见。接下来主要介绍C/S模型的开发。

C/S模型的开发,就是在服务器端上绑定一个IP地址和一个端口号,然后进行监听,等待客户端的连接,而客户端则是向服务器端指定的IP地址和端口号发起连接,服务器接受客户端的连接后,双方可以进行通信,这是基于TCP协议的通信。而基于UDP协议就简单多了,服务器只要绑定IP地址和端口号就可以了,客户端不需要进行连接直接就可以和服务器进行通信。从基于TCP和基于UDP的服务器可以看出,TCP比UDP要可靠,而UDP要比TCP效率高。

2.2.2 Winsock

Windows下的网络应用开发大部分是通过Winsock完成的(除了Winsock以外还有其他的),Winsock有两种开发模式,一种是阻塞模式,另一种是非阻塞模式。阻塞模式是基于同步的开发方式,非阻塞模式是基于异步的开发方式。非阻塞模式结合了Windows的消息机制,更符合Windows下的开发。在我们的学习过程中主要讨论非阻塞模式的开发。

下面开始讲解Windows下网络开发所需要了解的API函数。在介绍这些函数的时候,笔者只重点介绍部分参数,详细的参数介绍不是本书的重点,但是很多函数都有其固定的参数,大家会用即可。

2.2.3 Winsock的相关函数

每个需要使用Winsock进行网络开发的Windows应用程序都必须包含Winsock2.h(这是第二个版本的Winsock库),除了这个头文件以外,还有一个静态库ws2_32.1ib。在使用它们的时候需要对这个库进行一次初始化,使用完毕后要对该库进行释放。下面分别介绍这两个函数。

首先来看初始化ws2_32.dll动态链接库的函数:

2.2 网络编程基础知识 - 图1

这个函数是用来初始化ws2_32. dll动态链接库的,这个动态链接库是所有网络应用程序会加载的动态链接库,在使用这个动态链接库时就需要用WSAStartup()函数进行初始化。如果不初始化这个动态链接库,其余相关的基于这个动态链接库的网络函数的调用都会失败。

参数说明如下。

(1) wVersionRequested: Windows Sockets API提供的调用方可使用的最高版本号。高位字节指出副版本(修正)号,低位字节指出主版本号。

(2) lpWSAData:指向WSADATA数据结构的指针,用来接收Windows Sockets实现的细节。

释放ws2_32.dll动态链接库:

2.2 网络编程基础知识 - 图2

这个函数是结束这个动态链接库的,一般在程序退出时使用。

创建套接字:

2.2 网络编程基础知识 - 图3

参数说明如下。

(1) af:指定应用程序使用的通信协议族,对于TCP/IP协议族,该参数始终为PF_INET。有的教材里这个参数使用的是AF_INET.AF_INET是地址族,虽然使用这个没错,但还是建议使用PF_INET。

(2) type:指定要创建的套接字的类型,流套接字类型为SOCK_STREAM,数据包套接字类型为SOCK_DGRAM。前者通常是TCP协议使用,后者通常是UDP协议使用;

(3) protocal:指定应用程序所使用的通信协议。该参数根据第二个参数的不同而不同,第二个参数为SOCK_STREAM,该参数为IPPROTO_TCP;如果第二个参数为SOCK_DGRAM,那么该参数为IPPROTO_UDP。

该函数的返回值是一个新创建的类型为SOCKET的套接字的描述符。

关闭套接字:

2.2 网络编程基础知识 - 图4

程序结束时要对Socket创建的套接字进行关闭,完成资源的释放。

参数说明如下。

s: socket()函数创建的套接字描述符。

当创建了一个Socket后,服务器程序必须要绑定一个IP地址和特定的端口号。客户端程序不需要绑定端口号和IP地址,因为Socket会选择合适IP地址及端口来使用。

绑定IP地址和端口号:

2.2 网络编程基础知识 - 图5

参数说明如下。

(1) s:指定待绑定的Socket描述符。

(2) name:指定一个sockaddr结构,该结构的定义如下:

2.2 网络编程基础知识 - 图6

函数中提供的参数类型是sockaddr,在实际使用的过程中,结构体是sockaddr_in,该结构的定义如下:

2.2 网络编程基础知识 - 图7

成员变量sin_family设置为PF_INET; sin_port设置为端口号;sin_addr结构体中只包含一个公用体,in_addr的定义如下:

2.2 网络编程基础知识 - 图8

2.2 网络编程基础知识 - 图9

该成员变量是一个整数,一般用函数inet_addr()把字符串形式的IP地址转换成unsigned long整型的整数值。

namelen:指定name缓冲区的长度。

inet_addr函数的原型如下:

2.2 网络编程基础知识 - 图10

参数cp为一个点分多进制的IP地址。

inet_addr函数的逆函数如下:

2.2 网络编程基础知识 - 图11

参数为一个addr_in类型的变量。

监听端口:

2.2 网络编程基础知识 - 图12

参数说明如下。

(1) s:使流套接字s处于监听状态。

(2) backlog:为处于监听状态的流套接字s维护一个客户连接请求队列。

接受请求:

2.2 网络编程基础知识 - 图13

服务端程序调用该函数从处于监听状态的流套接字的客户端请求队列中取出第一个请求,并创建一个新的套接字与客户端进行连接通信。

参数说明如下。

(1) s:指定处于监听状态的套接字。

(2) addr:用来返回新创建的套接字的地址。

(3) addrlen:用来返回新创建套接字的地址结构的长度。

连接函数如下:

2.2 网络编程基础知识 - 图14

客户端程序调用该函数来完成与远程服务器端的连接。

参数说明如下。

(1) s:客户端创建的套接字。

(2) name:该结构中包含了要服务器端中的IP地址和端口号。  (3) namelen:指定name缓冲区的长度。

具体进行通信的函数分为两类,一类是基于TCP协议的,另一类是基于UDP协议的。数据的通信主要体现在数据的收发上,分别看一下这两种协议的收发数据的函数定义。

基于TCP的发送函数:

2.2 网络编程基础知识 - 图15

参数说明如下。

(1) s:指定发送端套接字描述符。

(2) buf:指明一个存放应用程序要发送数据的缓冲区。

(3) len:指明实际要发送到数据的字节数。

(4) flags:一般设置为0。

基于TCP的接收函数:

2.2 网络编程基础知识 - 图16

参数说明如下。

(1) s:指定接收端套接字描述符。

(2) buf:指定一个缓冲区,用来存放接收到的数据。

(3) len:指定缓冲区的长度。

(4) flags:一般设置为0。

基于UDP的发送函数:

2.2 网络编程基础知识 - 图17

基于UDP的接收函数:

2.2 网络编程基础知识 - 图18

2.2.4 字节顺序

数据在存储器中是按一定方式的,根据不同的CPU架构,其存储方式也不相同。比如Intel ×86CPU架构使用小尾顺序,即高位存放高字节,低位存放低字节。例如0×12345678,在内存里的表示方式为78 56 34 12。

还有一种是大尾方式,即按网络字节顺序,也就是说所有的网络中传输的数据使用的均是大尾方式。大尾方式是高位存放低字节,低位存放高字节。例如0×12345678,在内存里的表示方式为12 34 56 78,如图2-1所示。

2.2 网络编程基础知识 - 图19

图2-1 字节顺序

本地字节顺序根据CPU架构的不同可能是小尾方式,也可能是大尾方式,但是对于网络字节顺序来说,一定是大尾方式。Winsock提供了一些用来处理本地的字节顺序和网络的字节顺序的转换函数。这些函数如下:

本地字节顺序转换为网络字节顺序:

2.2 网络编程基础知识 - 图20

网络字节顺序转换为本地字节顺序:

2.2 网络编程基础知识 - 图21

当我们在调试软件的时候,经常会在内存中查找某一个数据,但是怎么也找不到,在这个时候就应该想到字节顺序的问题。在做破解和外挂的时候,不了解这些基本概念的话经常会感到困惑,请大家一定要注意这个细小的问题。