栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

基于IO、NIO、Netty的Client和Server之间的Java程序

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

基于IO、NIO、Netty的Client和Server之间的Java程序

文章目录
    • 一、IO实现
      • 1.1 传统IO的特点
      • 1.2 项目实现
      • 1.3 结果
    • 二、NIO实现
      • 2.1NIO特点
      • 2.2 IO与NIO的差别
      • 2.3 项目实现
      • 2.4 结果
    • 三、Netty
      • 3.1 环境配置
      • 3.2 代码实现
      • 3.3 结果
    • 四、总结
    • 五、参考文献

一、IO实现 1.1 传统IO的特点
  • 代码执行时会存在两个阻塞点
server.accept();     等待链接
inputStream.read(bytes);     等待输入
  • 单线程情况下只能为一个客户端服务;
  • 用线程池可以有多个客户端连接,但是非常消耗性能;
  • 使用传统的I/O程序读取文件内容, 并写入到另一个文件(或Socket), 如下程序:
File.read(fileDesc, buf, len);
Socket.send(socket, buf, len);

会有较大的性能开销, 主要表现在一下两方面:

上下文切换(context switch), 此处有4次用户态和内核态的切换;
Buffer内存开销, 一个是应用程序buffer, 另一个是系统读取buffer以及socket buffer;

1.2 项目实现

1.创建两个Java项目

2.分别创建两个类,Server和Client
3.编写代码
Server服务端:

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

public class Server {
    public static void main(String[] args) throws IOException{
        //创建客户端的Socket对象(SevereSocket)
        //ServerSocket (int port)创建绑定到指定端口的服务器套接字
        ServerSocket serverSocket=new ServerSocket(6000);

        //Socket accept()侦听要连接到此套接字并接受它
        Socket socket=serverSocket.accept();

        //获取输入流,读数据,并把数据显示在控制台
        InputStream inputStream=socket.getInputStream();
        byte[] bytes=new byte[1024];
        int len=inputStream.read(bytes);
        String data=new String(bytes,0,len);
        System.out.println("数据是:"+data);
        //释放资源
        socket.close();
        serverSocket.close();
    }
}

Client客户端:

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

public class Client {
    public static void main(String[] args) throws IOException{
        //创建客户端的Socket对象
        //Socket (InetAddress adress,int port)创建流套接字并将其连接到指定IP地址的指定端口号
        //Socket (String host,int port)创建流套接字并将其连接到指定主机的指定端口号
        Socket s=new Socket("192.168.0.102", 6000);

        //获取输出流,写数据
        //OutputStream getOutputStream();返回此套接字的输出流
        OutputStream os=s.getOutputStream();
        os.write("你好,服务器端!".getBytes());

        //释放资源
        s.close();
    }
}
1.3 结果

二、NIO实现 2.1NIO特点
  • NIO在单线程下可以同时为多个客户端服务
  • NIO技术省去了将操作系统的read buffer拷贝到程序的buffer, 以及从程序buffer拷贝到socket buffer的步骤, 直接将 read buffer 拷贝到 socket buffer. java 的 FileChannel.transferTo() 方法就是这样的实现, 这个实现是依赖于操作系统底层的sendFile()实现的.
	public void transferTo(long position, long count, WritableByteChannel target);

它的底层调用的是系统调用sendFile()方法

	sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
2.2 IO与NIO的差别


具体参考:Netty入门(一)——传统IO与NIO比较(一)

2.3 项目实现

1.同样新建两个Java项目
2.分别建一个Server类和Client类

Server服务器端:

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

public class Server {
    //网络通信IO操作,TCP协议,针对面向流的监听套接字的可选择通道(一般用于服务端)
    private ServerSocketChannel serverSocketChannel;
    private Selector selector;

    
    public void start(Integer port) throws Exception{
        serverSocketChannel = ServerSocketChannel.open();
        selector = Selector.open();
        //绑定监听端口
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        //设置为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //注册到Selector上
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        startListener();
    }

    private void startListener() throws Exception{
        while (true) {
            // 如果客户端有请求select的方法返回值将不为零
            if (selector.select(1000) == 0) {
                System.out.println("当前没有任务!!!");
                continue;
            }
            // 如果有事件集合中就存在对应通道的key
            Set selectionKeys = selector.selectedKeys();
            Iterator iterator = selectionKeys.iterator();
            // 遍历所有的key找到其中事件类型为Accept的key
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                if (key.isAcceptable())
                    handleConnection();
                if (key.isReadable())
                    handleMsg(key);
                iterator.remove();
            }
        }
    }
    
    private void handleConnection() throws Exception{
        SocketChannel socketChannel = serverSocketChannel.accept();
        socketChannel.configureBlocking(false);
        socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
    }
    
    private void handleMsg(SelectionKey key) throws Exception{
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer attachment = (ByteBuffer) key.attachment();
        channel.read(attachment);
        System.out.println("接收的信息是: " + new String(attachment.array()));
    }
    public static void main(String[] args) throws Exception {
        Server myServer = new Server();
        myServer.start(6000);
    }
}

Client客户端:

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class Client {
    public static void main(String[] args) throws Exception{
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);

        // 连接服务器
        if (!socketChannel.connect(new InetSocketAddress("192.168.0.102", 6000))) {
            while (!socketChannel.finishConnect()) {
                System.out.println("Connecting...");
            }
        }
        //发送数据
        String str = "你好服务器端,我是客户端!!";
        ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
        socketChannel.write(byteBuffer);
        System.in.read();
    }
}
2.4 结果

三、Netty

简介
1.Netty是基于Java NIO的网络应用框架.

2.Netty是一个NIO client-server(客户端服务器)框架,使用Netty可以快速开发网络应用,例如服务器和客户端协议。

3.Netty提供了一种新的方式来使开发网络应用程序,这种新的方式使得它很容易使用和有很强的扩展性。

4.Netty的内部实现是很复杂的,但是Netty提供了简单易用的api从网络处理代码中解耦业务逻辑。

5.Netty是完全基于NIO实现的,所以整个Netty都是异步的。

通信的步骤:

①创建两个NIO线程组,一个专门用于网络事件处理(接受客户端的连接),另一个则进行网络通信的读写。

②创建一个ServerBootstrap对象,配置Netty的一系列参数,例如接受传出数据的缓存大小等。

③创建一个用于实际处理数据的类ChannelInitializer,进行初始化的准备工作,比如设置接受传出数据的字符集、格式以及实际处理数据的接口。

④绑定端口,执行同步阻塞方法等待服务器端启动即可。

3.1 环境配置

1.打开Project Structure…

2.接着点击+


4.下载netty的jar包
输入io.netty:netty-all,点击搜索,等待一段时间,选择第一个,勾选Download


点击ok

5.勾选

3.2 代码实现

Server端:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class Server {
    public static void main(String[] args) throws Exception {
        //用于处理服务器端接受客户端连接的线程组
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        //用于进行网络通讯(读写)的线程组
        NioEventLoopGroup workGroup = new NioEventLoopGroup();

        //创建辅助工具类,用于服务器通道的一系列的配置
        ServerBootstrap sb = new ServerBootstrap();
        sb.group(bossGroup,workGroup)//绑定两个线程组
                .channel(NioServerSocketChannel.class)//指定NIO的网络传输模式为TCP,UDP:NioDatagramChannel
                .option(ChannelOption.SO_BACKLOG,1024)//设置tcp缓冲
                .option(ChannelOption.SO_SNDBUF,32*1024)//设置发送缓冲大小
                .option(ChannelOption.SO_RCVBUF,32*1024)//设置接收缓冲大小
                .option(ChannelOption.SO_KEEPALIVE,true)//保持连接
                .childHandler(new ChannelInitializer() {
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast(new ServerHandler());//这里配置具体数据接收方法的处理
                    }
                });

        ChannelFuture cf1 = sb.bind(6000).sync();//异步的绑定指定的端口
        ChannelFuture cf2 = sb.bind(7000).sync();//netty可以绑定多个端口

        cf1.channel().closeFuture().sync();//等待关闭,相当于Thread.sleep(Integer.MAX_VALUE)
        cf2.channel().closeFuture().sync();

        //关闭线程组
        bossGroup.shutdownGracefully();
        workGroup.shutdownGracefully();
    }
}

ServerHandler:

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

public class ServerHandler extends ChannelHandlerAdapter {
    
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        ByteBuf buf = (ByteBuf) msg;

        //声明字节数组,buf.readableBytes()返回的是buf缓冲中可读的字节数
        byte[] req = new byte[buf.readableBytes()];
        //将buf缓冲区中的字节读取到字节数组req中
        buf.readBytes(req);
        String body = new String(req, "utf-8");
        System.out.println("服务器端接收到信息:" + body);
        String response = "服务器端返回给客户端的响应信息:" + body;

        //1.ctx.writeAndFlush()方法相当于连续调用了write()和flush()方法,因为write()方法只是将buf写到了渠道的缓冲区中,flush()方法会将缓冲区中的数据传给客户端
        //2.这里Unpooled工具类的作用就是讲字节数组转成netty的ByteBuf对象
        //3.这里使用了writeAndFlush()方法会自动释放buf缓冲区所以不需要想ClientHandler中那样finally中手动释放buf缓冲区了
        //4.addListener()方法:当监听到服务器将数据写给客户端,并且确认客户端已经收到信息后,
        // 服务器端就会主动去关闭跟客户端的连接,因为客户端调用了cf1.channel().closeFuture().sync()方法,所以客户端这里的阻塞就会打开,继续向后执行代码
        ctx.writeAndFlush(Unpooled.copiedBuffer(response.getBytes()));
//                .addListener(ChannelFutureListener.CLOSE);
    }

    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

Client:

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;


public class Client {
    public static void main(String[] args) throws Exception{

        NioEventLoopGroup group = new NioEventLoopGroup();//用于处理网络通信(读写)的线程组

        Bootstrap b = new Bootstrap();//创建客户端辅助类工具
        b.group(group)//绑定线程组
                .channel(NioSocketChannel.class)//设置通信渠道为TCP协议
                .handler(new ChannelInitializer() {
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast(new ClientHandler());//这里配置具体数据接收方法的处理
                    }
                });

        
        ChannelFuture cf1 = b.connect("192.168.0.102", 6000).sync();//异步建立连接

        cf1.channel().write(Unpooled.copiedBuffer("hello 6000端口".getBytes()));//将“hello world”写到buf缓冲区
        cf1.channel().flush();//这里必须使用flush(),只用冲刷才能将buf缓冲区中的数据传给服务器端

        
        ChannelFuture cf2 = b.connect("192.168.0.102", 7000).sync();
        cf2.channel().writeAndFlush(Unpooled.copiedBuffer("hello 7000端口".getBytes()));

        cf1.channel().closeFuture().sync();//等待关闭,相当于Thread.sleep(Integer.MAX_VALUE)
        cf2.channel().closeFuture().sync();

        group.shutdownGracefully();//关闭线程组
    }

}

ClientHandler:

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;

public class ClientHandler extends ChannelHandlerAdapter {
    
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        try {
            ByteBuf buf = (ByteBuf) msg;

            //声明字节数组,buf.readableBytes()返回的是buf缓冲中可读的字节数
            byte[] req = new byte[buf.readableBytes()];
            buf.readBytes(req);
            String body = new String(req, "utf-8");
            System.out.println("客户端接收到信息:" + body);

        }finally {
            ReferenceCountUtil.release(msg);//buf缓冲区使用完了,必须释放
        }

    }

    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}
3.3 结果

四、总结

这次作业初步了解到了IO、NIO和Netty,通过简单的代码用三种方式去实现了客户端和服务器端通信,三者各有各的不同,性能一个优于一个。

五、参考文献

Java NIO详解
添加链Netty3学习笔记(一) — 传统IO与NIO比较接描述
https://blog.csdn.net/weixin_56102526/article/details/121805391

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/658291.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号