从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);
            }
        }
    }
}

  

未完待续.... 

posted @ 2018-01-28 21:47  GoldArowana  阅读(637)  评论(2编辑  收藏  举报