基于IO、NIO、Netty的Java网络程序简单示例

一、基于IO的java网络程序

(一)循环输出多条数据

1.使用IDEA创建服务端和客户端程序,注意是新建两个java工程,运行的时候两个都要运行:
在这里插入图片描述
在这里插入图片描述

2.服务器程序代码(IOServer.java)

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

public class IOServer {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8000);
        // (1) 接收新连接线程
        new Thread(() -> {

            while (true) {
                try {
                    // (1) 阻塞方法获取新的连接
                    Socket socket = serverSocket.accept();
                    // (2) 每一个新的连接都创建一个线程,负责读取数据
                    new Thread(() -> {
                        try {
                            byte[] data = new byte[1024];
                            InputStream inputStream = socket.getInputStream();
                            while (true) {
                                int len;
                                // (3) 按字节流方式读取数据
                                while ((len = inputStream.read(data)) != -1) {
                                    System.out.println(new String(data, 0, len));
                                }
                            }
                        } catch (IOException e) {
                        }
                    }).start();
                } catch (IOException e) {
                }
            }
        }).start();
    }
}

3.客户端程序代码(IOClient.java)

import java.io.IOException;
import java.net.Socket;
import java.util.Date;
public class IOClient {
    public static void main(String[] args) {
        new Thread(() -> {
            try {
                Socket socket = new Socket("127.0.0.1", 8000);

                while (true) {
                    try {
                        socket.getOutputStream().write((new Date() + ": hello 死妖阿!").getBytes());
                        socket.getOutputStream().flush();

                        Thread.sleep(2000);

                    } catch (Exception e) {

                    }

                }

            } catch (IOException e) {

            }

        }).start();

    }

}

4.运行结果就是while循环里不停发送消息
在这里插入图片描述

(二)输出一条数据

1.创建项目步骤和上面相同

2.服务器程序代码(IOServer_2.java)


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

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

        //Socket accept()侦听要连接到此套接字并接受他
        Socket s=ss.accept();

        //获取输入流,读数据,并把数据显示在控制台
        InputStream is=s.getInputStream();
        byte[] bys=new byte[1024];
        int len=is.read(bys);
        String data=new String(bys,0,len);
        System.out.println("数据是:"+data);

        //释放资源
        s.close();
        ss.close();
    }

}

3.客户端程序代码(IOClient_2.java)

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

public class IOClient_2 {
    public static void main(String[] args) throws IOException{
        //创建客户端的Socket对象
        Socket s=new Socket("127.0.0.1", 50000);

        //获取输出流,写数据
        OutputStream os=s.getOutputStream();
        os.write("hello 死妖阿".getBytes());
        //释放资源
        s.close();
    }
}


4.运行结果
在这里插入图片描述
IO方式中,我们看到数据读写是以字节流为单位,效率很低

二、基于NIO的java网络程序

1.创建项目步骤和上面相同

2.服务器程序代码(NIOServer.java)

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 NIOServer {
    //网络通信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<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> 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 {
        NIOServer myServer = new NIOServer();
        myServer.start(8887);
    }
}

3.客户端程序代码(NIOClient.java)

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

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

        // 连接服务器
        if (!socketChannel.connect(new InetSocketAddress("127.0.0.1", 8887))) {
            while (!socketChannel.finishConnect()) {
                System.out.println("connecting...");
            }
        }
        //发送数据
        String str = "hello  死妖阿";
        ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
        socketChannel.write(byteBuffer);
        System.in.read();
    }

}


4.运行结果
在这里插入图片描述

三、基于Netty的java网络程序

(一)导入包

1.创建两个项目,导入包,两个项目都要导入,FIle->Project Structure
在这里插入图片描述
2.Moudles→Dependencies→右边加号
在这里插入图片描述
3.下载netty的jar包
搜索输入io.netty:netty-all,等待一下,时间可能有几分钟,记得勾上Download to
在这里插入图片描述
4.搜索完成后选择第一个,如下图,点击OK
在这里插入图片描述
5.会出现下面的界面,直接OK,就完事了
在这里插入图片描述

(二)代码

1.服务器端(NettyServer.java)

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class NettyServer {
    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<SocketChannel>() {
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast(new ServerHandler());//这里配置具体数据接收方法的处理
                    }
                });

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

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

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


2.ServerHandler.java

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 {
    /**
     * 重写读数据时处理的方法
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @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("Server打印接收到的信息:" + body);
        String response = "Server返回给Client的响应信息:" + 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);
    }

    /**
     * 重写读数据出现异常处理的方法
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }

}


3.客户端(NettyClient.java)

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 NettyClient {
    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<SocketChannel>() {
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast(new ClientHandler());//这里配置具体数据接收方法的处理
                    }
                });

        /*与8787端口通讯*/
        ChannelFuture cf1 = b.connect("127.0.0.1", 8787).sync();//异步建立连接

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

        /*与8686端口通讯*/
        ChannelFuture cf2 = b.connect("127.0.0.1", 8686).sync();
        cf2.channel().writeAndFlush(Unpooled.copiedBuffer("hello netty".getBytes()));

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

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

4.ClientHandler.java

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 {
    /**
     * 重写读数据时处理的方法
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @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("Client打印接收到的信息:" + body);

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

    }

    /**
     * 重写读数据出现异常处理的方法
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}


5.运行结果
在这里插入图片描述

四、总结

本次实验学习了IO、NIO、Netty三者的原理和特点,三种编程方式性能方面,NIO优于IO。

五、参考链接

1.Java NIO详解

2.Netty入门(一)——传统IO与NIO比较(一)

3.Netty入门(二)——传统IO与NIO比较(二)

4.Netty入门(三)——服务端与客户端案例netty3.x

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值