这里我这边举一个非阻塞式地Socket服务端,代码如下:
public class Server {private static ByteBuffer byteBuffer = ByteBuffer.allocate(1024);public static void main(String[] args) {try {//1、创建ServerSocketChannelServerSocketChannel 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 blocke.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、创建ServerSocketChannelServerSocketChannel 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 blocke.printStackTrace();}}}
这样就相当于一个主线程就监控了所有的tcp连接,不需要按照我们之前的有一个连接就new一个线程出去。
这个也是为何redis的单线程效率这么高的原因,不过redis和我这个还是有很大区别的,redis只可以在linux上使用的原因就是应为可以借助于Linux内核实现的epoll来实现,而不是我这里以及window的循环,我这种实现是靠一个for循环,这样子假如有一万个连接,其中只有一两个是活跃的,我都需要循环遍历所有!这效率会很低,但是epoll的模式就不同了,只会返回有数据的值,具体在以后的文章了解下,为啥epoll这么牛逼!
顺带说一句,redis的window版本都是大神改了底层源码用select模式(也就是轮询模式)实现多路IO复用的。
