Java Socket 网络编程实例(阻塞IO、非阻塞IO、多路复用Selector、AIO)

文章目录

  • 1. 概述
  • 2. TCP 阻塞式IO 网络编程实例
    • 2.1 TCP网络编程服务端
    • 2.2 ByteBufferUtil
    • 2.3 客户端代码
    • 2.4 运行截图
  • 3. TCP 非阻塞式IO 网络编程实例
    • 3.1 服务端
    • 3.2 客户端
    • 3.3 运行截图
  • 4. 多路复用
    • 4.1 服务器端
    • 4.2 客户端
    • 4.3 运行截图
  • 5. AIO
    • 5.1 AIO 服务端
    • 5.2 客户端
    • 5.3 运行截图
  • 6. Channel / Buffer
    • 6.1 Channel
    • 6.2 ByteBuffer
  • 参考文献

1. 概述

  • 网络编程, 就是编写程序, 使两台联网的电脑可以交换数据,
  • 套接字是网络数据传输用的软件设备, 用来连接网络的工具
  • 在 linux中 socket被认为是文件中的一种, 在网络数据传输过程中, 使用文件I/O的相关函数
  • socket 帮助程序员封装了网络的底层细节,如:错误检测、包大小、包分解、包重传、网络地址等,让程序员将网络连接看作可以读/写字节的流
  • 套接字常用网络协议: TCP、UDP

之前还有一篇文章: Linux C++ Socket 套接字、select、poll、epoll 实例

套接字进行网络连接流程, 如下图:

服务器端:

  1. 创建服务器套接字 socket()
  2. 绑定端口 bind()
  3. 监听端口 listen()
  4. 接受客户端请求 accept()
  5. 读取客户端请求的数据 read()
  6. 返回客户端要响应的数据 write()
  7. 关闭与客户端的连接 close()
  8. 关闭服务器套接字 close()

客户端:

  1. 创建客户端套接字 socket()
  2. 连接服务端 connect()
  3. 请求服务端数据, 发送操作数和操作符到服务器 write()
  4. 从服务器读取操作结果 read()
  5. 关闭客户端套接字 close()

流程图如下, 具体代码示例可以看下面的 2. TCP 阻塞式IO 网络编程实例在这里插入图片描述

2. TCP 阻塞式IO 网络编程实例

accept 和 read 都是阻塞的, 当 accept 到新连接, 或者 read 到数据程序才往下走

为了提高服务端处理能力, 一个客户端连接一个线程处理

不能一个线程处理多个客户端, 某个客户端会阻塞这个线程处理其他客户端

在这里插入图片描述

2.1 TCP网络编程服务端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;public class BlockServer {public static void main(String[] args) throws IOException {// 0. ByteBufferByteBuffer buffer = ByteBuffer.allocate(16);// 1. 创建了服务器ServerSocketChannel ssc = ServerSocketChannel.open();// 2. 绑定监听端口ssc.bind(new InetSocketAddress(8080));// 3. 连接集合List<SocketChannel> channels = new ArrayList<>();while (true) {// 4. accept 建立与客户端连接, SocketChannel 用来与客户端之间通信System.out.println("等待客户端连接...");SocketChannel sc = ssc.accept(); // 阻塞方法,线程停止运行System.out.println("接收到客户端连接: " + sc);channels.add(sc);for (SocketChannel channel : channels) {// 5. 接收客户端发送的数据System.out.println("开始读取客户端中的数据:" + channel);channel.read(buffer); // 阻塞方法,线程停止运行buffer.flip();String request = ByteBufferUtil.read(buffer);System.out.println(request);buffer.clear();System.out.println("已经读取完客户端中的数据:" + channel);}}}
}

2.2 ByteBufferUtil

public class ByteBufferUtil {public static String read(ByteBuffer byteBuffer) throws CharacterCodingException {CharBuffer charBuffer = StandardCharsets.UTF_8.decode(byteBuffer);return charBuffer.toString();}public static ByteBuffer read(String string) throws CharacterCodingException {return StandardCharsets.UTF_8.encode(string);}public static void main(String[] args) throws CharacterCodingException {System.out.println(ByteBufferUtil.read(ByteBufferUtil.read("test")));}}

2.3 客户端代码

public class BlockClient {public static void main(String[] args) throws IOException {SocketChannel sc = SocketChannel.open();System.out.println("开始连接服务端...");sc.connect(new InetSocketAddress("localhost", 8080));String str = "test";System.out.println("连接服务端成功,写入数据: " + str);sc.write(ByteBufferUtil.read(str));}
}

2.4 运行截图

在这里插入图片描述
在这里插入图片描述

3. TCP 非阻塞式IO 网络编程实例

不停的轮询, 看看有没有accept 到新连接, 没有连接不阻塞等待, 继续去看看已经建立的连接有没有read到客户端的新数据, read到新数据处理, read不到不处理

为了提高服务端处理能力, 可以一个客户端连接一个线程处理, 线程不停的轮询自己要处理的客户端

也可以一个线程处理多个客户端, 相较于上面的阻塞I/O模型, 非阻塞不至于某个客户端阻塞这个线程处理其他客户端

在这里插入图片描述

3.1 服务端

ssc.configureBlocking(false); 设置为非阻塞模式

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;public class NonBlockServer {public static void main(String[] args) throws IOException, InterruptedException {// 0. ByteBufferByteBuffer buffer = ByteBuffer.allocate(16);// 1. 创建了服务器ServerSocketChannel ssc = ServerSocketChannel.open();ssc.configureBlocking(false); // 非阻塞模式// 2. 绑定监听端口ssc.bind(new InetSocketAddress(8080));// 3. 连接集合List<SocketChannel> channels = new ArrayList<>();while (true) {// 4. accept 建立与客户端连接, SocketChannel 用来与客户端之间通信SocketChannel sc = ssc.accept(); // 非阻塞,线程还会继续运行,如果没有连接建立,但sc是nullif (sc != null) {System.out.println("接收到客户端连接: " + sc);sc.configureBlocking(false); // 非阻塞模式channels.add(sc);}for (SocketChannel channel : channels) {System.out.println("开始读取客户端中的数据:" + channel);// 5. 接收客户端发送的数据int read = channel.read(buffer);// 非阻塞,线程仍然会继续运行,如果没有读到数据,read 返回 0if (read > 0) {buffer.flip();System.out.println((ByteBufferUtil.read(buffer)));buffer.clear();System.out.println("已经读取完客户端中的数据:" + channel);} else {TimeUnit.MILLISECONDS.sleep(100);}}}}
}

3.2 客户端

客户端同上

3.3 运行截图

在这里插入图片描述

在这里插入图片描述

4. 多路复用

可以调用 select/poll/epoll , 阻塞在select/poll/epoll, select/poll/epoll 监听多个客户端连接事件或写入的数据, 然后这些事件可再有多个线程分一分处理掉

在这里插入图片描述

4.1 服务器端

打开选择器并将其与通道注册,监听接受连接操作:

Selector selector = Selector.open();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_ACCEPT);

监听选择器上的事件,返回已就绪的通道数量:

int count = selector.select();

获取所有事件(连接、读取):

Set<SelectionKey> keys = selector.selectedKeys();
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;public class SelectorServer {public static void main(String[] args) {try (ServerSocketChannel channel = ServerSocketChannel.open()) {// 绑定端口并打印通道信息channel.bind(new InetSocketAddress(6666));System.out.println(channel);// 打开选择器并将其与通道注册,监听接受连接操作Selector selector = Selector.open();channel.configureBlocking(false);channel.register(selector, SelectionKey.OP_ACCEPT);// 无限循环,等待选择器上的事件while (true) {// 监听选择器上的事件,返回已就绪的通道数量int count = selector.select();System.out.println("select count: " + count);// 如果没有就绪的通道,则继续循环等待if (count <= 0) {continue;}// 获取并迭代处理所有就绪的事件// 获取所有事件Set<SelectionKey> keys = selector.selectedKeys();// 遍历所有事件,逐一处理Iterator<SelectionKey> iter = keys.iterator();while (iter.hasNext()) {SelectionKey key = iter.next();// 处理接受连接事件// 判断事件类型if (key.isAcceptable()) {ServerSocketChannel c = (ServerSocketChannel) key.channel();// 必须处理SocketChannel sc = c.accept();sc.configureBlocking(false);sc.register(selector, SelectionKey.OP_READ);System.out.println("连接已建立:" + sc);}// 处理读取数据事件else if (key.isReadable()) {SocketChannel sc = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(128);int read = sc.read(buffer);if (read == -1) {// 如果读取返回-1,表示连接已关闭key.cancel();sc.close();} else {// 否则,将缓冲区反转并打印读取的数据buffer.flip();System.out.println(new String(buffer.array(), StandardCharsets.UTF_8));}}// 事件处理完毕后,从迭代器中移除,避免重复处理// 处理完毕,必须将事件移除iter.remove();}}} catch (IOException e) {// 打印IO异常堆栈跟踪e.printStackTrace();}}
}

4.2 客户端

import netty.ByteBufferUtil;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;public class SelectorClient {public static void main(String[] args) throws IOException {// 创建Socket通道并连接到服务器SocketChannel sc = SocketChannel.open();sc.connect(new InetSocketAddress("localhost", 6666));// 初始化输入和输出ByteBufferByteBuffer inputBuffer = ByteBuffer.allocate(512);ByteBuffer serverOutput = ByteBuffer.allocate(512);// 循环接收用户输入并发送给服务器while (true) {// 使用Scanner获取用户输入Scanner in = new Scanner(System.in);String input = in.nextLine();System.out.println("user input: " + input);// 清空输入缓冲区,放入用户输入,然后反转准备写入inputBuffer.clear();inputBuffer.put(input.getBytes(StandardCharsets.UTF_8));inputBuffer.flip();// 将输入数据写入Socket通道sc.write(inputBuffer);System.out.println("send to server " + input);// 循环读取服务器响应while (true) {// 清空服务器响应缓冲区,准备读取数据serverOutput.clear();// 从Socket通道读取数据sc.read(serverOutput);// 如果没有读取到数据,继续尝试读取if (!serverOutput.hasRemaining()) {continue;}// 反转缓冲区,读取数据并打印serverOutput.flip();System.out.println("server response " + ByteBufferUtil.read(serverOutput));// 读取完成后退出内层循环break;}}}
}

4.3 运行截图

在这里插入图片描述
在这里插入图片描述

5. AIO

异步I/O模型

告诉内核启动某个操作, 并且把数据copy到用户缓冲区再通知我们

在这里插入图片描述

5.1 AIO 服务端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.Charset;/*** AIO服务器类,用于演示异步IO的服务器端实现。* 使用AsynchronousServerSocketChannel处理客户端连接和数据传输。*/
public class AIOServer {/*** 程序入口,初始化并启动AIO服务器。* 绑定服务器端口并等待客户端连接。** @param args 命令行参数* @throws IOException 如果绑定端口失败*/public static void main(String[] args) throws IOException {AsynchronousServerSocketChannel ssc = AsynchronousServerSocketChannel.open();ssc.bind(new InetSocketAddress(6666));ssc.accept(null, new AcceptHandler(ssc));while (true) ;}/*** 关闭客户端通道的方法。* 用于处理读取或写入操作失败时关闭通道。** @param sc 客户端通道*/private static void closeChannel(AsynchronousSocketChannel sc) {try {System.out.printf("[%s] %s close\n", Thread.currentThread().getName(), sc.getRemoteAddress());sc.close();} catch (IOException e) {e.printStackTrace();}}/*** 读取数据的完成处理器,实现读取客户端数据并响应的逻辑。*/private static class ReadHandler implements CompletionHandler<Integer, ByteBuffer> {private final AsynchronousSocketChannel sc;public ReadHandler(AsynchronousSocketChannel sc) {this.sc = sc;}/*** 当读取操作完成时被调用。* 解析读取的数据并写回响应到客户端。** @param result     读取操作的结果* @param attachment 读取操作的附加上下文*/@Overridepublic void completed(Integer result, ByteBuffer attachment) {try {if (result == -1) {return;}System.out.printf("[%s] %s read\n", Thread.currentThread().getName(), sc.getRemoteAddress());attachment.flip();String request = Charset.defaultCharset().decode(attachment).toString();System.out.println(request.toString());attachment.clear();attachment.put(("你好:" + request).getBytes());attachment.flip();sc.write(attachment);attachment.clear();// 读取下一个读时间sc.read(attachment, attachment, new ReadHandler(sc));} catch (IOException e) {e.printStackTrace();}}/*** 当读取操作失败时被调用。* 关闭客户端通道并打印异常堆栈跟踪。** @param exc        引发的异常* @param attachment 读取操作的附加上下文*/@Overridepublic void failed(Throwable exc, ByteBuffer attachment) {closeChannel(sc);exc.printStackTrace();}}/*** 接受连接的完成处理器,用于处理客户端的连接请求。*/private static class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, Object> {private final AsynchronousServerSocketChannel ssc;public AcceptHandler(AsynchronousServerSocketChannel ssc) {this.ssc = ssc;}/*** 当接受操作完成时被调用。* 设置读取缓冲区并开始读取客户端发送的数据。** @param sc         接受到的客户端通道* @param attachment 接受操作的附加上下文*/@Overridepublic void completed(AsynchronousSocketChannel sc, Object attachment) {try {System.out.printf("[%s] %s connected\n", Thread.currentThread().getName(), sc.getRemoteAddress());} catch (IOException e) {e.printStackTrace();}ByteBuffer buffer = ByteBuffer.allocate(1024);// 读事件由 ReadHandler 处理System.out.println("开始读");sc.read(buffer, buffer, new ReadHandler(sc));System.out.println("读完成");// 处理完第一个 accept 时,需要再次调用 accept 方法来处理下一个 accept 事件ssc.accept(null, this);}/*** 当接受操作失败时被调用。* 打印异常堆栈跟踪。** @param exc        引发的异常* @param attachment 接受操作的附加上下文*/@Overridepublic void failed(Throwable exc, Object attachment) {exc.printStackTrace();}}
}

5.2 客户端

同 4.2

5.3 运行截图

在这里插入图片描述
在这里插入图片描述

6. Channel / Buffer

6.1 Channel

Channel: 传输数据的通道

其实和数据流挺像的,不过数据流是单向的而Channel 是双向的,可以向channel中写数据,也可以从channel中读取数据

NIO 基础组件之 Channel

6.2 ByteBuffer

ByteBuffer是Buffer子类,是字节缓冲区,特点如下所示。

大小不可变。一旦创建,无法改变其容量大小,无法扩容或者缩容;
读写灵活。内部通过指针移动来实现灵活读写;
支持堆上内存分配和直接内存分配

一文搞懂ByteBuffer使用与原理

参考文献

  • UNIX 网络编程 卷1: 套接字联网API
  • TCP/IP网络编程 尹圣雨 著 金国哲 译
  • Linux IO模式及 select、poll、epoll详解
  • 浅谈select,poll和epoll的区别
  • 黑马 Netty 课程

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++笔试强训day39

目录 1.神奇的字母&#xff08;二&#xff09; 2.字符编码 3.最少的完全平方数 1.神奇的字母&#xff08;二&#xff09; 链接https://ac.nowcoder.com/acm/problem/205832 看输出描述即可知输出次数最多的那个字母即可。 哈希表直接秒了&#xff1a; #include <iostre…

一维时间序列突变检测方法(小波等,MATLAB R2021B)

信号的突变点检测问题是指在生产实践中&#xff0c;反映各种系统工作状态的信号&#xff0c;可能因为受到不同类型的噪声或外界干扰而发生了信号突变&#xff0c;导致严重失真的信号出现&#xff0c;因此必须探测突变出现的起点和终点。研究目的在于设计出检测方案&#xff0c;…

【python】python租房数据分析可视化(源码+数据+报告)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

OpenCV学习(4.3) 图像阈值

1.目的 在本教程中&#xff1a; 你会学到简单阈值法&#xff0c;自适应阈值法&#xff0c;以及 Otsu 阈值法(俗称大津法)等。你会学到如下函数&#xff1a;**cv.threshold&#xff0c;cv.adaptiveThreshold** 等。 2.简单阈值法 此方法是直截了当的。如果像素值大于阈值&am…

word2016版本中同时显示多个页面

为了方便查看word内容&#xff0c;我们会将多个页面同时显示。 对于2016版&#xff0c;操作方法如下&#xff1a; 视图 ---》多页

Jan任意文件读取/下载和上传漏洞

自从ChatGPT横空出世以来&#xff0c;我一直想找一个可以自己训练的AI大模型&#xff0c;然而在使用Jan的过程中&#xff0c;数据包中传递的参数引起了我的兴趣&#xff0c;简单尝试后发现了任意文件读取和任意文件上传漏洞。 简介 Jan是ChatGPT的开源替代品&#xff0c;它在…

vuInhub靶场实战系列--bulldog-1

免责声明 本文档仅供学习和研究使用,请勿使用文中的技术源码用于非法用途,任何人造成的任何负面影响,与本人无关。 目录 免责声明前言一、环境配置1.1 靶场信息1.2 靶场配置 二、信息收集2.1 主机发现2.1.1 netdiscover2.1.2 nmap主机扫描2.1.3 arp-scan主机扫描 2.2 端口扫描…

友思特案例 | 自动快速定位:使用波长选择器测量滤光片的关键光学性能指标

导读 光学滤光片检测的手动调节校准的传统方法存在诸多不确定误差和高昂的成本消耗。友思特全自动可调谐光源检测解决方案&#xff0c;可全自动调节波长带宽&#xff0c;快速收集光谱数据&#xff0c;缩短检测时间、降低质检成本&#xff0c;实现极高的准确率和快速检测效率。…

RA8D1-Vision Board上OSPI-Flash实践

Vision-Board 开发板是 RT-Thread 推出基于瑞萨 Cortex-M85 架构 RA8D1 芯片,拥有Helium和TrustZone技术的加持,性能非常强大。 内核:480 MHz Arm Cortex-M85,包含Helium和TrustZone技术 存储:集成2MB/1MB闪存和1MB SRAM(包括TCM,512KB ECC保护) 外设:兼容xSPI的四线O…

如何在强数据一致性要求下设计数据库的高可用架构

在高可用的三大架构设计(基于数据层的高可用、基于业务层的高可用,以及融合的高可用架构设计)中。仅仅解决了业务连续性的问题:也就是当服务器因为各种原因,发生宕机,导致MySQL 数据库不可用之后,快速恢复业务。但对有状态的数据库服务来说,在一些核心业务系统中,比如…

运营商卷大模型,云厂商霸主地位不保?

文&#xff5c;艺 思 编&#xff5c;王一粟 经过了2023年的小试牛刀&#xff0c;2024年&#xff0c;三大运营商带着大模型一路狂飙。 刚刚过去的5月&#xff0c;中国电信、中国移动、中国联通三大运营商集体完成了新一轮的大模型进化&#xff0c;特别是围绕大模型的研发与…

04--Tomcat

前言&#xff1a;本章整理tomcat的知识点&#xff0c;tomcat知识点相较nginx比较少&#xff0c;但是也是运维必会的软件&#xff0c;这里结合实际项目整理一下。 1、tomcat简介 Tomcat 服务器是一个免费的开放源代码的Web 应用服务器&#xff0c;属于轻量级应用服务器&#x…

强烈安利10款手机App!

AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频https://aitools.jurilu.com/ 1.听书神器——昊昊听书 昊昊听书app是一款专门为用户提供有声读物的应用程序。它不仅提供了各种类型的有声书籍&#xff0c;还有各种知名的电…

pw命令1

1、查看集群状态命令 gs_om -t status --detail 2、备节点升主&#xff08;本例子升2节点为主&#xff09; date && time cm_ctl switchover -n 2 -D /database/panweidb/data 3、cm_ctl是全局的&#xff0c;在一个节点运行 cm_ctl stop && cm_ctl start 就重…

铸铁机械5G智能工厂工业物联数字孪生平台,推进制造业数字化转型

铸铁机械5G智能工厂工业物联数字孪生平台&#xff0c;推进制造业数字化转型。工业物联数字孪生平台以5G技术为基础&#xff0c;通过工业物联网连接铸铁机械生产过程中的各个环节&#xff0c;运用数字孪生技术构建虚拟工厂&#xff0c;实现生产过程的实时监测、模拟与优化&#…

C语言的printf输出问题

看到这段代码的时候&#xff0c;想到这个printf输出的值是多少? 若您想到的答案是1-2&#xff0c;真的是这样吗&#xff1f; #include <stdio.h>int main(int argc, char *argv[]) {int i 1;printf("%d-%d\r\n", i, i);return 0; }先了解一个知识点&#xf…

I.MX6ULL UART 串口通信实验

系列文章目录 I.MX6ULL UART 串口通信实验 I.MX6ULL UART 串口通信实验 系列文章目录一、前言二、I.MX6U 串口简介2.1 UART 简介2.2 I.MX6U UART 简介 三、硬件原理分析四、实验程序编写五、编译下载验证5.1编写 Makefile 和链接脚本5.2 编译下载 一、前言 不管是单片机开发还…

解锁用Mermaid绘制图表的神奇力量

在这个快节奏、信息爆炸的时代&#xff0c;我们迫切需要一种简单、高效且美观的方式来表达复杂的思想和流程。 幸运的是&#xff0c;Mermaid就是这样一种工具。无论你是开发者、项目经理还是设计师&#xff0c;Mermaid都能帮助你轻松绘制各种类型的图表&#xff0c;让你的文档…

免费开源图片转文字识别软件:Umi-OCR

目录 1.介绍 2.项目亮点 3.项目功能&#xff08;已实现&#xff09; 4.功能体验 5.项目集成&#xff08;调用接口&#xff09; 6.项目地址 1.介绍 Umi-OCR&#xff1a;免费&#xff0c;开源&#xff0c;可批量的离线OCR软件&#xff0c;目前适用于 Windows7 x64 及以上。…

自动化办公02 用openpyxl库操作excel.xlsx文件(新版本)

目录 一、文件读操作 二、文件写操作 三、修改单元格样式 openpyxl 是一个处理Excel表格的第三方库。openpyxl 库可以处理Excel2010以后的电子表格格式&#xff0c;包括&#xff1a;xlsx/xlsm/xltx/xltm。 openpyxl教程 一、文件读操作 工作簿(workbook): excel文件 工作表…