网络分层

疑问

  • 为什么需要分层?
    网络传输流程比较复杂,涉及到传输稳定,安全性加密等等等等

  • 进程通信的网络版?
    是的,进程间通信在同一个计算机,没有什么外部影响,而网络会有各种各样的苛刻条件

意义:

  • 将一个非常复杂的过程分部,更效率地解决

学习重点:

  • 站在应用层,学习传输层的基础协议TCP/UDP和他们给用户层提供的接口的使用。

网络编程的一些元素

套接字 socket 插座

意义

  • 理解为一个端点,和文件描述符号一样,是一个句柄,提供数据交互的一个口

sockaddr_in

意义

  • 专门用来放ipv4地址和端口的结构体,而且是网络端序,无法直接用%s或者%d读取

套接字端点在希望与别人连接时,就应该要有一个sockaddr和他绑定,无论是为了连接别人(使用发送接口)还是暴露自己(使用接收接口)。

端口号

意义:

  • 提供精确到进程的点对点网络通信

除了1023个系统端口(没有注册权限)
和注册端口(尽量不要使用)
还有可以使用的49151~65535端口,而且同一个端口可以让两种协议同时使用,不冲突。

字节序

意义:

  • 提高跨平台能力,网络统一大端序

哪些内容需要转换大端序,哪些不用

  1. 以一个字节为单位的数据不用转换,比如字符,包括字符数组(字符串)
  2. 以多个字节为单位的数据需要转换,包括端口号,IP地址,int,short等等

典例:

  • 端口号:端口号转为网络字节后,再放到一个无符号短整型,如sockaddr_in.sin_port中,最后的s如果换成l表示无符号长整型,端口号使用s即可
1
2
3
4
5
6
7
//本地转网络
struct sockaddr_in s;
s.sin_port= htons(8080);

//网络转本地
uint16_t port = ntohs(s.sin_port);

  • IP地址,由于IP地址表示时是字符串,和端口号不一样
1
2
3
4
5
6
7
8
9
10
11
12
//本地字符串转换为网络字节
inet_pton(AF_INET,"192.168.1.1",&s.sin_addr);

//网络字节序转换为本地字符串
inet_ntop(AF_INET,&s.sin_addr,buf,sizeof(buf));//同时防止缓冲区溢出

//IPv4专用,本机字符串转换为网络字节
inet_aton("192.168.1.1",&s.sin_addr);

//IPv4专用,网络字节转换后放在静态缓冲区,下次调用会覆盖
char* ip = inet_ntoa(s.sin_addr)

UDP和TCP

UDP (user datagram protocol)

意义:

  • 提供一种方便快速的传输,但是顺序和完整性不保证

典例:

  • 小尺寸数据,DNS查询域名IP地址
  • 需要实时性但不严格完整性,流媒体
  • 更适合一对多,有自带的广播机制

过程:

  • 接收方
  1. 创建一个套接字,通信的端点
  2. 将一个sockaddr与这个端点绑定,暴露自己的信息,给别人连
  3. 使用接口recvfrom与这个端点内部相连,读取端点来的数据
  • 发送方
  1. 创建一个套接字,通信的端点
  2. 提供需要连接端点绑定的sockaddr,才知道信息发到哪里
  3. 使用接口sendto与这个端点内部相连,将数据搜到端点去

TCP (Transmission Control Protocol)

意义:

  • 提供一个不易丢失数据的连接方式,副作用是连接麻烦,占资源多

使用:

  • 传输的内容远远大于连接的占用资源
  • 需要严格保证数据完整性的情况

过程:

  • 接收方,创建socket、绑定bind(IP等)、设为监听listen、阻塞等待连接accept、读写数据(通过上一步返回的socket号)、断开
  • 发送方,创建socke、(不绑定)直接连接对方connect(对方IP)、读写数据、断开

细节:

  • 传输过程出现了3个套接字,分别是服务端的监听套接字,建立连接收发信息的套接字,客户端收发信息的套接字。connect套接字都是由一个监听套接字产生的
  • connect三次握手也是阻塞的,如果对方没有回复,将阻塞,超时会失败
  • 如何判断对方离线,释放网络套接字的必然时机?当调用recv时,如果离线,马上返回零,此时是绝对的释放时机。

三次握手

意义

  • 建立一个可靠的、全双工的连接,确保客户端和服务器双方都确认对方的接收能力。

过程

  1. 客户端向服务器发送一个SYN报文,包含一个初始序列号Seq=A(第一次握手)。
  2. 服务器收到SYN报文后,发送一个SYN-ACK报文,包含服务器的初始序列号Seq=B,并确认收到客户端的序列号Ack=A+1(第二次握手)。
  3. 客户端收到SYN-ACK后,发送一个ACK报文,确认服务器的序列号Ack=B+1,并通知服务器可以开始数据传输(第三次握手)。

安全性体现:

  • 伪造IP的客户端无法完成三次握手,因为它无法接收到服务器返回的SYN-ACK报文(其中包含的Seq=B和Ack=A+1),这些报文被路由到真正的IP地址。因此,伪造IP的客户端无法建立连接。

UDP和TCP的对比

从步骤上来说,UDP作为接收方时,少了设置监听和将等待连接和接收合成一步
UDP作为发送方时,将连接和发送合成一步

网络编程中的一些ioctl

意义

  • 提供了获取本地网络信息的接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//获取某个网口的ip内容
int fd = socket(AF_INET,SOCK_DGRAM,0);

struct ifreq i;

//将要查询的网口作为字符串放入结构体ifreq下的ifr_name
strcpy(i.ifr_name,"en0");

//调用这个宏后,需要的信息将会被填到ifreq下的ifr_ifru下的类型为sockaddr的联合体中
//IPV4应该拿到联合体地址之后转换为sockaddr_in*,取下面的sin_addr才是要的内容,需要显示还需要将网络字节转换一下
ioctl(fd,SIOCGIFADDR,&i);

printf("en0 IP : %s\n",inet_ntoa(((struct sockaddr_in*)&(i.ifr_ifru.ifru_addr))->sin_addr));
//再查
ioctl(fd,SIOCGIFBRDADDR,&i);
printf("en0 Broacast: %s\n",inet_ntoa(((struct sockaddr_in *)&(i.ifr_ifru.ifru_broadaddr))->sin_addr));

ioctl(fd,SIOCGIFNETMASK,&i);
printf("en0 MASK : %s\n",inet_ntoa(((struct sockaddr_in*)&(i.ifr_ifru.ifru_devmtu))->sin_addr));

return 0;

  • 还有一些常用获取地址信息方式
    getsockname( ) 获得本端套接字的地址信息
    getpeername( ) 获得对端套接字的地址信息
    gethostbyname( ) 根据主机名取得主机信息

一些经典模型

单线程设置非阻塞轮询

意义:

  • 一个简单的非阻塞模型

缺点很明显,让CPU利用率达到百分之百。

信号驱动模型

意义:
给UDP提供了异步信号处理机制,提高工作效率同时无需多线程处理也能达到并发

缺点:
如果UDP信号并发数量过大,可能丢失信号
信号模式让参数传递变得麻烦,可能不适合带参的情况,需要使用全局变量

结论:
信号驱动模型比较适合较轻量级,UDP,不需要传递参数到服务函数的情况

select 多路复用

意义:

  • 无需使用多线程,又可以接受多个套接字的任务,达到高并发,一种非常节省资源效率又高效的模型,TCP可用

疑问:

  • 哪些套接字放在读就绪,哪些放在写就绪?
  1. 希望接收消息的套接字放在读就绪
  2. 网络套接字也是放在读就绪,可以监控握手和挥手请求
  3. 一般写就绪状态被阻塞的情况是缓冲区满,消息堆积的情况,正常情况下不会阻塞写就绪

过程

  1. 声明一个客户端套接字数组,全部清零
  2. 开始无限循环
  3. 清空监听集,将数组中的所有非零套接字和监听套接字放到读监听集中,并且更新套接字最大值
  4. 设置监听,参数1:最大套接字+1,参数234:套接字集地址,NULL
  5. 监听套接字响应方式设为将新得到的客户端套接字放入数组,并更新套接字最大值
  6. 遍历非零套接字响应方式为接受消息返回消息等自定义操作,(客户端发送消息和断开连接都会触发套接字可读)根据接受消息的返回值可区分是否断开连接

详细

  • 多路复用利用了内核的事件通知机制,不需要进行轮询也能一直监控所有套接字的变化,非常高效省资源。

缺点

  • 对于需要消耗较多时间的连接,会阻塞其他连接,毕竟是单线程,因此只适合快连接快断开的情况。

指数补偿

设置一个阈值,越等越久,直到达到阈值# 超时控制
意义

超时控制

意义

  • 提供一个更加精确的网络阻塞控制方案

详细

  • TCP和UDP都有超时属性,有连接超时和读取数据超时

步骤

1.使用signal和alarm设置超时控制

TCP粘包问题

疑问

  • 粘包为什么会出现?

因为TCP发送是有缓冲机制的,多条小消息可能会合成一条长消息发送,而接受方会一次接到一条长消息,从而粘包。

解决方案

  • 1.通过分割符(类似JSON)发送,接收端能够识别消息
  • 2.消息长度前缀,先读取消息长度再根据长度读取内容(HTTP)
  • 3.约定固定长度(使用结构化数据)
  • 4.分段发送,先发OK,这边收到确认,再发下一段数据

OneNet云平台

Cjson

疑问

  • json数据不是都一样吗?为什么要分出一个Cjson,C的json和java的json难道不一样?

意义

  • 提供了C中可以使用的json库,对嵌入式设备优化更好

注意

  • 在json中,有键值对和对象两个概念,对象object是需要创建节点的,而键值对只需要add函数添加
  • 对象嵌套键是一个键不是一个对象,不需要创建节点
  • 数组健是一个键,不需要创建对象,但是数组元素是对象,需要创建节点

HTTP

意义

  • 提供了更完善的传输协议

特点

  • HTTP是将 TCP/IP协议封装的应用层的协议
  • 无连接无状态,每次请求与响应无关联

请求报文

  1. 请求行:请求方法 URL 协议版本\r\n
  2. 请求头:头部字段:值\r\n
  3. \r\n
  4. 请求包体(一般为空)

响应报文

  1. 状态行:协议版本 状态码 状态码描述\r\n
  2. 响应头:头部字段:值\r\n
  3. \r\n
  4. 响应包体:数据块
  5. 响应包体:2a\r\n数据块\r\n4ad\r\n数据块\r\n (分块发送的情况)