socket概念
socket的中文翻译为插座,插座,一直就在那里,你想要充电、想要使用电器,把插头插上就能供电。从这方面理解,socket就是一套网络通讯的编程接口,当使用者想要开发不同主机间通讯的程序时,只需要提供目的主机IP地址和端口号,socket就能自动调用传输层、网络层、数据链路层的协议,包装好报文,发送出数据包。可以看到,socket是屏蔽了各个协议栈的,对使用者非常友好。重复造轮子是耗时耗力的,socket就是这个网络通讯的轮子,我们使用它就好了。
以上是个人理解,下面来点官方的:
所谓 socket(套接字),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。
Linux下,一切皆文件,socket也不例外。所以,当创建一个socket时,我们就拿到了操作socket的文件描述符fd。文件嘛,内核就会给它生成读写缓冲区,发送端创建socket,使用socket的文件描述符往写缓冲区放数据,传送,接收端接收到socket,使用对应文件描述符从读缓冲区读数据。这也是一个进程间通讯的方法,有点类似于管道,但是区别在于:管道用于本地进程间通讯,socket用于网络进程间通讯。
socket最重要的是指定IP地址和端口号,又因为不同主机内存排列顺序可能不同,所以引出了字节序的问题。
字节序
现代 CPU 的累加器一次都能装载(至少)4 字节(这里考虑 32 位机),即一个整数。那么这 4字节在内存中排列的顺序将影响它被累加器装载的整数的值,这就是字节序问题。有两个字节序:
大端字节序
大端字节序是指一个整数的最高位字节(23 ~ 31 bit)存储在内存的低地址处,低位字节(0 ~ 7 bit)存储在内存的高地址处,比如下面这个:
如果按照大端字节序,那么它表示的十六进制数为:0x12 34 56 78 11 22 33 44(12在数字中是高位)。大端字节序符合人的习惯,奥,那起码符合我的习惯。
小端字节序
小端字节序则是指整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的低地址处,还是这个例子:
如果按照小端字节序,那么它表示的十六进制数为:0x44 33 22 11 78 56 34 12
举个例子:古代人喜欢从右往左书写,阅读;现代人喜欢从左往右书写,阅读。如果按照现代人的思维去看古代人的书籍,而没有说明这个规则,那你必然是看不懂的。主机间socket通讯也存在这个问题,所以socket定义了相应的方法来解决这个问题。
字节序转化
#include <arpa/inet.h>
// 转换端口
uint16_t htons(uint16_t hostshort); // 主机字节序 - 网络字节序
uint16_t ntohs(uint16_t netshort); // 主机字节序 - 网络字节序
// 转IP
uint32_t htonl(uint32_t hostlong); // 主机字节序 - 网络字节序
uint32_t ntohl(uint32_t netlong); // 主机字节序 - 网络字节序
转换函数包含在arpa/inet.h头文件中,h对应host,n对应network,s对应short,l对应long。端口号范围是2^16,所以端口转化只需要short型,IP地址32位,4字节,需要int32。网络中数据都是按照大端字节序的,而主机知道自己的字节序,因此只需要在主机和网络间进行字节序转换。
清楚了字节序,我们可以指定地址、端口去创建一个socket,而为了方便开发者,这一套socket接口定义好了一些地址族供我们使用。
地址族
首先是socket定义的地址结构体:
通常使用sockaddr_in,即IPv4。
按照人的习惯,都喜欢把IP地址写成点分十进制,因此这一套接口也实现了IP地址字符串和网络字节序相互转化的函数。
#include <arpa/inet.h>
// p:点分十进制的IP字符串,n:表示network,网络字节序的整数
int inet_pton(int af, const char *src, void *dst);
/*
af:地址族: AF_INET AF_INET6
src:需要转换的点分十进制的IP字符串
dst:转换后的结果保存在这个里面
*/
// 将网络字节序的整数,转换成点分十进制的IP地址字符串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
/*
af:地址族: AF_INET AF_INET6
src: 要转换的ip的整数的地址
dst: 转换成IP地址字符串保存的地方
size:第三个参数的大小(数组的大小)
返回值:返回转换后的数据的地址(字符串),和 dst 是一样的
*/