欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java NIO實戰(zhàn)之聊天室功能詳解

 更新時間:2019年11月07日 10:46:32   作者:my_codeONE  
這篇文章主要介紹了Java NIO實戰(zhàn)之聊天室功能,結合實例形式詳細分析了java NIO聊天室具體的服務端、客戶端相關實現方法與操作注意事項,需要的朋友可以參考下

本文實例講述了Java NIO實戰(zhàn)之聊天室功能。分享給大家供大家參考,具體如下:

在工作之余花了兩個星期看完了《Java NIO》,總體來說這本書把NIO寫的很詳細,沒有過多的廢話,講的都是重點,只是翻譯的中文版看的確實吃力,英文水平太低也沒辦法,總算也堅持看完了?!禞ava NIO》這本書的重點在于第四章講解的“選擇器”,要理解透還是要反復琢磨推敲;愚鈍的我花了大概3天的時間才將NIO的選擇器機制理解透并能較熟練的運用,于是便寫了這個聊天室程序。

下面直接上代碼,jdk1.5以上經過測試,可以支持多人同時在線聊天;

將以下代碼復制到項目中便可運行,源碼下載地址:聊天室源碼。

一、服務器端

package com.chat.server;
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.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Vector;
/**
 * 聊天室:服務端
 * @author zing
 * 
 */
public class ChatServer implements Runnable {
    //選擇器
    private Selector selector;
    //注冊ServerSocketChannel后的選擇鍵
    private SelectionKey serverKey;
    //標識是否運行
    private boolean isRun;
    //當前聊天室中的用戶名稱列表
    private Vector<String> unames;
    //時間格式化器
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /**
     * 構造函數
     * @param port 服務端監(jiān)控的端口號
     */
    public ChatServer(int port) {
        isRun = true;
        unames = new Vector<String>();
        init(port);
    }
    /**
     * 初始化選擇器和服務器套接字
     * 
     * @param port 服務端監(jiān)控的端口號
     */
    private void init(int port) {
        try {
            //獲得選擇器實例
            selector = Selector.open();
            //獲得服務器套接字實例
            ServerSocketChannel serverChannel = ServerSocketChannel.open();
            //綁定端口號
            serverChannel.socket().bind(new InetSocketAddress(port));
            //設置為非阻塞
            serverChannel.configureBlocking(false);
            //將ServerSocketChannel注冊到選擇器,指定其行為為"等待接受連接"
            serverKey = serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            printInfo("server starting...");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void run() {
        try {
            //輪詢選擇器選擇鍵
            while (isRun) {
                //選擇一組已準備進行IO操作的通道的key,等于1時表示有這樣的key
                int n = selector.select();
                if (n > 0) {
                    //從選擇器上獲取已選擇的key的集合并進行迭代
                    Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                    while (iter.hasNext()) {
                        SelectionKey key = iter.next();
                        //若此key的通道是等待接受新的套接字連接
                        if (key.isAcceptable()) {
                            //記住一定要remove這個key,否則之后的新連接將被阻塞無法連接服務器
                            iter.remove();
                            //獲取key對應的通道
                            ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                            //接受新的連接返回和客戶端對等的套接字通道
                            SocketChannel channel = serverChannel.accept();
                            if (channel == null) {
                                continue;
                            }
                            //設置為非阻塞
                            channel.configureBlocking(false);
                            //將這個套接字通道注冊到選擇器,指定其行為為"讀"
                            channel.register(selector, SelectionKey.OP_READ);
                        }
                        //若此key的通道的行為是"讀"
                        if (key.isReadable()) {
                            readMsg(key);
                        }
                        //若次key的通道的行為是"寫"
                        if (key.isWritable()) {
                            writeMsg(key);
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 從key對應的套接字通道上讀數據
     * @param key 選擇鍵
     * @throws IOException
     */
    private void readMsg(SelectionKey key) throws IOException {
        //獲取此key對應的套接字通道
        SocketChannel channel = (SocketChannel) key.channel();
        //創(chuàng)建一個大小為1024k的緩存區(qū)
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        StringBuffer sb = new StringBuffer();
        //將通道的數據讀到緩存區(qū)
        int count = channel.read(buffer);
        if (count > 0) {
            //翻轉緩存區(qū)(將緩存區(qū)由寫進數據模式變成讀出數據模式)
            buffer.flip();
            //將緩存區(qū)的數據轉成String
            sb.append(new String(buffer.array(), 0, count));
        }
        String str = sb.toString();
        //若消息中有"open_",表示客戶端準備進入聊天界面
        //客戶端傳過來的數據格式是"open_zing",表示名稱為zing的用戶請求打開聊天窗體
        //用戶名稱列表有更新,則應將用戶名稱數據寫給每一個已連接的客戶端
        if (str.indexOf("open_") != -1) {//客戶端連接服務器
            String name = str.substring(5);
            printInfo(name + " online");
            unames.add(name);
            //獲取選擇器已選擇的key并迭代
            Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
            while (iter.hasNext()) {
                SelectionKey selKey = iter.next();
                //若不是服務器套接字通道的key,則將數據設置到此key中
                //并更新此key感興趣的動作
                if (selKey != serverKey) {
                    selKey.attach(unames);
                    selKey.interestOps(selKey.interestOps() | SelectionKey.OP_WRITE);
                }
            }
        } else if (str.indexOf("exit_") != -1) {// 客戶端發(fā)送退出命令
            String uname = str.substring(5);
            //刪除此用戶名稱
            unames.remove(uname);
            //將"close"字符串附加到key
            key.attach("close");
            //更新此key感興趣的動作
            key.interestOps(SelectionKey.OP_WRITE);
            //獲取選擇器上的已選擇的key并迭代
            //將更新后的名稱列表數據附加到每個套接字通道key上,并重設key感興趣的操作
            Iterator<SelectionKey> iter = key.selector().selectedKeys().iterator();
            while (iter.hasNext()) {
                SelectionKey selKey = iter.next();
                if (selKey != serverKey && selKey != key) {
                    selKey.attach(unames);
                    selKey.interestOps(selKey.interestOps() | SelectionKey.OP_WRITE);
                }
            }
            printInfo(uname + " offline");
        } else {// 讀取客戶端聊天消息
            String uname = str.substring(0, str.indexOf("^"));
            String msg = str.substring(str.indexOf("^") + 1);
            printInfo("("+uname+")說:" + msg);
            String dateTime = sdf.format(new Date());
            String smsg = uname + " " + dateTime + "\n " + msg + "\n";
            Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
            while (iter.hasNext()) {
                SelectionKey selKey = iter.next();
                if (selKey != serverKey) {
                    selKey.attach(smsg);
                    selKey.interestOps(selKey.interestOps() | SelectionKey.OP_WRITE);
                }
            }
        }
    }
    /**
     * 寫數據到key對應的套接字通道
     * @param key
     * @throws IOException
     */
    private void writeMsg(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        Object obj = key.attachment();
        //這里必要要將key的附加數據設置為空,否則會有問題
        key.attach("");
        //附加值為"close",則取消此key,并關閉對應通道
        if (obj.toString().equals("close")) {
            key.cancel();
            channel.socket().close();
            channel.close();
            return;
        }else {
            //將數據寫到通道
            channel.write(ByteBuffer.wrap(obj.toString().getBytes()));
        }
        //重設此key興趣
        key.interestOps(SelectionKey.OP_READ);
    }
    private void printInfo(String str) {
        System.out.println("[" + sdf.format(new Date()) + "] -> " + str);
    }
    public static void main(String[] args) {
        ChatServer server = new ChatServer(19999);
        new Thread(server).start();
    }
}

二、客戶端

1、服務類,用于與服務端交互

package com.chat.client;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class ClientService {
    private static final String HOST = "127.0.0.1";
    private static final int PORT = 19999;
    private static SocketChannel sc;
    private static Object lock = new Object();
    private static ClientService service;
    public static ClientService getInstance(){
        synchronized (lock) {
            if(service == null){
                try {
                    service = new ClientService();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return service;
        }
    }
    private ClientService() throws IOException {
        sc = SocketChannel.open();
        sc.configureBlocking(false);
        sc.connect(new InetSocketAddress(HOST, PORT));
    }
    public void sendMsg(String msg) {
        try {
            while (!sc.finishConnect()) {
            }
            sc.write(ByteBuffer.wrap(msg.getBytes()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public String receiveMsg() {
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.clear();
        StringBuffer sb = new StringBuffer();
        int count = 0;
        String msg = null;
        try {
            while ((count = sc.read(buffer)) > 0) {
                sb.append(new String(buffer.array(), 0, count));
            }
            if (sb.length() > 0) {
                msg = sb.toString();
                if ("close".equals(sb.toString())) {
                    msg = null;
                    sc.close();
                    sc.socket().close();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return msg;
    }
}

2、登陸窗體,用戶設置名稱

package com.chat.client;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
/**
 * 設置名稱窗體
 * 
 * @author zing
 * 
 */
public class SetNameFrame extends JFrame {
    private static final long serialVersionUID = 1L;
    private static JTextField txtName;// 文本框
    private static JButton btnOK;// ok按鈕
    private static JLabel label;// 標簽
    public SetNameFrame() {
        this.setLayout(null);
        Toolkit kit = Toolkit.getDefaultToolkit();
        int w = kit.getScreenSize().width;
        int h = kit.getScreenSize().height;
        this.setBounds(w / 2 - 230 / 2, h / 2 - 200 / 2, 230, 200);
        this.setTitle("設置名稱");
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        this.setResizable(false);
        txtName = new JTextField(4);
        this.add(txtName);
        txtName.setBounds(10, 10, 100, 25);
        btnOK = new JButton("OK");
        this.add(btnOK);
        btnOK.setBounds(120, 10, 80, 25);
        label = new JLabel("[w:" + w + ",h:" + h + "]");
        this.add(label);
        label.setBounds(10, 40, 200, 100);
        label.setText("<html>在上面的文本框中輸入名字<br/>顯示器寬度:" + w + "<br/>顯示器高度:" + h
                + "</html>");
        btnOK.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String uname = txtName.getText();
                ClientService service = ClientService.getInstance();
                ChatFrame chatFrame = new ChatFrame(service, uname);
                chatFrame.show();
                setVisible(false);
            }
        });
    }
    public static void main(String[] args) {
        SetNameFrame setNameFrame = new SetNameFrame();
        setNameFrame.setVisible(true);
    }
}

3、聊天室窗體

package com.chat.client;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
/**
 * 聊天室窗體
 * @author zing
 *
 */
public class ChatFrame {
    private JTextArea readContext = new JTextArea(18, 30);// 顯示消息文本框
    private JTextArea writeContext = new JTextArea(6, 30);// 發(fā)送消息文本框
    private DefaultListModel modle = new DefaultListModel();// 用戶列表模型
    private JList list = new JList(modle);// 用戶列表
    private JButton btnSend = new JButton("發(fā)送");// 發(fā)送消息按鈕
    private JButton btnClose = new JButton("關閉");// 關閉聊天窗口按鈕
    private JFrame frame = new JFrame("ChatFrame");// 窗體界面
    private String uname;// 用戶姓名
    private ClientService service;// 用于與服務器交互
    private boolean isRun = false;// 是否運行
    public ChatFrame(ClientService service, String uname) {
        this.isRun = true;
        this.uname = uname;
        this.service = service;
    }
    // 初始化界面控件及事件
    private void init() {
        frame.setLayout(null);
        frame.setTitle(uname + " 聊天窗口");
        frame.setSize(500, 500);
        frame.setLocation(400, 200);
        //設置可關閉
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //不能改變窗體大小
        frame.setResizable(false);
        //聊天消息顯示區(qū)帶滾動條
        JScrollPane readScroll = new JScrollPane(readContext);
        readScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        frame.add(readScroll);
        //消息編輯區(qū)帶滾動條
        JScrollPane writeScroll = new JScrollPane(writeContext);
        writeScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        frame.add(writeScroll);
        frame.add(list);
        frame.add(btnSend);
        frame.add(btnClose);
        readScroll.setBounds(10, 10, 320, 300);
        readContext.setBounds(0, 0, 320, 300);
        readContext.setEditable(false);//設置為不可編輯
        readContext.setLineWrap(true);// 自動換行
        writeScroll.setBounds(10, 315, 320, 100);
        writeContext.setBounds(0, 0, 320, 100);
        writeContext.setLineWrap(true);// 自動換行
        list.setBounds(340, 10, 140, 445);
        btnSend.setBounds(150, 420, 80, 30);
        btnClose.setBounds(250, 420, 80, 30);
        //窗體關閉事件
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                isRun = false;
                service.sendMsg("exit_" + uname);
                System.exit(0);
            }
        });
        //發(fā)送按鈕事件
        btnSend.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String msg = writeContext.getText().trim();
                if(msg.length() > 0){
                    service.sendMsg(uname + "^" + writeContext.getText());
                }
                //發(fā)送消息后,去掉編輯區(qū)文本,并獲得光標焦點
                writeContext.setText(null);
                writeContext.requestFocus();
            }
        });
        //關閉按鈕事件
        btnClose.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                isRun = false;
                service.sendMsg("exit_" + uname);
                System.exit(0);
            }
        });
        //右邊名稱列表選擇事件
        list.addListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {
                // JOptionPane.showMessageDialog(null,
                // list.getSelectedValue().toString());
            }
        });
        //消息編輯區(qū)鍵盤按鍵事件
        writeContext.addKeyListener(new KeyListener() {
            @Override
            public void keyTyped(KeyEvent e) {
                // TODO Auto-generated method stub
            }
            //按下鍵盤按鍵后釋放
            @Override
            public void keyReleased(KeyEvent e) {
                //按下enter鍵發(fā)送消息
                if(e.getKeyCode() == KeyEvent.VK_ENTER){
                    String msg = writeContext.getText().trim();
                    if(msg.length() > 0){
                        service.sendMsg(uname + "^" + writeContext.getText());
                    }
                    writeContext.setText(null);
                    writeContext.requestFocus();
                }
            }
            @Override
            public void keyPressed(KeyEvent e) {
                // TODO Auto-generated method stub
            }
        });
    }
    // 此線程類用于輪詢讀取服務器發(fā)送的消息
    private class MsgThread extends Thread {
        @Override
        public void run() {
            while (isRun) {
                String msg = service.receiveMsg();
                if (msg != null) {
                    //若是名稱列表數據,則更新聊天窗體右邊的列表
                    if (msg.indexOf("[") != -1 && msg.lastIndexOf("]") != -1) {
                        msg = msg.substring(1, msg.length() - 1);
                        String[] userNames = msg.split(",");
                        modle.removeAllElements();
                        for (int i = 0; i < userNames.length; i++) {
                            modle.addElement(userNames[i].trim());
                        }
                    } else {
                        //將聊天數據設置到聊天消息顯示區(qū)
                        String str = readContext.getText() + msg;
                        readContext.setText(str);
                        readContext.selectAll();//保持滾動條在最下面
                    }
                }
            }
        }
    }
    // 顯示界面
    public void show() {
        this.init();
        service.sendMsg("open_" + uname);
        MsgThread msgThread = new MsgThread();
        msgThread.start();
        this.frame.setVisible(true);
    }
}

更多java相關內容感興趣的讀者可查看本站專題:《Java面向對象程序設計入門與進階教程》、《Java數據結構與算法教程》、《Java操作DOM節(jié)點技巧總結》、《Java文件與目錄操作技巧匯總》和《Java緩存操作技巧匯總

希望本文所述對大家java程序設計有所幫助。

相關文章

  • springboot項目連接不上nacos配置,報‘url‘異常問題

    springboot項目連接不上nacos配置,報‘url‘異常問題

    這篇文章主要介紹了springboot項目連接不上nacos配置,報‘url‘異常問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-06-06
  • 如何調用chatGPT實現代碼機器人

    如何調用chatGPT實現代碼機器人

    最近chatGPT也是非常的火爆,相信大家都看到了,現在提供一種Java調用chatGPT的方法,我們主要通過兩個工具來實現,一就是httpclient,二就是hutool,你覺得那種好理解你就用那種即可,今天通過本文給大家分享調用chatGPT實現代碼機器人,感興趣的朋友一起看看吧
    2022-12-12
  • Mybatis-plus新版本分頁失效PaginationInterceptor過時的問題

    Mybatis-plus新版本分頁失效PaginationInterceptor過時的問題

    這篇文章主要介紹了Mybatis-plus新版本分頁失效,PaginationInterceptor過時問題,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-11-11
  • Springboot中靜態(tài)文件的兩種引入方式總結

    Springboot中靜態(tài)文件的兩種引入方式總結

    這篇文章主要介紹了Springboot中靜態(tài)文件的兩種引入方式總結,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • java冒泡排序和選擇排序詳解

    java冒泡排序和選擇排序詳解

    這篇文章主要介紹了java數組算法例題代碼詳解(冒泡排序,選擇排序),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-07-07
  • Javadoc 具體使用詳解

    Javadoc 具體使用詳解

    這篇文章主要介紹了Javadoc 具體使用詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-08-08
  • Java?jar打包成exe應用程序的詳細步驟

    Java?jar打包成exe應用程序的詳細步驟

    本文主要介紹了Java?jar打包成exe應用程序的詳細步驟,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • Spring bean的實例化和IOC依賴注入詳解

    Spring bean的實例化和IOC依賴注入詳解

    這篇文章主要介紹了Spring bean的實例化和IOC依賴注入詳解,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-03-03
  • springboot+mybatis-plus 兩種方式打印sql語句的方法

    springboot+mybatis-plus 兩種方式打印sql語句的方法

    這篇文章主要介紹了springboot+mybatis-plus 兩種方式打印sql語句的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-10-10
  • IO密集型任務設置線程池線程數實現方式

    IO密集型任務設置線程池線程數實現方式

    這篇文章主要介紹了IO密集型任務設置線程池線程數實現方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-07-07

最新評論