个人随笔
目录
实战:ServerSocketChannel(模拟多路IO复用)(NIO)
2020-01-07 23:37:18

这里我这边举一个非阻塞式地Socket服务端,代码如下:

  1. public class Server {
  2. private static ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
  3. public static void main(String[] args) {
  4. try {
  5. //1、创建ServerSocketChannel
  6. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
  7. //2、绑定我们的端口号
  8. serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", 80));
  9. //3、设置连接模式为非阻塞式
  10. serverSocketChannel.configureBlocking(false);
  11. //4、非阻塞式不会阻塞
  12. SocketChannel socketChannel = serverSocketChannel.accept();
  13. if(socketChannel!=null) {
  14. int read = socketChannel.read(byteBuffer);
  15. if(read>0) {
  16. //Buffer切换为读模式
  17. byteBuffer.flip();
  18. //设定编码
  19. Charset charset = Charset.forName("UTF-8");
  20. String receiveText = charset.newDecoder().decode(byteBuffer.asReadOnlyBuffer()).toString();
  21. System.out.println("receiveText:"+receiveText);
  22. }
  23. }
  24. System.out.println("结束");
  25. } catch (IOException e) {
  26. // TODO Auto-generated catch block
  27. e.printStackTrace();
  28. }
  29. }
  30. }

这段代码一执行就会执行到末尾,根本就不会停下来,没有任何客户端能够连的到。

客户端我就直接用最简单的Socket来实现,代码如下:

  1. public class Client {
  2. public static void main(String[] args) throws IOException {
  3. Socket socket = new Socket();
  4. socket.connect(new InetSocketAddress("127.0.0.1", 80));
  5. while(true) {
  6. Scanner scanner = new Scanner(System.in);
  7. socket.getOutputStream().write(scanner.next().getBytes());
  8. }
  9. }
  10. }

当然,此时客户端也永远连接不到服务端,因为服务端已启动就执行完了,当然我们可以改为阻塞式地方法来测试:

  1. serverSocketChannel.configureBlocking(true);

当然也可以模拟Selector的多路复用模式,当然这里用的是循环,我们将代码改为while循环:

  1. public class Server {
  2. private static ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
  3. private static List<SocketChannel> socketChannels = new ArrayList<SocketChannel>();
  4. public static void main(String[] args) {
  5. try {
  6. //1、创建ServerSocketChannel
  7. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
  8. //2、绑定我们的端口号
  9. serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", 80));
  10. //3、设置连接模式为非阻塞式
  11. serverSocketChannel.configureBlocking(false);
  12. while(true) {
  13. //4、非阻塞式不会阻塞
  14. SocketChannel socketChannel = serverSocketChannel.accept();
  15. //有连接就加入
  16. if(socketChannel!=null) {
  17. System.out.println("有新连接");
  18. //这里必须加上这个,不然新进入的socket不会识别
  19. socketChannel.configureBlocking(false);
  20. socketChannels.add(socketChannel);
  21. }
  22. for(SocketChannel socket:socketChannels) {
  23. //清除缓存中的信息
  24. byteBuffer.clear();
  25. int read = socket.read(byteBuffer);
  26. if(read>0) {
  27. //Buffer切换为读模式
  28. byteBuffer.flip();
  29. //设定编码
  30. Charset charset = Charset.forName("UTF-8");
  31. String receiveText = charset.newDecoder().decode(byteBuffer.asReadOnlyBuffer()).toString();
  32. System.out.println("receiveText:"+receiveText);
  33. }
  34. }
  35. }
  36. } catch (IOException e) {
  37. // TODO Auto-generated catch block
  38. e.printStackTrace();
  39. }
  40. }
  41. }

这样就相当于一个主线程就监控了所有的tcp连接,不需要按照我们之前的有一个连接就new一个线程出去。

这个也是为何redis的单线程效率这么高的原因,不过redis和我这个还是有很大区别的,redis只可以在linux上使用的原因就是应为可以借助于Linux内核实现的epoll来实现,而不是我这里以及window的循环,我这种实现是靠一个for循环,这样子假如有一万个连接,其中只有一两个是活跃的,我都需要循环遍历所有!这效率会很低,但是epoll的模式就不同了,只会返回有数据的值,具体在以后的文章了解下,为啥epoll这么牛逼!

顺带说一句,redis的window版本都是大神改了底层源码用select模式(也就是轮询模式)实现多路IO复用的。

 204

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2