资讯专栏INFORMATION COLUMN

网络协议及编码

fizz / 2695人阅读

摘要:协议族协议协议协议应用程序通过套接字对协议和协议所提供的服务进行访问。网络层完成将分组报文传输到它的目的地,即路由功能,一般采用协议。程序间达成的这种包含了信息交换的形式和意义的共识称为协议。

TCP/IP协议族:IP协议、TCP协议、UDP协议

应用程序通过套接字API对UPD协议和TCP协议所提供的服务进行访问。

底层由物理层:基础的通信信道构成,如以太网/WIFI或调制解调器拨号连接。

网络层:完成将分组报文(packet)传输到它的目的地,即路由功能,一般采用IP协议。IP协议提供了一种数据服务:每组分组报文都有网络独立处理和分发,就像信件或包裹通过邮政系统发送一样。每个IP报文必须包含一个保存其目的地址的字段。

传输层:提供了TCP协议和UDP协议,这两种协议都建立在IP层提供的服务之上,IP协议只是将分组报文分发到不同的主机,然后还需要更细粒度的寻址将报文发送到主机指定的应用程序端口,这个寻址功能是TCP或UDP要完成的,因此他们也成为端到端的传输协议。IP协议只是将数据从一个主机传到另一个主机。

TCP协议提供一个可信赖的字节流信道(处理报文丢失、重传及顺序混乱问题),一种面向连接的协议:在使用它进行通信之前,两个应用程序之间首先要建立一个TCP连接,这涉及到两台机器的TCP部件完成握手消息的交互。

UDP协议不尝试对IP层产生的错误进行修复,仅仅是简单地扩展IP协议,传输到端口。

网络层中的IP协议就像把邮件送到某个街道的某个楼的信箱,而传输层则是将信件送到该楼层的具体某个房间里头。

IP协议其实是单播协议,还有多播协议,广播到任意数量的地址。

什么是套接字(Socket)

一种抽象层,应用程序通过它来发送和接受数据,就像应用程序打开一个文件句柄,将数据读写到稳定的存储器上一样。使用socket可以将应用程序添加到网络中,并与处于同一个网络中的其他应用程序进行通信。一台计算机上的应用程序向socket写入的信息能够被另一台计算机上的另一个应用程序读取。

不同类型的socket与不同类型的底层协议族以及同一个协议族的不同协议栈相关联。

TCP/IP协议族中的主要socket类型为流套接字(stream scoket)和数据报套接字(datagram socket)。

流套接字将TCP作为其端对端协议,提供了一个可信赖的字节流服务。
数据报套接字使用UDP协议,提供了一个best-effort的数据报服务。

一个套接字抽象层可以被多个应用程序引用,每个使用了特定套接字的程序都可以通过那个套接字进行通信。每个端口都标识了一台主机上的一个应用程序。实际上,一个端口确定了一台主机上的一个套接字。

任何要交换信息的程序之间在信息的编码方式上必须达成共识(比如将信息表示为位序列),以及哪个程序发送消息,什么时候和怎么接受信息都将影响程序的行为。程序间达成的这种包含了信息交换的形式和意义的共识称为协议。用来实现特定应用程序的协议称为应用程序协议。

TCP/IP协议的唯一约束是,信息必须在块(chunk)中发送和接收,而块的长度必须是8位的倍数,因此我们可以认为TCP/IP协议中传输的信息是字节序列。

如果是自己设计和编写套接字的客户端和服务器端,则可以随心所欲地定义自己的应用程序协议。

对于需要超过一个字节来表示的数据类型,我们必须知道这些字节的发送顺序。
一种是从整数的右边开始,由低位到高位发送,即little-endian顺序;一种是从左边开始,由高位到低位发送,即big-endian顺序。

对于任何多字节的整数,发送者和接收者必须在使用big-endian顺序还是使用little-endian顺序上达成共识。

另外一个需要达成的共识是:所传输的数值是有符号的还是无符号的。
对于给定的k位,我们可以通过二进制补码来表示-2的k-1次方到2的k-1次方-1范围的值,如果使用无符号,则可以表示0到2的k次方-1之间的数值。

DataOutputStream允许你将基本数据类型按big-endian顺序进行编码,即将整数以适当大小的二进制补码的形式写到流中。

在一组符号与一组整数之间的映射称为编码字符集,比如ASCII(将英文字母、数字、标点符号以及一些特殊符号映射为0-127的整数),Unicode(映射到0~65535之间的的整数)。
编码方案:发送者和接收者需要对这些整数如何表示成字节序列达成一致。

字符集:charset,由编码字符集和字符的编码方法结合起来。

Java的输入输出流

Java里头内置了特定的序列化,隐藏了所有繁琐的参数编码解码细节。Serialization处理了将实际的Java对象转换成字节序列的工作,因此你可以在不同虚拟机之间传递Java对象实例。
缺点是:它们比较笼统,在通信开销上不能做到最高效,比如一个对象的序列化形式其包含的信息在JVM环境以外是毫无意义的;其次是Serializable和Externalizable接口不能用于已经定义了不同传输格式的情况;最后用户自定义的类必须自己实现序列化接口,容易出错。

自定义协议实例: 1、基于文本的编码方式
public byte[] toWire(VoteMsg msg) throws IOException {
        String msgString = MAGIC + DELIMSTR + (msg.isInquiry() ? INQSTR : VOTESTR)
                + DELIMSTR + (msg.isResponse() ? RESPONSESTR + DELIMSTR : "")
                + Integer.toString(msg.getCandidateID()) + DELIMSTR
                + Long.toString(msg.getVoteCount());
        byte data[] = msgString.getBytes(CHARSETNAME);
        return data;
    }

文本方式通常使用一个魔数来开头。

2、基于二进制的编码方式

二进制格式使用固定大小的消息,每条消息由一个特殊字节开始(魔数)。

/* Wire Format
 *                                1  1  1  1  1  1
 *  0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 * |     Magic       |Flags|       ZERO            |
 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 * |                  Candidate ID                 |
 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 * |                                               |
 * |         Vote Count (only in response)         |
 * |                                               |
 * |                                               |
 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 */
public byte[] toWire(VoteMsg msg) throws IOException {
        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
        DataOutputStream out = new DataOutputStream(byteStream); // converts ints
        short magicAndFlags = MAGIC;
        if (msg.isInquiry()) {
            magicAndFlags |= INQUIRE_FLAG;
        }
        if (msg.isResponse()) {
            magicAndFlags |= RESPONSE_FLAG;
        }
        out.writeShort(magicAndFlags);
        // We know the candidate ID will fit in a short: it"s > 0 && < 1000
        out.writeShort((short) msg.getCandidateID());
        if (msg.isResponse()) {
            out.writeLong(msg.getVoteCount());
        }
        out.flush();
        byte[] data = byteStream.toByteArray();
        return data;
    }

TCP协议是一个基于流的服务,因而需要提供字节的帧。

// Create an inquiry request (2nd arg = true)
        VoteMsg msg = new VoteMsg(false, true, CANDIDATEID, 0);
        byte[] encodedMsg = coder.toWire(msg);
        // Send request
        System.out.println("Sending Inquiry (" + encodedMsg.length + " bytes): ");
        System.out.println(msg);
        framer.frameMsg(encodedMsg, out);
这里采用了基于显示长度的方式来标识帧大小:LengthFarmer类为每条消息添加一个长度前缀。
public void frameMsg(byte[] message, OutputStream out) throws IOException {
        if (message.length > MAXMESSAGELENGTH) {
            throw new IOException("message too long");
        }
        // write length prefix
        out.write((message.length >> BYTESHIFT) & BYTEMASK);
        out.write(message.length & BYTEMASK);
        // write message
        out.write(message);
        out.flush();
    }

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/65889.html

相关文章

  • 以太坊连载(二):如何使用Homestead文档以太坊路线图

    摘要:以太坊的使用基础指南通过本节可以获取用户参与到以太坊项目中的基本方法。的发布是以太坊平台的第二个主要版本,也是以太坊发布的第一个正式版本。硬分叉变更以太坊从狭义上来说,是一系列协议。 以太坊的使用:基础指南通过本节可以获取用户参与到以太坊项目中的基本方法。首先,要想成为网络中的节点,需要运行一个以太坊客户端。在选择客户端这一节中列出了多重实现,同时针对不同的安装应选择什么样的客户端给出...

    fireflow 评论0 收藏0
  • 造轮子系列(二): 史上最简单的长连接通信协议实现

    摘要:背景现在写客户端或者网页的时候越来越多的需要与长连接打交道尤其是在这个老板动不动就要搞一个聊天系统的时代后端大哥们于是分分钟就能造一个基于或者的消息协议出来但是问题在于每做一个新项目后端大哥们就能造出一个新协议而且能有各种神奇的限制比如说要 背景 现在写客户端或者网页的时候, 越来越多的需要与长连接打交道, 尤其是在这个老板动不动就要搞一个聊天系统的时代, 后端大哥们于是分分钟就能造一...

    Alliot 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<