目录
1.客户端相关知识与代码:
?SocketChannel API列表:
客户端代码执行流程:
Client端代码:
2.服务端知识与代码:
ServerSocketChannel API列表:
Server端代码执行流程:
Server端代码:
涉及到Buffer, ServerSocketChannel, SocketChannel, Selector,?SelectionKey等类的使用。
1.客户端相关知识与代码:
?SocketChannel API列表:
客户端代码执行流程:
Note: 这里只是连接服务端,并且向服务端发送数据的代码,没有从服务端读取数据的代码,感兴趣的可以自行研究,后期我会补上。
- 得到一个网络通道?SocketChannel socketChannel = SocketChannel.open(), 调用了open()这个API
- 设置非阻塞:socketChannel.configureBlocking(false), 调用了configureBlocking这个API
- 连接服务器:socketChannel.connect(inetSocketAddress),调用了connect(......)这个API
- 如果上面的连接不成功,则调用socketChannel.finishConnect(),非阻塞的持续连接服务器,直到成功为止,调用了finishConnect()这个API
- 连接成功,发送数据: socketChannel.write(buffer),调用了write(......)这个API
Client端代码:
package com.bruce.javanio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NIOClient {
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
//得到一个网络通道
SocketChannel socketChannel = SocketChannel.open();
//设置非阻塞
try {
socketChannel.configureBlocking(false);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//设置服务端的ip和端口号
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
//连接服务器
if (!socketChannel.connect(inetSocketAddress)) {
//如果连接不成功,那么就用finishConnect(),进行非阻塞的循环
while (!socketChannel.finishConnect()) {
System.out.println("因为连接需要时间,客户端不会阻塞,可以做其它工作");
}
}
//如果连接成功,则发送数据
String str = "hello, Bruce";
ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
//发送数据
socketChannel.write(buffer);
System.in.read();//让代码停在这里,不要结束。
}
}
2.服务端知识与代码:
ServerSocketChannel API列表:
Server端代码执行流程:
Note: 这里只是读取客户端数据的代码,如果想要向客户端发送数据,很简单,调用selector.keys()得到所有注册到selector的通道(包括一个ServerSocketChannel和一个或多个SocketChannel),从中剔除掉ServerSocketChannel, 然后使用socketChannel.write(buffer),就可以完成向客户端发送数据。
- 创建serverSocketChannel: ServerSocketChannel.open(),调用了open()这个API
- 绑定端口: serverSocketChannel.socket().bind(new InetSocketAddress(6666)),调用了bind(......)这个API
- 设置为非阻塞:serverSocketChannel.configureBlocking(false),调用了configureBlocking(......)这个API
- 得到一个Selector对象:Selector selector = Selector.open(),调用了open()这个API
- 把serverSocketChannel注册到selector:serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT),调用了register(......)这个API
- 检测注册的通道有没有发生事件:if (selector.select(1000) == 0),前面只注册了一个serverSocketChannel,所有目前只能是客户端连接事件SelectionKey.OP_ACCEPT。如果有事件发生,则获取发生的事件的集合Set<SelectionKey> selectedKeys = selector.selectedKeys(),然后遍历这个集合。
- (1).如果发生了客户端连接事件,那么得到客户端的SockerChannel(通过serverSocketChannel.accept()),然后将其注册到selector。
- (2).如果客户端发送了数据,那么通过SelectionKey反向获取到对应的SocketChannel,然后将数据读取出来socketChannel.read(buffer),进行业务处理
Server端代码:
package com.bruce.javanio;
import java.io.IOException;
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 {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
//1.创建serverSocketChannel -> 对应于BIO的serverSocket
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2绑定一个端口6666,在服务器端监听
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
//3.设置为非阻塞
serverSocketChannel.configureBlocking(false);
//4.得到一个Selector对象
Selector selector = Selector.open();
//5.把serverSocketChannel注册到selector
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//循环等待客户端连接
while(true){
//等待1s,没有事件发生,继续select
if (selector.select(1000) == 0) {
System.out.println("Debug: 没有任何事件发生");
System.out.println("Debug: 服务器等待了1s,无连接");
continue;
}
//有事件发生,拿到有事件发生的SelectionKey集合(对应Selector类中的集合对象 Set<SelectionKey> selectedKeys)
//通过SelectionKey反向获取通道
Set<SelectionKey> selectedKeys = selector.selectedKeys();
//使用迭代器遍历Set<SelectionKey> selectedKeys
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
//循环处理每个事件
while (keyIterator.hasNext()) {
//获取到SelectionKey
SelectionKey key = keyIterator.next();
//根据key所对应的通道发生的事件,进行相应的处理
if (key.isAcceptable()) {//如果是OP_ACCEPT,有新的客户端连接
//为发送连接请求的客户端生成一个SocketChannel,这里不会阻塞,因为有客户端来连接了,这里会立即执行
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println("Debug: 客户端连接成功,生成了一个sockerChannel " + socketChannel.hashCode() );
//将socketChannel设置为非阻塞
socketChannel.configureBlocking(false);
//将SocketChannel注册到selector, 本例中,由于是客户端发送数据到服务端,所以服务端应该是读取数据SelectionKey.OP_READ。
//并且关联一个Buffer, 服务器端也是有Buffer的。
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if(key.isReadable()){
//通过SelectionKey反向获取SocketChannel
SocketChannel socketChannel = (SocketChannel) key.channel();
//获取到socketChannel关联的buffer(上面key.isAcceptable()注册的时候指定的Buffer)
ByteBuffer buffer = (ByteBuffer) key.attachment();
socketChannel.read(buffer);
System.out.println("from 客户端 :" + new String(buffer.array()) );
}
//手动从集合中移除当前的key,因为已经处理完了,防止下次重复操作。
keyIterator.remove();
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
两个错误的处理:
1.服务端在注册连接过来的SockerChannel的时候,首先要将其设置为非阻塞,不然会报下面的错误。
//将socketChannel设置为非阻塞 socketChannel.configureBlocking(false);
2. 客户端发送数据后,不能立即结束,不然服务端会报错:
需要在客户端代码的最后,加上下面两行代码,因为socketChannel.write(buffer);不是阻塞操作,如果不加System.in.read(),那么客户端代码直接结束,而socketChannel.write(buffer)还没有执行完毕。
?? ???? socketChannel.write(buffer); ?? ??? ?System.in.read();//让代码停在这里,不要结束。
?执行结果如下:
?此时再起另外一个客户端,两个客户端SockerChannel的hashCode不一致。
|