- 一、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 结果
- 四、总结
- 五、参考文献
- 代码执行时会存在两个阻塞点
server.accept(); 等待链接 inputStream.read(bytes); 等待输入
- 单线程情况下只能为一个客户端服务;
- 用线程池可以有多个客户端连接,但是非常消耗性能;
- 使用传统的I/O程序读取文件内容, 并写入到另一个文件(或Socket), 如下程序:
File.read(fileDesc, buf, len); Socket.send(socket, buf, len);
会有较大的性能开销, 主要表现在一下两方面:
1.2 项目实现上下文切换(context switch), 此处有4次用户态和内核态的切换;
Buffer内存开销, 一个是应用程序buffer, 另一个是系统读取buffer以及socket buffer;
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比较(一)
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.勾选
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



