Java网络编程

本篇以及后面的都是基于并发编程网来的 

转载自 : http://ifeve.com/java-networking/

 

本篇介绍学习大纲 : 

1 Java 网络教程: 基础
2 Java 网络教程: Socket
3 Java 网络教程: ServerSocket
4 Java Networking: UDP DatagramSocket
5 Java 网络教程: URL + URLConnection
6 Java网络教程:JarURLConnection
7 Java 网络教程: InetAddress
8 Java网络教程:Protocol Design

Java网络教程-基础 

Java提供了非常易用的网络API,调用这些API我们可以很方便的通过建立TCP/IP或UDP套接字,在网络之间进行相互通信,其中TCP要比UDP更加常用,但在本教程中我们对这两种方式都有说明。

在网站上还有其他三个与Java网络相关的教程,如下: 

 

1.Java IO 教程  (在我个人的JAVA的IO篇幅当中有介绍)

2.Java NIO 教程

3.Java服务器多线程教程 

尽管Java网络API允许我们通过套接字(Socket)打开或关闭网络连接,但所有的网络通信均是基于Java IO类 InputStreamOutputStream实现的。

此外,我们还可以使用Java NIO API中相关的网络类,用法与Java网络API基本类似,Java NIO API可以以非阻塞模式工作,在某些特定的场景中使用非阻塞模式可以获得较大的性能提升。

 

Java TCP网络基础

通常情况下,客户端打开一个连接到服务器端的TCP/IP连接,然后客户端开始与服务器之间通信,当通信结束后客户端关闭连接,过程如下图所示:

​客户端通过一个已打开的连接可以发送不止一个请求。事实上在服务器处于接收状态下,客户端可以发送尽可能多的数据,服务器也可以主动关闭连接。

JavaSocket类和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() 方法返回一个InetAddressIP地址与给定主机名匹配的实例。

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 ,然后通过将数据接收到它DatagramSocketreceive()方法。这里是一个例子:

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接收数据,而不是发送它。因此不需要目的地地址。

最后DatagramSocketreceive()方法被调用。此方法阻塞,直到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协议,同样的人最可能不会运行多个“主机”。在这种情况下需要头字段是不必要的开销(但是个小开销)。

 

posted @ 2018-04-20 18:45  白云是世界的公民  阅读(603)  评论(0编辑  收藏  举报