Java Socket - 使用套接字进行 Java 网络编程

位置:首页>文章>详情   分类: Java教程 > 编程技术   阅读(304)   2023-06-26 07:54:18

Java Socket 教程展示了如何在 Java 中使用套接字进行网络编程。套接字编程是低级的。本教程的目的是介绍网络编程,包括这些底层细节。有更高级的 API 可能更适合实际任务。例如,Java 11 引入了 HttpClient,Spring 引入了 Webclient。

套接字

在编程中,插件座 是网络上运行的两个程序之间通信的端点。套接字类用于在客户端程序和服务器程序之间创建连接。 Socket 表示客户端套接字,ServerSocket 表示服务器套接字。

笔记: 在网络中,术语套接字具有不同的含义。它用于 IP 地址和端口号的组合。

一个ServerSocket绑定了一个端口号,它是一个唯一的Id,通过客户端和服务器的约定来进行通信。

SocketServerSocket 用于 TCP 协议。 DatagramSocketDatagramPacket 用于 UDP 协议。

TCP 更可靠,具有广泛的错误检查,并且需要更多资源。它由 HTTP、SMTP 或 FTP 等服务使用。 UDP 的可靠性要差得多,错误检查有限,并且需要的资源更少。它由 VoIP 等服务使用。

DatagramSocket 是用于发送和接收数据报包的套接字。数据报包由 DatagramPacket 类表示。在数据报套接字上发送或接收的每个数据包都被单独寻址和路由。从一台机器发送到另一台机器的多个数据包可能会有不同的路由,并且可能以任何顺序到达。

Java 套接字时间客户端

这些是提供当前时间的服务器。客户端无需命令即可简单地连接到服务器,服务器会以当前时间作为响应。

笔记: 时间服务器来来去去,所以我们可能需要在 https://www.ntppool.org/en/ 上找到一个工作服务器。

在我们的示例中,我们选择了瑞典的服务器。

com/zetcode/SocketTimeClient.java
package com.zetcode;

import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

// time servers come and go; we might need to
// find a functioning server on https://www.ntppool.org/en/

public class SocketTimeClient {

    public static void main(String[] args) throws IOException {

        var hostname = "3.se.pool.ntp.org";
        int port = 13;

        try (var socket = new Socket(hostname, port)) {

            try (var reader = new InputStreamReader(socket.getInputStream())) {

                int character;
                var output = new StringBuilder();

                while ((character = reader.read()) != -1) {

                    output.append((char) character);
                }

                System.out.println(output);
            }
        }
    }
}

该示例连接到时间服务器并接收当前时间。

var hostname = "3.se.pool.ntp.org";
int port = 13;

这是来自瑞典的时间服务器; 13 端口是白天服务的标准端口。

try (var socket = new Socket(hostname, port)) {

创建一个流客户端套接字。它连接到指定主机上的指定端口号。使用 Java 的 try-with-resources 语句自动关闭套接字。

try (var reader = new InputStreamReader(socket.getInputStream())) {

getInputStream 返回此套接字的输入流。我们从这个输入流中读取服务器的响应。套接字之间的通信以字节为单位;因此,我们使用 InputStreamReader 作为字节和字符之间的桥梁。

int character;
var output = new StringBuilder();

while ((character = reader.read()) != -1) {

    output.append((char) character);
}

System.out.println(output);

由于响应消息很小,我们可以逐个字符地读取它,而性能损失很小。

Java Socket Whois 客户端

Whois 是一种基于 TCP 的面向事务的查询/响应协议,广泛用于向 Internet 用户提供信息服务。用于查询域名或IP地址块所有者等信息。

笔记: 大多数 whois 服务器只提供有限的信息(例如,只提供选定的域名),并且有关所有者的信息通常由域名注册商匿名化。

Whois 协议使用端口 43。

com/zetcode/WhoisClientEx.java
package com.zetcode;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

// probing whois.iana.org might give the right
// whois server

public class WhoisClientEx {

    public static void main(String[] args) throws IOException {

        var domainName = "example.com";
        var whoisServer = "whois.nic.me";
        int port = 43;

        try (var socket = new Socket(whoisServer, port)) {

            try (var writer = new PrintWriter(socket.getOutputStream(), true)) {

                writer.println(domainName);

                try (var reader = new BufferedReader(
                        new InputStreamReader(socket.getInputStream()))) {

                    String line;

                    while ((line = reader.readLine()) != null) {

                        System.out.println(line);
                    }
                }
            }
        }
    }
}

在示例中,我们探测有关域名所有者的信息。

try (var writer = new PrintWriter(socket.getOutputStream(), true)) {

    writer.println(domainName);
...

我们获取套接字的输出流并将其包装到 PrintWriter 中。 PrintWriter 会将我们的字符转换为字节。使用 println,我们将域名写入流。通过套接字的通信被缓冲。 PrintWriter 的第二个参数是 autoFlush;如果设置为 true,缓冲区将在每次 println 后刷新。

try (var reader = new BufferedReader(
        new InputStreamReader(socket.getInputStream()))) {

    String line;

    while ((line = reader.readLine()) != null) {

        System.out.println(line);
    }
}

来自服务器的响应被读取并写入控制台。

Java 套接字 GET 请求

在以下示例中,我们创建一个 GET 请求。 HTTP GET 请求用于检索特定资源。

com/zetcode/JavaSocketGetRequest.java
package com.zetcode;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class SocketGetRequest {

    public static void main(String[] args) throws IOException {

        try (var socket = new Socket("webcode.me", 80)) {

            try (var wtr = new PrintWriter(socket.getOutputStream())) {

                // create GET request
                wtr.print("GET / HTTP/1.1\r\n");
                wtr.print("Host: www.webcode.me\r\n");
                wtr.print("\r\n");
                wtr.flush();
                socket.shutdownOutput();

                String outStr;

                try (var bufRead = new BufferedReader(new InputStreamReader(
                        socket.getInputStream()))) {

                    while ((outStr = bufRead.readLine()) != null) {

                        System.out.println(outStr);
                    }

                    socket.shutdownInput();
                }
            }
        }
    }
}

该示例从网站检索 HTML 页面。

try (var socket = new Socket("webcode.me", 80)) {

我们在指定网页的80端口上打开一个socket。HTTP协议使用80端口。

try (var wtr = new PrintWriter(socket.getOutputStream())) {

我们将在协议上发出文本命令;因此,我们为套接字输出流创建了一个PrintWriter。由于我们没有将 autoFlush 选项设置为 true,因此我们需要手动刷新缓冲区。

// create GET request
wtr.print("GET / HTTP/1.1\r\n");
wtr.print("Host: www.webcode.me\r\n");
wtr.print("\r\n");
wtr.flush();

我们创建一个 HTTP GET 请求,它检索指定网页的主页。请注意,文本命令以 \r\n (CRLF) 字符结束。这些是 RFC 2616 文档中描述的必要通信细节。

socket.shutdownOutput();

shutdownOutput 禁用此套接字的输出流。这是最终关闭连接所必需的。

try (var bufRead = new BufferedReader(new InputStreamReader(
    socket.getInputStream()))) {

对于服务器响应,我们打开套接字输入流并使用 InputStreamReader 将字节转换为字符。我们还缓冲读取操作。

while ((outStr = bufRead.readLine()) != null) {

    System.out.println(outStr);
}

我们逐行读取数据。

socket.shutdownInput();

最后,我们也关闭了输入流。

Java Socket HEAD 请求

在下一个示例中,我们使用 Java 套接字创建 HEAD 请求。 HEAD 方法与 GET 方法相同,只是服务器不在响应中返回消息体;它只返回标题。

com/zetcode/SocketHeadRequest.java
package com.zetcode;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class SocketHeadRequest {

    public static void main(String[] args) throws IOException {

        var hostname = "webcode.me";
        int port = 80;

        try (var socket = new Socket(hostname, port)) {

            try (var writer = new PrintWriter(socket.getOutputStream(), true)) {

                writer.println("HEAD / HTTP/1.1");
                writer.println("Host: " + hostname);
                writer.println("User-Agent: Console Http Client");
                writer.println("Accept: text/html");
                writer.println("Accept-Language: en-US");
                writer.println("Connection: close");
                writer.println();

                try (var reader = new BufferedReader(new InputStreamReader(
                        socket.getInputStream()))) {

                    String line;

                    while ((line = reader.readLine()) != null) {

                        System.out.println(line);
                    }
                }
            }
        }
    }
}

该示例检索指定网页的标头。

writer.println("HEAD / HTTP/1.1");

我们发出 HEAD 命令。

writer.println("Connection: close");

在 HTTP 协议版本 1.1 中,除非另有声明,否则所有连接都被认为是持久的(keep-alive)。通过将选项设置为 false,我们通知我们希望在请求/响应周期后完成连接。

Java ServerSocket 日期服务器

以下示例使用 ServerSocket 创建了一个非常简单的服务器。 ServerSocket 创建一个服务器套接字,绑定到指定的端口。

com/zetcode/DateServer.java
package com.zetcode;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.time.LocalDate;

public class DateServer {

    public static void main(String[] args) throws IOException {

        int port = 8081;

        try (var listener = new ServerSocket(port)) {

            System.out.printf("The started on port %d%n", port);

            while (true) {

                try (var socket = listener.accept()) {

                    try (var pw = new PrintWriter(socket.getOutputStream(), true)) {

                        pw.println(LocalDate.now());
                    }
                }
            }
        }
    }
}

该示例创建一个返回当前日期的服务器。最后必须手动杀死该程序。

int port = 8081;

try (var listener = new ServerSocket(port)) {

创建端口 8081 上的服务器套接字。

try (var socket = listener.accept()) {

accept 方法侦听与此套接字建立的连接并接受它。该方法会阻塞,直到建立连接。

try (var pw = new PrintWriter(socket.getOutputStream(), true)) {

    pw.println(LocalDate.now());
}

我们将当前日期写入套接字输出流。

get_request.py
#!/usr/bin/env python3

import socket

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:

    s.connect(("localhost" , 8081))
    s.sendall(b"GET / HTTP/1.1\r\nHost: localhost\r\nAccept: text/html\r\n\r\n")
    print(str(s.recv(4096),  'utf-8'))

我们有一个 Python 脚本向服务器发出 GET 请求。

$ get_request.py
2019-07-15

Java 套接字客户端/服务器示例

在下面的示例中,我们有一个服务器和一个客户端。服务器反转从客户端发送的文本。该示例很简单且阻塞。为了改进它,我们需要包括线程。

com/zetcode/ReverseServer.java
package com.zetcode;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;

// This server communicates only with one client at a time.
// It must disconnect from a client first to communicate
// with another client. It receives a bye command from a client
// to close a connection.

public class ReverseServer {

    public static void main(String[] args) throws IOException {

        int port = 8081;

        try (var serverSocket = new ServerSocket(port)) {

            System.out.println("Server is listening on port " + port);

            while (true) {

                try (var socket = serverSocket.accept()) {

                    System.out.println("client connected");

                    try (var reader = new BufferedReader(new InputStreamReader(
                                socket.getInputStream()));
                         var writer = new PrintWriter(socket.getOutputStream(), true)) {

                        String text;

                        do {

                            text = reader.readLine();

                            if (text != null) {

                                var reversed = new StringBuilder(text).reverse().toString();
                                writer.println("Server: " + reversed);

                                System.out.println(text);
                            }
                        } while (!"bye".equals(text));

                        System.out.println("client disconnected");
                    }
                }
            }
        }
    }
}

ReverseServer 将反向字符串发送回客户端。它一次只与一个客户端通信。它必须首先断开与客户端的连接才能与另一个客户端通信。它从客户端接收到关闭连接的再见命令。

try (var reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    var writer = new PrintWriter(socket.getOutputStream(), true)) {

我们有一个用于读取客户端数据的套接字输入流和用于将响应发送回客户端的套接字输出流;输出流和连接关闭。

do {

    text = reader.readLine();

    if (text != null) {

        var reversed = new StringBuilder(text).reverse().toString();
        writer.println("Server: " + reversed);

        System.out.println(text);
    }
} while (!"bye".equals(text));

do-while 循环是为单个客户端创建的。我们从客户端读取数据并将修改后的内容发回。循环在收到来自客户端的 bye 命令后结束。在此之前,没有其他客户端可以连接到服务器。

com/zetcode/ReverseClient.java
package com.zetcode;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

// the client must send a bye command to
// inform the server to close the connection

public class ReverseClient {

    public static void main(String[] args) throws IOException {

        var hostname = "localhost";
        int port = 8081;

        try (var socket = new Socket(hostname, port)) {

            try (var writer = new PrintWriter(socket.getOutputStream(), true)) {

                try (var scanner = new Scanner(System.in)) {

                    try (var reader = new BufferedReader(new InputStreamReader(
                            socket.getInputStream()))) {

                        String command;

                        do {

                            System.out.print("Enter command: ");

                            command = scanner.nextLine();

                            writer.println(command);

                            var data = reader.readLine();
                            System.out.println(data);

                        } while (!command.equals("bye"));
                    }
                }
            }
        }
    }
}

客户端向服务器发送文本数据。

do {

    System.out.print("Enter command: ");

    command = scanner.nextLine();

    writer.println(command);

    var data = reader.readLine();
    System.out.println(data);

} while (!command.equals("bye"));

我们从控制台读取输入并将其发送到服务器。当我们发送 bye 命令时 while 循环结束,它通知服务器可以关闭连接。

Java 数据报套接字示例

UDP 是一种通过网络传输独立数据包的通信协议,不保证到达且不保证传递顺序。使用 UDP 的一项服务是每日报价 (QOTD)。

以下示例创建一个连接到 QOTD 服务的客户端程序。

com/zetcode/DatagramSocketEx.java
package com.zetcode;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

// DatagramSocket provides network communication via UDP protocol
// The Quote of the Day (QOTD) service is a member of the Internet protocol
// suite, defined in RFC 865

public class DatagramSocketEx {

    public static void main(String[] args) throws IOException {

        var hostname = "djxmmx.net";
        int port = 17;

        var address = InetAddress.getByName(hostname);

        try (var socket = new DatagramSocket()) {

            var request = new DatagramPacket(new byte[1], 1, address, port);
            socket.send(request);

            var buffer = new byte[512];
            var response = new DatagramPacket(buffer, buffer.length);
            socket.receive(response);

            var quote = new String(buffer, 0, response.getLength());
            System.out.println(quote);
        }
    }
}

该示例从报价服务中检索报价并将其打印到终端。

var address = InetAddress.getByName(hostname);

我们从主机名中获取 IP 地址。

try (var socket = new DatagramSocket()) {

创建了一个 DatagramSocket

var request = new DatagramPacket(new byte[1], 1, address, port);

创建了一个 DatagramPacket。由于 QOTD 服务不需要来自客户端的数据,因此我们发送一个空的小数组。每次发送数据包时,我们都需要指定数据、地址和端口。

socket.send(request);

使用 send 将数据包发送到其目的地。

var buffer = new byte[512];
var response = new DatagramPacket(buffer, buffer.length);
socket.receive(response);

我们从服务收到一个数据包。

var quote = new String(buffer, 0, response.getLength());
System.out.println(quote);

我们将接收到的字节转换为字符串并打印出来。

在本文中,我们创建了带有套接字的网络 Java 程序。

List 所有 Java 教程.

地址:https://www.cundage.com/article/java-socket.html

相关阅读

Spring Boot Vue.js 教程展示了如何使用 Vue.js 框架创建一个简单的 Spring Boot。 Vue.js Vue.js 是一个用于构建用户界面的 JavaScript ...
JavaScript 是否已经取代 Java 成为新的“一次编写,随处运行”的编程语言?这完全取决于您的观点。随着 WebAssembly 等技术的出现,Java 可以在新奇的“一次编写,随处编...
Usage of TypeScript,微软基于 JavaScript 的强类型语言, has soared compared to six years ago, according to th...
云莓将基于 Spring 构建的 Java 后端与使用 Lit 构建的 TypeScript 前端相结合,一个快速、响应式的 JavaScript 框架。基于 Vaadin Fusion 的 H...
本博客严重偏向于 GWT(和基于 GWT 的框架),但我们牢记 GWT 将来可能会被其他技术接管,因此我们始终对探索其他平台/框架持开放态度。正如他们所说,多元化可以降低风险。每种编程语言,即使...
Java JSON 教程展示了如何使用 JSON-Java 在 Java 中进行 JSON 序列化和反序列化。 JSON(JavaScript 对象显示法) 是一种轻量级数据交换格式。人类易于读...
JHipster 是一个长期存在且雄心勃勃的混合 Java 和 JavaScript 项目,致力于使用现代反应式前端简化全栈 Java 应用程序的开发。 JHipster 开发团队不断发布新版本...
解析器是强大的工具,使用 ANTLR 可以编写可用于多种不同语言的各种解析器。 在这个完整的教程中,我们将: 解释基础:什么是解析器,它可以用来做什么 查看如何设置 ANTLR 以便在 Java...
Spring Boot JSON 教程展示了如何在 Spring Boot 注释中提供 JSON 数据。 春天 是一个流行的 Java 应用程序框架,弹簧贴 是 Spring 的演变,有助于创建...
根据最近一项全球开发人员调查,在开发人员偏好方面,JavaScript 和 Python 保持了持久力,而 锈 的使用率正在上升. 5 月 4 日的一份题为“开发者国家情况,第 22nd 版”的...