微信公众号【Java技术江湖】一位阿里 Java 工程师的技术小站。(关注公众号后回复”Java“便可领取 Java基础、进阶、项目和架构师等免费学习资料,更有数据库、分布式、微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送做者原创的Java学习指南、Java程序员面试指南等干货资源)
转自:https://mp.weixin.qq.com/s/XX...html
咱们是幸运的,由于咱们拥有网络。网络是一个神奇的东西,它改变了你和个人生活方式,改变了整个世界。 然而,网络的无标度和小世界特性使得它又是复杂的,无所不在,无所不能,以至于咱们没法区分甚至没法描述。java
对于一个码农而言,了解网络的基础知识可能仍是从了解定义开始,认识OSI的七层协议模型,深刻Socket内部,进而熟练地进行网络编程。node
关于网络python
关于网络,在词典中的定义是这样的:程序员
在电的系统中,由若干元件组成的用来使电信号按必定要求传输的电路或这种电路的部分,叫网络。web
做为一名从事过TMN开发的通讯专业毕业生,执拗地认为网络是从通讯系统中诞生的。通讯是人与人之间经过某种媒介进行的信息交流与传递。传统的通讯网络(即电话网络)是由传输、交换和终端三大部分组成,通讯网络是指将各个孤立的设备进行物理链接,实现信息交换的链路,从而达到资源共享和通讯的目的。通讯网络能够从覆盖范围,拓扑结构,交换方式等诸多视角进行分类...... 满满的回忆,仍是留在书架上吧。面试
网络的概念外延被不断的放大着,抽象的思惟能力是人们创新乃至创造的根源。网络用来表示诸多对象及其相互联系,数学上的图,物理学上的模型,交通网络,人际网络,城市网络等等,总之,网络被总结成从同类问题中抽象出来用数学中的图论科学来表达并研究的一种模型。算法
不少伙伴认为,了解这些以后呢,然并卵。咱们关心的只是计算机网络,算机网络是用通讯线路和设备将分布在不一样地点的多台计算机系统互相链接起来,按照网络协议,分享软硬件功能,最终实现资源共享的系统。特别的,咱们谈到的网络只是互联网——Internet,或者移动互联网,须要的是写互连网应用程序。可是,一位工做了五六年的编程高手曾对我说,如今终于了解到基础知识有多重要,技术在不断演进,而相对不变的就是那些原理和编程模型了。数据库
老码农深觉得然,编程实践就是从具体到抽象,再到具体,循环往复,螺旋式上升的过程。了解前世此生,只是为了可能触摸到“势”。基础越扎实,建筑就会越有想象的空间。 对于网络编程的基础,大概要从OSI的七层协议模型开始了。编程
七层模型(OSI,Open System Interconnection参考模型),是参考是国际标准化组织制定的一个用于计算机或通讯系统间互联的标准体系。它是一个七层抽象的模型,不只包括一系列抽象的术语和概念,也包括具体的协议。 经典的描述以下:
简述每一层的含义:
每一层利用下一层提供的服务与对等层通讯,每一层使用本身的协议。了解了这些,然并卵。可是,这一模型确实是绝大多数网络编程的基础,做为抽象类存在的,而TCP/IP协议栈只是这一模型的一个具体实现。
TCP/IP是Internet的基础,是一组协议的代名词,包括许多协议,组成了TCP/IP协议栈。TCP/IP 有四层模型和五层模型之说,区别在于数据链路层是否做为独立的一层存在。我的倾向于5层模型,这样2层和3层的交换设备更容易弄明白。当谈到网络的2层或3层交换机的时候,就知道指的是那些协议。
数据是如何传递呢?这就要了解网络层和传输层的协议,咱们熟知的IP包结构是这样的:
IP协议和IP地址是两个不一样的概念,这里没有涉及IPV6的。不关注网络安全的话,对这些结构没必要耳熟能详的。传输层使用这样的数据包进行传输,传输层又分为面向链接的可靠传输TCP和数据报UDP。TCP的包结构:
TCP 链接创建的三次握手确定是必知必会,在系统调优的时候,内核中关于网络的相关参数与这个图息息相关。UDP是一种无链接的传输层协议,提供的是简单不可靠的信息传输。协议结构相对简单,包括源和目标的端口号,长度以及校验和。基于TCP和UDP的数据封装及解析示例以下:
仍是然并卵么?一个数据包的大小了解了,会发现什么呢?PayLoad究竟是多少?在设计协议通讯的时候,这些都为咱们提供了粒度定义的依据。进一步,经过一个例子看看吧。
FTP是一个比较好的例子。为了方便起见,假设两条计算机分别是A 和 B,将使用FTP 将A上的一个文件X传输到B上。
首先,计算机A和B之间要有物理层的链接,能够是有线好比同轴电缆或者双绞线经过RJ-45的电路接口链接,也能够是无线链接例如WIFI。先简化一下,考虑局域网,暂不讨论路由器和交换机以及WIFI热点。这些物理层的链接创建了比特流的原始传输通路。
接下来,数据链路层登场,创建两台计算机的数据链路。若是A和B所在的网络上同时链接着计算机C,D,E等等,A和B之间如何创建的数据链路呢?这一过程就是物理寻址,A要在众多的物理链接中找到B,依赖的是计算机的物理地址即MAC地址,对就是网卡上的MAC地址。以太网采用CSMA/CD方式来传输数据,数据在以太网的局域网中都是以广播方式传输的,整个局域网中的全部节点都会收到该帧,只有目标MAC地址与本身的MAC地址相同的帧才会被接收。A经过差错控制和接入控制找到了B的网卡,创建可靠的数据通路。
那IP地址呢? 数据链路创建起来了,还须要IP地址么?咱们FTP 命令中制定的是IP地址而不是MAC地址呀?IP地址是逻辑地址,包括网络地址和主机地址。若是A和B在不一样的局域网中,中间有着多个路由器,A须要对B进行逻辑寻址才能够的。物理地址用于底层的硬件的通讯,逻辑地址用于上层的协议间的通讯。在以太网中:逻辑地址就是IP地址,物理地址就是MAC 地址。在使用中,两种地址是用必定的算法将他们两个联系起来的。因此,IP是用来在网络上选择路由的,在FTP的命令中,IP中的原地址就是A的IP地址,目标地址就是B的IP地址。这应该就是网络层,负责将分组数据从源端传输到目的端。
A向B传输一个文件时,若是文件中有部分数据丢失,就可能会形成在B上没法正常阅读或使用。因此须要一个可靠的链接,可以确保传输过程的完整性,这就是传输层的TCP协议,FTP就是创建在TCP之上的。TCP的三次握手肯定了双方数据包的序号、最大接受数据的大小(window)以及MSS(Maximum Segment Size)。TCP利用IP完成寻址,TCP中的提供了端口号,FTP中目的端口号通常是21。传输层的端口号对应主机进程,指本地主机与远程主机正在进行的会话。
会话层用来创建、维护、管理应用程序之间的会话,主要功能是对话控制和同步,编程中所涉及的session是会话层的具体体现。表示层完成数据的解编码,加解密,压缩解压缩等,例如FTP中bin命令,表明了二进制传输,即所传输层数据的格式。 HTTP协议里body中的Json,XML等均可以认为是表示层。应用层就是具体应用的自己了,FTP中的PUT,GET等命令都是应用的具体功能特性。
简单地,物理层到电缆链接,数据链路层到网卡,网络层路由到主机,传输层到端口,会话层维持会话,表示层表达数据格式,应用层就是具体FTP中的各类命令功能了。
了解了7层模型就能够编程了么,拿起编程语言就能够耍了么?刚开始上手尝试仍是能够的,若是要进一步,老码农以为仍是看看底层实现的好,由于一切归根到底都会归结为系统调用。到了操做系统层面如何看网络呢?Socket登场了。
在Linux世界,“一切皆文件”,操做系统把网络读写做为IO操做,就像读写文件那样,对外提供出来的编程接口就是Socket。因此,socket(套接字)是通讯的基石,是支持TCP/IP协议网络通讯的基本操做单元。socket实质上提供了进程通讯的端点。进程通讯以前,双方首先必须各自建立一个端点,不然是没有办法创建联系并相互通讯的。一个完整的socket有一个本地惟一的socket号,这是由操做系统分配的。
从设计模式的角度看, Socket实际上是一个外观模式,它把复杂的TCP/IP协议栈隐藏在Socket接口后面,对用户来讲,一组简单的Socket接口就是所有。当应用程序建立一个socket时,操做系统就返回一个整数做为描述符(descriptor)来标识这个套接字。而后,应用程序以该描述符为传递参数,经过调用函数来完成某种操做(例如经过网络传送数据或接收输入的数据)。
在许多操做系统中,Socket描述符和其余I/O描述符是集成在一块儿的,操做系统把socket描述符实现为一个指针数组,这些指针指向内部数据结构。进一步看,操做系统为每一个运行的进程维护一张单独的文件描述符表。当进程打开一个文件时,系统把一个指向此文件内部数据结构的指针写入文件描述符表,并把该表的索引值返回给调用者 。
既然Socket和操做系统的IO操做相关,那么各操做系统IO实现上的差别会致使Socket编程上的些许不一样。看看我Mac上的Socket.so 会发现和CentOS上的仍是些不一样的。
进程进行Socket操做时,也有着多种处理方式,如阻塞式IO,非阻塞式IO,多路复用(select/poll/epoll),AIO等等。
多路复用每每在提高性能方面有着重要的做用。select系统调用的功能是对多个文件描述符进行监视,当有文件描述符的文件读写操做完成以及发生异常或者超时,该调用会返回这些文件描述符。select 须要遍历全部的文件描述符,就遍历操做而言,复杂度是 O(N)。
epoll相关系统调用是在Linux 2.5 后的某个版本开始引入的。该系统调用针对传统的select/poll不足,设计上做了很大的改动。select/poll 的缺点在于:
epoll 是把 select/poll 单个的操做拆分为 1 个 epollcreate,多个 epollctrl和一个 wait。此外,操做系统内核针对 epoll 操做添加了一个文件系统,每个或者多个要监视的文件描述符都有一个对应的inode 节点,主要信息保存在 eventpoll 结构中。而被监视的文件的重要信息则保存在 epitem 结构中,是一对多的关系。因为在执行 epollcreate 和 epollctrl 时,已经把用户模式的信息保存到内核了, 因此以后即使反复地调用 epoll_wait,也不会重复地拷贝参数,不会重复扫描文件描述符,也不反复地把当前进程放入/拿出等待队列。
因此,当前主流的Server侧Socket实现大都采用了epoll的方式,例如Nginx, 在配置文件能够显式地看到 use epoll
。
了解了7层协议模型和操做系统层面的Socket实现,能够方便咱们理解网络编程。
在系统架构的时候,有重要的一环就是拓扑架构,这里涉及了网络等基础设施,那么7层协议下四层就会有助于咱们对业务系统网络结构的观察和判断。在系统设计的时候,每每采用面向接口的设计,而接口也每每是基于HTTP协议的Restful API。 那接口的粒度就能够将data segment做为一个约束了,同时能够关注到移动互联网中的弱网环境。
不一样的编程语言,有着不一样的框架和库,真正的编写网络程序代码并不复杂,例如,用Erlang 中 gen_tcp 用于编写一个简单的Echo服务器:
Start_echo_server()-> {ok,Listen}= gen_tcp:listen(1234,[binary,{packet,4},{reuseaddr,true},{active,true}]), {ok,socket}=get_tcp:accept(Listen), gen_tcp:close(Listen), loop(Socket). loop(Socket) -> receive {tcp,Socket,Bin} -> io:format(“serverreceived binary = ~p~n”,[Bin]) Str= binary_to_term(Bin), io:format(“server (unpacked) ~p~n”,[Str]), Reply= lib_misc:string2value(Str), io:format(“serverreplying = ~p~n”,[Reply]), gen_tcp:send(Socket,term_to_binary(Reply)), loop(Socket); {tcp_closed,Socket} -> Io:format(“ServerSocket closed ~n”) end.
然而,写出漂亮的服务器程序仍然是一件很是吃功夫的事情,例如,我的很是喜欢的python Tornado 代码, 在ioloop.py 中有对多路复用的选择:
@classmethod def configurable_default(cls): if hasattr(select, "epoll"): from tornado.platform.epoll import EPollIOLoop return EPollIOLoop if hasattr(select, "kqueue"): # Python 2.6+ on BSD or Mac from tornado.platform.kqueue import KQueueIOLoop return KQueueIOLoop from tornado.platform.select import SelectIOLoop return SelectIOLoop
在HTTPServer.py 中一样继承了TCPServer,进而实现了HTTP协议,代码片断以下:
class HTTPServer(TCPServer, Configurable, httputil.HTTPServerConnectionDelegate): ... def initialize(self, request_callback, no_keep_alive=False, io_loop=None, xheaders=False, ssl_options=None, protocol=None, decompress_request=False, chunk_size=None, max_header_size=None, idle_connection_timeout=None, body_timeout=None, max_body_size=None, max_buffer_size=None): self.request_callback = request_callback self.no_keep_alive = no_keep_alive self.xheaders = xheaders self.protocol = protocol self.conn_params = HTTP1ConnectionParameters( decompress=decompress_request, chunk_size=chunk_size, max_header_size=max_header_size, header_timeout=idle_connection_timeout or 3600, max_body_size=max_body_size, body_timeout=body_timeout) TCPServer.__init__(self, io_loop=io_loop, ssl_options=ssl_options, max_buffer_size=max_buffer_size, read_chunk_size=chunk_size) self._connections = set() ...
Java提供了很是易用的网络API,调用这些API咱们能够很方便的经过创建TCP/IP或UDP套接字,在网络之间进行相互通讯,其中TCP要比UDP更加经常使用,但在本教程中咱们对这两种方式都有说明。
在网站上还有其余三个与Java网络相关的教程,以下:
3.Java服务器多线程教程 (参与翻译能够联系咱们)
尽管Java网络API容许咱们经过套接字(Socket)打开或关闭网络链接,但全部的网络通讯均是基于Java IO类 InputStream和OutputStream实现的。
此外,咱们还可使用Java NIO API中相关的网络类,用法与Java网络API基本相似,Java NIO API能够以非阻塞模式工做,在某些特定的场景中使用非阻塞模式能够得到较大的性能提高。
Java TCP网络基础
一般状况下,客户端打开一个链接到服务器端的TCP/IP链接,而后客户端开始与服务器之间通讯,当通讯结束后客户端关闭链接,过程以下图所示:
ClientServerOpen ConnectionSend RequestReceive ResponseClose Connection
客户端经过一个已打开的链接能够发送不止一个请求。事实上在服务器处于接收状态下,客户端能够发送尽量多的数据,服务器也能够主动关闭链接。
Java中Socket类和ServerSocket类
当客户端想要打开一个链接到服务器的TCP/IP链接时,就要使用到Java Socket类。socket类只须要被告知链接的IP地址和TCP端口,其他的都有Java实现。
假如咱们想要打开一个监听服务,来监听客户端链接某些指定TCP端口的链接,那就须要使用Java ServerSocket类。当客户端经过Socket链接服务器端的ServerSocket监听时,服务器端会指定这个链接的一个Socket,此时客户端与服务器端间的通讯就变成Socket与Socket之间的通讯。
关于Socket类和ServerSocket类会在后面的文章中有详细的介绍。
Java UDP网络基础
UDP的工做方式与TCP相比略有不一样。使用UDP通讯时,在客户端与服务器之间并无创建链接的概念,客户端发送到服务器的数据,服务器可能(也可能并无)收到这些数据,并且客户端也并不知道这些数据是否被服务器成功接收。当服务器向客户端发送数据时也是如此。
正由于是不可靠的数据传输,UDP相比与TCP来讲少了不少的协议开销。
在某些场景中,使用无链接的UDP要优于TCP,这些在文章Java UDP DatagramSocket类介绍中会有更多介绍。
当咱们想要在Java中使用TCP/IP经过网络链接到服务器时,就须要建立java.net.Socket对象并链接到服务器。假如但愿使用Java NIO,也能够建立Java NIO中的SocketChannel对象。
建立Socket
下面的示例代码是链接到IP地址为78.64.84.171服务器上的80端口,这台服务器就是咱们的Web服务器(www.jenkov.com),而80端口就是Web服务端口。
Socket socket = new Socket("78.46.84.171", 80);
咱们也能够像以下示例中使用域名代替IP地址:
Socket socket = new Socket("jenkov.com", 80);
Socket发送数据
要经过Socket发送数据,咱们须要获取Socket的输出流(OutputStream),示例代码以下:
Socket socket = new Socket("jenkov.com", 80); OutputStream out = socket.getOutputStream(); out.write("some data".getBytes()); out.flush(); out.close(); socket.close();
代码很是简单,可是想要经过网络将数据发送到服务器端,必定不要忘记调用flush()方法。操做系统底层的TCP/IP实现会先将数据放入一个更大的数据缓存块中,而缓存块的大小是与TCP/IP的数据包大小相适应的。(译者注:调用flush()方法只是将数据写入操做系统缓存中,并不保证数据会当即发送)
Socket读取数据
从Socket中读取数据,咱们就须要获取Socket的输入流(InputStream),代码以下:
Socket socket = new Socket("jenkov.com", 80); InputStream in = socket.getInputStream(); int data = in.read(); //... read more data... in.close(); socket.close();
代码也并不复杂,但须要注意的是,从Socket的输入流中读取数据并不能读取文件那样,一直调用read()方法直到返回-1为止,由于对Socket而言,只有当服务端关闭链接时,Socket的输入流才会返回-1,而是事实上服务器并不会不停地关闭链接。假设咱们想要经过一个链接发送多个请求,那么在这种状况下关闭链接就显得很是愚蠢。
所以,从Socket的输入流中读取数据时咱们必需要知道须要读取的字节数,这能够经过让服务器在数据中告知发送了多少字节来实现,也能够采用在数据末尾设置特殊字符标记的方式连实现。
关闭Socket
当使用完Socket后咱们必须将Socket关闭,断开与服务器之间的链接。关闭Socket只须要调用Socket.close()方法便可,代码以下:
Socket socket = new Socket("jenkov.com", 80); socket.close();
Java 网络教程: ServerSocket
用java.net.ServerSocket实现java服务经过TCP/IP监听客户端链接,你也能够用Java NIO 来代替java网络标准API,这时候须要用到 ServerSocketChannel。
如下是一个建立ServerSocket类来监听9000端口的一个简单的代码
ServerSocket serverSocket = new ServerSocket(9000);
要获取请求的链接须要用ServerSocket.accept()方法。该方法返回一个Socket类,该类具备普通java Socket类的全部特性。代码以下:
ServerSocket serverSocket = new ServerSocket(9000); boolean isStopped = false;while(!isStopped){ Socket clientSocket = serverSocket.accept(); //do something with clientSocket}
对每一个调用了accept()方法的类都只得到一个请求的链接。
另外,请求的链接也只能在线程运行的server中调用了accept()方法以后才可以接受请求。线程运行在server中其它全部的方法上的时候都不能接受客户端的链接请求。因此”接受”请求的线程一般都会把Socket的请求链接放入一个工做线程池中,而后再和客户端链接。更多关于多线程服务端设计的文档请参考 java多线程服务
客户端请求执行完毕,而且不会再有该客户端的其它请求发送过来的时候,就须要关闭Socket链接,这和关闭一个普通的客户端Socket链接同样。以下代码来执行关闭:
socket.close();
要关闭服务的时候须要关掉 ServerSocket链接。经过执行以下代码:
serverSocket.close();
DatagramSocket类是java经过UDP通讯的途径。UDP仍位于IP层的上面。 你能够用DatagramSocket类发送和接收UDP数据包。
UDP 和TCP
UDP工做方式和TCP有点不一样。当你经过TCP发送数据时,你先要建立链接。一旦TCP链接创建了,TCP会保证你的数据传递到对端,不然它将告诉你已发生的错误。
仅仅用UDP来发送数据包(datagrams)到网络间的某个IP地址。你不能保证数据会不会到达。你也不能保证UDP数据包到达接收方的指令。这意味着UDP比TCP有更少的协议开销(无完整检查流)。
当数据传输过程当中不在意数据包是否丢失时,UDP就比较适合这样的数据传输。好比,网上的电视信号的传输。你但愿信号到达客户端时尽量地接近直播。所以,若是丢失一两个画面,你一点都不在意。你不但愿直播延迟,值想确保全部的画面显示在客户端。你宁肯跳过丢失的画面,但愿一直看到最新的画面。
这种状况也会发生在网上摄像机直播节目中。谁会关心过去发生的什么,你只想显示当前的画面。你不但愿比实际状况慢30s结束,只由于你想看到摄像机显示给观众的全部画面。这跟摄像机录像有点不一样。从摄像机录制画面到磁盘,你不但愿丢失一个画面。你可能还但愿有点延迟,若是有重大的状况发生,就不须要倒回去检查画面。
经过Java的DatagramSocket类发送数据,首先须要建立DatagramPacket。以下:
1 |
buffer = `new byte[ 65508`]; |
---|
2 |
---|
3 |
InetAddress address = `new DatagramPacket(buffer, buffer.length, address,9000 );` |
---|
字节缓冲块(字节数组)就是UDP数据包中用来发送的数据。缓冲块上限长度为65508字节,是单一UDP数据包发送的最大的数据量。
数据包构造函数的长度就是缓存块中用于发送的数据的长度。全部多于最大容量的数据都会被忽略。
包含节点(例如服务器)地址的InetAddress实例携带节点(如服务器)的地址发送的UDP数据包。InetAddress类表示一个ip地址(网络地址)。getByName()方法返回带有一个InetAddress实例,该实例带有匹配主机名的ip地址。
端口参数是UDP端口服务器用来接收正在监听的数据。UDP端口和TCP端口是不同的。一台电脑同时有不一样的进程监听UDP和TCP 80端口。
为了发送数据包,你须要建立DatagramSocket来发送数据。以下:
1 |
DatagramSocketdatagramSocket = `new DatagramSocket();` |
---|
调用send()方法发送数据,像这样:
1 |
datagramSocket.send(packet); |
---|
完整示例:
1 |
DatagramSocket datagramSocket = `new DatagramSocket();` |
---|
2 |
---|
3 |
byte `[] buffer = "0123456789" .getBytes();` |
---|
4 |
---|
5 |
InetAddress receiverAddress = InetAddress.getLocalHost(); |
---|
6 |
---|
7 |
DataframPacket packet = `new DatagramPacket( buffer, buffer.length, receiverAddress,80 );` |
---|
8 |
datagramSocket.send(packet); |
---|
从DataframSocket获取数据时,首先建立DataframPacket ,而后经过DatagramSocket类的receive()方法接收数据。例如:
1 |
DatagramSocket datagramSocket = `new DatagramSocket(80 );` |
---|
2 |
---|
3 |
byte `[] buffer = new` `byte [10 ];` |
---|
4 |
---|
5 |
DatagramPacket packet = `new DatagramPacket(buffer, buffer.length);` |
---|
6 |
---|
7 |
datagramSocket.receive(packet); |
---|
注意DatagramSocket是如何经过传递参数80到它的构造器初始化的。这个参数是UDP端口的DatagramSocket用来接收UDP数据包的。像以前提到的,TCP和UDP端口是不同的,也不重叠。你能够有俩个不一样的进程同时在端口80监听TCP和UDP,没有任何冲突。
第二,字节缓存块和DatagramPacket建立了。注意DatagramPacket是没有关于节点如何发送数据的信息的,当建立一个方数据的DatagramPacket时,它会直到这个信息。这就是为何咱们会用DatagramPacket接收数据而不是发送数据。所以没有目标地址是必须的。
最后,调用DatagramSocket的receive()方法。直到数据包接收到为止,这个方法都是阻塞的。
接收的数据位于DatagramPacket的字节缓冲块。缓冲块能够经过调用getData()得到:
1 |
byte `[] buffer = packet.getData();` |
---|
缓冲块接收了多少的数据须要你去找出来。你用的协议应该定义每一个UDP包发多少数据,活着定义一个你能找到的数据结束标记。
一个真正的服务端程序可能会在一个loop中调用receive()方法,传送全部接收到的DatagramPacket到工做的线程池中,就像TCP服务器处理请求链接同样(查看Java Multithreaded Servers获取更多详情)
在java.net包中包含两个有趣的类:URL类和URLConnection类。这两个类能够用来建立客户端到web服务器(HTTP服务器)的链接。下面是一个简单的代码例子:
1 |
URL url = `new URL("[http://jenkov.com](https://yq.aliyun.com/go/articleRenderRedirect?url=http%3A%2F%2Fjenkov.com%2F)" );` |
---|
2 |
URLConnection urlConnection = url.openConnection(); |
---|
3 |
InputStream input = urlConnection.getInputStream(); |
---|
4 |
int data = input.read(); |
---|
5 |
while `(data != -1 ){` |
---|
6 |
System.out.print(( `char`) data); |
---|
7 |
data = input.read(); |
---|
8 |
} |
---|
9 |
input.close(); |
---|
默认状况下URLConnection发送一个HTTP GET请求到web服务器。若是你想发送一个HTTP POST请求,要调用URLConnection.setDoOutput(true)方法,以下:
1 |
URL url = `new URL("[http://jenkov.com](https://yq.aliyun.com/go/articleRenderRedirect?url=http%3A%2F%2Fjenkov.com%2F)" );` |
---|
2 |
URLConnection urlConnection = url.openConnection(); |
---|
3 |
urlConnection.setDoOutput( `true`); |
---|
一旦你调用了setDoOutput(true),你就能够打开URLConnection的OutputStream,以下:
1 |
OutputStream output = urlConnection.getOutputStream(); |
---|
你可使用这个OutputStream向相应的HTTP请求中写任何数据,但你要记得将其转换成URL编码(关于URL编码的解释,自行Google)(译者注:具体名字是:application/x-www-form-urlencoded MIME 格式编码)。
当你写完数据的时候要记得关闭OutputStream。
URL也被叫作统一资源定位符。若是你的代码不关心文件是来自网络仍是来自本地文件系统,URL类是另一种打开文件的方式。
下面是一个如何使用URL类打开一个本地文件系统文件的例子:
1 |
URL url = `new URL("file:/c:/data/test.txt" );` |
---|
2 |
URLConnection urlConnection = url.openConnection(); |
---|
3 |
InputStream input = urlConnection.getInputStream(); |
---|
4 |
int data = input.read(); |
---|
5 |
while `(data != -1 ){` |
---|
6 |
System.out.print(( `char`) data); |
---|
7 |
data = input.read(); |
---|
8 |
} |
---|
9 |
input.close(); |
---|
注意:这和经过HTTP访问一个web服务器上的文件的惟一不一样处就是URL:”file:/c:/data/test.txt”。
Java网络教程:JarURLConnection
Java的JarURLConnection类用来链接Java Jar文件。一旦链接上,你能够获取Jar文件的信息。一个简单的例子以下:
01 |
String urlString = `"http://butterfly.jenkov.com/"` |
---|
02 |
+ `"container/download/"` |
---|
03 |
+ `"jenkov-butterfly-container-2.9.9-beta.jar"`; |
---|
04 |
---|
05 |
URL jarUrl = `new URL(urlString);` |
---|
06 |
JarURLConnection connection = `new JarURLConnection(jarUrl);` |
---|
07 |
---|
08 |
Manifest manifest = connection.getManifest(); |
---|
09 |
---|
10 |
JarFile jarFile = connection.getJarFile(); |
---|
11 |
//do something with Jar file... |
---|
_InetAddress_ 是 Java 对 IP 地址的封装。这个类的实例常常和 UDP DatagramSockets 和 Socket,ServerSocket 类一块儿使用。
_InetAddress_ 没有公开的构造方法,所以你必须经过一系列静态方法中的某一个来获取它的实例。
<!–more–>
下面是为一个域名实例化 InetAddres 类的例子:
InetAddress address = InetAddress.getByName("jenkov.com");
固然也会有为匹配某个 IP 地址来实例化一个 InetAddress:
InetAddress address = InetAddress.getByName("78.46.84.171");
另外,它还有经过获取本地 IP 地址的来获取 _InetAddress_ 的方法(正在运行程序的那台机器)
InetAddress address = InetAddress.getLocalHost();
_InetAddress_ 类还拥有大量你能够调用的其它方法。例如:你能够经过调用_getAddress()_方法来获取 IP 地址的 byte 数组。若是要了解更多的方法,最简单的方式就是读 JavaDoc 文档中关于 _InetAddress_ 类的部分。
若是设计一个客户端到服务器的系统,那么同时也须要设计客户端和服务器之间的通讯协议。固然,有时候协议已经为你决定好了,好比HTTP、XML_RPC(http response 的 body 使用xml)、或者SOAP(也是http response 的 body 使用xml)。设计客户端到服务端协议的时候,一旦协议决定开启一下子,来看一些你必须考虑的地方:
1. 客户端到服务端的往返通信
2.区分请求结束和响应结束。
3.防火墙穿透
客户端-服务端往返
当客户端和服务端通讯,执行操做时,他们在交换信息。好比,客户端执行一个服务请求,服务端尝试完成这个请求,发回响应告诉客户端结果。这种客户端和服务端的信息交换就叫作往返。示意图以下:
当一个计算机(客户端或者服务端)在网络中发送数据到另外一个计算机时,从数据发送到另外一端接收数据完会花费必定时间。这就是数据在网络间的传送的时间花费。这个时间叫作延迟。
协议中含有越多的往返,协议变得越慢,延迟特别高。HTTP协议只包含一个单独的响应来执行服务。换句话说就是一个单独的往返。另外一方面,在一封邮件发送前,SMTP协议包含了几个客户端和服务端的往返。
在协议中有多个往返的缘由是:有大量的数据从客户端发送到服务端。这种状况下你有2个选择:
1.在分开往返中发送头信息;
2.将消息分红更小的数据块。
若是服务端能完成头信息的一些初始验证 ,那么分开发送头信息是很明智的。若是头信息是空白的,发送大量数据自己就是浪费资源。
在传输大量数据时,若是网络链接失败了,得从头开始从新发送数据。数据分割发送时,只须要在网络链接失败处从新发送数据块。已经发送成功的数据块不须要从新发送。
区分请求结束和响应结束
若是协议允许在同一个链接中发送多个请求,须要一个让服务端知道当前请求什么时候结束、下一个请求什么时候开始。客户端也须要知道一个响应什么时候结束了,下一个响应什么时候开始。
对于请求有2个方法区分结束:
1.在请求的开始处发送请求的字长
2.在请求数据的最后发送一个结束标记。
HTTP用第一个机制。在请求头中 发送了“Content-Length”。请求头会告诉服务端在头文件后有多少字节是属于请求的。
这个模型的优点在于没有请求结束标志的开销。为了不数据看上去像请求结束标志,也不须要对数据体进行编码。
第一个方法的劣势:在数据传输前,发送者必须知道多少字节数将被传输。若是数据时动态生成的,在发送前,首先你得缓存全部的数据,这样才能计算出数据的字节数。
运用请求结束标志时,不须要知道发送了多少字节数。只须要知道请求结束标志在数据的末尾。固然,必须确认已发送的数据中不包含会致使请求结束标志错误的数据。能够这样作:
能够说请求结束标志是字节值255。固然数据可能包含值255。所以,对数据中包含值255的每个字节添加一个额外的字节,还有值255。结束请求标志被从字节值255到255以后的值为0。以下编码:
255 in data –>255, 255
end-of-request –> 255, 0
这种255,0的序列永远不会出如今数据中,由于你把全部的值255变成了255,255。同时,255,255,0也不会被错认为255,0。255,255被理解成在一块儿的,0是单独的。
防火墙穿透
比起HTTP协议,大多数防火墙会拦截全部的其余通讯。所以把协议放在HTTP的上层是个好方法,像XML-RPC,SOAP和REST也能够这样作。
协议置于HTTP的上层,在客户端和服务端的HTTP请求和响应中能够来回发送数据。记住,HTTP请求和响应不止包含text或者HTML。也能够在里面发送二进制数据。
将请求放置在HTTP协议上,惟一有点奇怪的是:HTTP请求必须包含一个“主机”头字段。若是你在HTTP协议上设计P2P协议,一样的人最可能不会运行多个“主机”。在这种状况下须要头字段是没必要要的开销(可是个小开销)。