Java中的Socket你了解吗

☆* o(≧▽≦)o *☆嗨~我是小奥🍹
📄📄📄个人博客:小奥的博客
📄📄📄CSDN:个人CSDN
📙📙📙Github:传送门
📅📅📅面经分享(牛客主页):传送门
🍹文章作者技术和水平有限,如果文中出现错误,希望大家多多指正!
📜 如果觉得内容还不错,欢迎点赞收藏关注哟! ❤️

文章目录

  • Java中的Socket你了解吗?
    • 1. 普通Socket
      • (1) Server
      • (2) Client
      • (3) 结果演示
    • 2. NioSocket
      • (1) Channel
      • (2) Buffer
      • (3) Selector
      • (4) NioSocket

Java中的Socket你了解吗?

Java中的Socket可以分为普通SocketNioSocket两种。

1. 普通Socket

Java中的网络通信是通过Socket实现的。Socket分为ServerSocketSocket两大类 。

  • ServerSocket用于服务端,可以通过accept方法监听请求,监听请求后返回Socket
  • Socket用于具体完成数据传输,客户端直接使用Socket发起请求并传输数据;

(1) Server

ServerSocket的使用可以分为三步:

  • 创建ServerSocketServerSocket的构造方法一共有5个,用起来最方便的是ServerSocket(int port),只需要一个port(端口号)即可。
  • 调用创建的ServerSocketaccept方法进行监听accept方法是阻塞方法,也就是调用该方法后程序会停下来等待连接请求,不会继续执行,当接收到请求后accept方法会返回一个Socket
  • 使用accept方法返回的Socket与客户端进行通信。

一个ServerSocket简单使用示例

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

/**
 * @Author zal
 * @Date 2024/01/15  20:12
 * @Description: ServerSocket
 * @Version: 1.0
 */
public class Server {
    public static void main(String[] args) {
        try {
            // 创建一个ServerSocket监听8080端口
            ServerSocket server = new ServerSocket(8080);
            // 等待请求
            Socket socket = server.accept();
            // 接收到请求后使用socket进行通信,创建BufferedReader用于读取数据
            BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String line = is.readLine();
            System.out.println("received from client: " + line);
            // 创建PrintWriter,用于发送数据
            PrintWriter pw = new PrintWriter(socket.getOutputStream());
            pw.println("received data:" + line);
            pw.flush();
            // 关闭资源 
            pw.close();
            socket.close();
            server.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在上述的代码实现中,先创建了ServerSocket,然后调用accept方法等待请求,当接收到请求后,用返回的Socket创建ReaderWriter来接收和发送数据,Reader接收到数据后保存到line,然后打印到控制台,再将数据发送到client

(2) Client

Socket的使用也是一样的:

  • 创建Socket。使用Socket(String host, int port),把目标主机地址和端口号传给Socket;
  • Socket创建的过程就会跟服务端建立连接,然后进行通信即可。

一个Socket的简单使用示例

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

/**
 * @Author zal
 * @Date 2024/01/15  20:27
 * @Description: Client
 * @Version: 1.0
 */
public class Client {
    public static void main(String[] args) {
        String msg = "Client Data";
        try {
            // 创建一个Socket。跟本机的8080端口连接
            Socket socket = new Socket("127.0.0.1", 8080);
            // 使用Socket创建的PrintWriter和BufferedReader进行读写数据
            PrintWriter pw = new PrintWriter(socket.getOutputStream());
            BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            // 发送数据
            pw.println(msg);
            pw.flush();
            // 接收数据
            String line = is.readLine();
            System.out.println("received from server:" + line);
            // 关闭资源
            pw.close();
            is.close();
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在上述的代码实现中,创建Socket将msg发送给服务端,然后再接收服务端返回的数据并打印到控制台,最后释放资源关闭连接。

(3) 结果演示

先启动Server然后启动Clinet就可以完成一次通信。

Server运行结果:

在这里插入图片描述

Client运行结果:

在这里插入图片描述

2. NioSocket

从JDK1.4开始,Java增加了新的IO模式 —— nio(new IO),nio在底层采用了新的处理方式,极大地提高了IO的效率。

我们使用的Socket也是IO的一种,nio也提供了相应的工具:ServerSocketChannelSocketChannel,它们分别对应原来的ServerSocketSocket

想要理解NioSocket必须先理解三个概念:BufferChannelSelector

我们可以先举一个例子:

之前的送货上门的服务,过程是有客户打电话预约服务,然后服务人员就去处理,提供上门服务,然后完成服务后就继续等待电话,等待下一次服务。(我们假设只有一个服务人员)

这种模式其实就相当于普通Socket的处理请求的模式,是阻塞式的,每次只能处理一个请求。

但是当有很多请求时,这种模式的弊端就很明显了。

现在的电商网站配送都是以快递的形式,快递会有很多件汇总在一起,进行出库、分拣,并且还要经历中转站,中转站会有分拣员将同一区域的快件给区分开,最后到达每一个快递点。

这样的方式效率就很高了,这种模式就相当于NioSocket的处理模式,Buffer就是要送快件,Channel就是快递送货员,Selector就是中转站的分拣员。

下面我们来介绍一下它们的概念。

(1) Channel

channel 有一点类似于 stream,它就是读写数据的双向通道,可以从 channel 将数据读入 buffer,也可以将 buffer 的数据写入 channel,而之前的 stream 要么是输入,要么是输出,channel stream 更为底层。

channel
buffer

常见的 Channel 有

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

(2) Buffer

Buffer则用来缓冲读写数据,Buffer里面有四个属性非常重要。

  • capacity容量,也就是Buffer最多可以保存元素的数量,在创建时设置,使用过程中不可以改变。
  • limit可以使用的上限,开始创建时limitcapacity的值相同,如果给limit设置一个值之后,limit就变成了最大可以访问的值,其值不可以超过capacity
  • position当前所操作元素所在的索引位置position从0开始,随着getput方法自动更新;
  • mark用来暂时保存position的值position保存到mark后就可以修改并进行相关的操作,操作完后可以通过reset方法将mark的值恢复到position

这四个属性的大小关系是:mark <= position <= limit <= capacity

常见的 buffer 有

  • ByteBuffer
    • MappedByteBuffer
    • DirectByteBuffer
    • HeapByteBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer
  • CharBuffer

(3) Selector

selector 单从字面意思不好理解,需要结合服务器的设计演化来理解它的用途。

多线程版设计

多线程版
socket1
thread
socket2
thread
socket3
thread

多线程版设计缺点:

  • 内存占用高
  • 线程上下文切换成本高
  • 只适合连接数少的场景

线程池版设计

线程池版
socket1
thread
socket2
thread
socket3
socket4

线程池版设计缺点:

  • 阻塞模式下,线程仅能处理一个 socket 连接
  • 仅适合短连接场景

selector 版设计

selector 的作用就是配合一个线程来管理多个 channel,获取这些 channel 上发生的事件,这些 channel 工作在非阻塞模式下,不会让线程吊死在一个 channel 上。适合连接数特别多,但流量低的场景(low traffic)

selector 版
selector
thread
channel
channel
channel

调用 selector 的 select() 会阻塞直到 channel 发生了读写就绪事件,这些事件发生,select 方法就会返回这些事件交给 thread 来处理。

(4) NioSocket

介绍完这三大组件,我们再来学习如何使用NioSocket

NioSocket的使用可以分为五步:

  • 创建ServerSocketChannel并设置相应参数
  • 创建Selector并注册到ServerSocketChannel
  • 调用Selectorselect方法等待请求
  • Selector接收到请求后使用selectedKeys返回selectionKey集合
  • 使用SelectionKey获取到ChannelSelector和操作类型并进行具体操作

创建ServerSocketChannel

ServerSocketChannel可以使用自己的静态工厂方法open获取。

每个ServerSocketChannel对应一个ServerSocket,可以调用其socket方法获取,但是要注意,需要通过configureBlocking()方法来设置是否采用阻塞模式,设置了非阻塞模式之后才能调用register方法注册Selector使用。(另外,阻塞模式不能使用Selector

创建Selector

Selector可以通过其静态工厂方法open创建,创建后通过Channelregister注册到ServerSocketChannel或者SocketChannel上,注册完成之后可以通过select方法来等待请求。

select方法有一个long类型参数,代表最长等待时间。如果在这段时间内接收到相应操作的请求则可以返回处理的请求的数量,否则超时后返回0.

SelectionKey

SelectionKey保存了处理当前请求的Channel和Selector,并且提供了不同的操作类型。

  • SelectionKey.OP_ACCEPT 接收请求操作
  • SelectionKey.OP_CONNECT 连接操作
  • SelectionKey.OP_READ 读操作
  • SelectionKey.OP_WRITE 写操作

只有在register方法中注册了相应的操作Selector才会关心相应类型操作的请求。

现在我们将普通SocketServer改写成使用Nio方式进行处理的NioServer,代码如下:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;

/**
 * @Author zal
 * @Date 2024/01/15  21:10
 * @Description: NioServer
 * @Version: 1.0
 */
public class NioServer {
    public static void main(String[] args) throws Exception {
        // 创建ServerSocketChannel,并监听8080端口
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.socket().bind(new InetSocketAddress(8080));
        // 设置为非阻塞模式
        ssc.configureBlocking(false);
        // 为ssc注册选择器
        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        // 创建处理器
        Handler handler = new Handler(1024);
        while (true) {
            // 等待请求,每次等待阻塞3s,超过3s后线程继续向下执行,如果传入0或者不传参数则一直阻塞
            if (selector.select(3000) == 0) {
                System.out.println("等待请求超时。。。。");
                continue;
            }
            System.out.println("处理请求。。。。");
            // 获取等待处理的SelectionKey
            Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                try {
                    // 接收连接请求 
                    if (key.isAcceptable()) {
                        handler.handleAccept(key);
                    }
                    // 读数据 
                    if (key.isReadable()) {
                        handler.handleRead(key);
                    }
                } catch (IOException ex) {
                    keyIterator.remove();
                    continue;
                }
                // 处理完成后,从待处理的SelectionKey中移除当前使用的key
                keyIterator.remove();
            }
        }
    }

    /**
     * 静态内部类,用于处理连接和读取数据
     */
    private static class Handler {
        private int bufferSize = 1024;
        private String localCharset = "UTF-8";

        public Handler() {
        }

        public Handler(int bufferSize) {
            this(bufferSize, null);
        }

        public Handler(String localCharset) {
            this(-1, localCharset);
        }

        public Handler(int bufferSize, String localCharset) {
            // 如果指定了有效的缓冲区大小,则使用指定值
            if (bufferSize > 0) {
                this.bufferSize = bufferSize;
            }
            // 如果指定了有效的字符集,则使用指定值
            if (localCharset != null) {
                this.localCharset = localCharset;
            }
        }

        /**
         * 处理接受连接事件
         * @param key
         * @throws IOException
         */
        public void handleAccept(SelectionKey key) throws IOException {
            // 通过服务器套接字通道接受客户端连接
            SocketChannel sc = ((ServerSocketChannel) key.channel()).accept();
            // 配置为非阻塞模式
            sc.configureBlocking(false);
            // 将客户端套接字通道注册到选择器,关注事件为可读,同时附带一个缓冲区
            sc.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));
        }

        /**
         * 处理读取数据事件
         * @param key
         * @throws IOException
         */
        public void handleRead(SelectionKey key) throws IOException {
            // 获取Channel
            SocketChannel sc = (SocketChannel) key.channel();
            // 获取附加到事件的缓冲区
            ByteBuffer buffer = (ByteBuffer) key.attachment();
            buffer.clear(); // 清空缓冲区,准备读取数据
            // 从客户端通道读取数据到缓冲区,如果返回-1表示客户端关闭连接
            if (sc.read(buffer) == -1) {
                // 关闭channel
                sc.close();
            } else {
                // 切换buffer为读模式
                buffer.flip();
                // 将buffer中的数据解码为字符串后保存到receivedString
                String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString();
                System.out.println("received from client: " + receivedString);

                // 返回数据给客户端
                String sendString = "received data: " + receivedString;
                buffer = ByteBuffer.wrap(sendString.getBytes(localCharset));
                sc.write(buffer);
                sc.close();
            }
        }
    }
}

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

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

相关文章

java基础 - 03 List之AbstractSequentialList、LinkedList

上一篇我们围绕了ArrayList以及List进行简单介绍&#xff0c;本篇我们将围绕AbstractSequentialList、LinkedList进行。 AbstractSequentialList AbstractSequentialList是Java集合框架中的一个抽象类&#xff0c;它实现了List接口&#xff0c;并且是针对顺序访问的列表数据结…

探索设计模式的魅力:工厂方法模式

工厂方法模式是一种创建型设计模式&#xff0c;它提供了一种创建对象的接口&#xff0c;但将具体实例化对象的工作推迟到子类中完成。这样做的目的是创建对象时不用依赖于具体的类&#xff0c;而是依赖于抽象&#xff0c;这提高了系统的灵活性和可扩展性。 以下是工厂方法模式的…

基于物联网设计的智能储物柜(4G+华为云IOT+微信小程序)

一、项目介绍 在游乐场、商场、景区等人流量较大的地方&#xff0c;往往存在用户需要临时存放物品的情况&#xff0c;例如行李箱、外套、购物袋等。为了满足用户的储物需求&#xff0c;并提供更加便捷的服务体验&#xff0c;当前设计了一款物联网智能储物柜。 该智能储物柜通…

矩阵快速幂技巧练习(一)— 经典牛问题

上一篇文章简单介绍了斐波那契数列的矩阵乘法&#xff0c;并做了一个小推广&#xff0c;这篇文章来小试牛刀&#xff0c;做一个经典的练习题。 求斐波那契数列矩阵乘法的方法 题目 第一年农场有一只成熟的母牛A&#xff0c;往后的每年&#xff1a; 每一只成熟的母牛都会生一只…

MySQL面试题 | 09.精选MySQL面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

【Debian】非图形界面Debian10.0.0安装xfce和lxde桌面

一、安装 1. Debian10.0.0安装xfce桌面 sudo apt update sudo apt install xfce4 startxfce4 2. Debian10.0.0安装lxde桌面 sudo apt-get install lxde安装后重启电脑。 二、说明 XFCE、LXDE 和 GNOME 是三个流行的桌面环境&#xff0c;它们都是为类 Unix 操作系统设计…

C语言:编译和链接

目录 一&#xff1a;翻译环境和运行环境 二&#xff1a;翻译环境 2.1预处理&#xff08;预编译&#xff09; 2.2编译 2.2.1 词法分析&#xff1a; 2.2.2语法分析 2.2.3语义分析 2.3 汇编 三&#xff1a;运行环境 一&#xff1a;翻译环境和运行环境 在ANSI C的任何一种…

微信小程序------WXML模板语法之条件渲染和列表渲染

目录 前言 一、条件渲染 1.wx:if 2. 结合 使用 wx:if 3. hidden 4. wx:if 与 hidden 的对比 二、列表渲染 1. wx:for 2. 手动指定索引和当前项的变量名* 3. wx:key 的使用 前言 上一期我们讲解wxml模版语法中的数据绑定和事件绑定&#xff08;上一期链接&#xff1a;…

MATLAB - 使用运动学 DH 参数构建机械臂

系列文章目录 前言 一、 使用 Puma560 机械手机器人的 Denavit-Hartenberg (DH) 参数&#xff0c;逐步建立刚体树形机器人模型。在连接每个关节时&#xff0c;指定其相对 DH 参数。可视化机器人坐标系&#xff0c;并与最终模型进行交互。 DH 参数定义了每个刚体通过关节与其父…

Go-gin-example 第二部分 jwt验证

文章目录 使用 JWT 进行身份校验jwt知识点补充认识JWTTOKEN是什么jwt的使用场景jwt的组成headerpayloadsignature 下载依赖包编写 jwt 工具包jwt中间件编写如何获取token 编写获取token的Apimodels逻辑编写路由逻辑编写修改路由逻辑 验证token将中间件接入Gin功能验证模块 续接…

gitlab 命令执行漏洞(CVE-2022-2992)

1.漏洞影响版本 GitLab CE/EE 中的一个漏洞影响从 11.10 开始到 15.1.6 之前的所有版本、从 15.2 开始到 15.2.4 之前的所有版本、从 15.3 开始到 15.3.2 之前的所有版本。允许经过身份验证的用户通过从 GitHub API 端点导入实现远程代码执行。 查看 gitlab 版本。(登录后才能…

【目标检测】YOLOv7算法实现(一):模型搭建

本系列文章记录本人硕士阶段YOLO系列目标检测算法自学及其代码实现的过程。其中算法具体实现借鉴于ultralytics YOLO源码Github&#xff0c;删减了源码中部分内容&#xff0c;满足个人科研需求。   本系列文章在YOLOv5算法实现的基础上&#xff0c;进一步完成YOLOv7算法的实现…

使用STM32Cube库开发USB虚拟串口设备

开发基于STM32Cube库的USB虚拟串口设备需要了解USB通信协议、虚拟串口设备的基本原理以及STM32Cube库的使用。在这篇文章中&#xff0c;我们将介绍如何利用STM32Cube库开发一个USB虚拟串口设备&#xff0c;并提供相应的代码示例。 1. USB虚拟串口设备概述 USB虚拟串口设备是指…

力扣刷题(无重复字符的最长子串)

3. 无重复字符的最长子串https://leetcode.cn/problems/longest-substring-without-repeating-characters/ 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: s "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是…

使用vue快速开发一个带弹窗的Chrome插件

vue-chrome-extension-quickstart 说在前面 &#x1f388;平时我们使用Chrome插件通常都只是用来编写简单的js注入脚本&#xff0c;大家有没有遇到过需要插件在页面上注入一个弹窗呢&#xff1f;比如我们希望可以通过快捷键快速唤起ChatGPT面板或者快速唤起一个翻译面板&#x…

动态规划:01背包问题(一)

本题力扣上没有&#xff0c;是刷的卡码网第46题感兴趣的小伙伴可以去刷一下&#xff0c;是ACM模式。本篇讲解二维dp数组来解决01背包问题&#xff0c;下篇博客将用一维dp数组来解决01背包问题。 题目&#xff1a; 46. 携带研究材料 时间限制&#xff1a;5.000S 空间限制&…

Spark---RDD持久化

文章目录 1.RDD持久化1.1 RDD Cache 缓存1.2 RDD CheckPoint 检查点1.3 缓存和检查点区别 2.RDD分区器2.1 Hash 分区&#xff1a;2.2 Range 分区&#xff1a;2.3 用户自定义分区 1.RDD持久化 在Spark中&#xff0c;持久化是将RDD存储在内存中&#xff0c;以便在多次计算之间重…

HDFS WebHDFS 读写文件分析及HTTP Chunk Transfer Encoding相关问题探究

文章目录 前言需要回答的首要问题DataNode端基于Netty的WebHDFS Service的实现基于重定向的文件写入流程写入一个大文件时WebHDFS和Hadoop Native的块分布差异 基于重定向的数据读取流程尝试读取一个小文件尝试读取一个大文件 读写过程中的Chunk Transfer-Encoding支持写文件使…

快慢指针-Floyd判圈算法

对于环形链表是否存在环的做法&#xff0c;普通算法可以通过额外Hash数组来存储链表元素&#xff0c;直到Hash数组中出现重复元素。时间复杂度O(n)&#xff0c;空间复杂度O(n) Floyd判圈算法通过利用快慢指针的移动来实现&#xff0c;时间复杂度O&#xff08;n&#xff09;&am…

Elasticsearch:聊天机器人教程(二)

这是继上一篇文章 “Elasticsearch&#xff1a;聊天机器人教程&#xff08;一&#xff09;”的续篇。本教程的这一部分讨论聊天机器人实现中最有趣的方面&#xff0c;以帮助你理解它并对其进行自定义。 数据摄入 在此应用程序中&#xff0c;所有示例文档的摄取都是通过 flask …