#define SOMAXCONN 0x7fffffff
接受连接请求的函数定义如下:
SOCKET accept(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen);
该函数从连接请求队列中获得连接信息,创建新的套接字描述符,获取客户端地址。新创建的套接字用于和客户端进行通信,在服务器和客户端通信完成后,该套接字也需要使用closesocket()函数进行关闭,以释放相应的资源。该函数有3个参数,第1个参数s是处于监听的套接字描述符,第2个参数addr是一个指向sockaddr结构体的指针,用来返回客户端的地址信息,第3个参数addrlen是一个指向int型的指针变量,用来传入sockaddr结构体的大小。
上面介绍的是面向连接的服务器端的函数,完成了一系列服务器应有的基本的动作,具体如下。
① bind()函数将套接字描述符与地址信息进行绑定。
② listen()函数将绑定过套接字描述符置于监听状态。
③ accept()函数获取连接队列中的连接信息,创建新的套接字描述符,以便与客户端通信。
面向连接的客户端只需要完成与服务器的连接这样一个动作就可以实现和服务器端的通信了。创建套接字描述符后,使用connect()函数就可以完成与服务器的连接。
connect()函数的定义如下:
int connect(SOCKET s, const struct sockaddr FAR *name, int namelen);
该函数的作用是将套接字进行连接。该函数有3个参数,第1个参数s表示创建好的套接字描述符,第2个参数name是指向sockaddr结构体的指针,sockaddr结构体中保存了服务器的IP地址和端口号,第3个参数namelen是指定sockaddr结构体的长度。
当客户端使用connect()函数与服务器连接后,客户端和服务器就可以进行通信了。通信时主要就是信息的发送和信息的接收。这里介绍的函数有两个,分别是send()和recv()。
发送函数send()的定义如下:
int send(SOCKET s, const char FAR *buf, int len, int flags);
该函数有4个参数,第1个参数s是套接字描述符,该套接字描述符对于服务器端而言,使用的是accept()函数返回的套接字描述符,对于客户端而言,使用的是socket()函数创建的套接字描述符,第2个参数buf是发送消息的缓冲区,第3个参数len是缓冲区的长度,第4个参数flags通常赋为0值。
接收函数recv()的定义如下:
int recv(SOCKET s, char FAR *buf, int len, int flags);
该函数有4个参数。该函数的使用方法与send()函数的使用方法相同,这里不再进行介绍。
从send()和recv()两个函数的名称来看分别是发送和接受的意思,但是实际上对于数据的发送和接收依靠的是网络协议来完成的,send()函数和recv()函数只是完成了将数据从网络协议所使用的缓冲区中进行拷贝的一个动作。
4. 非面向连接协议的函数
在面向连接的TCP中,服务器端将套接字描述符与地址进行绑定后,需要将端口进行监听,等待接受客户端的连接请求,而在客户端则需要连接服务器,完成这些步骤就可以保证面向连接的TCP的可靠传输,在调用connect()函数的过程中也完成了TCP的“三次握手”的过程。非面向连接的UDP协议在开发上基本与面向连接TCP的协议相同。在非面向连接的UDP开发中,服务器端不需要对端口进行监听,也就不需要等待接受客户端的连接请求,而客户端也不需要完成与服务器端的连接。中间的“三次握手”过程也就省略了,这样UDP相对于TCP来讲就显得不可靠了,但是在效率方面却要快于TCP。
在非面向连接协议开发中,服务器端不再需要调用listen()、accept()函数,客户端不再需要调用connect()函数。而服务器和客户端的通信函数使用sendto()和recvfrom()函数即可。
sendto()函数的定义如下:
int sendto( SOCKET s, const char FAR *buf, int len, int flags, const struct sockaddr FAR *to, int tolen);
该函数是来用在UDP通信双方进行发送数据的函数,该函数有6个参数,第1个参数s是套接字描述符,第2个参数buf是要发送数据的缓冲区,第3个参数len是指定第2个参数的长度,第4个参数通常赋0值,第5个参数to是一个指向sockaddr结构体的指针,这里给出接收消息的地址信息,第6个参数tolen是指定第5个参数的长度。
recvfrom()函数的定义如下:
int recvfrom( SOCKET s, char FAR* buf, int len, int flags, struct sockaddr FAR *from, int FAR *fromlen);
该函数是用来在UDP通信双方进行接收数据的函数。该函数的用法与sendto()相同,这里不再进行介绍。
sendto()函数和recvfrom()函数的功能与send()函数和recv()函数类似,它们都是用于向网络协议缓冲区进行数据复制的函数,并不是真正的去完成数据的发送和接收的。
04 字节顺序
字节序的存在是由于不同架构CPU在访问数据时所采取的顺序不同,在计算机内存中对数值的存储有一定的标准,而该标准随着系统架构的不同而不同。了解字节存储顺序对于逆向工程是一项基础的知识,在动态分析程序的时候,往往需要观察内存数据的变化情况,如果不了解字节存储顺序,那么可能会迷失在内存的汪洋大海中而无法继续逆向航行。
1. 字节序基础
通常情况下,数值在内存中存储的方式有两种,一种是大尾方式(大尾字节序就是网络字节序),另一种是小尾方式。
先来看一个简单的例子,比如0x01020304这样一个数值,如果用大尾方式存储,其存储方式为01 02 03 04,而用小尾方式存储则是04 03 02 01。这样表示也许不直观,用表格的形式展示其具体的区别,如表3所示。
表3 字节顺序的对比表
从表中可以得到如下结论。
大尾存储方式:内存高位地址存放数据低位字节数据,内存低位地址存放数据高位字节数据;
小尾存储方式:内存高位地址存放数据高位字节数据,内存低位地址存放数据低位字节数据。
2. 主机字节序与网络字节序
主机字节序与网络字节序是相对的概念。
所谓主机字节序,是指主机在存储数据时的字节顺序,主机字节序根据系统架构的不同而不同。通常情况下,Windows操作系统兼容的CPU为小尾方式,而UNIX操作系统所兼容的CPU多为大尾方式。因此,主机字节序并非固定的字节序,需要根据不同的系统架构进行确定。
所谓网络字节序,是指网络传输相关协议所规定的字节传输顺序,TCP/IP所使用的字节序为大尾方式。
3. 字节序相关函数
涉及字节序常用的相关函数有htons()、htonl()、ntohs()和ntohl()。这4个函数的定义分别如下:
u_short htons(u_short hostshort);u_long htonl(u_long hostlong);u_short ntohs(u_short netshort);u_long ntohl(u_long netlong);
在Windows下,使用以上4个转换函数会改变值的大小,因为其在内存中的存放方式改变了。如果在UNIX系统下,使用以上4个转换函数是不会发生任何改变的。无论是何种系统,在进行网络开始时都需要调用这些函数进行转换,因为这样做可以有效的保证在网络中传输的确实是网络字节序。
4. 编程判断主机字节序
“编程判断主机字节序”是很多杀毒软件公司或者是安全开发职位的一道面试题,因为这题比较基础。这里给出对于该题目的实现方法。完成该题目有两种方法,第1种方法是“取值比较法”,第2种方法是“直接转换比较法”。
方法一:取值比较法
所谓取值比较法,首先定义一个4字节的十六进制数。因为使用调试器查看内存最直观的就是十六进制值,所以定义十六进制数是一个操作起来比较直观的方法。而后通过指针方式取出这个十六进制数在“内存”中的某一字节,最后和实际数值中相对应的数进行比较。由于字节序的问题,内存中的某字节与实际数值中对应的字节可能不同,这样就可以确定字节序了。
代码如下:
int main(int argc, char* argv[]){ DWORD dwSmallNum = 0x01020304; if ( *(BYTE *)&dwSmallNum == 0x04 ) { printf("Small Sequence. \r\n"); } else { printf("Big Sequence. \r\n"); } return 0;}
以上代码中,定义了0x01020304这个十六进制数,其在小尾方式内存中的存储顺序为04 03 02 01。取*(BYTE *)&dwSmallNum内存中低地址位的值,如果是小尾方式的话,那么低地址位存储的值为0x04,如果是大尾方式则为0x01。
方法二:直接转换比较法
所谓直接转换比较法,是利用字节序转换函数将所定义的值进行转换,然后用转换后的值和原值进行比较。如果原值与转换后的值相同,说明为大尾方式,否则为小尾方式。
代码如下:
int main(int argc, char* argv[]){ DWORD dwSmallNum = 0x01020304; if ( dwSmallNum == htonl(dwSmallNum) ) { printf("Big Sequence. \r\n"); } else { printf("Small Sequence. \r\n"); } return 0;}
这种方法比较直接,如果转换后的结果与原值相等,就说明是大尾方式,因为转换后的结果是网络字节序,网络字节序等同于大尾方式。
关于字节序的内容大家一定要自行调试体会一下,因为在网络开发中只需要进行简单的转换即可,不需要过多的关心它的细节。而如果是做逆向工程时,在内存中要进行数据的查找时,这时字节序的知识会使用到了。
本文地址:百科问答频道 https://www.neebe.cn/wenda/916638_3.html,易企推百科一个免费的知识分享平台,本站部分文章来网络分享,本着互联网分享的精神,如有涉及到您的权益,请联系我们删除,谢谢!