初步接触 Java Net 网络编程

本文目的是大概了解 Java 网络编程体系,须要一点点 Java IO 基础,推荐教程 系统学习 Java IO。主要参考 JavaDoc 和 Jakob Jenkov 的英文教程《Java Networking》 http://tutorials.jenkov.com/java-networking/index.htmlhtml

Java 网络编程概览

Java 有一个至关容易使用的内置网络 API,能够很容易地经过互联网上的 TCP / IP 套接字或 UDP 套接字进行通讯。 TCP 一般比 UDP 使用得更频繁。java

即便 Java Networking API 容许经过套接字打开和关闭网络链接,但全部通讯都经过 Java IO 类 InputStream 和 OutputStream 实现的。
或者,咱们可使用 Java NIO API 中的网络类。 用法相似于 Java Networking API 中的类,但 Java NIO API 能够在非阻塞模式下工做。 在某些状况下,非阻塞模式可提高性能。git

Java TCP 网络基础

一般,客户端会打开与服务器的 TCP / IP 链接,而后开始与服务器通讯,当通讯结束后客户端关闭链接。以下图:
github

客户端能够经过一个已打开的链接发送多个请求,实际上,客户端能够向服务器发送尽量多的数据。 固然,若是须要,服务器也能够关闭链接。编程

Java 中 Socket 类和 ServerSocket 类

当客户端想要打开到服务器的 TCP / IP 链接时,它使用 Java Socket 类来实现。 套接字被告知链接到哪一个 IP 地址和 TCP 端口,其他部分由 Java 完成。数组

若是要启动服务器以侦听来自某个 TCP 端口上的客户端的传入链接,则必须使用 Java ServerSocket 类。 当客户端经过客户端套接字链接到服务器的 ServerSocket 时,服务器上会为该链接分配一个 Socket 。 客户端和服务器的通讯就是 Socket 到 Socket 的通讯了。服务器

Socket和ServerSocket在后面的文本中有更详细的介绍。网络

Java UDP 网络基础

UDP 的工做方式与 TCP 略有不一样。 使用 UDP ,客户端和服务器之间没有链接。 客户端能够向服务器发送数据,而且服务器能够(或能够不)接收该数据。 客户端永远不会知道数据是否在另外一端收到。 从服务器到客户端发送的数据也是如此。
因为没法保证数据传输,所以 UDP 协议的协议开销较小。多线程

在一些状况下,无链接 UDP 模型优于 TCP ,好比传输视频等多媒体文件,缺乏一些数据是不影响观看的。socket

TCP Socket(套接字)

为了经过 Internet 链接到服务器(经过TCP / IP),须要建立一个 Socket 并将其链接到服务器。 或者,若是您更喜欢使用 Java NIO ,则可使用 Java NIO SocketChannel 。

建立一个Socket
Socket socket = new Socket("baidu.com", 80);

第一个参数是地址,能够是 ip 或者域名字符串,第二个参数是端口,端口80是Web服务器端口。

写入 Socket

要写入 Socket,必须获取其 OutputStream :

Socket socket = new Socket("baidu.com", 80);
OutputStream outputStream = socket.getOutputStream();

outputStream.write("some data".getBytes());
outputStream.flush();
outputStream.close();

socket.close();

当真的但愿经过互联网向服务器发送数据时,不要忘记调用 flush() 。操做系统中的底层 TCP / IP 实现会先缓冲数据,缓冲块的大小是与 TCP ​​/ IP 数据包的大小相适应的,这就是说,调用 flush() 只是通知系统发送,但系统并非当即就帮忙发出去。

从 Socket 读取

要从 Socket 读取,须要获取其 InputStream :

Socket socket = new Socket("baidu.com", 80);
InputStream in = socket.getInputStream();

int data = in.read();
//... read more data...

in.close();
socket.close();

记住,在读取时咱们不能使用读取 InputStream 返回 -1 来判断数据读取结束 ,由于只有在服务器关闭链接时才返回 -1 。 可是服务器可能并不老是关闭链接,好比经过同一链接发送多个请求。 在这种状况下,关闭链接将是很是愚蠢的。

相反,必须知道从 Socket 的 InputStream 中读取多少字节。 服务器会告知 Socket 它发送的字节数,或者经过查找特殊的数据结束字符来完成。

使用 Socket 后,必须关闭它以关闭与服务器的链接,这能够经过调用 Socket 对象的 close() 方法完成。

ServerSocket

可使用 ServerSocket 来实现 Java 服务器,这样就能够经过 TCP / IP 侦听来自客户端的传入链接。若是更喜欢使用 Java NIO 而不是 Java Networking(标准API),那么也可使用 ServerSocketChannel 。

建立一个 ServerSocket

这是一个简单的代码示例,它建立一个侦听端口 9000 的 ServerSocket:

ServerSocket serverSocket = new ServerSocket(9000);
监听传入的链接

要接受传入链接,必须调用 ServerSocket.accept() 方法。 accept() 方法返回一个 Socket ,其行为相似于普通的 Socket ,示例:

ServerSocket serverSocket = new ServerSocket(9000);
boolean isStopped = false;
while(!isStopped){
    Socket clientSocket = serverSocket.accept();
    //do something with clientSocket
}

每次调用 accept() 方法时只打开一个传入链接。
此外,只有在运行服务器的线程调用 accept() 时才能接受传入链接。 线程在此方法以外执行的全部时间都没有客户端能够链接。 所以,“accept”线程一般将传入链接(Socket)传递给工做线程池,而后工做线程与客户端进行通讯。 有关多线程服务器设计的更多信息,请参阅教程跟踪 Java 多线程服务器。

关闭客户端 Sockets

一旦客户端请求完成,而且不会从该客户端收到进一步的请求,必须关闭该Socket,就像关闭普通客户端Socket同样。调用:socket.close();

关闭服务端 Sockets

一旦服务器关闭,就须要关闭 ServerSocket 。 调用:serverSocket.close();

UDP DatagramSocket(UDP数据报套接字)

DatagramSocket 是 Java 经过 UDP 而不是 TCP 进行网络通讯的机制。 UDP 也是 IP 协议的上层。 可使用 DatagramSocket 来发送和接收 UPD 数据报。

UDP 对比 TCP

经过 TCP 发送数据时,首先要建立链接。 创建 TCP 链接后,TCP 保证数据到达另外一端,或者它会告诉你发生了错误。

使用 UDP,只需将数据包(数据报)发送到网络上的某个 IP 地址。 没法保证数据会到达,也没法保证 UDP 数据包到达的顺序。 这意味着 UDP 比 TCP 具备更少的协议开销(没有流完整性检查)。

UDP 适用于数据传输,若是数据包在转换过程当中丢失则可有可无。 例如,想象一下经过互联网传输直播电视信号,若是一两帧丢失,这是可有可无的。咱们更不但愿直播延迟只是为了确保全部帧都显示出来。 宁愿跳过错过的帧,并直接查看最新的帧。

还有实时监控视频,宁愿丢失一两帧,也不想延迟于现实 30 秒。与摄像机录像的存储有点不一样,将图像从相机录制到磁盘时, 为了保证完整性,可能不但愿丢失单帧,而是更愿意稍微延迟。

DatagramPacket 类

此类表示数据报包。数据报包用来实现无链接包投递服务。

Java 使用 DatagramSocket 表明 UDP 协议的 Socket ,DatagramSocket 自己只是码头,不维护状态,不能产生IO流,它的惟一做用就是接收和发送数据报,使用 DatagramPacket 来表明数据报,DatagramSocket 接收和发送的数据都是经过 DatagramPacket 对象完成的。
每条报文仅根据该包中包含的信息从一台机器路由到另外一台机器。从一台机器发送到另外一台机器的多个包可能选择不一样的路由,也可能按不一样的顺序到达。不对包投递作出保证。
引用自 李刚《疯狂Java讲义(第2版)》

其全部构造器以下:

方法 描述
DatagramPacket(byte[] buf, int length) 构造 DatagramPacket,用来接收长度为 length 的数据包。
DatagramPacket(byte[] buf, int length, InetAddress address, int port) 构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
DatagramPacket(byte[] buf, int length, SocketAddress address) 构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
以上3个在 byte[] buf 参数后面追加 int offset 为长度为 length 的包设置偏移量为 offset

其中

  • InetAddress 类表示互联网协议 (IP) 地址,能够经过静态方法 getByName(String host) 得到其对象。
  • SocketAddress 类里面什么都没有。其子类 InetSocketAddress是(IP地址+端口号)类型,也就是端口地址类型,一样可使用静态方法 createUnresolved(String host, int port) 获取对象,另外也能由构造函数 InetSocketAddress(InetAddress addr, int port) 建立,其中 InetAddress 对象可省略,也可用字符串代替。
经过 DatagramSocket 发送数据(DatagramPacket )

要经过 DatagramSocket 发送数据,必须首先建立一个 DatagramPacket :

byte[] buffer = new byte[65508];
InetAddress address = InetAddress.getByName("baidu.com");

DatagramPacket packet = new DatagramPacket(buffer, buffer.length, address, 9000);

字节缓冲区(字节数组)是要在 UDP 数据报中发送的数据。 上述缓冲区的长度(65508字节)是能够在单个 UDP 数据包中发送的最大数据量。

DatagramPacket 构造函数中的 buffer.length 是要发送的缓冲区中数据的长度,忽略该数据量以后缓冲区中的全部数据。

InetAddress 实例包含发送 UDP 数据包的节点(例如服务器)的地址。 InetAddress 类表示 IP 地址(Internet地址)。 getByName() 方法返回一个 InetAddress 实例,其 IP 地址与给定的主机名匹配。

port 参数是服务器接收数据正在侦听的 UDP 端口,UDP 和 TCP 端口是不同的。同一台计算机能够有不一样的线程同时监听 UDP 的 80 端口和 TCP 中的 80 端口。不一样协议下,端口号互不干扰,端口只是应用程序的标识。

建立一个 DatagramSocket :

DatagramSocket datagramSocket = new DatagramSocket();

要发送数据,请调用 send() 方法,以下所示:

datagramSocket.send(packet);

这是一个完整的例子:

public class DatagramExample {
    public static void main(String[] args) throws Exception {
        DatagramSocket datagramSocket = new DatagramSocket();

        byte[] buffer = "123456789".getBytes();
        InetAddress receiverAddress = InetAddress.getLocalHost();

        DatagramPacket packet = new DatagramPacket(buffer, buffer.length, receiverAddress, 80);
        datagramSocket.send(packet);
    }
}
经过 DatagramSocket 接收数据 (DatagramPacket )

经过 DatagramSocket 接收数据是经过首先建立 DatagramPacket 而后经过 DatagramSocket 的 receive() 方法接收数据来完成的。 这是一个例子:

DatagramSocket socket = new DatagramSocket(80);
byte[] buffer = new byte[10];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

socket.receive(packet);

使用传递给构造函数的参数值 80 来实例化 DatagramSocket , 此参数是 DatagramSocket 接收 UDP 数据包的 UDP 端口。 如前所述,TCP 和 UDP 端口不相同,所以不重叠。 能够在 TCP 和 UDP 80 端口上侦听两个不一样的进程,而不会发生任何冲突。

其次,建立字节缓冲区和 DatagramPacket 。 注意 DatagramPacket 没有关于要发送数据的节点的信息,就像建立 DatagramPacket 用于发送数据时同样。 这是由于咱们将使用 DatagramPacket 接收数据而不是发送数据,所以,不须要目标地址。

最后调用 DatagramSocket 的 receive() 方法。 此方法将一直阻塞,直到收到 DatagramPacket 。

收到的数据位于 DatagramPacket 的字节缓冲区中。 这个缓冲区能够经过调用以下代码获取:

byte[] buffer = packet.getData();

缓冲区会接收多少数据应该由你找到答案。 正在使用的协议应指定每一个 UDP 数据包发送的数据量,或指定能够查找到的数据结束标记。真正的服务器程序可能会在循环中调用 receive() 方法,并将全部收到的 DatagramPacket 传递给工做线程池,就像 TCP 服务器对传入链接同样。

URL + URLConnection

java.net 包中两个有趣的类:URL 类和 URLConnection 类,这些类可用于建立与 Web 服务器(HTTP 服务器)的客户端链接。 这是一个简单的代码示例:

public class URLExample {
    public static void main(String[] args) throws IOException {
        URL url = new URL("http://baidu.com");

        URLConnection urlConnection = url.openConnection();
        InputStream inputStream = urlConnection.getInputStream();

        int data = inputStream.read();
        while (data != -1) {
            System.out.print((char) data);
            data = inputStream.read();
        }
        inputStream.close();
    }
}

将会输出

<html>
<meta http-equiv="refresh" content="0;url=http://www.baidu.com/">
</html>
HTTP GET 和 POST

URLConnection 类的做用是构造一个到指定 URL 的 URL 链接。它只有一个构造函数:URLConnection(URL url)
默认状况下,URLConnection 向 Web 服务器发送 HTTP GET 请求,即查询数据。若是要发送 HTTP POST 请求提交数据,请调用URLConnection.setDoOutput(true) 方法,以下所示:

URL url = new URL("http://baidu.com");
URLConnection urlConnection = url.openConnection();
urlConnection.setDoOutput(true);

一旦设置了 setDoOutput(true) ,由于要提交数据,因此须要输出流。能够打开 URLConnection 的 OutputStream ,以下所示:

OutputStream output = urlConnection.getOutputStream();

使用此 OutputStream ,能够在 HTTP 请求的正文中编写所需的任何数据。 请记住对其进行 URL 编码(参考 【基础进阶】URL详解与URL编码 ,并记得在完成向其写入数据后关闭 OutputStream 。

本地文件的URL

URL 类还可用于访问本地文件系统中的文件。 所以,若是须要代码处理来源不明的文件,好比是来自网络仍是本地文件系统,则 URL 类是打开文件的便捷方式。
如下是使用 URL 类在本地文件系统中打开文件的示例:

URL url = new URL("file:/D:/test/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:/D:/test/test.txt""http://baidu.com"

JarURLConnection

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...
相关文章
相关标签/搜索