应用程序经过套接字API对UPD协议和TCP协议所提供的服务进行访问。java
底层由物理层:基础的通讯信道构成,如以太网/WIFI或调制解调器拨号链接。服务器
网络层:完成将分组报文(packet)传输到它的目的地,即路由功能,通常采用IP协议。IP协议提供了一种数据服务:每组分组报文都有网络独立处理和分发,就像信件或包裹经过邮政系统发送同样。每一个IP报文必须包含一个保存其目的地址的字段。网络
传输层:提供了TCP协议和UDP协议,这两种协议都创建在IP层提供的服务之上,IP协议只是将分组报文分发到不一样的主机,而后还须要更细粒度的寻址将报文发送到主机指定的应用程序端口,这个寻址功能是TCP或UDP要完成的,所以他们也成为端到端的传输协议。IP协议只是将数据从一个主机传到另外一个主机。socket
TCP协议提供一个可信赖的字节流信道(处理报文丢失、重传及顺序混乱问题),一种面向链接的协议:在使用它进行通讯以前,两个应用程序之间首先要创建一个TCP链接,这涉及到两台机器的TCP部件完成握手消息的交互。ui
UDP协议不尝试对IP层产生的错误进行修复,仅仅是简单地扩展IP协议,传输到端口。编码
网络层中的IP协议就像把邮件送到某个街道的某个楼的信箱,而传输层则是将信件送到该楼层的具体某个房间里头。spa
IP协议实际上是单播协议,还有多播协议,广播到任意数量的地址。设计
一种抽象层,应用程序经过它来发送和接受数据,就像应用程序打开一个文件句柄,将数据读写到稳定的存储器上同样。使用socket能够将应用程序添加到网络中,并与处于同一个网络中的其余应用程序进行通讯。一台计算机上的应用程序向socket写入的信息可以被另外一台计算机上的另外一个应用程序读取。code
不一样类型的socket与不一样类型的底层协议族以及同一个协议族的不一样协议栈相关联。orm
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里头内置了特定的序列化,隐藏了全部繁琐的参数编码解码细节。Serialization处理了将实际的Java对象转换成字节序列的工做,所以你能够在不一样虚拟机之间传递Java对象实例。
缺点是:它们比较笼统,在通讯开销上不能作到最高效,好比一个对象的序列化形式其包含的信息在JVM环境之外是毫无心义的;其次是Serializable和Externalizable接口不能用于已经定义了不一样传输格式的状况;最后用户自定义的类必须本身实现序列化接口,容易出错。
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; }
文本方式一般使用一个魔数来开头。
二进制格式使用固定大小的消息,每条消息由一个特殊字节开始(魔数)。
/* 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(); }