初步接触 Java Net 网络编程

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

Java 网络编程概览

Java 有一个相当容易使用的内置网络 API,可以很容易地通过互联网上的 TCP / IP 套接字或 UDP 套接字进行通信。 TCP 通常比 UDP 使用得更频繁。

即使 Java Networking API 允许通过套接字打开和关闭网络连接,但所有通信都通过 Java IO 类 InputStream 和 OutputStream 实现的。
或者,我们可以使用 Java NIO API 中的网络类。 用法类似于 Java Networking API 中的类,但 Java NIO API 可以在非阻塞模式下工作。 在某些情况下,非阻塞模式可提升性能。

Java TCP 网络基础

通常,客户端会打开与服务器的 TCP / IP 连接,然后开始与服务器通信,当通信结束后客户端关闭连接。如下图:


image

客户端可以通过一个已打开的连接发送多个请求,实际上,客户端可以向服务器发送尽可能多的数据。 当然,如果需要,服务器也可以关闭连接。

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 ,比如传输视频等多媒体文件,缺少一些数据是不影响观看的。

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...
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • 计算机网络概述 网络编程的实质就是两个(或多个)设备(例如计算机)之间的数据传输。 按照计算机网络的定义,通过一定...
    蛋炒饭_By阅读 1,209评论 0 10
  • 网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编...
    程序员欧阳阅读 1,998评论 1 37
  • 7.2 面向套接字编程我们已经通过了解Socket的接口,知其所以然,下面我们就将通过具体的案例,来熟悉Socke...
    lucas777阅读 1,172评论 0 2
  • 一、网络通信协议 定义:对数据的传输格式、传输效率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换,...
    聂叼叼阅读 495评论 0 2
  • 目标:我可以轻松的在2019年1月份以前支付银行2-3万元,然后2019年以后的每一个月的工资,将都是完完全全的属...
    殷琴阅读 137评论 0 2