本文将通过Channel(通道)、Buffer(缓冲区)以及Selector(选择器)来实现TCP下NIO的实例。
主要通过ServerSocketChannel与SocketChannel信道来完成本次实例。
1.服务端
public class ChannelServer2 {
public static void main(String[] args) {
ServerSocketChannel serverChannel;
Selector selector;
try {
// 打开监听信道,ServerSocketChannel最终实现的是channel接口
serverChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverChannel.socket(); // 创建一个服务端socket
InetSocketAddress address = new InetSocketAddress(7456);
serverSocket.bind(address); // 绑定IP及端口
serverChannel.configureBlocking(false);// 设置为非阻塞方式,如果为true 那么就为传统的阻塞方式
selector = Selector.open();// 静态方法 实例化selector 创建选择器
/*如果你注册不止一种事件,那么可以用“位或”操作符将常量连接起来
SelectionKey.OP_READ |SelectionKey.OP_WRITE;*/
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务端启动......");
while (true) {
// 等待某信道就绪(或超时) 监听注册通道,当其中有注册的 IO操作可以进行时,该函数返回
// 并将对应的SelectionKey加入selected-key
if (selector.select(3000) == 0) {
System.out.println("重新等待");
continue;
}
// selectedKeys()中包含了每个准备好某一I/O操作的信道的SelectionKey
Set<SelectionKey> readyKeys = selector.selectedKeys();
// Selected-key代表了所有通过select()方法监测到可以进行IO操作的channel
Iterator<SelectionKey> iterator = readyKeys.iterator();
while (iterator.hasNext()) {// 对每个信道进行一次循环,查看各个信道是否有事件需要处理
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) { // 有客户端连接请求时
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
System.out.println("接收到连接:" + client);
client.configureBlocking(false);
SelectionKey clientKey = client.register(selector,
SelectionKey.OP_WRITE | SelectionKey.OP_READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);// 分配一个新的1024字节的缓冲区
clientKey.attach(buffer);// 将给定的缓冲区附加到此键
}
if (key.isReadable()) {// 判断是否有数据发送过来
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
buffer.clear();
// 读取信息获得读取的字节数
long bytesRead = client.read(buffer);
if (bytesRead == -1) {
// 没有读取到内容的情况
client.close();
} else {
// 将缓冲区准备为数据传出状态
buffer.flip();
// 将字节转化为为UTF-8的字符串
String receivedString = Charset.forName("UTF-8").newDecoder()
.decode(buffer).toString();
// 控制台打印出来
System.out.println(client.socket().getRemoteSocketAddress());
System.out.println("信息内容:" + receivedString);
// 准备发送的文本
String sendString = "你好,已经收到你的信息" + receivedString;
// 将byte数组包装到缓冲区中
buffer = ByteBuffer.wrap(sendString.getBytes("UTF-8"));
// 将字节序列从给定的缓冲区中写入此通道,并返回给客户端
client.write(buffer);
}
}
}
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
2.客户端
客户端信道连接类
public class ChannelClient {
// 信道选择器
Selector selector;
// 与服务器通信的信道
SocketChannel socketChannel;
// 要连接的服务器Ip地址
String hostIp;
// 要连接的远程服务器在监听的端口
int hostListenningPort;
public ChannelClient(String HostIp, int HostListenningPort) throws IOException {
this.hostIp = HostIp;
this.hostListenningPort = HostListenningPort;
// 打开监听信道并设置为非阻塞模式
socketChannel = SocketChannel.open(new InetSocketAddress(hostIp, hostListenningPort));
socketChannel.configureBlocking(false);
// 打开并注册选择器(监听读)到信道
selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_READ);
// 启动读取线程
ChannelClientReadThread ccrt = new ChannelClientReadThread(selector);
ccrt.start();
}
// 发送字符串到服务器
public void sendMsg(String message) throws IOException {
ByteBuffer writeBuffer = ByteBuffer.wrap(message.getBytes("UTF-8"));
socketChannel.write(writeBuffer);
}
public static void main(String[] args) throws IOException {
ChannelClient2 client = new ChannelClient2("localhost", 7456);
try {
client.sendMsg("我是客户端");
while (true) {
Scanner scan = new Scanner(System.in);// 等待键盘输入数据
String string = scan.nextLine();
client.sendMsg(string);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端处理读的线程
public class ChannelClientReadThread extends Thread {
private Selector selector;
public ChannelClientReadThread(Selector selector) {
this.selector = selector;
}
@Override
public void run() {
try {
while (selector.select() > 0) {
// 遍历每个有可用IO操作Channel对应的SelectionKey
for (SelectionKey sk : selector.selectedKeys()) {
// 如果该SelectionKey对应的Channel中有可读的数据
if (sk.isReadable()) {
// 使用NIO读取Channel中的数据
SocketChannel sc = (SocketChannel) sk.channel();// 获取通道信息
ByteBuffer buffer = ByteBuffer.allocate(1024);// 分配缓冲区大小
sc.read(buffer);// 读取通道里面的数据放在缓冲区内
buffer.flip();// 调用此方法为一系列通道写入或相对获取 操作做好准备
// 将字节转化为为UTF-16的字符串
String receivedString = Charset.forName("UTF-8").newDecoder().decode(buffer)
.toString();
// 控制台打印返回信息
System.out.println("服务器:" + sc.socket().getRemoteSocketAddress());
System.out.println("信息内容:" + receivedString);
// 为下一次读取作准备
sk.interestOps(SelectionKey.OP_READ);
}
// 删除正在处理的SelectionKey
selector.selectedKeys().remove(sk);
}
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
目前TCP部分大致完结,之后会继续研究UDP部分内容