import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;

/**
 * 非阻塞的Echoserver
 *  在这个非阻塞的模式下,Echoserver只用启动一个主线程就能同事处理其他三件事:
 * 1.接收客户连接
 * 2.接收客户发送的数据
 * 3.向客户发回响应数据
 *
 * @author Administrator
 *
 */
public class NZSEchoServer {
    private Selector selector = null;
    private ServerSocketChannel serverSocketChannel = null;
    private int port = 8000;

    private Charset charset = Charset.forName("GBK");

    /**
     * 构造方法负责启动服务器,绑定端口
     *
     * @throws IOException
     */
    private NZSEchoServer() throws IOException {
        // 创建一个selector 对象
        selector = Selector.open();
        // 创建serverSocketChannel对象
        serverSocketChannel = ServerSocketChannel.open();
        // 设置参数 使得在同一个主机上关闭了服务器程序,紧接着再启动该服务器程序时可以顺利绑定到相同的端口
        serverSocketChannel.socket().setReuseAddress(true);
        // 使erverSocketChannel工作处于非阻塞模式
        serverSocketChannel.configureBlocking(false);
        // 把服务器进程与一个本地端口绑定
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        System.out.println("服务器启动!");
    }

    /**
     * 负责开头说的三件事 * 1.接收客户连接 2.接收客户发送的数据 3.向客户发回响应数据
     *
     * @throws IOException
     */
    public void service() throws IOException {
        // serverSocketChannel向Selector注册接收连接就绪事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (selector.select() > 0) {
            // 获得Selector的selected-keys集合
            Set readyKeys = selector.selectedKeys();
            Iterator it = readyKeys.iterator();

            //从集合中依次取出SelectionKey对象,判断是那种事件发生,然后进行处理
            while (it.hasNext()) {
                SelectionKey key = null;
                try {
                    // 处理selectionkey 取出第一个selectionkey
                    key = (SelectionKey) it.next();
                    // 把selectionKey从selected-key集合中删除
                    it.remove();

                    // 这个key 标识连接就绪事件 处理
                    if (key.isAcceptable()) {
                        //获得与SelectionKey相连的ServerSocketChannel
                        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                        //获得与客户端连接的SocketChannel
                        SocketChannel sockChannel = ssc.accept();
                        System.out.println("接收到客户端连接,来自:" + sockChannel.socket().getInetAddress() + ":"
                                + sockChannel.socket().getPort());

                        //设置SocketChannel为非阻塞模式
                        sockChannel.configureBlocking(false);
                        //创建一个用于存放用户发送来的数据的缓冲区
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        //SocketChannel向Selector注册读就绪事件和写就绪事件   关联了一个buffer
                        //这个byteBuffer将作为附件与新建的selectionKey对象关联
                        sockChannel.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE,buffer);

                    }
                    // 这个key 标识读就绪事件 处理
                    if (key.isReadable()) {
                        receive(key);
                    }
                    // 这个key 标识写就绪事件 处理
                    if (key.isWritable()) {
                        send(key);
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                    try {
                    //使得这个selctionkey失效
                    //使得selectory不再监控这个selectionkey感兴趣的事件
                        if (key != null) {
                            key.cancel();
                            key.channel().close();
                        }
                    } catch (Exception e2) {
                        e.printStackTrace();
                    }

                }

            }

        }

    }
    /**
     * 处理写就绪事件
     * @param key
     * @throws IOException
     */
    private void send(SelectionKey key) throws IOException {
        //获取与selectionKey相关联的附件
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        SocketChannel socketChannel = (SocketChannel) key.channel();
//        flip的作用有两个:
//        1. 把limit设置为当前的position值
//        2. 把position设置为0
//        然后处理的数据就是从position到limit直接的数据,也就是你刚刚读取过来的数据
        buffer.flip();
        //按照gbk编码,把buffer中的字节转换成字符串
        String data = decode(buffer);
        //如果还没有读到一行数据,之间返回
        if (data.indexOf("\r\n") == -1)
            return;

        //已经有一行以上数据,截取一行数据
        String outputData = data.substring(0, data.indexOf("\n") + 1);
        System.out.println(outputData);
        //把输出的字符串按照gbk编码,转换成字节,把他反正outputBuffer中
        ByteBuffer outputBuffer = encode("echo" + outputData);

        //outputBuffer.hasRemaining()判断是否还有未处理的字节
        //非阻塞模式下不确保write方法一次就把outputBuffer所有字节发送完,只是奉行能发多少就发送多少的原则,所以我们要采用循环
        while (outputBuffer.hasRemaining()) {
            socketChannel.write(outputBuffer);
        }

            //我觉得  相当于移动 栈指针 然后删除没有指向的数据段
            ByteBuffer temp = encode(outputData);
            //设置buffer的位置为temp的极限
            buffer.position(temp.limit());
            //删除buffer中已经处理的数据
            buffer.compact();
            //如果已经输出了字符串  "bye\r\n" ,就使selectionKet失效,并关闭SocketChannel
            if (outputData.equals("bye\r\n")) {
                key.cancel();
                socketChannel.close();
                System.out.println("关闭与客户端的连接!");
            }

    }

    /**
     *
     * 处理读就绪事件
     * 把收到的数据放入buffer
     *
     * @param key
     * @throws IOException
     */
    public void receive(SelectionKey key) throws IOException {
        //获得与SelectionKey关联的附件
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        //获得与SelectionKey关联的Sockethannel
        SocketChannel socketChannel = (SocketChannel) key.channel();
        //创建一个byteBuffer,用于存放读到的数据
        ByteBuffer readBuffer = ByteBuffer.allocate(32);
        socketChannel.read(readBuffer);
        readBuffer.flip();

        //把buffer的极限设为容量
        buffer.limit(buffer.capacity());
        //把readBuffer中的内容拷贝到buffer中
        //假定buffer的容量足够大,不会出现缓冲区溢出异常
        buffer.put(readBuffer); // 把读到的数据放到buffer中
    }

    /**
     * 编码  把字符串转换成自己序列
     *
     * @param string
     * @return
     */
    private ByteBuffer encode(String string) {
        return charset.encode(string);
    }

    /**
     * 解码  把字节序列转换为字符串
     *
     * @param buffer
     * @return
     */
    private String decode(ByteBuffer buffer) {
        CharBuffer charBuffer = charset.decode(buffer);
        return charBuffer.toString();
    }
    /**
     * 主程序
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        NZSEchoServer server=new NZSEchoServer();
        server.service();
    }
}

非阻塞模式ServerSocketChannel 聊天室服务器端的更多相关文章

  1. Python编写基于socket的非阻塞多人聊天室程序(单线程&多线程)

    前置知识:socket非阻塞函数(socket.setblocking(False)),简单多线程编程 代码示例: 1. 单线程非阻塞版本: 服务端: #!/usr/bin/env python # ...

  2. NIO Socket非阻塞模式

    NIO主要原理和适用 NIO 有一个主要的类Selector,这个类似一个观察者,只要我们把需要探知的socketchannel告诉Selector,我们接着做别的事情,当有 事件发生时,他会通知我们 ...

  3. 深入 CSocket 编程之阻塞和非阻塞模式

    有时,花上几个小时阅读.调试.跟踪优秀的源码程序,能够更快地掌握某些技术关键点和精髓.当然,前提是对这些技术大致上有一个了解. 我通过几个采用 CSocket 类编写并基于 Client/Server ...

  4. JavaNIO非阻塞模式

    package com.java.NIO; import java.io.IOException; import java.net.InetSocketAddress; import java.nio ...

  5. 非阻塞模式(ioctlsocket)

    //Server.cpp #include <stdio.h> #include <winsock2.h> //winsock.h (2种套接字版本) #pragma comm ...

  6. 使用命名管道的OVERLAPPED方式实现非阻塞模式编程 .

    命令管道是进程间通讯的一种常用方式,对于命令管道的介绍可以参考别的资料和书籍,这里推荐一个<VC++下命名管道编程的原理及实现>这篇博文,写得比较清楚.但是都是介绍了阻塞模式的编程,我这里 ...

  7. PHP非阻塞模式 (转自 尘缘)

    让PHP不再阻塞当PHP作为后端处理需要完成一些长时间处理,为了快速响应页面请求,不作结果返回判断的情况下,可以有如下措施: 一.若你使用的是FastCGI模式,使用fastcgi_finish_re ...

  8. TCP同步与异步及阻塞模式,多线程+阻塞模式,非阻塞模式简单介绍

    首先我简单介绍一下同步TCP编程 与异步TCP编程. 在服务端我们通常用一个TcpListener来监听一个IP和端口.客户端来一个请求的连接,在服务端可以用同步的方式来接收,也可以用异步的方式去接收 ...

  9. UDP socket 设置为的非阻塞模式

    UDP socket 设置为的非阻塞模式 Len = recvfrom(SocketFD, szRecvBuf, sizeof(szRecvBuf), MSG_DONTWAIT, (struct so ...

随机推荐

  1. docker问题:docker端口映射错误

    1 docker端口映射错误 1.1 问题描述 利用docker启动nginx容器的时候报错: 1.2 解决办法 一次执行下面的命令就可以解决 pkill docker iptables -t nat ...

  2. 496. Next Greater Element I 另一个数组中对应的更大元素

    [抄题]: You are given two arrays (without duplicates) nums1 and nums2 where nums1’s elements are subse ...

  3. 《the art of software testing》第四章 测试用例的设计

    白盒测试 逻辑覆盖测试: 逻辑覆盖是以程序内部的逻辑结构为基础的设计测试用例的技术.它属白盒测试.白盒测试的测试方法有代码检查法.静态结构分析法.静态质量度量法.逻辑覆盖法.基本路径测试法.域测试.符 ...

  4. .net core MVC 通过 Filters 过滤器拦截请求及响应内容

    前提: 需要nuget   Microsoft.Extensions.Logging.Log4Net.AspNetCore   2.2.6: Swashbuckle.AspNetCore 我暂时用的是 ...

  5. XML在C#与Unity3D中的实战运用

    一.xml文件是什么? XML是指可扩展标记语言,英文:Extensible Markup Language的缩写. 二.xml文件有什么用? 用来传输和存储数据,比如网络通信,本地配置文件等. 三. ...

  6. IIS将http强转为https(重定向和重写)

    最近接到一个需求,客户希望无论是http还是https请求都可以访问,并且http能转换成https.研究了一圈发现iis的重定向和重写都可以实现http强转https,记录一下. 用到的东东: In ...

  7. 协程《二》greenlet模块

    一 greenlet模块 如果我们在单个线程内有20个任务,要想实现在多个任务之间切换,使用yield生成器的方式过于麻烦(需要先得到初始化一次的生成器,然后再调用send...非常麻烦),而使用gr ...

  8. (转)使用vs调试的时候,如何知道程序阻塞在哪里?

    遇到一个问题,加了两个断点当运行到断点A后,我释放掉了,理想状态应该是在断点B停住,但并没有,程序感觉就像是阻塞了一样请问,这种状况如何知道程序当前是在哪里阻塞着? 回复: 可以让调试器停住,然后在调 ...

  9. linux下vim python代码自动补全

    一.vim python自动补全插件:pydiction 可以实现下面python代码的自动补全: 1.简单python关键词补全 2.python 函数补全带括号 3.python 模块补全 4.p ...

  10. python之路径拼接urljoin

    方法一:使用+进行路径拼接 url='http://ip/ path='api/user/login' url+path拼接后的路径为'http://ip//api/user/login' 方法二:使 ...