使用java NIO编写简易的服务器

学了两天NIO,很多地方还不是很透彻,所以以下代码仅做参考…

selector选择器单线程管理所有通道,用新线程接收客户端数据,再用新线程响应客户端数据

可直接运行,通过浏览器直接就可以访问,打印原生socket,更好学习http协议,并发访问没测,有兴趣的可以自己写客户端测试

package com.ideal.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class NioServerTest{
    public static Selector selector = null;
    public static void main(String[] args) throws IOException {
        selector = Selector.open();                                                  //创建selector
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();        //创建serversocketchannel监听8080端口
        serverSocketChannel.configureBlocking(false);                                //设置成非阻塞!
        serverSocketChannel.bind(new InetSocketAddress(8080));                 //监听8080端口
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);             //注册事件为可接受事件
        System.out.println("服务器成功启动,并且监听8080端口,等待请求...");
        //循环selector关注的事件
        while(true){
            int selectNum = selector.selectNow();                                    //selectNow是非阻塞的,所以socket.write()后不需要唤起selector,区别于select()是阻塞的
            if(selectNum>0){
                Set<SelectionKey> selectionKeys = selector.selectedKeys();           //已选择键集
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()){
                    SelectionKey key = iterator.next();
                    iterator.remove();                                               //从当前已选择键集中移除
                    if(key.isAcceptable()){                                          //可接受事件(客户端访问服务器的时候触发)
                        handleAccept(key);
                    }else if(key.isReadable()){                                      //读事件(服务器开始接收客户端的套接字通道数据)
                        handleRequest(key);
                    }else if(key.isWritable()){                                      //写事件(服务器响应客户端)
                        handleResponse(key);
                    }else{
                        System.out.println("未关注事件......");
                    }
                }
            }
        }
    }



    private static void handleAccept(SelectionKey key){                                 //这里就不单独拿个线程出来了,也可以做,只要锁住同一个socketchannel就好了
        try{
            ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
            SocketChannel sc = ssc.accept();
            sc.configureBlocking(false);                                                //把获取到的socketchannel设置成非阻塞的
            sc.register(selector,SelectionKey.OP_READ);                                //把socketchannel通道注册到selector中,并且是读事件
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private static void handleRequest(SelectionKey key) {
        key.cancel();                                       //这一步很关键,这行表示取消channel和selector之间的关联,直接从选择键中移除这个通道,之前的迭代器remove表示从有效键中移除,注意区别
        ExecutorService executor = getThread();
        executor.submit(()->{
            //Selector selector = key.selector();
            SocketChannel channel = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            try {
                channel.read(buffer);
                System.out.println("客户端访问信息:"+System.getProperty("line.separator")+new String(buffer.array()));
                /*
                * 这里进行业务操作.......
                */
                ByteBuffer writeBuffer = ByteBuffer.allocate(2048*10);
                writeBuffer.put(("HTTP/1.1 200 OK"+System.getProperty("line.separator")).getBytes());
                writeBuffer.put(("Server:NioServerTest/1.0"+System.getProperty("line.separator")).getBytes());
                writeBuffer.put(("Content-Type:text/html;charset=UTF-8"+System.getProperty("line.separator")).getBytes());
                writeBuffer.put(("Date:"+ new Date().toLocaleString()+System.getProperty("line.separator")+System.getProperty("line.separator")).getBytes());
                writeBuffer.put(("我是HTML响应内容").getBytes());
                channel.register(selector,SelectionKey.OP_WRITE,writeBuffer);       //由于前面已经把当前通道和selector解除关联,所以这里重新进行关联,并且关注写事件
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }

    private static void handleResponse(SelectionKey key) {
        key.cancel();                                                               //一定要取消当前的key,否则主线程会不断进入该子线程,直到最后socketchannel.close()
        ExecutorService executor = getThread();
        executor.submit(()->{
            SocketChannel channel = (SocketChannel) key.channel();
            ByteBuffer buffer = (ByteBuffer) key.attachment();
            buffer.flip();
            try {
                while(buffer.hasRemaining()){
                    channel.write(buffer);
                }
                System.out.println("成功响应客户端.......");
                channel.close();                                                   //关闭通道,成功完成一次请求响应
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }

    private static ExecutorService getThread(){
        ThreadPoolExecutor executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(200);    //线程池暂定200
        return executor;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值