在学习NIO的过程中看了很多博客和其他人的demo,现在算是对NIO有了一些了解,在此记录
本demo中只有服务端是NIO,客户端不是,而且客户端是简单的new线程而不是使用线程池,在生产实践中并不使用
与此同时,本demo涉及JDBC连接,但因为博文重点为C/S因此没有放上来
以下为客户端代码
package com.hsq.client;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
public class Client2 {
//https://www.cnblogs.com/jing99/p/12000371.html
// public static void client() throws IOException {
public static void main(String[] args) throws IOException {
SocketChannel channel = SocketChannel.open();
InetSocketAddress socketAddress = new InetSocketAddress("localhost", 8080);
boolean connect = channel.connect(socketAddress);
if (connect) {
System.out.println("客户端2启动");
}
String uname = "老王";
String upass = "1";
while (true) {
for (int i = 0; i < 5; i++) {
Thread thread = new Thread();
thread.start();
// System.out.println("第" + i + "个客户端2的线程");
String post = uname + "/" + upass;
byte[] bytes1 = post.getBytes(StandardCharsets.UTF_8);
if (bytes1 != null && bytes1.length > 0) {
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes1.length);
writeBuffer.put(bytes1);
writeBuffer.flip();
channel.write(writeBuffer);
}
ByteBuffer readbuffer = ByteBuffer.allocate(1024);
int readbytes = channel.read(readbuffer);
if (readbytes > 0) {
readbuffer.flip();
byte[] bytes = new byte[readbuffer.remaining()];
readbuffer.get(bytes);
String reply = new String(bytes);
System.out.println("服务器回复 :" + reply);
}
}
}
// socket.close();
}
}
以下为服务端代码
package com.hsq.server;
import com.hsq.bean.User;
import com.hsq.jdbc.SqlOperation;
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 LoginServer implements Runnable {
private ServerSocketChannel serverSocketChannel;
private Selector selector;
private volatile boolean stop = false;
public static void main(String[] args) throws IOException {
int port = 8081;
LoginServer loginServer = new LoginServer(port);
new Thread(loginServer, "nioserver-001").start();
}
public LoginServer(int port) {
try {
serverSocketChannel = ServerSocketChannel.open();
//获得一个serverChannel
selector = Selector.open();
////创建选择器 获得一个多路复用器
serverSocketChannel.configureBlocking(false);
//设置为非阻塞模式 如果为 true,则此通道将被置于阻塞模式;如果为 false,则此通道将被置于非阻塞模式
serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024);
//绑定一个端口和等待队列长度
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//把selector注册到channel,关注链接事件
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public void stop() {
this.stop = true;
}
@Override
public void run() {
while (!stop) {
try {
//无论是否有读写事件发生,selector每隔1s被唤醒一次。如果一定时间内没有事件,就需要做些其他的事情,就可以使用带超时的
int client = selector.select(1000);
System.out.println("1:" + client);
// 阻塞,只有当至少一个注册的事件发生的时候才会继续.
// int client = selector.select(); 不设置超时时间为线程阻塞,但是IO上支持多个文件描述符就绪
if (client == 0) {
continue;
}
System.out.println("2:" + client);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
SelectionKey key = null;
while (it.hasNext()) {
key = it.next();
it.remove();
try {
//处理事件
handle(key);
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
}
}
if (selector != null) {
// selector关闭后会自动释放里面管理的资源
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void handle(SelectionKey key) throws IOException {
if (key.isValid()) {
//连接事件
if (key.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
// 通过ServerSocketChannel的accept创建SocketChannel实例
// 完成该操作意味着完成TCP三次握手,TCP物理链路正式建立
SocketChannel sc = ssc.accept();
//3次握手
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
//连接建立后关注读事件F
}
//读事件
if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
// ByteBuffer readbuffer = ByteBuffer.allocate(1024);//写 0 1024 1024
// 申请直接内存,也就是堆外内存
ByteBuffer readbuffer = ByteBuffer.allocateDirect(1024);
// 读取请求码流,返回读取到的字节数
int readBytes = socketChannel.read(readbuffer);
// 读取到字节,对字节进行编解码
if (readBytes > 0) {
// 将缓冲区当前的limit设置为position=0,用于后续对缓冲区的读取操作
readbuffer.flip();
//读写模式反转
// 将缓冲区可读字节数组复制到新建的数组中
byte[] bytes = new byte[readbuffer.remaining()];
readbuffer.get(bytes);
String body = new String(bytes);
// String body = "登录成功";
// res(socketChannel, body);
// 这下服务器回复的也是字符串了,且按/分割
String[] arr = body.split("/");
String name = arr[0];
String password = arr[1];
//调用查询方法
User user = new User(name, password);
try {
int type = SqlOperation.loginModele(user);
String response;
if (type >= 1 && type <= 3) {
response = "登录成功";
} else {
response = "登录失败";
}
System.out.println(response);
res(socketChannel, response);
} catch (Exception e) {
e.printStackTrace();
}
} else if (readBytes < 0) {
// 链路已经关闭 释放资源
key.cancel();
socketChannel.close();
} else {
// 没有读到字节忽略
}
}
}
}
private void res(SocketChannel channel, String response) throws IOException {
if (response != null && response.length() > 0) {
byte[] bytes = response.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
channel.write(writeBuffer);
System.out.println("res end");
}
}
}