NIO Socket實現聊天

package com.lc.test.test04;

import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

/**
 * @author liuchao
 * @date 2020/6/26
 */
public class ChatThread extends Thread {

    private Selector selector;
    private SocketChannel socket;

    public ChatThread(Selector selector, SocketChannel socket) {
        super();
        this.selector = selector;
        this.socket = socket;
    }


    @Override
    public void run() {
        try {
            //等待連接建立
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入您要發送給服務端的消息");
        System.out.println("=========================================================");
        while (scanner.hasNextLine()) {
            String s = scanner.nextLine();
            try {
                //用戶已輸入,註冊寫事件,將輸入的消息發送給客戶端
                socket.register(selector, SelectionKey.OP_WRITE, ByteBuffer.wrap(s.getBytes()));
                //喚醒之前因爲監聽OP_READ而阻塞的select()
                selector.wakeup();
            } catch (ClosedChannelException e) {
                e.printStackTrace();
            }
        }
    }
}
package com.lc.test.test04;

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.Calendar;
import java.util.Iterator;

/**
 * @author liuchao
 * @date 2020/6/26
 */
public class ChatServer {
    public static void main(String[] args) {
        try {
            //服務初始化
            ServerSocketChannel serverSocket = ServerSocketChannel.open();
            //設置爲非阻塞
            serverSocket.configureBlocking(false);
            //綁定端口
            serverSocket.bind(new InetSocketAddress("localhost", 9999));
            //註冊OP_ACCEPT事件(即監聽該事件,如果有客戶端發來連接請求,則該鍵在select()後被選中)
            Selector selector = Selector.open();
            serverSocket.register(selector, SelectionKey.OP_ACCEPT);
            Calendar ca = Calendar.getInstance();
            System.out.println("服務端開啓了");
            System.out.println("=========================================================");
            //輪詢服務
            while (true) {
                //選擇準備好的事件
                selector.select();
                //已選擇的鍵集
                Iterator<SelectionKey> it = selector.selectedKeys().iterator();
                //處理已選擇鍵集事件
                while (it.hasNext()) {
                    SelectionKey key = it.next();
                    //處理掉後將鍵移除,避免重複消費(因爲下次選擇後,還在已選擇鍵集中)
                    it.remove();
                    //處理連接請求
                    if (key.isAcceptable()) {
                        //處理請求
                        SocketChannel socket = serverSocket.accept();
                        socket.configureBlocking(false);
                        //註冊read,監聽客戶端發送的消息
                        socket.register(selector, SelectionKey.OP_READ);
                        //keys爲所有鍵,除掉serverSocket註冊的鍵就是已連接socketChannel的數量
                        String message = "連接成功 你是第" + (selector.keys().size() - 1) + "個用戶";
                        //向客戶端發送消息
                        socket.write(ByteBuffer.wrap(message.getBytes()));
                        InetSocketAddress address = (InetSocketAddress) socket.getRemoteAddress();
                        //輸出客戶端地址
                        System.out.println(ca.getTime() + "\t" + address.getHostString() +
                                ":" + address.getPort() + "\t");
                        System.out.println("客戶端已連接");
                        System.out.println("=========================================================");
                    }

                    if (key.isReadable()) {
                        SocketChannel socket = (SocketChannel) key.channel();
                        InetSocketAddress address = (InetSocketAddress) socket.getRemoteAddress();
                        System.out.println(ca.getTime() + "\t" + address.getHostString() +
                                ":" + address.getPort() + "\t");
                        ByteBuffer bf = ByteBuffer.allocate(1024 * 4);
                        int len = 0;
                        byte[] res = new byte[1024 * 4];
                        //捕獲異常,因爲在客戶端關閉後會發送FIN報文,會觸發read事件,但連接已關閉,此時read()會產生異常
                        try {
                            while ((len = socket.read(bf)) > 0) {
                                bf.flip();
                                bf.get(res, 0, len);
                                System.out.println(new String(res, 0, len));
                                bf.clear();
                            }
                            System.out.println("=========================================================");
                        } catch (IOException e) {
                            //客戶端關閉了
                            key.cancel();
                            socket.close();
                            System.out.println("客戶端已斷開");
                            System.out.println("=========================================================");
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("服務器異常,即將關閉..........");
            System.out.println("=========================================================");
        }
    }
}
package com.lc.test.test04;

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.SocketChannel;
import java.util.Calendar;
import java.util.Iterator;
import java.util.Set;

/**
 * @author liuchao
 * @date 2020/6/26
 */
public class ChatClient {

    public static void main(String[] args) throws Exception {
        Selector selector = Selector.open();
        SocketChannel socket = SocketChannel.open(new InetSocketAddress(9999));
        socket.configureBlocking(Boolean.FALSE);
        socket.register(selector, SelectionKey.OP_CONNECT);
        //開啓控制檯輸入監聽
        new ChatThread(selector, socket).start();
        Calendar ca = Calendar.getInstance();
        //輪詢處理
        while (true) {
            if (socket.isOpen()) {
                //在註冊的鍵中選擇已準備就緒的事件
                selector.select();
                //已選擇鍵集
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = keys.iterator();
                //處理準備就緒的事件
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    //刪除當前鍵,避免重複消費
                    iterator.remove();
                    //連接
                    if (key.isConnectable()) {
                        //在非阻塞模式下connect也是非阻塞的,所以要確保連接已經建立完成
                        while (!socket.finishConnect()) {
                            System.out.println("連接中");
                        }
                        socket.register(selector, SelectionKey.OP_READ);
                    }
                    //控制檯監聽到有輸入,註冊OP_WRITE,然後將消息附在attachment中
                    if (key.isWritable()) {
                        //發送消息給服務端
                        socket.write((ByteBuffer) key.attachment());
                            /*
	                            已處理完此次輸入,但OP_WRITE只要當前通道輸出方向沒有被佔用
	                            就會準備就緒,select()不會阻塞(但我們需要控制檯觸發,在沒有輸入時
	                            select()需要阻塞),因此改爲監聽OP_READ事件,該事件只有在socket
	                            有輸入時select()纔會返回。
                            */
                        socket.register(selector, SelectionKey.OP_READ);
                        System.out.println("==============" + ca.getTime() + " ==============");
                    }
                    //處理輸入事件
                    if (key.isReadable()) {

                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 4);
                        int len;
                        //捕獲異常,因爲在服務端關閉後會發送FIN報文,會觸發read事件,但連接已關閉,此時read()會產生異常
                        try {

                            if ((len = socket.read(byteBuffer)) > 0) {
                                System.out.println("接收到來自服務器的消息\t");
                                System.out.println(new String(byteBuffer.array(), 0, len));
                            }
                        } catch (IOException e) {
                            System.out.println("服務器異常,請聯繫客服人員!正在關閉客戶端.........");
                            key.cancel();
                            socket.close();
                        }
                        System.out.println("=========================================================");
                    }
                }
            } else {
                break;
            }
        }
    }
}

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章