Socket之bio和nio

在此之前先谈论一下网络io.当一个客户端和服务端之间相互通信,交互我们称之为网络io(网络通讯).网络通讯基本都是通过socket来通讯的。客户端和服务端这样建立连接:第一步客户端发起建立连接的请求,第二部服务端收到请求建立连接的请求,并同意和该客户端建立连接,并响应给客户端,第三步客户端收到服务端响应的建立连接的消息,并确认和服务端建立连接,通过这样三部客户端和服务端就真正的建立了连接,服务端和客户端就可以开始通讯,交互了.通过这样三次的握手交互服务端和客户端就成功的建立了连接..

如图所示:

image

辣么在我们传统的bio里面服务端是怎么处理客户端的请求的呢,首先我们的服务端需要实例化一个socket给我们的该实例socket 绑定ip(本机)和端口,然后进行监听,然后就会一直堵塞等待和客户端stoket建立连接,此时客户端也需要实例化一个socket来和我们服务端建立请求,具体操作和服务端一样,绑定ip和端口(服务端监控的ip和port),然后通过三次握手确认和服务端建立连接通讯.成功建立连接之后服务端需要新建一个线程去处理该客户端的通讯请求(io操作),然后主线程会一直堵塞等到下一个客户端socket发起连接请求,在处理下一个,这就是最原始的堵塞式的bio,这种方式的最明显的缺点就是,服务端与每个客户端socket建立连接都需要实时的去创建一个线程去处理该客户端操作,加入同时又1000个客户端socket同时求情建立连接,那我们的服务端就需要同时创建1000个线程去处理,完全没有一点点的缓冲也不能拒绝,显然我们的服务器会吃不消,如果客户端增加socket增加到2000个达到了我们的系统所承受的上线,那我们的服务端就会直接蹦掉.但是jdk1.5以后出现了线程池和队列,能稍稍减轻一点点服务器的负担,吧客户端请求直接扔给我们的线程池去处理,再对我们的线程池进行配置最大线程处理数量和队列处理请求排队,等方式去解决服务器的并发过大的情况。

如图所示:

java代码实现

首先我们需要一个处理请求的类,就是通过输入输出流去读取和反馈客户端的这样的一个类,这个类实现Runnable接口


package liuzw.bio;  
  
import java.io.BufferedReader;  
import java.io.IOException;  
import java.io.InputStreamReader;  
import java.io.PrintWriter;  
import java.net.Socket;  
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;  
        }  
    }  
}  

然后就是用于接受客户端请求的入口

package liuzw.bio;  
  
import java.io.IOException;  
import java.net.ServerSocket;  
import java.net.Socket;  
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;  
        }  
    }  
}  

然后就是用于接受客户端请求的入口

package liuzw.bio;  
  
import java.io.IOException;  
import java.net.ServerSocket;  
import java.net.Socket;  
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;  
        }  
    }  
}  

接下来就是我那的客户端了

package liuzw.bio;  
  
import java.io.BufferedReader;  
import java.io.IOException;  
import java.io.InputStreamReader;  
import java.io.PrintWriter;  
import java.net.Socket;  
  
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;  
        }  
    }  
}  

上面说了bio是一种堵塞式的同步的网络io,接下来的讲到了的nio是一种非堵塞式的网络io但是最开始的时候也是同步的.我们前面介绍了bio是客户端和服务端各自创建一个socket实例建立连接相互通信,并且通信是通过单向io客户端发送数据的时候客户端就必须同步去接收收据,同样的服务端向客户端相应数据的时候也必须同保持连接,如果说客户端和服务端的网速非常慢,这样就会导致客户端和服务端的连接长时间不能关闭,从而浪费了很多资源。因为是直连和单向io所以每请次求和响应客户端和服务端都需要创建一个输出流和输入流,收网速的影响流长时间不能关闭那我们nio的改进方式是建立一个缓冲区buffer,这个是nio里面特有的,有了这个缓冲区我们客户端和服务端的输出流和输入流就不用直连了,之前是输出流和输入流都是单向的流(单向io),但是nio里面的buffer可以当做双向流的既可以写数据有可以读数据.同时nio和引入了管道的概念channel,和选择器也叫多路复用器Selector.相比传统的bio的建立连接的方式,nio的练级方式是在原有socket的基础上进行了封装和加强,通过管道注册的方式去建立通讯,首先我们的服务端的socketChanenl(我们称之为通讯管道)注册到我们的多路复用器上,然后客户端的通讯管道也注册到我们的多路复用器上,区别是服务端的管道状态是堵塞状态,而客户端的管道是可读状态,在这里补充一下管道的状态有四种,分别是连接状态(Connect),堵塞状态(Accecp),可读状态(Read),可写状态(Write),连接状态就是管道刚刚连接,堵塞就是一直堵塞(多半用于服务端管道),可读状态就是该管道可以读取数据,可写状态是该管道可以写入数据.那刚刚说到服务端管道一直堵塞在这里,然后服务端会起一个线程去轮询注册器也就是多路复用器上已经注册并且处于连接状态的客户端socketChannel,并和他简历管道通讯(注意这里不是socket直连)而是通过管道和缓冲区做通讯交互.在根据通道的状态变化去执行相应的操作,顺带说明一下管道注册其实并不是吧管道本身注册在多路复用器上,而是通过selectedKey去注册的,可以理解为selectedKeys是唯一识别指定管道的标识列,同样Selector咋轮询获取的也不是管道本身,而是获取的一组管道的key,然后建立通讯的时候通过key获取该管道,再在次深挖一下每个socketChinnel底层必然对应一个socket实例,获取该管道本身以后然后就开始根据管道的状态执行对应的操作,这样就达到了通讯。讲完了nio这里就简单的对应一下之前的bio有哪些优势和好处,最明显的就是传统的bio, 是一个服务端socket和一个客户端socket建立直连,并且服务端需要为每个客户端新起一个线程去处理客户端的通讯交互,这样必然会无故的开销服务端的很多资源.而我们的nio只需要一个选择器和一个轮询线程就能接入成千上万甚至更多的客户端连接,这点是nio相比bio最大的进步和改变.其次就是建立连接的方式,传统的bio是通过客户端服务端三次握手的方式建立tcp连接,而nio是客户端直接把通道注册到服务端的多路复用器上,然后服务端去轮询,这就减少了三次握手请求响应的开销。再次之就是缓冲区代码直连流,传统的bio请求和响应数据读是通过一端创建输出流直接向另一端输出,而另一点穿件输入流写入数据,这样就很依赖网络,如果网络不好就会导致流长时间不能关闭,从而导致资源无故浪费,增加开销.而nio引入了缓冲区都数据写数据都是直接向缓冲区读写,这样就不依赖网络,一端吧数据写完到缓冲区就可以关闭写入流,这时候只需要通知另一端去读。另一端开启读取流快速的读取缓冲区的数据,然后就可以快速的关闭.如果网络不好情况向就不会开销另一端的资源。

如图所示:


nio代码实现

首先我们的nio主要就通过多路复用器来对客户的管道请求进行注册然后服务端进行轮询

package liuzw.nio;  
  
import java.io.IOException;  
import java.net.InetSocketAddress;  
import java.nio.ByteBuffer;  
import java.nio.channels.SelectionKey;  
import java.nio.channels.Selector;  
import java.nio.channels.ServerSocketChannel;  
import java.nio.channels.SocketChannel;  
import java.util.Iterator;  
  
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();;  
    }  
}  

客户端代码:

package bhz.nio;  
  
import java.io.IOException;  
import java.net.InetSocketAddress;  
import java.nio.ByteBuffer;  
import java.nio.channels.SocketChannel;  
  
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();  
                }  
            }  
        }  
    }  
}  

原文:https://blog.csdn.net/u013239236/article/details/52551322

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354