java原生NIO实现客户端服务端简单通讯
Posted FreeFly辉
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java原生NIO实现客户端服务端简单通讯相关的知识,希望对你有一定的参考价值。
服务端
模拟http形式,客户端请求一次,服务端响应一次
解释以注释形式加入代码中,想要理解可看注释
public class Server {
private static Selector selector ;
public static void main(String[] args) throws IOException {
init();
}
public static void init() throws IOException{
selector = Selector.open(); //nio是通过selector管理多个管道的,所以一定要有selector
ServerSocketChannel ssc = ServerSocketChannel.open();//开启一个服务端
ssc.configureBlocking(false);//设置服务socket非阻塞
ssc.bind(new InetSocketAddress(8899));//绑定监听端口
ssc.register(selector, SelectionKey.OP_ACCEPT);//绑定selector,此时只需在意 打开 OP_ACCEPT事件,相当于传统socket中的accept()操作
listen(); //监听连接事件方法
}
public static void listen() throws IOException{
while(true){ //死循环一直监听
int select = selector.select();//获取检测到的连接完成,因为此时只注册了 OP_ACCEPT事件ssc.register(selector,SelectionKey.OP_ACCEPT);,所以就相当于传统socket中的accpet方法,只是这个select会一次返回所有accept状态的连接,而非传统socket中的阻塞式单个accept方法
if(select == 0){//没有建立连接的则直接下一次循环
continue;
}
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
while(it.hasNext()){
SelectionKey key = it.next();
// 由于select操作只管对selectedKeys进行添加,所以key处理后我们需要从里面把key去掉
it.remove();
if(key.isAcceptable()){
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();//获取通道,相当于传统io的iostream作用,不同的是channel是批量传输buffer对象的,而对于buffer是可以0拷贝的,因此速度会相对有提升
SocketChannel channel = serverChannel.accept();// 得到与客户端的套接字通道,没啥好说的
channel.configureBlocking(false);//设置channel非阻塞
channel.register(selector, SelectionKey.OP_READ); //注册读事件,会覆盖之前注册的OP_ACCEPT事件,因为模仿http请求单次请求,单次响应,所以作为服务端肯定是先注册读事件用于处理。
}else if(key.isReadable()){//当注册完读事件,下一次循环读就绪时就会进入此方法
read(key);//读取对应channel事件
key.interestOps(SelectionKey.OP_WRITE);// 改变自身关注事件,可以用位或操作|组合时间,读完之后注册写事件模拟http的响应,所以用写事件覆盖读事件
}else if(key.isWritable()){//处理完读会注册写事件,下一次循环会进入此方法
write(key);//处理写事件
key.interestOps(0);// 改变自身关注事件,此处为清理所有监听事件,模拟http中相应完关闭此连接
key.channel().close();
}
}
}
}
private static void write(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
channel.write(ByteBuffer.wrap("server write\\n".getBytes()));//此处自行搜索ByteBuffer相关使用
}
private static void read(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int count = channel.read(buffer);
byte[] bytes = new byte[count];
buffer.flip();
buffer.get(bytes);
System.out.println(new String(bytes));
}
}
nio实现的客户端
模拟http形式,客户端请求一次,得到服务端响应一次,客户端就关闭
public class Client {
private static Selector selector;
public static void main(String[] args) throws IOException {
init();
request();
}
public static void init() throws IOException{
selector = Selector.open();//客户端也用nio,事实上并不是非要用selector形式的nio,一单通过网络连接,和如何实现就无关了,后面贴了个传统socket客户端的代码
SocketChannel sc = SocketChannel.open(); //打开客户端连接,相对服务端只是少了个Server前缀和传统Socket一样
sc.configureBlocking(false);
sc.connect(new InetSocketAddress(InetAddress.getLocalHost(), 8899));//绑定请求地址端口
sc.register(selector, SelectionKey.OP_CONNECT);//注册connect事件,相对于服务端注册的OP_ACCEPT
}
public static void request() throws IOException{
out:while(true){
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
while(it.hasNext()){
SelectionKey key = it.next();
it.remove();//以上代码解释同服务端
if(key.isConnectable()){//如果selector检测到连接事件,第一次进来肯定是连接事件,因为上面只注册了连接事件
SocketChannel channel = (SocketChannel) key.channel();
if(channel.finishConnect()){ //如果完成了连接
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_WRITE);//注册写事件,因为模拟http的形式,所以客户端是先写,再读,
//即先请求在得到响应数据,如果是想保持长连接则写成 channel.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE);形式
}
}else if(key.isReadable()){//在第二次进来写数据后注册了读事件,则第三次进来就会读到 读就绪事件
read(key); //读取数据
key.interestOps(0);//清理事件监听
key.channel().close();//关闭连接
break out;//跳出循环,此处不建议这么
}else if(key.isWritable()){//连接完成注册好写事件,第二次进来则检测到的就是写事件
write(key);//写出数据
key.interestOps(SelectionKey.OP_READ);//写完数据注册读事件,用于接受数据
}
}
}
}
private static void write(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
channel.write(ByteBuffer.wrap("client write".getBytes()));
}
private static void read(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int count = channel.read(buffer);
byte[] bytes = new byte[count];
buffer.flip();
buffer.get(bytes);
System.out.println(new String(bytes));
}
}
传统socket实现的客户端
模拟http形式,客户端请求一次,得到服务端响应一次,客户端就关闭
public class IOSocketClient {
public static void main(String[] args) throws Exception{
socketIo();
}
static void socketIo() throws Exception{
Socket socket = new Socket();
socket.connect(new InetSocketAddress(InetAddress.getLocalHost(),8899));
socket.getOutputStream().write("io socket client write\\n".getBytes());//发起请求写数据
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));//读取一行响应数据
System.out.println(reader.readLine()); //打印数据
socket.close(); //关闭连接
}
}
总结
1、无论是什么形式,服务端肯定要先创建服务,绑定端口。客户端创建连接 绑定请求地址+端口。2、服务端要通过accept,客户端要通过connect,从而建立通道(即inputstream和outputstream或者channel,其实都差不多用来传数据的)。
3、nio借助 selector 去管理多个连接,通过注册的管理项,进行select()方法返回集合用于 编程人员处理从而代替了一个连接就用一个线程处理。(具体场景可自行搜索)。
4、nio传输数据通过Buffer类,而Buffer类的方法可以申请堆外内存,实现非拷贝批量传输,从而提升速率。
5、最后提一点netty是基于nio的网络框架,有兴趣的可以连接nio后去学习一下netty
以上是关于java原生NIO实现客户端服务端简单通讯的主要内容,如果未能解决你的问题,请参考以下文章
JAVA NIO 异步TCP服务端向客户端消息群发代码教程实战