Java网络编程
本篇以及后面的都是基于并发编程网来的
转载自 : http://ifeve.com/java-networking/
本篇介绍学习大纲 :
Java网络教程-基础
Java提供了非常易用的网络API,调用这些API我们可以很方便的通过建立TCP/IP或UDP套接字,在网络之间进行相互通信,其中TCP要比UDP更加常用,但在本教程中我们对这两种方式都有说明。
在网站上还有其他三个与Java网络相关的教程,如下:
1.Java IO 教程 (在我个人的JAVA的IO篇幅当中有介绍)
尽管Java网络API允许我们通过套接字(Socket)打开或关闭网络连接,但所有的网络通信均是基于Java IO类 InputStream和OutputStream实现的。
此外,我们还可以使用Java NIO API中相关的网络类,用法与Java网络API基本类似,Java NIO API可以以非阻塞模式工作,在某些特定的场景中使用非阻塞模式可以获得较大的性能提升。
Java TCP网络基础
通常情况下,客户端打开一个连接到服务器端的TCP/IP连接,然后客户端开始与服务器之间通信,当通信结束后客户端关闭连接,过程如下图所示:
客户端通过一个已打开的连接可以发送不止一个请求。事实上在服务器处于接收状态下,客户端可以发送尽可能多的数据,服务器也可以主动关闭连接。
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网络教程之Socket
原文地址 译者:贾毅
当我们想要在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
原文链接 作者:Jakob Jenkov 译者:homesick
用java.net.ServerSocket实现java服务通过TCP/IP监听客户端连接,你也可以用Java NIO 来代替java网络标准API,这时候需要用到 ServerSocketChannel。
创建一个 ServerSocket连接
以下是一个创建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连接一样。如下代码来执行关闭:
socket.close();
关闭服务端Sockets
要关闭服务的时候需要关掉 ServerSocket连接。通过执行如下代码:
serverSocket.close();
Java网络:UDP DatagramSocket
转载自 : http://tutorials.jenkov.com/java-networking/udp-datagram-sockets.html
DatagramSocket是Java通过UDP而不是TCP进行网络通信的机制。UDP仍然在IP的顶层。您可以使用Java DatagramSocket
来发送和接收UPD数据报。
UDP与TCP
UDP与TCP有点不同。当你通过TCP发送数据时,你首先创建一个连接。TCP连接建立后TCP保证你的数据到达另一端,或者它会告诉你发生了一个错误。
使用UDP,您只需将数据包(数据报)发送到网络上的某个IP地址即可。您无法保证数据会到达。您也无法保证UDP数据包到达接收器的顺序。这意味着UDP具有比TCP更少的协议开销(没有流完整性检查)。
UDP适用于数据传输,如果数据包在转换中丢失,则无关紧要。例如,想象通过互联网传输实况电视信号。你希望信号尽可能靠近客户。因此,如果一帧或两帧丢失,你并不在乎。您不希望现场直播延迟,以确保所有帧都显示在客户端。您宁愿跳过错过的帧,直接移动到最新的帧。
在互联网上播放监控摄像头也可能会发生这种情况。谁在乎过去发生的事情,当你试图监视现在。你不想最终落后于现实30秒,这是因为你想向监视相机的人显示所有帧。这与相机记录的存储有些不同。将相机中的图像记录到磁盘时,您可能不想丢失一帧。如果发生了重要的事情,你可能宁愿稍微延迟一点,也不愿让这些帧返回并检查。
通过DatagramSocket发送数据
要通过Java发送数据,DatagramSocket
您必须先创建一个DatagramPacket
。这是如何完成的:
byte [] buffer = new byte [65508]; InetAddress地址= InetAddress.getByName(“jenkov.com”); DatagramPacket数据包=新的DatagramPacket( 缓冲区,缓冲区长度,地址,9000);
字节缓冲区(字节数组)是要在UDP数据报中发送的数据。上述缓冲区的长度为65508字节,是可以在单个UDP数据包中发送的最大数据量。
赋给DatagramPacket
构造函数的长度是要发送的缓冲区中数据的长度。在该数据量之后的缓冲区中的所有数据都将被忽略。
该InetAddress
实例包含要发送UDP数据包的节点(例如服务器)的地址。本InetAddress
类代表一个IP地址(Internet地址)。该getByName()
方法返回一个InetAddress
IP地址与给定主机名匹配的实例。
port参数是服务器到接收器数据正在侦听的UDP端口。UDP和TCP端口不一样。计算机可以有不同的进程在UDP和TCP中同时监听端口80。
要发送DatagramPacket
您必须创建一个DatagramSocket
针对发送数据。这是如何完成的:
DatagramSocket datagramSocket = new DatagramSocket();
要发送数据,请调用该send()
方法,如下所示:
datagramSocket.send(分组);
这里是一个完整的例子:
DatagramSocket datagramSocket = new DatagramSocket(); byte [] buffer =“0123456789”.getBytes(); InetAddress receiverAddress = InetAddress.getLocalHost(); DatagramPacket数据包=新的DatagramPacket( buffer,buffer.length,receiverAddress,80); datagramSocket.send(分组);
通过DatagramSocket接收数据
接收经由数据DatagramSocket
是通过首先建立一个完成DatagramPacket
,然后通过将数据接收到它DatagramSocket
的receive()
方法。这里是一个例子:
DatagramSocket datagramSocket = new DatagramSocket(80); byte [] buffer = new byte [10]; DatagramPacket packet = new DatagramPacket(buffer,buffer.length); datagramSocket.receive(分组);
注意如何DatagramSocket
用参数值80实例化它传递给它的构造函数。该参数是DatagramSocket
接收UDP数据包的UDP端口。如前所述,TCP和UDP端口不一样,因此不会重叠。您可以有两个不同的进程在TCP和UDP端口80上进行侦听,没有任何冲突。
其次,DatagramPacket
创建一个字节缓冲区和一个。注意,DatagramPacket
没有关于节点发送数据的信息,就像创建DatagramPacket
发送数据时一样。这是因为我们将使用DatagramPacket
接收数据,而不是发送它。因此不需要目的地地址。
最后DatagramSocket
的receive()
方法被调用。此方法阻塞,直到DatagramPacket
收到。
接收到的数据位于DatagramPacket
字节缓冲区中。这个缓冲区可以通过调用:
byte [] buffer = packet.getData();
缓冲区中收到多少数据取决于您。您使用的协议应指定每个UDP数据包发送多少数据,或者指定一个可以查找的数据结束标记。
一个真正的服务器程序可能会receive()
在一个循环中调用该方法,并将所有接收到DatagramPacket
的传递给工作线程池,就像TCP服务器对传入连接进行处理一样(有关更多详细信息,请参阅Java多线程服务器)。
Java网络教程:URL + URLConnection
目录
- HTTP GET和POST
- 从URLs到本地文件
在java.net包中包含两个有趣的类:URL类和URLConnection类。这两个类可以用来创建客户端到web服务器(HTTP服务器)的连接。下面是一个简单的代码例子:
URL url = new URL("http://jenkov.com"); URLConnection urlConnection = url.openConnection(); InputStream input = urlConnection.getInputStream(); int data = input.read(); while(data != -1){ System.out.print((char) data); data = input.read(); } input.close();
HTTP GET和POST
默认情况下URLConnection发送一个HTTP GET请求到web服务器。如果你想发送一个HTTP POST请求,要调用URLConnection.setDoOutput(true)方法,如下:
URL url = new URL("http://jenkov.com"); URLConnection urlConnection = url.openConnection(); urlConnection.setDoOutput(true);
一旦你调用了setDoOutput(true),你就可以打开URLConnection的OutputStream,如下:
OutputStream output = urlConnection.getOutputStream();
你可以使用这个OutputStream向相应的HTTP请求中写任何数据,但你要记得将其转换成URL编码(关于URL编码的解释,自行Google)(译者注:具体名字是:application/x-www-form-urlencoded MIME 格式编码)。
当你写完数据的时候要记得关闭OutputStream。
从URLs到本地文件
URL也被叫做统一资源定位符。如果你的代码不关心文件是来自网络还是来自本地文件系统,URL类是另外一种打开文件的方式。
下面是一个如何使用URL类打开一个本地文件系统文件的例子:
URL url = new URL("file:/c:/data/test.txt"); URLConnection urlConnection = url.openConnection(); InputStream input = urlConnection.getInputStream(); int data = input.read(); while(data != -1){ System.out.print((char) data); data = input.read(); } input.close();
注意:这和通过HTTP访问一个web服务器上的文件的唯一不同处就是URL:”file:/c:/data/test.txt”。
Java网络教程:JarURLConnection
Java的JarURLConnection类用来连接Java Jar文件。一旦连接上,你可以获取Jar文件的信息。一个简单的例子如下:
String urlString = "http://butterfly.jenkov.com/" + "container/download/" + "jenkov-butterfly-container-2.9.9-beta.jar"; URL jarUrl = new URL(urlString); JarURLConnection connection = new JarURLConnection(jarUrl); Manifest manifest = connection.getManifest(); JarFile jarFile = connection.getJarFile(); //do something with Jar file...
Java 网络教程: InetAddress
Java 网络教程: InetAddress
- 创建一个 InetAddress 实例
- InetAddress 的内部方法
InetAddress 是 Java 对 IP 地址的封装。这个类的实例经常和 UDP DatagramSockets 和 Socket,ServerSocket 类一起使用。
创建一个 InetAddress 实例
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 内部方法
InetAddress 类还拥有大量你可以调用的其它方法。例如:你可以通过调用getAddress()方法来获取 IP 地址的 byte 数组。如果要了解更多的方法,最简单的方式就是读 JavaDoc 文档中关于 InetAddress 类的部分。
Java网络教程:Protocol Design
如果设计一个客户端到服务器的系统,那么同时也需要设计客户端和服务器之间的通信协议。当然,有时候协议已经为你决定好了,比如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协议,同样的人最可能不会运行多个“主机”。在这种情况下需要头字段是不必要的开销(但是个小开销)。