NIO TCP/IP协议 网络通信

解释:NIO → non-blocking input output(非阻塞式的输入和输出)
使用场景:一般用于高并发场景下;请求 (客户端)、响应 (服务端)两方,约定好使用TCP/IP通信;双方约定好报文格式 (报文头+报文体)及编码格式 (这里用UTF-8),报文头内容 (约定好长度比如8位,不够前面补零)里面内容为报文体长度,再根据报文头内容,获取后面的报问体的内容。
例如:报文示例: 00000007aaaaaaa;报文体内容为7个a,所以报文头长度为7不够八位前面补零。

一、 服务端

NioServerThread: nio的服务类(selector、buffer、channel)
ServerThreadOperate: 新线程用来处理客户端请求

/**
 * selector轮询线程的开启并绑定channel通道
 * @author zhb
 */
public class NioServerThread {
    
    private static int count = 0;
    // 事件轮询器
    private static Selector selector;
    // 服务通道
    private static ServerSocketChannel serverSocketChannel;
    
    // 开启一个服务线程
    public void action() throws IOException, InterruptedException{
        // 开启服务端的事件轮询器,只有这一个线程
        selector = Selector.open();
        // 开通服务端的socket通道
        serverSocketChannel = ServerSocketChannel.open();
        // socket为非阻塞
        serverSocketChannel.configureBlocking(false);
        // 绑定端口
        serverSocketChannel.socket().bind(new InetSocketAddress(Constant.serverSocketPort));    
        // channel和socket绑定,注册可以接入事件
        serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
        System.err.println("---------------------线程开启-----------------------");
        handerSelector();
    }
    
    /**
     * 处理轮询事件
     * @throws IOException 
     */
    private void handerSelector() throws IOException {
        
        while(true){
            //多长时间轮询一次
//          selector.select(1000);
            selector.select();
            //事件轮询器,查出的kernel中的所有的事件标识
            Set<SelectionKey> selectedKeys = selector.selectedKeys();   
            Iterator<SelectionKey> iterator = selectedKeys.iterator();
            
            // SelectionKey 事件标识,每个请求的客户端都有自己的一个key作为唯一标识,该key不同的事件处理
            SelectionKey key;
            while(iterator.hasNext()){
                key = iterator.next();
                // 将该事件去除掉,防止重复
                iterator.remove();      
                //检查key事件是否被处理过,如果没有被处理过
                if(key.isValid()){
                    // 处理客户端的请求接入
                    if(key.isAcceptable()){
                        handlerAccept(key);
                    // 处理可读事件   
                    }else if(key.isReadable()){ 
                        handlerRead(key);
                    // 处理可写事件   
                    }if(key.isWritable()){ 
                        handlerWrite(key); 
                    }
                }
            }
        }
    }
    private void handlerWrite(SelectionKey key) throws UnsupportedEncodingException, IOException, ClosedChannelException {
        SocketChannel accept = (SocketChannel)key.channel();
        String resStr = "ab";
        System.err.println("可以写了");
        byte[] resByte = resStr.getBytes(Constant.charset);
        
        ByteBuffer resBuffer = ByteBuffer.allocate(resByte.length);
        resBuffer.put(resByte);
        resBuffer.flip();
        accept.write(resBuffer);
        accept.register(selector, SelectionKey.OP_CONNECT);
    }   

    /**
     * 处理可读的事件 
     * (1)只处理读取数据,不管响应信息
     * (2)读取到请求信息,根据请求信息返回处理后的响应信息(常见的),
     * 
     * @param selector 事件轮询器
     * @param key  某个客户端的标识
     * @throws IOException
     * @throws UnsupportedEncodingException
     * @throws ClosedChannelException
     */
    private static void handlerRead(SelectionKey key) throws IOException, UnsupportedEncodingException, ClosedChannelException {
        // 该通道是接收该客户连接请求时,开启的通道
        SocketChannel socketChannel = (SocketChannel)key.channel();
        
        socketChannel.configureBlocking(false);
        
        // 按照客户端和服务端约定好的(报文头+报文体)的格式信息
        String reqHeadStr = getStrFromChannel(socketChannel, Constant.reqHeadLength);
        String reqBodyStr = getStrFromChannel(socketChannel, Integer.valueOf(reqHeadStr));
        System.err.println("-------------------nio服务端接收请求数据→" + reqBodyStr);
        // 这种情况可以开启一个新的线程处理
        new Thread(new ServerThreadOperate(reqBodyStr, socketChannel)).start();
        
        // 下面是简单的处理信息,可以用简单的测试信息
//      ByteBuffer buffer = ByteBuffer.allocate(1024);
//      socketChannel.read(buffer);
//      // 整理buffer中的数据,position复位
//      buffer.flip();
//      // 建立和buffer中相同长度的字节的数组
//      byte[] bytes = new byte[buffer.remaining()];
//      buffer.get(bytes);
//      buffer.clear();
//      String reqStr = new String(bytes,"utf-8");
//      // 这里简单处理请返回的信息
//      simplehandler(socketChannel, reqBodyStr);
        
        // 根据上面的处理情况,再注册这里的信息
        socketChannel.register(selector, SelectionKey.OP_CONNECT);
    }
    
    
    // 根据参数长度,从该客户的channel中获取返回字符串
    public static String getStrFromChannel(SocketChannel socketChannel, int bufferLength) throws IOException{
        
        // 先读取报文头内容
        ByteBuffer buffer = ByteBuffer.allocate(bufferLength);
        byte[] reqbyte = new byte[buffer.remaining()];
        // 先从通道中读取请求报文头长度的buffer信息
        socketChannel.read(buffer);
        // 整理buffer中的数据,position复位
        buffer.flip();
        // 把buffer中的信息放入到byte字节数组中
        buffer.get(reqbyte);
        buffer.clear();
        // 将字节数组转成字符串,返回
        return new String(reqbyte, Constant.charset);
    }

    // 不用开启新线程 ,简单模拟烦回信息
    private static void simplehandler(SocketChannel socketChannel, String reqStr) throws UnsupportedEncodingException, IOException, ClosedChannelException {
        ByteBuffer respBuffer = ByteBuffer.allocate(1024);
        byte[] respByte = ("响应信息:"+reqStr).getBytes(Constant.charset);
        // 将响应信息写入buffer中
        respBuffer.put(respByte);
        System.err.println("服务端返回了请求信息"+respBuffer);
        // 整理buffer,position复位
        respBuffer.flip();
        // 发送响应信息
        socketChannel.write(respBuffer);
    }   

    /**
     * 处理客户端的请求接入的事件
     * 
     * @param selector  事件轮询器
     * @param serverSocketChannel  注册OP_ACCEPT事件的服务端channel
     * @throws IOException
     * @throws ClosedChannelException
     */
    private static void handlerAccept(SelectionKey key) throws IOException,
            ClosedChannelException {
        System.err.println(count+"--------------------------OP_READ------------------");
        
        //返回创建此键的通道
        serverSocketChannel = (ServerSocketChannel)key.channel();
        // 建立和客户端的链接, 因为 OP_ACCEPT是注册在 serverSocketChannel上;每个客户有自己的SocketChannel通道
        SocketChannel accept = serverSocketChannel.accept();
        // 非阻塞
        accept.configureBlocking(false);
        // 开启注册该客户端的可读时间,即该客户上传完所有的请求数据到系统的kernel的buffer缓存中后,开启的的可读事件通知
        accept.register(selector, SelectionKey.OP_READ);
    }
    
    
    public static void main(String[] args) throws IOException, InterruptedException {
        // nio线程的开启
        NioServerThread thread  = new NioServerThread();
        thread.action();
    }
    
}


/**
 * 开启一个新线程,用来处理客户端的请求 ;也可以用简单模式不用这个类
 * @author zhb
 */
public class ServerThreadOperate implements Runnable {
    
    // 客户端请求的信息
    private String reqStr;
    private int count;
    
    // 和某个客户端的通道
    private SocketChannel socketChannel;

    public ServerThreadOperate(String reqStr) {
        this.reqStr = reqStr;
    }

    public ServerThreadOperate(String reqStr, SocketChannel socketChannel) {
        this.reqStr = reqStr;
        this.socketChannel = socketChannel;
    }

    // 线程主体
    public void run() {
        
        ByteBuffer respBuffer = ByteBuffer.allocate(1024);
        byte[] respBodyByte = null;
        try {
            respBodyByte = ("响应信息为:"+reqStr).getBytes(Constant.charset);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
            
        //转成约定好的报文头长度,不足的前面补零
        StringBuilder format = new StringBuilder("%0").append(Constant.respHeadLength).append("d");
        byte[] respHeadByte = String.format(format.toString(), respBodyByte.length).getBytes();
        
        respBuffer.put(respHeadByte);
        respBuffer.put(respBodyByte);
        System.err.println("服务端返回了请求信息:"+respBuffer);
        // buffer内的信息要复位整理
        respBuffer.flip();
        try {
            // 烦回要烦回的信息
            socketChannel.write(respBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }   
    }

}

/**
 * 系统常量
 * 
 * @author zhb
 *
 */
public class Constant {
    
    // 请求报文头的位数
    public static final int reqHeadLength = 8;
    // 返回响应报文头的长度
    public static final int respHeadLength = 8;
    
    
    // 字节数组和字符创之间的转换时的编码 
    public static final String charset = "UTF-8";
    
    //接收请求的最长时间 单位毫秒
    public static final int reqTimeOut = 5000;
    
    //接收请求的服务端口
    public static final int serverSocketPort = 8080;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 1.这篇文章不是本人原创的,只是个人为了对这部分知识做一个整理和系统的输出而编辑成的,在此郑重地向本文所引用文章的...
    SOMCENT阅读 13,037评论 6 174
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,865评论 6 13
  • 个人认为,Goodboy1881先生的TCP /IP 协议详解学习博客系列博客是一部非常精彩的学习笔记,这虽然只是...
    贰零壹柒_fc10阅读 5,051评论 0 8
  • 前言 开始这篇文章之前,我非常的紧张,因为要写好这个TCP协议说实话并不简单。作为TCP/IP协议簇最为核心的部分...
    Noskthing阅读 2,268评论 1 17
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139