这里我这边举一个非阻塞式地Socket服务端,代码如下:
public class Server {
private static ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
public static void main(String[] args) {
try {
//1、创建ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2、绑定我们的端口号
serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", 80));
//3、设置连接模式为非阻塞式
serverSocketChannel.configureBlocking(false);
//4、非阻塞式不会阻塞
SocketChannel socketChannel = serverSocketChannel.accept();
if(socketChannel!=null) {
int read = socketChannel.read(byteBuffer);
if(read>0) {
//Buffer切换为读模式
byteBuffer.flip();
//设定编码
Charset charset = Charset.forName("UTF-8");
String receiveText = charset.newDecoder().decode(byteBuffer.asReadOnlyBuffer()).toString();
System.out.println("receiveText:"+receiveText);
}
}
System.out.println("结束");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
这段代码一执行就会执行到末尾,根本就不会停下来,没有任何客户端能够连的到。
客户端我就直接用最简单的Socket来实现,代码如下:
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1", 80));
while(true) {
Scanner scanner = new Scanner(System.in);
socket.getOutputStream().write(scanner.next().getBytes());
}
}
}
当然,此时客户端也永远连接不到服务端,因为服务端已启动就执行完了,当然我们可以改为阻塞式地方法来测试:
serverSocketChannel.configureBlocking(true);
当然也可以模拟Selector的多路复用模式,当然这里用的是循环,我们将代码改为while循环:
public class Server {
private static ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
private static List<SocketChannel> socketChannels = new ArrayList<SocketChannel>();
public static void main(String[] args) {
try {
//1、创建ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2、绑定我们的端口号
serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", 80));
//3、设置连接模式为非阻塞式
serverSocketChannel.configureBlocking(false);
while(true) {
//4、非阻塞式不会阻塞
SocketChannel socketChannel = serverSocketChannel.accept();
//有连接就加入
if(socketChannel!=null) {
System.out.println("有新连接");
//这里必须加上这个,不然新进入的socket不会识别
socketChannel.configureBlocking(false);
socketChannels.add(socketChannel);
}
for(SocketChannel socket:socketChannels) {
//清除缓存中的信息
byteBuffer.clear();
int read = socket.read(byteBuffer);
if(read>0) {
//Buffer切换为读模式
byteBuffer.flip();
//设定编码
Charset charset = Charset.forName("UTF-8");
String receiveText = charset.newDecoder().decode(byteBuffer.asReadOnlyBuffer()).toString();
System.out.println("receiveText:"+receiveText);
}
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
这样就相当于一个主线程就监控了所有的tcp连接,不需要按照我们之前的有一个连接就new一个线程出去。
这个也是为何redis的单线程效率这么高的原因,不过redis和我这个还是有很大区别的,redis只可以在linux上使用的原因就是应为可以借助于Linux内核实现的epoll来实现,而不是我这里以及window的循环,我这种实现是靠一个for循环,这样子假如有一万个连接,其中只有一两个是活跃的,我都需要循环遍历所有!这效率会很低,但是epoll的模式就不同了,只会返回有数据的值,具体在以后的文章了解下,为啥epoll这么牛逼!
顺带说一句,redis的window版本都是大神改了底层源码用select模式(也就是轮询模式)实现多路IO复用的。