Java Socket:飞鸽传书的网络套接字

套接字(Socket)是一个抽象层,应用程序可以通过它发送或接收数据;就像操作文件那样可以打开、读写和关闭。套接字允许应用程序将 I/O 应用于网络中,并与其他应用程序进行通信。网络套接字是 IP 地址与端口的组合。

01、ping 与 telnet

“老王啊,能不能帮我看一下这个问题呢,明明本地可以进行网络通信,可等我部署到服务器上时就通信不了了,搞了半天也不知道什么原因,我看代码是没有问题的。”小二的语气中充满了沮丧。

“ping 过吗?或者 telnet 了吗?”老王头都没回,冷冰冰地扔出去了这句话。

“哦,我去试试。”小二心头掠过一丝愧疚。

ping 与 telnet 这两个命令,对调试网络程序有着非常大的帮助。

ping,一种计算机网络工具,用来测试数据包能否透过 IP 协议到达特定主机。ping 会向目标主机发出一个 ICMP 的请求回显数据包,并等待接收回显响应数据包。

例如,我们 ping 一下博客园。截图如下。
在这里插入图片描述
telnet,Internet 远程登录服务的标准协议和主要方式,可以让我们坐在家里的计算机面前,登录到另一台远在天涯海角的远程计算机上。

在这里插入图片描述
在 Windows 系统中,telnet 一般是默认安装的,但未激活(可以在控制面板中激活它)。

例如,我们 telnet 一下火(shui)土(mu)社区。截图如下。
在这里插入图片描述
使用 telnet 登录远程计算机时,需要远程计算机上运行一个服务,它一直不停地等待那些希望和它进行连接的网络请求;当接收到一个客户端的网络连接时,它便唤醒正在监听网络连接请求的服务器进程,并为两者建立连接。连接会一直保持,直到某一方中止。

不过,需要注意的是,telnet 在格外重视安全的现代网络技术中并不受到重用。因为 telnet 是一个明文传输协议,用户的所有内容(包括用户名和密码)都没有经过加密,安全隐患非常大。

02、Socket 实例

不知道你有没有体验一下 telnet 火土社区的那条命令,结果非常有趣。我们也可以通过 Java 的客户端套接字(Socket)实现,代码示例如下。

try (Socket socket = new Socket("bbs.newsmth.net", 23);) {
    InputStream is = socket.getInputStream();
    Scanner scanner = new Scanner(is, "gbk");

    while (scanner.hasNextLine()) {
        String line = scanner.nextLine();
        System.out.println(line);
    }

} catch (UnknownHostException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

1)建立套接字连接非常简单,只需要一行代码:

Socket socket = new Socket(host, port)

host 为主机名,port 为端口号(23 为默认的 telnet 端口号)。如果无法确定主机的 IP 地址,则抛出 UnknownHostException 异常;如果在创建套接字时发生 IO 错误,则抛出 IOException 异常。

需要注意的是,套接字在建立的时候,如果远程主机不可访问,这段代码就会阻塞很长时间,直到底层操作系统的限制而抛出异常。所以一般会在套接字建立后设置一个超时时间。

Socket socket = new Socket(...);
socket.setSoTimeout(10000); // 单位为毫秒

2)套接字连接成功后,可以通过 java.net.Socket 类的 getInputStream() 方法获取输入流。有了 InputStream 对象后,可以借助文本扫描器类(Scanner)将其中的内容打印出来。

InputStream is = socket.getInputStream();
Scanner scanner = new Scanner(is, "gbk");

while (scanner.hasNextLine()) {
    String line = scanner.nextLine();
    System.out.println(line);
}

部分结果(完整结果自己亲手实践一下哦)如下图所示:
在这里插入图片描述

03、ServerSocket 实例

接下来,我们模拟一个远程服务,通过 java.net.ServerSocket 实现。代码示例如下。

try (ServerSocket server = new ServerSocket(8888);
        Socket socket = server.accept();
        InputStream is = socket.getInputStream();
        OutputStream os = socket.getOutputStream();

        Scanner scanner = new Scanner(is)) {
    PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, "gbk"), true);
    pw.println("你好啊");

    boolean done = false;
    while (!done && scanner.hasNextLine()) {
        String line = scanner.nextLine();
        System.out.println(line);

        if ("2048".equals(line)) {
            done = true;
        }
    }
} catch (UnknownHostException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

1)建立服务器端的套接字也比较简单,只需要指定一个能够独占的端口号就可以了(0~1023 这些端口都已经被系统预留了)。

ServerSocket server = new ServerSocket(8888);

2)调用 ServerSocket 对象的 accept() 等待客户端套接字的连接请求。一旦监听到客户端的套接字请求,就会返回一个表示连接已建立的 Socket 对象,可以从中获取到输入流和输出流。

Socket socket = server.accept();
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();

客户端套接字发送的所有信息都会包裹在服务器端套接字的输入流中;而服务器端套接字发送的所有信息都会包裹在客户端套接字的输出流中。

3)服务器端可以通过以下代码向客户端发送消息。

PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, "gbk"), true);
pw.println("你好啊");

4)服务器端可以通过以下代码读取客户端发送过来的消息。

Scanner scanner = new Scanner(is);
boolean done = false;
while (!done && scanner.hasNextLine()) {
    String line = scanner.nextLine();
    System.out.println(line);

    if ("2048".equals(line)) {
        done = true;
    }
}

运行该服务后,可以通过 telnet localhost 8888 命令连接该远程服务,不出所料,你将会看到以下信息。
在这里插入图片描述
PS:可以在当前命令窗口中输入 2048,服务端收到该消息后会中断该套接字连接(当前窗口会显示“遗失对主机的连接”)。

04、为多个客户端服务

非常遗憾的是,上面的例子中,服务器端只能为一个客户端服务——这不符合服务器端一对多的要求。

优化方案也非常简单(你应该也能想得到):服务器端接收到客户端的套接字请求时,可以启动一个线程来处理,而主程序继续等待下一个连接。代码示例如下。

try (ServerSocket server = new ServerSocket(8888)) {

    while (true) {
        Socket socket = server.accept();
        Thread thread = new Thread(new Runnable() {

            @Override
            public void run() {
              // 套接字处理程序
            }
        });
        thread.start();

    }
} catch (IOException e) {
    e.printStackTrace();
}

线程内部(run(){} 方法里)用来处理套接字,代码示例如下:

try {
    InputStream is = socket.getInputStream();
    OutputStream os = socket.getOutputStream();
    Scanner scanner = new Scanner(is);

   // 其他代码省略
   // 向客户端发送消息
   // 读取客户端发送过来的消息
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        socket.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

服务器端代码优化后重新运行,你就可以通过 telnet 命令测试了。打开一个命令行窗口输入 telnet localhost 8888,再打开一个新的命令行窗口输入 telnet localhost 8888,多个窗口都可以和服务器端进行通信,除非服务器端代码中断运行。

05、加入多线程
多线程我们后面会详细讲,这里就主要是写个例子,好让大家感觉更有趣一些,其实也非常简单。

来看服务端:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class MultiThreadedServer {
    public static void main(String[] args) throws IOException {
        int port = 12345;
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("Server is listening on port " + port);

        while (true) {
            Socket socket = serverSocket.accept();
            System.out.println("Client connected");
            new ClientHandler(socket).start();
        }
    }
}
class ClientHandler extends Thread {
    private Socket socket;

    public ClientHandler(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        try {
            InputStream input = socket.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(input));

            OutputStream output = socket.getOutputStream();
            PrintWriter writer = new PrintWriter(output, true);

            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("Received: " + line);
                writer.println("Server: " + line);
            }

            socket.close();
        } catch (IOException e) {
            System.out.println("Client disconnected");
        }
    }
}

在这个示例中,我们使用了一个 ClientHandler 类,该类继承自 Thread 类。这使得每个客户端连接都可以在单独的线程中处理,从而允许服务器同时处理多个客户端连接。当一个新客户端连接到服务器时,服务器会创建一个新的 ClientHandler 对象,并使用 start() 方法启动线程。ClientHandler 类的 run() 方法包含处理客户端请求的逻辑。

来看客户端代码:

import java.io.*;
import java.net.Socket;

public class Client {
    public static void main(String[] args) throws IOException {
        String hostname = "localhost";
        int port = 12345;

        Socket socket = new Socket(hostname, port);
        System.out.println("Connected to the server");

        InputStream input = socket.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(input));

        OutputStream output = socket.getOutputStream();
        PrintWriter writer = new PrintWriter(output, true);

        writer.println("Hello, server!");
        String response = reader.readLine();
        System.out.println("Server response: " + response);

        socket.close();
    }
}

启动服务器端,然后多启动几个客户端,就可以体验到交互的乐趣了。
在这里插入图片描述

08、DatagramSocket 实例

DatagramSocket 类是 Java 中实现 UDP 协议的核心类。与基于 TCP 的 Socket 和 ServerSocket 类不同,DatagramSocket 类提供了无连接的通信服务,发送和接收数据包。由于无需建立连接,UDP 通常比 TCP 更快,但可能不如 TCP 可靠。

以下是一个简单的 DatagramSocket 示例,展示了如何使用 UDP 协议在客户端和服务器之间发送和接收消息。

服务器端代码:

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

public class UDPServer {
    public static void main(String[] args) throws IOException {
        int port = 12345;
        DatagramSocket serverSocket = new DatagramSocket(port);
        System.out.println("Server is listening on port " + port);

        byte[] buffer = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

        serverSocket.receive(packet);
        String message = new String(packet.getData(), 0, packet.getLength());
        System.out.println("Received: " + message);

        serverSocket.close();
    }
}

客户端代码:

import java.io.IOException;
import java.net.*;

public class UDPClient {
    public static void main(String[] args) throws IOException {
        String hostname = "localhost";
        int port = 12345;

        InetAddress address = InetAddress.getByName(hostname);
        DatagramSocket clientSocket = new DatagramSocket();

        String message = "Hello, server!";
        byte[] buffer = message.getBytes();

        DatagramPacket packet = new DatagramPacket(buffer, buffer.length, address, port);
        clientSocket.send(packet);
        System.out.println("Message sent");

        clientSocket.close();
    }
}

在这个示例中,服务器端创建一个 DatagramSocket 对象并监听端口 12345。然后,它创建一个 DatagramPacket 对象,用于存储接收到的数据包。serverSocket.receive(packet) 方法阻塞,直到收到一个数据包。收到数据包后,服务器从数据包中提取并打印消息。

客户端首先解析服务器的 IP 地址,然后创建一个 DatagramSocket 对象。接着,客户端创建一个包含要发送消息的 DatagramPacket 对象,并指定目标地址和端口。最后,客户端通过调用 clientSocket.send(packet) 方法发送数据包。

在这里插入图片描述

07、最后

如今大多数基于网络的软件,如浏览器、即时通讯工具甚至是 P2P 下载都是基于 Socket 实现的,所以掌握 Java Socket 编程还是蛮有必要的。Socket 编程也比较有趣,很多初学者都会编写一两个基于“客户端-服务器端”的小程序来提高自己的编程水平,建议你也试一试。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/445689.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

VUE3 使用axios网络请求

1.新建工程 参考,VUE3 环境搭建:https://blog.csdn.net/LQ_001/article/details/136293795,运行命令 vue create vue-demo 2.引入axios 不管何种引用,都要在工程中安装 axios 包。安装命令:npm install --save axio…

linux paddle For C++环境搭建

paddle介绍 Paddle是类似tesseract的文字识别ocr。因为tesseract-ocr的中文识别效果不好。因此才准备安装Paddle。Paddle最方便的安装方式的使用Python的包管理安装。pip3 install paddlepaddle。但我使用了一下感觉还是用C更加方便,QT OpenCV Paddle应当还不错。…

Go语言必知必会100问题-19 浮点数溢出问题

问题呈现 在Go语言中,有两种浮点数类型(虚数除外):float32和float64. 浮点数是用来解决整数不能表示小数的问题。我们需要知道浮点数算术运算是实数算术运算的近似,下面通过例子说明浮点数运算采用近似值的影响以及如…

⭐每天一道leetcode:83.删除排序链表中的重复元素(简单;链表遍历、删除经典题目)

⭐今日份题目 给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。 示例1 输入:head [1,1,2] 输出:[1,2] 示例2 输入:head [1,1,2,3,3] 输出:[1,2,3] …

【R语言爬虫实战】抓取省市级城市常务会议内容

🍉CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一|统计学|干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项,参与研究经费10w、40w级横向 文…

enumerate函数的用法

enumerate() 函数是 Python 内置函数之一,用于同时返回可迭代对象的索引和对应的值。 它的语法结构如下: enumerate(iterable, start0) iterable: 表示一个可迭代的对象,如列表、元组、字符串等。start: 可选参数,表示索引起始…

02hadoop伪分布式搭建

3. 环境安装 3.1 安装方式 单机模式 只能启动MapReduce 伪分布式 能启动HDFS、MapReduce 和 YARN的大部分功能 完全分布式 能启动Hadoop的所有功能 3.2 安装JDK 3.2.1 JDK安装步骤 下载JDK安装包(下载Linux系统的 .tar.gz 的安装包) https://www…

网络协议常见问题

网络协议常见问题 OSI(Open Systems Interconnection)模型OSI 封装 TCP/IP协议栈IP数据报的报头TCP头格式UDP头格式TCP (3-way shake)三次握手建立连接:为什么三次握手才可以初始化 Socket、序列号和窗口大小并建立 TCP 连接。每次建立TCP连接…

蓝桥杯递推与递归法|斐波那契数列|数字三角形|42点问题|数的计算|数的划分(C++)

递归是用来做dfs,是搜索算法的基础 递推是用来做dp部分,及部分其他算法,复杂度较低,不会出现爆栈问题递推法: 递推法是一种在数学和其他领域广泛应用的重要方法,它在计算机科学中被用作一种关键的数值求解…

自动化运维利器Ansible基础(环境部署)

Ansible 介绍及安装 1. 介绍 Ansible 是⼀个 IT ⾃动化⼯具。它能配置系统、部署软件、编 排更复杂的 IT 任务,如连续部署或零停机时间滚动更新。 Ansible ⽤ Python 编写,尽管市⾯上已经有很多可供选择的 配置管理解决⽅案(例如 Salt、Pupp…

OpenAI GPT LLMs 高级提示词工程方法汇总

原文地址:An Introduction to Prompt Engineering for OpenAI GPT LLMs Github:Prompt-Engineering-Intro 2023 年 3 月 2 日 提示工程指南 | Prompt Engineering Guide Naive 提示词:带有提示的情感分类器 prompt Decide whether a T…

复合查询【MySQL】

文章目录 复合查询测试表 单表查询多表查询子查询单行子查询多行子查询IN 关键字ALL 关键字ANY 关键字 多列子查询 合并查询 复合查询 测试表 雇员信息表中包含三张表,分别是员工表(emp)、部门表(dept)和工资等级表&…

GEE:基于ERA5数据集(U和V风速分量)计算风速的幅值和风向

作者:CSDN @ _养乐多_ 本文将介绍使用Google Earth Engine (GEE)平台提供的API加载ERA5月度数据集,该数据集包含了从1979年至今的全球月度气象数据。然后,定义了一个数据计算函数,用于将U和V风速分量转换为风速的幅值和风向。 结果如下图所示, 文章目录 一、核心函数1…

基于单片机的语音存储与回放系统设计

目 录 摘 要 I Abstract II 引 言 1 1 控制系统设计 3 1.1 系统方案设计 3 1.2 系统工作原理 4 1.2.1 单片机的选择 4 1.2.2 语音芯片的选择 5 2 硬件电路设计 6 2.1 时钟电路 6 2.2 复位电路 6 2.3 显示电路 7 2.4 电源电路 7 2.5 按键模块电路 8 2.6 LM386功放电路 8 2.7 总…

基于深度学习YOLOv8+Pyqt5的抽烟吸烟检测识别系统(源码+跑通说明文件)

wx供重浩:创享日记 对话框发送:39抽烟 获取完整源码源文件4000张已标注的数据集配置说明文件 可有偿59yuan一对一远程操作跑通 效果展示 基于深度学YOLOv8PyQt5的抽烟吸烟检测识别系统(完整源码跑通说明文件) 各文件说明 模型评价…

Seurat 中的数据可视化方法

本文[1]将使用从 2,700 PBMC 教程计算的 Seurat 对象来演示 Seurat 中的可视化技术。您可以从 SeuratData[2] 下载此数据集。 SeuratData::InstallData("pbmc3k")library(Seurat)library(SeuratData)library(ggplot2)library(patchwork)pbmc3k.final <- LoadData(…

【机器学习300问】31、不平衡数据集如何进行机器学习?

一、什么是不平衡的数据集&#xff1f; &#xff08;1&#xff09;认识不平衡数据 假如你正在管理一个果园&#xff0c;这个果园里主要有两种水果——苹果和樱桃。如果苹果树有1000棵&#xff0c;而樱桃树只有10棵&#xff0c;那么在收集果园的果实时&#xff0c;你会得到大量…

RocketMQ架构详解

文章目录 概述RocketMQ架构rocketmq的工作流程Broker 高可用集群刷盘策略 概述 RocketMQ一个纯java、分布式、队列模型的开源消息中间件&#xff0c;前身是MetaQ&#xff0c;是阿里研发的一个队列模型的消息中间件&#xff0c;后开源给apache基金会成为了apache的顶级开源项目…

全栈的自我修养 ———— css中常用的布局方法flex和grid

在项目里面有两种常用的主要布局:flex和grid布局&#xff08;b站布局&#xff09;&#xff0c;今天分享给大家这两种的常用的简单方法&#xff01; 一、flex布局1、原图2、中心对齐3、主轴末尾或者开始对其4、互相间隔 二、grid布局1、基本效果2、加间隔3、放大某一个元素 一、…

Nginx请求转发和Rewrite的URL重写及重定向的功能实现移动端和PC端前端服务转发和重定向配置。

应用场景说明一 应用系统分pc端和微信小程序&#xff0c;移动端和pc端分别申请二级子域名&#xff0c;通过Nginx域名解析匹配&#xff0c;将web访问统一转发至对应的域名请求中。部分配置如下所示&#xff1a; 1、WEB访问统一入口域名解析转发配置&#xff0c;PC端和移动端根域…