美文网首页
Socket 网络编程(一)

Socket 网络编程(一)

作者: zhzhgang | 来源:发表于2018-04-07 16:10 被阅读0次

    Socket

    Socket,又称“套接字”,应用程序通常通过“套接字”向网络发出请求或应答网络请求。

    Socket 和 ServerSocket类位于 java.net 包中。对于一个网络连接来说,套接字是平等的,不因为在服务器端或在客户端而产生不同级别,它们的工作都是通过 SocketImpl 类及其子类完成的。

    套接字之间的连接过程可以分为四个步骤:
    1)服务器监听
    2)客户端请求服务器
    3)服务器确认
    4)客户端确认

    示例代码:

    Server.java

    public class Server {
    
        final static int PROT = 8765;
        
        public static void main(String[] args) {
            
            ServerSocket server = null;
            try {
                server = new ServerSocket(PROT);
                System.out.println(" server start .. ");
                //进行阻塞
                Socket socket = server.accept();
                //新建一个线程执行客户端的任务
                new Thread(new ServerHandler(socket)).start();
                
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if(server != null){
                    try {
                        server.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                server = null;
            }
        }
    

    Client.java

    public class Client {
    
        final static String ADDRESS = "127.0.0.1";
        final static int PORT = 8765;
        
        public static void main(String[] args) {
            
            Socket socket = null;
            BufferedReader in = null;
            PrintWriter out = null;
            
            try {
                socket = new Socket(ADDRESS, PORT);
                in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                out = new PrintWriter(socket.getOutputStream(), true);
                
                //向服务器端发送数据
                out.println("接收到客户端的请求数据...");
                out.println("接收到客户端的请求数据1111...");
                String response = in.readLine();
                System.out.println("Client: " + response);
                
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if(in != null){
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(out != null){
                    try {
                        out.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                if(socket != null){
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                socket = null;
            }
        }
    }
    

    ServerHandler.java

    public class ServerHandler implements Runnable{
    
        private Socket socket ;
        
        public ServerHandler(Socket socket){
            this.socket = socket;
        }
        
        @Override
        public void run() {
            BufferedReader in = null;
            PrintWriter out = null;
            try {
                in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
                out = new PrintWriter(this.socket.getOutputStream(), true);
                String body = null;
                while(true){
                    body = in.readLine();
                    if(body == null) break;
                    System.out.println("Server :" + body);
                    out.println("服务器端回送响的应数据.");
                }
                
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if(in != null){
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(out != null){
                    try {
                        out.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                if(socket != null){
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                socket = null;
            }
        }
    }
    

    运行结果:

    服务端

    Server :接收到客户端的请求数据...
    Server :接收到客户端的请求数据1111...
    

    客户端

    Client: 服务器端回送响的应数据.
    

    采用线程池和任务队列实现伪异步 IO,将客户端的 socket 封装成 task 任务(实现 Runnable 接口的类),然后投递到线程池中去。

    示例代码:

    Server.java

    public class Server {
    
        final static int PORT = 8765;
    
        public static void main(String[] args) {
            ServerSocket server = null;
            BufferedReader in = null;
            PrintWriter out = null;
            try {
                server = new ServerSocket(PORT);
                System.out.println("Server start");
                Socket socket = null;
                HandlerExecutorPool executorPool = new HandlerExecutorPool(50, 1000);
                while(true){
                    socket = server.accept();
                    executorPool.execute(new ServerHandler(socket));
                }
                
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if(in != null){
                    try {
                        in.close();
                    } catch (Exception e1) {
                        e1.printStackTrace();
                    }
                }
                if(out != null){
                    try {
                        out.close();
                    } catch (Exception e2) {
                        e2.printStackTrace();
                    }
                }
                if(server != null){
                    try {
                        server.close();
                    } catch (Exception e3) {
                        e3.printStackTrace();
                    }
                }
                server = null;              
            }   
        }   
    }
    

    HandlerExecutorPool.java

    public class HandlerExecutorPool {
    
        private ExecutorService executor;
        public HandlerExecutorPool(int maxPoolSize, int queueSize){
            this.executor = new ThreadPoolExecutor(
                    Runtime.getRuntime().availableProcessors(),
                    maxPoolSize, 
                    120L, 
                    TimeUnit.SECONDS,
                    new ArrayBlockingQueue<Runnable>(queueSize));
        }
        
        public void execute(Runnable task){
            this.executor.execute(task);
        }   
    }
    

    ServerHandler.java

    public class ServerHandler implements Runnable {
    
        private Socket socket;
        public ServerHandler (Socket socket){
            this.socket = socket;
        }
        
        @Override
        public void run() {
            BufferedReader in = null;
            PrintWriter out = null;
            try {
                in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
                out = new PrintWriter(this.socket.getOutputStream(), true);
                String body = null;
                while(true){
                    body = in.readLine();
                    if(body == null) break;
                    System.out.println("Server:" + body);
                    out.println("Server response");
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if(in != null){
                    try {
                        in.close();
                    } catch (Exception e1) {
                        e1.printStackTrace();
                    }
                }
                if(out != null){
                    try {
                        out.close();
                    } catch (Exception e2) {
                        e2.printStackTrace();
                    }
                }
                if(socket != null){
                    try {
                        socket.close();
                    } catch (Exception e3) {
                        e3.printStackTrace();
                    }
                }
                socket = null;          
            }       
        }
    }
    

    Client.java

    public class Client {
        
        final static String ADDRESS = "127.0.0.1";
        final static int PORT =8765;
        
        public static void main(String[] args) {
            Socket socket = null;
            BufferedReader in = null;
            PrintWriter out = null;
            try {
                socket = new Socket(ADDRESS, PORT);
                in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                out = new PrintWriter(socket.getOutputStream(), true);
                
                out.println("Client request");
                
                String response = in.readLine();
                System.out.println("Client:" + response);
                
                
            }  catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } finally {
                if(in != null){
                    try {
                        in.close();
                    } catch (Exception e1) {
                        e1.printStackTrace();
                    }
                }
                if(out != null){
                    try {
                        out.close();
                    } catch (Exception e2) {
                        e2.printStackTrace();
                    }
                }
                if(socket != null){
                    try {
                        socket.close();
                    } catch (Exception e3) {
                        e3.printStackTrace();
                    }
                }
                socket = null;              
            }       
        }
    }
    

    运行结果:

    服务端

    Server start
    Server:Client request
    

    客户端

    Client:Server response
    

    IO (BIO) 和 NIO 的区别:其本质就是阻塞和非阻塞的区别。

    阻塞:应用程序在获取网络数据时,如果网络传输数据很慢,那么程序就一直等着,直到传输完毕为止。

    非阻塞:应用程序直接可以获取已经准备好的数据,无需等待。

    IO 为同步阻塞,NIO 为同步非阻塞。NIO 并没有实现异步,在 JDK1.7之后,升级了 NIO 库包,支持异步非阻塞通信模型,即 NIO2.0(AIO)。

    同步和异步:同步和异步一般是面向操作系统与应用程序对 IO 操作的层面上来区别的。

    同步时,应用程序会直接参与 IO 读写操作,并且应用程序会直接阻塞到某一个方法上,直到数据准备就绪,或者采用轮询的策略实时检查数据的就绪状态,如果就绪则获取数据。

    异步时,所有的 IO 读写操作交给操作系统处理,与应用程序没有直接关系,当操作系统完成 IO 读写操作时,会给应用程序发送通知,应用程序直接拿走数据即可。

    NIO 编程介绍

    NIO 中三个重要概念:Buffer,Channel,Selector。

    Buffer:Buffer 是一个对象,它包含一些要写入或者要读取的数据。在面向流的 IO 中,可以将数据直接写入或读取到 Stream 对象中。在 NIO 库中,所有数据都是用缓冲区处理的。缓冲区实际上是一个数组,通常是一个字节数组(ByteBuffer),也可以使用其他类型的数组。每一种 Java 基本数据类型都对应了一种缓冲区(除了 boolean 类型)。

    Channel:通道与流的不同之处在于,通道是双向的,而流是单向的(一个流必须是 InputStream 或 OutputStream 的子类)。通道可以用于读、写或者二者同时进行,最重要的是可以和多路复用器结合起来,有多种的状态位,方便多路复用器去识别。

    Selector:Selector 会不断地轮询注册在其上的通道,如果某个通道发生了读写操作,这个通道就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以取得就绪的 Channel 集合,从而进行后续的 IO 操作。一个 Selector 可以负责成千上万个 Channel 通道,没有上限,这也就意味着,只要一个线程负责 Selector 的轮询,就可以接入成千上万个客户端。

    Selector 线程就类似一个管理者,管理了成千上万个管道,然后轮询哪个管道的数据已经准备好,通知 CPU 执行 IO 的读取或写入操作。

    Selector 模式:当 IO 事件(管道)注册到 Selector 以后,Selector 会分配给每个管道一个 key 值。Selector 以轮询的方式进行查找注册的所有 IO 事件(管道),当 IO 事件(管道)准备就绪后,Selector 就会识别,通过 key 值找到相应的管道,进行相关的数据处理操作。

    示例代码:

    Server.java

    public class Server implements Runnable{
        //1 多路复用器(管理所有的通道)
        private Selector seletor;
        //2 建立缓冲区
        private ByteBuffer readBuf = ByteBuffer.allocate(1024);
        //3 
        private ByteBuffer writeBuf = ByteBuffer.allocate(1024);
        public Server(int port){
            try {
                //1 打开路复用器
                this.seletor = Selector.open();
                //2 打开服务器通道
                ServerSocketChannel ssc = ServerSocketChannel.open();
                //3 设置服务器通道为非阻塞模式
                ssc.configureBlocking(false);
                //4 绑定地址
                ssc.bind(new InetSocketAddress(port));
                //5 把服务器通道注册到多路复用器上,并且监听阻塞事件
                ssc.register(this.seletor, SelectionKey.OP_ACCEPT);
                
                System.out.println("Server start, port :" + port);
                
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void run() {
            while(true){
                try {
                    //1 必须要让多路复用器开始监听
                    this.seletor.select();
                    //2 返回多路复用器已经选择的结果集
                    Iterator<SelectionKey> keys = this.seletor.selectedKeys().iterator();
                    //3 进行遍历
                    while(keys.hasNext()){
                        //4 获取一个选择的元素
                        SelectionKey key = keys.next();
                        //5 直接从容器中移除就可以了
                        keys.remove();
                        //6 如果是有效的
                        if(key.isValid()){
                            //7 如果为阻塞状态
                            if(key.isAcceptable()){
                                this.accept(key);
                            }
                            //8 如果为可读状态
                            if(key.isReadable()){
                                this.read(key);
                            }
                            //9 写数据
                            if(key.isWritable()){
                                //this.write(key); //ssc
                            }
                        }
                        
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        
        private void write(SelectionKey key){
            //ServerSocketChannel ssc =  (ServerSocketChannel) key.channel();
            //ssc.register(this.seletor, SelectionKey.OP_WRITE);
        }
    
        private void read(SelectionKey key) {
            try {
                //1 清空缓冲区旧的数据
                this.readBuf.clear();
                //2 获取之前注册的socket通道对象
                SocketChannel sc = (SocketChannel) key.channel();
                //3 读取数据
                int count = sc.read(this.readBuf);
                //4 如果没有数据
                if(count == -1){
                    key.channel().close();
                    key.cancel();
                    return;
                }
                //5 有数据则进行读取 读取之前需要进行复位方法(把position 和limit进行复位)
                this.readBuf.flip();
                //6 根据缓冲区的数据长度创建相应大小的byte数组,接收缓冲区的数据
                byte[] bytes = new byte[this.readBuf.remaining()];
                //7 接收缓冲区数据
                this.readBuf.get(bytes);
                //8 打印结果
                String body = new String(bytes).trim();
                System.out.println("Server : " + body);
                
                // 9..可以写回给客户端数据 
                
            } catch (IOException e) {
                e.printStackTrace();
            }
            
        }
    
        private void accept(SelectionKey key) {
            try {
                //1 获取服务通道
                ServerSocketChannel ssc =  (ServerSocketChannel) key.channel();
                //2 执行阻塞方法
                SocketChannel sc = ssc.accept();
                //3 设置阻塞模式
                sc.configureBlocking(false);
                //4 注册到多路复用器上,并设置读取标识
                sc.register(this.seletor, SelectionKey.OP_READ);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        public static void main(String[] args) {
            
            new Thread(new Server(8765)).start();;
        }
    }
    

    Client.java

    public class Client {
    
        //需要一个Selector 
        public static void main(String[] args) {
            
            //创建连接的地址
            InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8765);
            
            //声明连接通道
            SocketChannel sc = null;
            
            //建立缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1024);
            
            try {
                //打开通道
                sc = SocketChannel.open();
                //进行连接
                sc.connect(address);
                
                while(true){
                    //定义一个字节数组,然后使用系统录入功能:
                    byte[] bytes = new byte[1024];
                    System.in.read(bytes);
                    
                    //把数据放到缓冲区中
                    buf.put(bytes);
                    //对缓冲区进行复位
                    buf.flip();
                    //写出数据
                    sc.write(buf);
                    //清空缓冲区数据
                    buf.clear();
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if(sc != null){
                    try {
                        sc.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            
        }
    }
    

    运行结果:

    客户端(控制台输入)

    hello nio
    

    服务端

    Server start, port :8765
    Server : hello nio
    

    相关文章

      网友评论

          本文标题:Socket 网络编程(一)

          本文链接:https://www.haomeiwen.com/subject/axdshftx.html