Java 中文官方教程 2022 版(四十五)(1)https://developer.aliyun.com/article/1488406
从 URLConnection 读取
以下程序执行与直接从 URL 读取中显示的URLReader
程序相同的功能。
然而,与直接从 URL 获取输入流不同,此程序显式检索URLConnection
对象并从连接获取输入流。通过调用getInputStream
隐式打开连接。然后,像URLReader
一样,此程序在输入流上创建一个BufferedReader
并从中读取。粗体语句突出了此示例与先前示例之间的区别:
import java.net.*; import java.io.*; public class URLConnectionReader { public static void main(String[] args) throws Exception { URL oracle = new URL("http://www.oracle.com/"); URLConnection yc = oracle.openConnection(); BufferedReader in = new BufferedReader(new InputStreamReader( yc.getInputStream())); String inputLine; while ((inputLine = in.readLine()) != null) System.out.println(inputLine); in.close(); } }
该程序的输出与直接从 URL 打开流的程序的输出相同。您可以使用任一方式从 URL 读取。但是,与直接从 URL 读取不同,从URLConnection
读取可能更有用。这是因为您可以同时将URLConnection
对象用于其他任务(如向 URL 写入)。
再次,如果程序挂起或出现错误消息,您可能需要设置代理主机,以便程序可以找到 Oracle 服务器。
向 URLConnection 写入
许多 HTML 页面包含表单——文本字段和其他 GUI 对象,让您输入要发送到服务器的数据。在输入所需信息并通过单击按钮启动查询后,您的 Web 浏览器通过网络将数据写入 URL。在另一端,服务器接收数据,处理数据,然后向您发送响应,通常以新的 HTML 页面形式。
这些 HTML 表单中的许多使用 HTTP POST 方法将数据发送到服务器。因此,向 URL 写入通常称为向 URL 发布。服务器识别 POST 请求并读取从客户端发送的数据。
要使 Java 程序与服务器端进程交互,它只需能够写入 URL,从而向服务器提供数据。它可以通过以下步骤实现:
- 创建一个
URL
。 - 检索
URLConnection
对象。 - 在
URLConnection
上设置输出功能。 - 打开到资源的连接。
- 从连接获取输出流。
- 写入输出流。
- 关闭输出流。
这里有一个名为ReverseServlet
的小servlet
(或者如果你更喜欢一个 cgi-bin 脚本)。您可以使用这个 servlet 来测试以下示例程序。
运行在容器中的 servlet 从其 InputStream 读取,反转字符串,并将其写入其 OutputStream。servlet 需要形式为string=string_to_reverse
的输入,其中string_to_reverse
是您想要以相反顺序显示其字符的字符串。
这是一个通过URLConnection
在网络上运行ReverseServlet
的示例程序:
import java.io.*; import java.net.*; public class Reverse { public static void main(String[] args) throws Exception { if (args.length != 2) { System.err.println("Usage: java Reverse " + "http://<location of your servlet/script>" + " string_to_reverse"); System.exit(1); } String stringToReverse = URLEncoder.encode(args[1], "UTF-8"); URL url = new URL(args[0]); URLConnection connection = url.openConnection(); connection.setDoOutput(true); OutputStreamWriter out = new OutputStreamWriter( connection.getOutputStream()); out.write("string=" + stringToReverse); out.close(); BufferedReader in = new BufferedReader( new InputStreamReader( connection.getInputStream())); String decodedString; while ((decodedString = in.readLine()) != null) { System.out.println(decodedString); } in.close(); } }
让我们来检查程序,看看它是如何工作的。首先,程序处理其命令行参数:
if (args.length != 2) { System.err.println("Usage: java Reverse " + "http://*<location of your servlet/script>*" + " string_to_reverse"); System.exit(1); } String stringToReverse = URLEncoder.encode(args[1], "UTF-8");
这些语句确保用户向程序提供两个且仅有两个命令行参数。命令行参数是ReverseServlet
的位置和将被反转的字符串。它可能包含空格或其他非字母数字字符。这些字符必须进行编码,因为字符串在传输到服务器时会被处理。URLEncoder
类的方法对字符进行编码。
接下来,程序创建URL
对象,并设置连接以便可以向其写入:
URL url = new URL(args[0]); URLConnection connection = url.openConnection(); connection.setDoOutput(true);
程序然后在连接上创建一个输出流,并在其上打开一个OutputStreamWriter
:
OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream());
如果 URL 不支持输出,getOutputStream
方法会抛出UnknownServiceException
。如果 URL 支持输出,则此方法返回一个连接到服务器端 URL 的输入流的输出流 —— 客户端的输出是服务器的输入。
接下来,程序向输出流写入所需的信息并关闭流:
out.write("string=" + stringToReverse); out.close();
这段代码使用write
方法向输出流写入数据。因此你可以看到,向 URL 写入数据就像向流写入数据一样简单。客户端输出流中写入的数据是服务器端 servlet 的输入。Reverse
程序通过在要反转的编码字符串前添加string=
来构造脚本所需的输入形式。
servlet 读取您写入的信息,对字符串值执行反转操作,然后将其发送回给您。现在您需要读取服务器发送回的字符串。Reverse
程序这样做:
BufferedReader in = new BufferedReader( new InputStreamReader( connection.getInputStream())); String decodedString; while ((decodedString = in.readLine()) != null) { System.out.println(decodedString); } in.close();
如果您的ReverseServlet
位于http://example.com/servlet/ReverseServlet
,那么当您使用Reverse
程序运行时
http://example.com/servlet/ReverseServlet "Reverse Me"
作为参数(包括双引号),你应该看到这个输出:
Reverse Me reversed is: eM esreveR
教程:关于套接字的一切
原文:
docs.oracle.com/javase/tutorial/networking/sockets/index.html
URL
和URLConnection
提供了一个相对高级的机制,用于访问互联网上的资源。有时,您的程序需要更低级别的网络通信,例如,当您想编写一个客户端-服务器应用程序时。
在客户端-服务器应用程序中,服务器提供某些服务,比如处理数据库查询或发送当前股票价格。客户端使用服务器提供的服务,要么将数据库查询结果显示给用户,要么向投资者提供股票购买建议。客户端和服务器之间发生的通信必须是可靠的。也就是说,不能丢失任何数据,并且必须按照服务器发送的顺序到达客户端。
TCP 提供了一个可靠的点对点通信通道,用于互联网上的客户端-服务器应用程序之间的通信。要通过 TCP 进行通信,客户端程序和服务器程序要建立连接。每个程序将套接字绑定到连接的端点。为了通信,客户端和服务器分别从绑定到连接的套接字读取和写入。
什么是套接字?
套接字是网络上运行的两个程序之间的双向通信链路的一个端点。Socket 类用于表示客户端程序和服务器程序之间的连接。java.net 包提供了两个类——Socket 和 ServerSocket——分别实现连接的客户端部分和服务器端部分。
从套接字读取和写入
本页包含一个小例子,演示了客户端程序如何从套接字读取和写入。
编写客户端/服务器对
前一页展示了一个示例,演示了如何编写一个与现有服务器通过 Socket 对象交互的客户端程序。本页将向您展示如何编写一个实现连接的另一端的程序——服务器程序。
什么是套接字?
原文:
docs.oracle.com/javase/tutorial/networking/sockets/definition.html
通常,服务器在特定计算机上运行,并具有绑定到特定端口号的套接字。服务器只是等待,监听套接字,等待客户端发出连接请求。
在客户端:客户端知道服务器正在运行的机器的主机名和服务器正在监听的端口号。为了发出连接请求,客户端尝试在服务器的机器和端口上与服务器会合。客户端还需要向服务器标识自己,因此它绑定到一个本地端口号,在此连接期间将使用该端口号。这通常由系统分配。
如果一切顺利,服务器接受连接。接受后,服务器会获得一个新的套接字,绑定到相同的本地端口,并且其远程端点设置为客户端的地址和端口。它需要一个新的套接字,以便在继续监听原始套接字以接受连接请求的同时,满足已连接客户端的需求。
在客户端,如果连接被接受,套接字将成功创建,客户端可以使用该套接字与服务器通信。
客户端和服务器现在可以通过写入或从套接字读取来进行通信。
定义:
套接字是网络上两个运行程序之间的双向通信链路的一个端点。套接字绑定到一个端口号,以便 TCP 层可以识别数据要发送到的应用程序。
一个端点是 IP 地址和端口号的组合。每个 TCP 连接可以通过其两个端点唯一标识。这样,您可以在主机和服务器之间建立多个连接。
Java 平台中的java.net
包提供了一个类Socket
,它实现了您的 Java 程序与网络上另一个程序之间的双向连接的一侧。Socket
类位于一个平台相关的实现之上,隐藏了任何特定系统的细节,使您的 Java 程序能够以与平台无关的方式在网络上通信,而不是依赖本机代码。
此外,java.net
包含ServerSocket
类,它实现了服务器可以用来监听并接受客户端连接的套接字。本课程将向您展示如何使用Socket
和ServerSocket
类。
如果你想连接到网络,URL
类及其相关类(URLConnection
,URLEncoder
)可能比套接字类更合适。实际上,URL 是与网络连接的相对高级的方式,并且在其底层实现中使用了套接字。有关通过 URL 连接到网络的信息,请参阅使用 URL。
从套接字读取和写入
译文:
docs.oracle.com/javase/tutorial/networking/sockets/readingWriting.html
让我们看一个简单的示例,说明程序如何使用Socket
类与服务器程序建立连接,然后客户端如何通过套接字向服务器发送数据并从服务器接收数据。
该示例程序实现了一个客户端,EchoClient
,连接到一个回显服务器。回显服务器从其客户端接收数据并将其回显。示例EchoServer
实现了一个回显服务器。(或者,客户端可以连接到支持Echo Protocol的任何主机。)
EchoClient
示例创建一个套接字,从而连接到回显服务器。它从标准输入流中读取用户输入,然后通过将文本写入套接字将该文本转发到回显服务器。服务器通过套接字将输入回显到客户端。客户端程序读取并显示从服务器传回的数据。
请注意,EchoClient
示例既向其套接字写入数据,也从其套接字读取数据,从而向回显服务器发送数据并接收数据。
让我们逐步走过程序并研究有趣的部分。在EchoClient
示例中的try
-with-resources 语句中,以下语句至关重要。这些行建立了客户端和服务器之间的套接字连接,并在套接字上打开了一个PrintWriter
和一个BufferedReader
:
String hostName = args[0]; int portNumber = Integer.parseInt(args[1]); try ( Socket echoSocket = new Socket(hostName, portNumber); // 1st statement PrintWriter out = // 2nd statement new PrintWriter(echoSocket.getOutputStream(), true); BufferedReader in = // 3rd statement new BufferedReader( new InputStreamReader(echoSocket.getInputStream())); BufferedReader stdIn = // 4th statement new BufferedReader( new InputStreamReader(System.in)) )
try
-with 资源语句中的第一条语句创建了一个新的Socket
对象,并命名为echoSocket
。此处使用的Socket
构造函数需要要连接的计算机的名称和端口号。示例程序使用第一个命令行参数作为计算机的名称(主机名),第二个命令行参数作为端口号。当您在计算机上运行此程序时,请确保您使用的主机名是要连接的计算机的完全限定 IP 名称。例如,如果您的回显服务器在计算机echoserver.example.com
上运行,并且正在侦听端口号 7,则如果您想将EchoServer
示例用作您的回显服务器,请先从计算机echoserver.example.com
运行以下命令:
java EchoServer 7
后续,使用以下命令运行EchoClient
示例:
java EchoClient echoserver.example.com 7
try
-with 资源语句中的第二个语句获取套接字的输出流,并在其上打开名为out
的PrintWriter
。类似地,第三个语句获取套接字的输入流,并在其上打开名为in
的BufferedReader
。该示例使用读取器和写入器,以便可以通过套接字写入 Unicode 字符。如果您还不熟悉 Java 平台的 I/O 类,请阅读基本 I/O。
程序的下一个有趣部分是while
循环。该循环从标准输入流中逐行读取数据,使用在try
-with 资源语句中创建的BufferedReader
对象stdIn
。然后,该循环立即将该行数据通过写入到与套接字连接的PrintWriter
发送到服务器:
String userInput; while ((userInput = stdIn.readLine()) != null) { out.println(userInput); System.out.println("echo: " + in.readLine()); }
while
循环中的最后一个语句从与套接字连接的BufferedReader
中读取一行信息。readLine
方法会等待服务器将信息回显给EchoClient
。当readline
返回时,EchoClient
将信息打印到标准输出。
while
循环会一直持续,直到用户输入结束符号。也就是说,EchoClient
示例从用户那里读取输入,将其发送到 Echo 服务器,从服务器获取响应,并显示它,直到达到输入结束。 (您可以通过按下Ctrl-C键来输入结束符号。)然后while
循环终止,并且 Java 运行时会自动关闭与套接字和标准输入流连接的读取器和写入器,并关闭与服务器的套接字连接。Java 运行时会自动关闭这些资源,因为它们是在try
-with 资源语句中创建的。Java 运行时会按照它们创建的相反顺序关闭这些资源。(这很好,因为连接到套接字的流应该在套接字本身关闭之前关闭。)
该客户端程序非常简单直接,因为回显服务器实现了一个简单的协议。客户端向服务器发送文本,服务器将其回显。当您的客户端程序与更复杂的服务器(如 HTTP 服务器)通信时,您的客户端程序也会更复杂。但是,基本原理与本程序中的相同:
- 打开一个套接字。
- 打开一个输入流和输出流到套接字。
- 根据服务器的协议从流中读取和写入数据。
- 关闭流。
- 关闭套接字。
仅步骤 3 因客户端而异,取决于服务器。其他步骤基本保持不变。
编写套接字的服务器端
原文:
docs.oracle.com/javase/tutorial/networking/sockets/clientServer.html
本部分向您展示如何编写一个服务器和与之配套的客户端。客户端/服务器对中的服务器提供叩叩笑话。叩叩笑话深受儿童喜爱,通常是糟糕双关语的载体。它们的形式如下:
服务器:“叩叩!”
客户端:“谁在那里?”
服务器:“迪克斯特尔。”
客户端:“迪克斯特尔是谁?”
服务器:“迪克斯特尔大厅挂满冬青树枝。”
客户端:“呻吟。”
该示例由两个独立运行的 Java 程序组成:客户端程序和服务器程序。客户端程序由一个类KnockKnockClient
实现,与前一部分的EchoClient
示例非常相似。服务器程序由两个类实现:KnockKnockServer
和KnockKnockProtocol
。KnockKnockServer
类似于EchoServer
,包含服务器程序的main
方法,并负责监听端口、建立连接以及读取和写入套接字。类KnockKnockProtocol
提供笑话。它跟踪当前笑话、当前状态(发送叩叩、发送提示等),并根据当前状态返回笑话的各个文本部分。该对象实现了协议——客户端和服务器约定用于通信的语言。
以下部分详细介绍了客户端和服务器中每个类的代码,然后演示如何运行它们。
叩叩笑话服务器
本部分将介绍实现叩叩笑话服务器程序KnockKnockServer
的代码。
服务器程序首先通过创建一个新的ServerSocket
对象来监听特定端口(请参见以下代码段中的粗体语句)。运行此服务器时,请选择一个尚未用于其他服务的端口。例如,以下命令启动服务器程序KnockKnockServer
,使其监听端口 4444:
java KnockKnockServer 4444
服务器程序在try
-with-resources 语句中创建ServerSocket
对象:
int portNumber = Integer.parseInt(args[0]); try ( ServerSocket serverSocket = new ServerSocket(portNumber); Socket clientSocket = serverSocket.accept(); PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true); BufferedReader in = new BufferedReader( new InputStreamReader(clientSocket.getInputStream())); ) {
ServerSocket
是java.net
类,提供了客户端/服务器套接字连接的服务器端的系统独立实现。如果ServerSocket
的构造函数无法监听指定端口(例如,端口已被使用),则会抛出异常。在这种情况下,KnockKnockServer
别无选择,只能退出。
如果服务器成功绑定到其端口,则ServerSocket
对象成功创建,服务器继续下一步——接受来自客户端的连接(try
-with-resources 语句中的下一条语句):
clientSocket = serverSocket.accept();
accept
方法会等待,直到客户端在此服务器的主机和端口上启动并请求连接。(假设您在名为knockknockserver.example.com
的计算机上运行了服务器程序KnockKnockServer
。)在这个例子中,服务器正在运行在第一个命令行参数指定的端口号上。当请求连接并成功建立连接时,accept 方法会返回一个新的Socket
对象,该对象绑定到相同的本地端口,并将其远程地址和远程端口设置为客户端的地址和端口。服务器可以通过这个新的Socket
与客户端通信,并继续监听原始ServerSocket
上的客户端连接请求。这个程序的特定版本不会监听更多的客户端连接请求。然而,在支持多个客户端中提供了程序的修改版本。
在服务器成功与客户端建立连接后,使用此代码与客户端通信:
try ( // ... PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true); BufferedReader in = new BufferedReader( new InputStreamReader(clientSocket.getInputStream())); ) { String inputLine, outputLine; // Initiate conversation with client KnockKnockProtocol kkp = new KnockKnockProtocol(); outputLine = kkp.processInput(null); out.println(outputLine); while ((inputLine = in.readLine()) != null) { outputLine = kkp.processInput(inputLine); out.println(outputLine); if (outputLine.equals("Bye.")) break; }
该代码执行以下操作:
- 获取套接字的输入和输出流,并在其上打开读取器和写入器。
- 通过向套接字写入来与客户端启动通信(以粗体显示)。
- 通过读取和写入套接字(
while
循环)与客户端通信。
第 1 步已经很熟悉。第 2 步以粗体显示,并值得一些评论。上面代码段中的粗体语句启动了与客户端的对话。代码创建了一个KnockKnockProtocol
对象,该对象跟踪当前笑话,笑话中的当前状态等。
创建KnockKnockProtocol
后,代码调用KnockKnockProtocol
的processInput
方法,以获取服务器发送给客户端的第一条消息。在这个例子中,服务器首先说的是“敲门!”。接下来,服务器将信息写入连接到客户端套接字的PrintWriter
,从而将消息发送给客户端。
第 3 步在while
循环中编码。只要客户端和服务器仍有话要说,服务器就会从套接字中读取并写入,来回发送消息。
服务器通过一个"敲门!敲门!“的方式开始对话,因此服务器必须等待客户端说"谁在那里?”。因此,while
循环在从输入流读取时进行迭代。readLine
方法会等待客户端通过写入内容到其输出流(服务器的输入流)来做出响应。当客户端做出响应时,服务器将客户端的响应传递给KnockKnockProtocol
对象,并要求KnockKnockProtocol
对象提供合适的回复。服务器立即通过连接到套接字的输出流将回复发送给客户端,使用println
方法。如果从KnockKnockServer
对象生成的服务器响应是"再见。",这表示客户端不想再听笑话了,循环结束。
Java 运行时会自动关闭输入和输出流、客户端套接字和服务器套接字,因为它们是在try
-with-resources 语句中创建的。
敲门协议
KnockKnockProtocol
类实现了客户端和服务器用于通信的协议。该类跟踪客户端和服务器在对话中的位置,并为客户端的陈述提供服务器的响应。KnockKnockProtocol
对象包含所有笑话的文本,并确保客户端对服务器的陈述给出正确的回应。当服务器说"敲门!敲门!"时,客户端说"谁在那里?"才是正确的回应。
所有的客户端/服务器对都必须有一些协议来进行通信;否则,来回传递的数据将毫无意义。你自己的客户端和服务器使用的协议完全取决于它们完成任务所需的通信。
敲门客户端
KnockKnockClient
类实现了与KnockKnockServer
通信的客户端程序。KnockKnockClient
基于前一节中的EchoClient
程序从套接字读取和写入,应该对您来说有些熟悉。但我们仍然会查看程序并了解客户端中发生的事情,以及与服务器中发生的情况相比较。
当您启动客户端程序时,服务器应该已经在运行并监听端口,等待客户端请求连接。因此,客户端程序的第一步是打开一个连接到指定主机名和端口上运行的服务器的套接字:
String hostName = args[0]; int portNumber = Integer.parseInt(args[1]); try ( Socket kkSocket = new Socket(hostName, portNumber); PrintWriter out = new PrintWriter(kkSocket.getOutputStream(), true); BufferedReader in = new BufferedReader( new InputStreamReader(kkSocket.getInputStream())); )
在创建套接字时,KnockKnockClient
示例使用第一个命令行参数的主机名,即运行服务器程序KnockKnockServer
的网络计算机的名称。
KnockKnockClient
示例在创建套接字时使用第二个命令行参数作为端口号。这是一个远程端口号——服务器计算机上的端口号——是KnockKnockServer
正在监听的端口。例如,以下命令在运行KnockKnockServer
程序的计算机上以knockknockserver.example.com
作为服务器程序KnockKnockServer
的计算机名称,4444 作为远程端口号运行KnockKnockClient
示例:
java KnockKnockClient knockknockserver.example.com 4444
客户端的套接字绑定到任何可用的本地端口——客户端计算机上的端口。请记住,服务器也会获得一个新的套接字。如果你使用前面示例中的命令行参数运行KnockKnockClient
示例,那么这个套接字将绑定到你运行KnockKnockClient
示例的计算机上的本地端口号 4444。服务器的套接字和客户端的套接字已连接。
接下来是实现客户端和服务器之间通信的while
循环。服务器先发言,所以客户端必须先听。客户端通过从连接到套接字的输入流中读取来实现这一点。如果服务器发言,它会说“再见”,然后客户端退出循环。否则,客户端将文本显示到标准输出,然后从用户那里读取响应,用户通过标准输入键入。用户键入回车后,客户端通过连接到套接字的输出流将文本发送到服务器。
while ((fromServer = in.readLine()) != null) { System.out.println("Server: " + fromServer); if (fromServer.equals("Bye.")) break; fromUser = stdIn.readLine(); if (fromUser != null) { System.out.println("Client: " + fromUser); out.println(fromUser); } }
当服务器询问客户端是否想听另一个笑话时,通信结束,客户端说不想听,服务器说“再见”。
客户端会自动关闭其输入和输出流以及套接字,因为它们是在try
-with-resources 语句中创建的。
Java 中文官方教程 2022 版(四十五)(3)https://developer.aliyun.com/article/1488410