1.再谈 "协议"
1.1.协议的概念
协议,网络协议的简称,网络协议是通信计算机双方必须共同遵从的一组约定,比如怎么建立连接、怎么互相识别等。
为了使数据在网络上能够从源到达目的,网络通信的参与方必须遵循相同的规则,我们将这套规则称为协议(protocol),而协议最终都需要通过计算机语言的方式表示出来。只有通信计算机双方都遵守相同的协议,计算机之间才能互相通信交流。
注:
1.我们在套接字部分所用的socket、bind、listen、accept、read/write、connect、recvform、sendto等接口都是操作系统向上提供的,属于应用层的接口。
2.为了满足不同的应用场景,早已经有很多前辈给我们写好了应用层协议,例如:http、https、DNS、FTP、SMTP等。我们之前学习网络通信写的套接字代码其实是在造轮子,也可以说是在定制化协议服务。
协议是一种 " 约定 "。socket api 的接口 , 在读写数据时 , 都是按 " 字符串 " 的方式来发送接收的 。 如果我们要传输一些 "结构化的数据"(例如结构体) 怎么办呢?
1.2. 结构化数据的传输
通信双方在进行网络通信时:
· 如果需要传输的数据是一个字符串,那么直接将这一个字符串发送到网络当中,此时对端也能从网络当中获取到这个字符串。
· 但如果需要传输的是一些结构化的数据(例如结构体),此时就不能将这些数据一个个发送到网络当中。比如现在要实现一个网络版的计算器,那么客户端每次给服务端发送的请求数据当中,就需要包括左操作数、右操作数以及对应需要进行的操作,此时客户端要发送的就不是一个简单的字符串,而是一组结构化的数据。
如果客户端将这些结构化的数据单独一个个的发送到网络当中,那么服务端从网络当中获取这些数据时也只能一个个获取,此时服务端还需要纠结如何将接收到的数据进行组合。因此客户端最好把这些结构化的数据打包后统一发送到网络当中,此时服务端每次从网络当中获取到的就是一个完整的请求数据,客户端常见的“打包”方式有以下两种。
方案一:将结构化的数据组合成一个字符串
约定:
· 客户端发送一个形如“1+1”的字符串。
· 这个字符串中有两个操作数,都是整型。
· 两个数字之间会有一个字符是运算符。
· 数字和运算符之间没有空格。
客户端可以按某种方式将这些结构化的数据组合成一个字符串,然后将这个字符串发送到网络当中,此时服务端每次从网络当中获取到的就是这样一个字符串,然后服务端再以相同的方式对这个字符串进行解析,此时服务端就能够从这个字符串当中提取出这些结构化的数据。方案二:定制结构体+序列化和反序列化
· 定制结构体来表示需要交互的信息。
· 发送数据时将这个结构体按照一个规则转换成网络标准数据格式(序列化),接收数据时再按照相同的规则把接收到的数据转化为结构体(反序列化)。
客户端可以定制一个结构体,将需要交互的信息定义到这个结构体当中。客户端发送数据时先对数据进行序列化,服务端接收到数据后再对其进行反序列化,此时服务端就能得到客户端发送过来的结构体,进而从该结构体当中提取出对应的信息。
问题:为什么发送端不能将结构化数据打包为结构体,直接发送结构体对象,这样接收端将接收数据强转成对应的结构体类型,不就可以读取结构化数据了吗?
答:真正的网络协议其实就是像这样通过结构体进行传输的,但是我们绝对不能自定义结构体来进行传输。单纯的定义结构体对象进行传输会有很多问题,网络协议虽然是通过结构体进行传输数据的,但协议中还解决了很多现实问题。
这里我们举一些单纯自定义结构体对象进行传输存在的问题:
问题一:同一个结构体的大小在不同的对齐规则下是不同的。如果服务器和客户端是不同的操作系统,例如服务器是Linux,客户端是windows或安卓,那么同一个结构体其大小很可能是不同的
问题二:即使我们保证了服务端一定是Linux,客户端一定是windows,通过一定的转换规则可以使得同一个结构体大小相同。但是时代是进步的,经过一段时间,客户端程序和服务端程序已经迭代了很多个版本了,编译器也迭代了很多个版本了,如果有客户不对客户端进程更新,那么有些客户用的老编译器编译的老客户端程序,有些客户用的新编译器编译的新客户端程序,同一个结构体对于老客户端程序和新客户端程序其大小可能也不相同(因为新老编译器的区别),而服务端接收到的数据无法判断该数据是从旧客户端发的还是新客户端发的,如何选取转换规则也存在问题。