socket是什么?它处于应用层和传输层之间的一个抽象层(就是你想象出来的它处于这两者之间),叫套接字(本质上是tcp/ip抽象出的一些方法可以通过它来调用,而我们在应用层可以使用它来进行通信),实现了一个接口的功能。
传输层广为使用的tcp/ip协议栈处于内核之中,我们对socket进行编程,就是主动编写(网络)进程间的通信(靠的是tcp/ip协议),实现一些真正通信上的服务功能。
在学习socket之前,我们首先应该先了解一下它的数据结构
//通用地址套接字结构
struct sockaddr{
uint8_t sin_len; //整个sockaddr的长度
sa_family_t sin_family;//地址家族,使用的协议
char sa_data[14]; //由sin_family决定它的形式
}
另外,在网络传输中还需要注意字节序的传输规定:
字节序是什么概念?
简单来说就是字节在内存中存储的格式,在大部分机器上分成两种:大端字节序和小端字节序。
大端:最高有效位存储在最低内存地址处,最低有效位存储在最高内存地址处。
小端:最高有效位存储于最高内存地址处,最低有效位存储于最低内存地址处。
大体看下面这张图片就知道了:
很好理解,从2^7,2^6 ….. 2^0,前面的是低地址,但对内存而言却是高内存。
在网络中,我们统一了使用大端字节序。
说了这么多,终于到了下面我们最经常用的几个字节序转换函数:
//这里是主机字节序 --> 网络字节序,在传输数据时候使用
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
//这里是网络字节序 --> 主机字节序,在数据到本地重新编码的时候使用
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
其中s—short两个字节,l—long四个字节,h—host,n—network
另外还有常用的一些地址转换函数:
int inet_aton(const char* cp,struct in_addr* inp);
in_addr_t inet_addr(const char* cp);
char* inet_ntoa(struct in_addr_in);
为什么要转换地址函数?
一般我们所熟悉的地址一般是32位ip地址,但是在网络传输中,机器不会认识这种格式的地址。所以,我们需要将它转换成为网络传输中所识别的地址,一般是一个10位的整形数字。
在上面的三种函数中,一般客户端要绑定本机某个地址的时候使用第二种方法。
在实际使用中我们继续讨论。
接下来我们再引入一个概念——字节流
由于TCP是基于字节流的传输数据,所以它的发送端不限定发送的数据的大小,因为接收端可以不限次数地去接收,同时它是端对端的,什么校验,去重,按序号整合啥的都有,因为TCP是非常稳定的,这唯一的不足之处就是相对UDP效率低。
知道了字节流,那就说下套接字的几种类型(也就是所谓的协议家族)
1.流式套接字(stream):面向连接的,可靠的数据传输服务,数据无差错,无重复的发送,而且按发送顺序接受
2.数据报套接字(dgram):无连接服务。可能丢失,重复,混乱
3.原始套接字(raw):允许对低层(IP层)的协议直接访问,它可以自行组装数据包(伪装本地IP,本地MAC),可以接受本机网卡上的数据帧。
另外还得再提一下一个数据结构,就是servaddr的内部结构,这里举一个ipv4的sockarrd_in(它的socket结构,其他的socket结构都类似,就是在通用socket上感觉地址家族决定了不同的data数据结构)
struct sockaddr_in{
uint8_t sin_len;
//实际中ipv4的协议家族是AF_INET
sa_family_t sin_family;
//上面的两个是通用socket都有的基本元素
in_port_t sin_port;
//网络(in ternet)地址,4个字节
struct in_addr sin_addr;
//保留字段,其中的8是sockaddr总字节大小减去结构体中所有其他成员所占字节得到的数字
unsigned char sin_zero[8];
}
好了,大概了解上面所提到的所有知识点之后,下一章开始写代码。