zoukankan      html  css  js  c++  java
  • 从Socket入门到BIO,PIO,NIO,multiplexing,AIO(未完待续)

    Socket入门

    最简单的Server端读取Client端内容的demo

    public class Server {
        public static void main(String [] args) throws Exception{
            ServerSocket ss = new ServerSocket(9000);
            Socket s = ss.accept();
            InputStream input = s.getInputStream();
            byte[] bytes = new byte[1024];
            int len = input.read(bytes);
            System.out.println(new String(bytes,0,len));
        }
    }
    

    打开浏览器,输入localhost:9000

    可以看到控制台输出了如下内容(HTTP请求头)

     或者命令行输入:telnet localhost 9000 , 然后按下ctrl+]   然后再输入: send 发送的内容    , 然后就可以再IDEA控制台看到send的内容了.

     但是现在手头是Mac系统,不知道为啥send不好使....windows下测过,肯定好使.

    最简单的Server端写入到Client端内容的demo

    public class Server2 {
        public static void main(String[] args) throws Exception {
            ServerSocket ssocket = new ServerSocket(9000);
            Socket socket = ssocket.accept();
            OutputStream os = socket.getOutputStream();
            os.write("http/1.0 200 OK\nContent-Type:text/html;charset:GBK\n\nhello".getBytes());
            os.flush();
            os.close();
        }
    }

     可以在浏览器输入localhost:9000来请求内容 , http响应头的那部分会被浏览器解析掉.所以只输出一段hello

    或者可以使用telnet localhost 9000来访问, 得到的结果就是那段写入的内容

     最简单的Client端写入到Server端的Demo 

    public class Client {
        public static void main(String[] args) throws Exception{
            Socket socket = new Socket("localhost",9000);
            OutputStream output = socket.getOutputStream();
            output.write("你好".getBytes());
            output.close();
            socket.close();
        }
    }
    

      注:需要先运行上面的Server类, 然后再运行这个Client.    然后再点击Server的控制台标签, 就会发现Server类的控制台输已经出了"你好"字样.

    最简单的用Client端来模拟浏览器http请求Demo

    用socket作为Client来模拟浏览器访问网站, 来获取网站的html内容.以www.sohu.com 为例...为什么选sohu呢? 你试试百度,会报302....

    public class Client2 {
        public static void main(String[] args) throws Exception {
            Socket socket = new Socket("www.sohu.com", 80);
            InputStream input = socket.getInputStream();
            OutputStream output = socket.getOutputStream();
            StringBuilder str = new StringBuilder();
            //http协议中请求行,必须,不然不会被识别为HTTP
            str.append("GET / HTTP/1.1\r\n");
            //http协议中的请求头
            str.append("Host: www.sohu.com\r\n");
            str.append("Connection: Keep-Alive\r\n");
            // 用于模拟浏览器的user-agent
            str.append("user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36\r\n");
            //这里一定要一个回车换行,表示消息头完,不然服务器会一直等待,认为客户端没发送完
            str.append("\r\n");
            byte[] bytes = new byte[1024];
            output.write(str.toString().getBytes());
            while (true) {
                int len = input.read(bytes);
                if (len > 0) {
                    String result = new String(bytes, 0, len);
                    System.out.println(result);
                } else {
                    break;
                }
            }
        }
    }

     运行后, 前面是http响应头, 后面是html代码

    改进Server类, 循环读取

    前面提到的"最简单的Server端读取Client端内容的demo"这段代码, 也就是Server类. 其实不管客户端发来多少内容, 都只能读取1024字节以内的数据. 因为代码就是这么写的....

    下面进行改进, 让他循环读取, 直到读完为止.

    public class Server3 {
        public static void main(String[] args) throws Exception {
            ServerSocket ss = new ServerSocket(9000);
            Socket s = ss.accept();
            InputStream input = s.getInputStream();
            byte[] bytes = new byte[1024];
            while (true) {
                int len = input.read(bytes);
                // 如果读到了内容,说明得输出啊
                if (len > 0) {
                    System.out.println(new String(bytes, 0, len));
                }
                // 如果没读取到内容,或者没读满bytes数组 说明读完了, 不用再读下一次了, 该退出循环了.
                if (len < bytes.length) {
                    break;
                }
            }
        }
    }

    可以配合着用前文中的Client类来进行测试.先跑Server来监听端口, 再运行Client发送请求. 去看Server类的控制台是否输出相应的内容.

    将服务器改为循环运行

    之前是Server响应一个客户端就终止了,这回用while(true)来让Server不停地进行服务.

    public class Server4 {
        public static void main(String[] args) throws Exception {
            ServerSocket ss = new ServerSocket(9000);
            while (true) {
                Socket s = ss.accept();
                InputStream input = s.getInputStream();
                byte[] bytes = new byte[1024];
                while (true) {
                    int len = input.read(bytes);
                    // 如果读到了内容,说明得输出啊
                    if (len > 0) {
                        System.out.println(new String(bytes, 0, len));
                    }
                    // 如果没读取到内容,或者没读满bytes数组 说明读完了, 不用再读下一次了, 该退出循环了.
                    if (len < bytes.length) {
                        break;
                    }
                }
            }
        }
    }

    可以配合着用前文中的Client类来进行测试.先跑Server来监听端口, 再运行Client发送请求. 去看Server类的控制台是否输出相应的内容.

    Server运行一次就行, 一直在提供服务.  而Client这回可以运行多次了, Client运行几次, Server的控制台下就会收到几个'你好'. 

    BIO

    同步阻塞IO, 每个线程都处理着一个客户端.客户端与线程数是1:1

    public class Server5 {
        public static void main(String[] args) throws Exception {
            ServerSocket ss = new ServerSocket(9000);
            System.out.println("服务端启动");
            // 循环着监听
            while (true) {
                Socket s = ss.accept();
                System.out.println("接收到客户端");
                // 一旦接收到客户端,就开一个线程
                new Thread(() -> {
                    //为了让代码简短,try多包一些代码...
                    try {
                        InputStream input = s.getInputStream();
                        OutputStream output = s.getOutputStream();
                        byte[] bytes = new byte[1024];
                        while (true) {
                            int len = input.read(bytes);
                            // 如果读到了内容,说明得输出啊
                            if (len > 0) {
                                System.out.println(new String(bytes, 0, len));
                            }
                            // 如果没读取到内容,或者没读满bytes数组 说明读完了, 不用再读下一次了, 该退出循环了.
                            if (len < bytes.length) {
                                break;
                            }
                        }
                        output.write("http/1.1 200 OK\nContent-Type:text/html;charset:GBK\n\nhello".getBytes());
                        output.flush();
                        input.close();
                        output.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        //...其实close应该出现在这里...
                        System.out.println("断开连接");
                    }
                }).start();
            }
        }
    }
    

     可以在浏览器上用两个标签栏来访问localhost:9000来进行测试.

    PIO

    伪异步IO,为了避免Server5无限制地开一个新线程,使用线程池来统一管理线程.

    public class Server6 {
        public static void main(String[] args) throws Exception {
            ServerSocket ss = new ServerSocket(9000);
            System.out.println("服务端启动");
            ExecutorService pool = Executors.newFixedThreadPool(40);
            // 循环着监听
            while (true) {
                Socket s = ss.accept();
                System.out.println("接收到客户端");
                // 一旦接收到客户端,就放入线程池
                pool.submit(() -> {
                    //为了让代码简短,try多包一些代码...
                    try {
                        InputStream input = s.getInputStream();
                        OutputStream output = s.getOutputStream();
                        byte[] bytes = new byte[1024];
                        while (true) {
                            int len = input.read(bytes);
                            // 如果读到了内容,说明得输出啊
                            if (len > 0) {
                                System.out.println(new String(bytes, 0, len));
                            }
                            // 如果没读取到内容,或者没读满bytes数组 说明读完了, 不用再读下一次了, 该退出循环了.
                            if (len < bytes.length) {
                                break;
                            }
                        }
                        output.write("http/1.1 200 OK\nContent-Type:text/html;charset:GBK\n\nhello".getBytes());
                        output.flush();
                        input.close();
                        output.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        //...其实close应该出现在这里...
                        System.out.println("断开连接");
                    }
                });
            }
        }
    }

    NIO

    首先推荐一本书,讲的详细:

    nio博客 http://ifeve.com/java-nio-all/  ,这个更适合快速入门,但原理最好还是看书..讲的很详细

    所以相关原理就不在这里粘贴了....再整理也没人家讲的全面易懂,这本书还是非常推荐看的,一开始我就是在网上看各种博客(当时也知道这本书), 但直到耐下心看了这本书,才发现很多地方这里讲的非常详细, 这本书是基于api讲的, 以后需要分析看源码的话还是借助博客更好一些,网上有很多大神的源码分析

    public class NIOServer {
        private static int BUFFER_SIZE = 1024;
        private static int PORT = 9000;
    
        public static void main(String[] args) throws Exception {
            Selector selector = Selector.open();
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    
            while (true) {
                selector.select();
                Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                while (iter.hasNext()) {
                    SelectionKey key = iter.next();
                    iter.remove();
                    if (key.isAcceptable()) {
                        SocketChannel socketChannle = serverSocketChannel.accept();
                        socketChannle.configureBlocking(false);
                        socketChannle.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(BUFFER_SIZE));
                    } else if (key.isReadable()) {
                        SocketChannel socketChannel = (SocketChannel) key.channel();
                        ByteBuffer buf = (ByteBuffer) key.attachment();
                        // 读浏览器发送的HTTP请求
                        long bytesRead = socketChannel.read(buf);
                        while (bytesRead > 0) {
                            buf.flip();
                            while (buf.hasRemaining()) {
                                System.out.print((char) buf.get());
                            }
                            System.out.println();
                            buf.clear();
                            bytesRead = socketChannel.read(buf);
                        }
    
                        //向浏览器返回HTTP请求
                        buf.put("http/1.1 200 OK\nContent-Type:text/html;charset:GBK\n\nhello".getBytes());
                        socketChannel = (SocketChannel) key.channel();
                        buf.flip();
                        while (buf.hasRemaining()) {
                            socketChannel.write(buf);
                        }
    
                        // 关闭
                        socketChannel.close();//这样浏览器才不继续拉取
                        key.cancel();
                    }
                }
            }
        }
    }  

    然后再浏览器中访问localhost:9000即可看到"hello"

    或者用普通SocketClient来访问

    或者用如下的NIOClient访问:

    public class NIOClient {
        public static void main(String[] args) throws Exception {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            socketChannel.connect(new InetSocketAddress("localhost", 9000));
            if (socketChannel.finishConnect()) {
                //向服务器写入
                for (int i = 0; i < 3; i++) {
                    String info = "hello:<" + i + ">";
                    buffer.clear();
                    buffer.put(info.getBytes());
                    buffer.flip();
                    while (buffer.hasRemaining()) {
                        socketChannel.write(buffer);
                    }
                }
                
                //从服务器读取
                buffer.clear();
                long bytesRead = socketChannel.read(buffer);
                while (bytesRead > 0) {
                    buffer.flip();
                    while (buffer.hasRemaining()) {
                        System.out.print((char) buffer.get());
                    }
                    System.out.println();
                    buffer.clear();
                    bytesRead = socketChannel.read(buffer);
                }
            }
        }
    }
    

      

    未完待续.... 

    ---------------------------------------------------------
    学如不及,犹恐失之
  • 相关阅读:
    OpenNESS & OpenVINO Demo 部署
    这种思路讲解数据仓库建模,你见过吗?数据人与架构师必看
    powerdesidgner1
    Java本地的项目,怎么可以让别人通过外网访问-内网穿透
    Java本地的项目,怎么可以让别人通过外网访问-内网穿透
    Java本地的项目,怎么可以让别人通过外网访问-内网穿透
    Java本地的项目,怎么可以让别人通过外网访问-内网穿透
    MySQL使用ProxySQL实现读写分离
    MySQL使用ProxySQL实现读写分离
    MySQL使用ProxySQL实现读写分离
  • 原文地址:https://www.cnblogs.com/noKing/p/8372536.html
Copyright © 2011-2022 走看看