Java 网络编程之TCP(三):基于NIO实现服务端,BIO实现客户端

news2024/11/17 23:51:18

前面的文章,我们讲述了BIO的概念,以及编程模型,由于BIO中服务器端的一些阻塞的点,导致服务端对于每一个客户端连接,都要开辟一个线程来处理,导致资源浪费,效率低。

为此,Linux 内核系统调用开始支持NIO(non-blocking IO),非阻塞IO,将之前BIO中的一些阻塞点,改为非阻塞,体现在Java API中就是:

服务端:
服务器等待客户端连接的accept方法不阻塞;java api中accept
服务器读取客户端数据不阻塞阻塞;java api中read

Java NIO编程中,主要涉及以下三个主要概念:

1.Channel :IO操作的联结,代表硬件,文件,网络套接字的连接,对应于BIO中的Socket; Channel需要与Buffer结合使用

2.Buffer:用于数据操作的缓冲区,就是一块内存,提供了一些操作,方便使用;

3.Selector:选择器,就是Linux 内核中的IO多路复用器,为了提高网络IO编程的效率,常用的有select, poll, epoll, 可以参考Linux对应系统调用

这三个概念,我们在后面的编程模型都会涉及。

下面我们先基于Channel和Buffer实现一个简单的服务端,用之前的BIO实现一个客户端;

Channel和Buffer对应的API的返回值含义,我都会在代码中注释清楚:

需求:

服务端:基于NIO,可以非阻塞的接收客户端连接,对客户端采用轮询接收数据

客户端:基于BIO,连接服务端,并发送数据

服务端代码:

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.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * 基于NIO中的Channel和Buffer实现服务端,对客户端采用轮询
 *
 * @author freddy
 */
class NIOServer {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false); // 非阻塞
        serverSocketChannel.bind(new InetSocketAddress(9090));

        List<SocketChannel> clients = new LinkedList<>();
        while (true) {
            try {
                Thread.sleep(3000); // 为了方便测试观察
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            // accept非阻塞, 没有连接时,返回null
            SocketChannel client = serverSocketChannel.accept();
            if (client != null) {
                // 设置client非阻塞
                client.configureBlocking(false);
                clients.add(client);
            }

            // 遍历处理所有的client,看有没有数据可以读取
            Iterator<SocketChannel> iterator = clients.iterator();
            ByteBuffer buffer = ByteBuffer.allocate(1024); // 共用buffer
            System.out.println("clients size :" + clients.size());
            while (iterator.hasNext()) {
                SocketChannel clientSocket = iterator.next();
                try {
                    int len = clientSocket.read(buffer); // read()返回:>0:读取到数据 0:没读到数据 -1:连接关闭
                    if (len > 0) {
                        // 读取到数据后,进行打印
                        buffer.flip();
                        byte[] bytes = new byte[buffer.limit()];
                        System.out.println(clientSocket + "read data len:" + bytes.length);
                        buffer.get(bytes);
                        System.out.println(clientSocket + " data: " + new String(bytes));
                    } else if (len == 0) {
                        System.out.println(clientSocket + " no data");
                    } else if (len == -1) {
                        // 连接关闭
                        iterator.remove();
                        System.out.println(clientSocket + " close, remove");
                    }
                    buffer.clear();
                } catch (IOException exception) {
                    iterator.remove();
                    System.out.println(clientSocket + " disconnect, remove");
                }
            }
        }
    }
}

客户端代码:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
 * 基于BIO的TCP网络通信的客户端,接收控制台输入的数据,然后通过字节流发送给服务端
 *
 * @author freddy
 */
class ChatClient {
    public static void main(String[] args) throws IOException {
        // 连接server
        Socket serverSocket = new Socket("localhost", 9090);
        System.out.println("client connected to server");

        // 读取用户在控制台上的输入,并发送给服务器
        new Thread(new ClientThread(serverSocket)).start();

        // 接收服务端发送过来的数据
        try (InputStream serverSocketInputStream = serverSocket.getInputStream();) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = serverSocketInputStream.read(buffer)) != -1) {
                String data = new String(buffer, 0, len);
                System.out.println(
                    "client receive data from server" + serverSocketInputStream + " data size:" + len + ": " + data);
            }
        }

    }
}

class ClientThread implements Runnable {
    private Socket serverSocket;

    public ClientThread(Socket serverSocket) {
        this.serverSocket = serverSocket;
    }

    @Override
    public void run() {
        // 读取用户在控制台上的输入,并发送给服务器
        InputStream in = System.in;
        byte[] buffer = new byte[1024];
        int len;
        try (OutputStream outputStream = serverSocket.getOutputStream();) {
            // read操作阻塞,直到有数据可读,由于后面还要接收服务端转发过来的数据,这两个操作都是阻塞的,所以需要两个线程
            while ((len = in.read(buffer)) != -1) {
                String data = new String(buffer, 0, len);
                System.out.println("client receive data from console" + in + " : " + new String(buffer, 0, len));
                if ("exit\n".equals(data)) {
                    // 模拟客户端关闭连接
                    System.out.println("client close :" + serverSocket);
                    // 这里跳出循环后,try-with-resources 会自动关闭outputStream
                    break;
                }
                // 发送数据给服务器端
                outputStream.write(new String(buffer, 0, len).getBytes()); // 此时buffer中是有换行符
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

测试:

先开启服务端,再开启两个客户端发送数据,服务端接受连接后,会打印当前接受到的客户端总数,然后轮询接收数据后打印;

当客户端发送exit后,客户端会关闭连接,服务端会识别到,去除该客户端;

当客户端发进程异常关闭后,客户端会断开连接,服务端会识别到,去除该客户端;

测试日志:

客户端1和2,正常发送数据

图1

客户端1发送exit后,关闭连接

客户端2断开连接

可以直接在Idea中关闭客户端2的程序,或者用nc命令模拟,Ctrl+C关闭nc

clients size :1
java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:17914] no data
clients size :1
java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:17914] disconnect, remove
clients size :0
clients size :0

我们在上面的图1中,可以看到,客户端短期内发送的两次内容,是在服务端一次性读到的;这个就是粘包、拆包现象的一种,后面我们会看。

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

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

相关文章

SVG 绘制微信订阅号icon

效果 代码 <!DOCTYPE html> <html> <body><svg xmlns"http://www.w3.org/2000/svg" version"1.1" width"600" height"600"><rect x"0" y"0" rx"0" ry"0" width&…

qt5-入门-自定义委托-可编辑的TableModel与信号接收

参考&#xff1a; C GUI Programming with Qt 4, Second Edition 本地环境&#xff1a; win10专业版&#xff0c;64位&#xff0c;Qt5.12 上一篇&#xff1a; qt5-入门-自定义委托-简单例子_qt 委托-CSDN博客 https://blog.csdn.net/pxy7896/article/details/137234839 本篇重…

如何从架构层面降低公有云多可用区同时故障的概率

阿里云和腾讯云都曾出现过因一个组件故障而导致所有可用区同时瘫痪的情况。本文将探讨如何从架构设计的角度减小故障域&#xff0c;在故障发生时最小化业务损失&#xff0c;并以 Sealos 的稳定性实践为例&#xff0c;分享经验教训。 抛弃主从&#xff0c;拥抱点对点架构 从腾…

分布式版本控制工具 Git 的使用方式

文章目录 Git简介下载安装基本使用起始配置Git 的三个区域基本操作流程查看仓库状态删除&#xff08;撤销暂存区&#xff09;差异对比查看版本日志版本回退修改提交日志分支概念&#xff1a;创建分支与切换分支合并分支&#xff08;快速合并&#xff09;合并分支&#xff08;提…

Vue3+.NET6前后端分离式管理后台实战(十六)

1&#xff0c;Vue3.NET6前后端分离式管理后台实战(十六)已经在微信公众号更新&#xff0c;有兴趣的扫码关注一起交流学习。 有兴趣的扫码关注

MySQL数据库精讲001——概述

MySQL数据库精讲001——概述 文章目录 MySQL数据库精讲001——概述1.1 安装1.1.1 版本1.1.2 安装一、下载二、解压三、配置1. 添加环境变量2. 初始化MySQL3. 注册MySQL服务4. 启动MySQL服务5. 修改默认账户密码 四、登录MySQL五、卸载MySQL 1.1.3 连接1.1.4 企业使用方式(了解)…

大数据入门——概念、工具等

目录 一、基本概念 1.大数据技术 2.大数据特点 3.常见概念 4.数据分析师、数据开发工程师 二、相关工具 三、应用场景 四、大数据业务流程及组织结构 一、基本概念 1.大数据技术 主要解决海量数据的采集、存储和分析计算问题 2.大数据特点 大量、高速、多样、价值、…

投票刷礼物链接怎么弄?最新投票活动创建系统源码 轻松创建活动

投票刷礼物链接怎么弄&#xff1f;投票活动创建系统的作用和功能多种多样&#xff0c;为用户提供一个便捷、高效且功能强大的平台&#xff0c;用于创建、管理和执行各种投票活动。分享一个最新投票活动创建系统源码&#xff0c;源码开源可二开&#xff0c;含完整代码包和详细搭…

源码篇--Nacos服务--中章(5):Nacos客户端启动-实例注册-grpc连接建立

文章目录 前言一、 前奏&#xff1a;二、客户端连接的建立&#xff1a;2.1 NacosNamingService 创建&#xff1a;2.2 NacosNamingService 初始化&#xff1a;2.3 NamingClientProxyDelegate 长连接建立&#xff1a;2.3.1 grpc 代理对象创建&#xff1a;2.3.2 NamingGrpcClientP…

3月魅力彩妆行业数据分析:某国产品牌彩妆产品销额将近30亿!

彩妆行业发展多年&#xff0c;经历了多重红利期和激烈的市场竞争后&#xff0c;进入到缓慢发展时期。 根据鲸参谋数据显示&#xff0c;今年3月在线上电商平台&#xff08;淘宝天猫京东&#xff09;彩妆产品销量累计超过6700万件&#xff0c;同比去年下降了29%&#xff1b;销售…

【智能算法】吉萨金子塔建造算法(GPC)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2021年&#xff0c;S Harifi等人受到观古代遗迹构造启发&#xff0c;提出了吉萨金子塔建造算法&#xff08;Giza Pyramids Construction, GPC&#xff09;。 2.算法原理 2.1算法思想 GPC模拟了古埃…

通配符(泛域名)SSL证书在使用上有什么优势?

通配符证书在使用上具有以下显著优势&#xff1a; 1. 安全性&#xff1a; - 统一加密保护&#xff1a;通配符证书基于SSL/TLS协议&#xff0c;为一个主域名及其所有子域名提供相同的高强度数据加密服务。无论用户访问的是mail.example.com、shop.example.com还是其他任何以exam…

jackson.dataformat.xml 反序列化 对象中包含泛型

重点&#xff1a; JacksonXmlProperty localName 指定本地名称 JacksonXmlRootElement localName 指定root的根路径的名称&#xff0c;默认值为类名 JsonIgnoreProperties(ignoreUnknown true) 这个注解写在类上&#xff0c;用来忽略在xml中有的属性但是在类中没有的情况 Jack…

SQL Server指南:从入门到进阶实战

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

代码随想录算法训练营DAY36|C++贪心算法Part.5|435.无重叠区间、763.划分字母区间、56. 合并区间

文章目录 435.无重叠区间按右边界排序CPP代码 按左边界排序如何判断相邻区间是否重叠如何判断一下一个区间与当前相邻区间是否重叠总结CPP代码 763.划分字母区间思路伪代码实现CPP代码 56. 合并区间思路CPP代码 435.无重叠区间 力扣题目链接 文章链接&#xff1a;435.无重叠区间…

[笔试训练](五)

013 游游的you__牛客网 (nowcoder.com) 题目&#xff1a; 题解&#xff1a; 组成一个you需要一个o且能得2分&#xff0c;而组成相邻字母oo需要两个o&#xff0c;只能得1分。优先考虑组成尽可能多的you&#xff0c;再考虑剩下的o&#xff0c;放一起。 #include <iostream…

VSCode通过跳板机免密连接远程服务器的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

JS -正则表达式

正则表达式 关于正则表达式&#xff0c;其实我写过几篇了&#xff0c;但是真正的正则表达式其实主要用于定义一些字符串的规则&#xff0c;计算机根据给出的正则表达式&#xff0c;来检查一个字符串是否符合规则。 我们来看一下&#xff0c;在JS中如何创建正则表达式对象。 语…

北京筑龙当选中招协第二届招标采购数字化专业委员会执行主任单位

4月18-19日&#xff0c;中国招标投标协会&#xff08;以下简称中招协&#xff09;2024年年会在宁波召开&#xff0c;北京筑龙作为中招协理事会员单位受邀出席会议。会议期间举行了“电子招标采购专业委员会换届会议暨第二届第一次工作会议”&#xff0c;北京筑龙当选第二届招标…

JavaScript 计算颜色的相对亮度,并确定相应的文本颜色

JavaScript 计算颜色的相对亮度&#xff0c;并确定相应的颜色 一、需求内容 需求点&#xff1a;给出一组颜色列表&#xff0c;渲染对应的颜色以及颜色值&#xff0c;但是要保证文本颜色和背景色不冲突&#xff0c;文本颜色保持 black 和 white 两种即可 示例如下&#xff1a…