Netty专题-(3)NIO网络编程
之前在上一章Netty專題-(2)NIO三大核心中介紹了NIO的三大核心類Selector 、 Channel 和 Buffer,這一章我們將利用這些核心進(jìn)行編程實現(xiàn)相關(guān)的一些功能。在正式進(jìn)入編程之前我們還需要介紹一些概念。
1 NIO網(wǎng)絡(luò)編程關(guān)系圖
(1)當(dāng)客戶端連接時,會通過 ServerSocketChannel 得到 SocketChannel
(2)Selector 進(jìn)行監(jiān)聽 select 方法, 返回有事件發(fā)生的通道的個數(shù)
(3)將 socketChannel 注冊到 Selector 上, register(Selector sel, int ops), 一個 selector 上可以注冊多個 SocketChannel
(4)注冊后返回一個 SelectionKey, 會和該 Selector 關(guān)聯(lián)(集合)
(5)進(jìn)一步得到各個 SelectionKey (有事件發(fā)生)
(6)在通過 SelectionKey 反向獲取 SocketChannel , 方法 channel()
(7)通過得到的 channel完成業(yè)務(wù)處理
2 NIO網(wǎng)絡(luò)編程入門案例
為了讓大家更好的對上面的流程理解,這里首先給大家一個樣例,使用NIO實現(xiàn)服務(wù)器端和客戶端之間的數(shù)據(jù)簡單通訊(非阻塞),希望大家好好看完和自己動手操作,這樣對大家理解 NIO 非阻塞網(wǎng)絡(luò)編程機制有很大的幫助。我們直接上代碼:
首先是服務(wù)端:
然后是客戶端:
//得到一個網(wǎng)絡(luò)通道SocketChannel socketChannel = SocketChannel.open();//設(shè)置非阻塞socketChannel.configureBlocking(false);//提供服務(wù)器端的ip 和 端口InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);//連接服務(wù)器if (!socketChannel.connect(inetSocketAddress)) {while (!socketChannel.finishConnect()) {System.out.println("因為連接需要時間,客戶端不會阻塞,可以做其它工作..");}}//...如果連接成功,就發(fā)送數(shù)據(jù)String str = "hello, likangmin~";//Wraps a byte array into a bufferByteBuffer buffer = ByteBuffer.wrap(str.getBytes());//發(fā)送數(shù)據(jù),將 buffer 數(shù)據(jù)寫入 channelsocketChannel.write(buffer);System.in.read();以上創(chuàng)建的步驟基本是按照之前介紹的流程走的,大家應(yīng)該可以看的明白。之后我們首先啟動服務(wù)端;
然后啟動客戶端:
成功接收。
3 NIO相關(guān)類介紹
3.1 SelectionKey
(1)SelectionKey基本介紹
SelectionKey,表示 Selector 和網(wǎng)絡(luò)通道的注冊關(guān)系, 共四種:
在其源碼中表示為:
public static final int OP_READ = 1 << 0; public static final int OP_WRITE = 1 << 2; public static final int OP_CONNECT = 1 << 3; public static final int OP_ACCEPT = 1 << 4;(2)SelectionKey 相關(guān)方法
3.2 ServerSocketChannel
(1)ServerSocketChannel 在服務(wù)器端監(jiān)聽新的客戶端 Socket 連接
(2)ServerSocketChannel相關(guān)方法
3.3 SocketChannel
(1)SocketChannel,網(wǎng)絡(luò) IO 通道,具體負(fù)責(zé)進(jìn)行讀寫操作。NIO 把緩沖區(qū)的數(shù)據(jù)寫入通道,或者把通道里的數(shù) 據(jù)讀到緩沖區(qū)。
(2)SocketChannel相關(guān)方法
4 NIO應(yīng)用實例-群聊系統(tǒng)
了解了NIO的網(wǎng)絡(luò)編程以后,接下來我們具體實現(xiàn)一個比較難一點的要求:
實現(xiàn)的效果圖大致如下:
大家在往下看之前可以自己先動手實現(xiàn)一下,然后跟我這里給出的示例進(jìn)行對比,這樣更有助于大家進(jìn)一步理解 NIO 非阻塞網(wǎng)絡(luò)編程機制。
同樣的首先編寫服務(wù)端代碼:
然后編寫客戶端的代碼:
//定義相關(guān)的屬性private final String HOST = "127.0.0.1"; // 服務(wù)器的ipprivate final int PORT = 6667; //服務(wù)器端口private Selector selector;private SocketChannel socketChannel;private String username;//構(gòu)造器, 完成初始化工作public GroupChatClient() throws IOException {selector = Selector.open();//連接服務(wù)器socketChannel = socketChannel.open(new InetSocketAddress("127.0.0.1", PORT));//設(shè)置非阻塞socketChannel.configureBlocking(false);//將channel 注冊到selectorsocketChannel.register(selector, SelectionKey.OP_READ);//得到usernameusername = socketChannel.getLocalAddress().toString().substring(1);System.out.println(username + " is ok...");}//向服務(wù)器發(fā)送消息public void sendInfo(String info) {info = username + " 說:" + info;try {socketChannel.write(ByteBuffer.wrap(info.getBytes()));}catch (IOException e) {e.printStackTrace();}}//讀取從服務(wù)器端回復(fù)的消息public void readInfo() {try {int readChannels = selector.select();if(readChannels > 0) {//有可以用的通道Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();if(key.isReadable()) {//得到相關(guān)的通道SocketChannel sc = (SocketChannel) key.channel();//得到一個BufferByteBuffer buffer = ByteBuffer.allocate(1024);//讀取sc.read(buffer);//把讀到的緩沖區(qū)的數(shù)據(jù)轉(zhuǎn)成字符串String msg = new String(buffer.array());System.out.println(msg.trim());}}iterator.remove(); //刪除當(dāng)前的selectionKey, 防止重復(fù)操作} else {//System.out.println("沒有可以用的通道...");}}catch (Exception e) {e.printStackTrace();}}然后在服務(wù)端的main方法中增加如下代碼:
//創(chuàng)建服務(wù)器對象GroupChatServer groupChatServer = new GroupChatServer();groupChatServer.listen();同樣的在客戶端的main方法中增加如下代碼:
//啟動我們客戶端GroupChatClient chatClient = new GroupChatClient();//啟動一個線程, 每個3秒,讀取從服務(wù)器發(fā)送數(shù)據(jù)new Thread() {public void run() {while (true) {chatClient.readInfo();try {Thread.currentThread().sleep(3000);}catch (InterruptedException e) {e.printStackTrace();}}}}.start();//發(fā)送數(shù)據(jù)給服務(wù)器端Scanner scanner = new Scanner(System.in);while (scanner.hasNextLine()) {String s = scanner.nextLine();chatClient.sendInfo(s);}然后就可以啟動服務(wù)端了:
之后我們可以啟動多個客戶端進(jìn)行測試,可能有的沒有開啟相關(guān)的配置,將客戶端run起來以后不能開啟第二個,這里只需要進(jìn)行一下簡單的配置即可,筆者用的是IDEA,所以介紹一下IDEA的開啟方式。大家按照我的方式即可。
我們開啟兩個客戶端進(jìn)行測試:
然后發(fā)消息進(jìn)行測試,在63886中發(fā)送:你好,我是63886:
可以看到服務(wù)器對消息進(jìn)行了轉(zhuǎn)發(fā):
同時63939也收到了來自63886的消息:
同樣的,我們在客戶端63939中發(fā)送消息:你好,我是63939:
同樣服務(wù)器也進(jìn)行了轉(zhuǎn)發(fā):
客戶端63886也收到了63939的消息:
使用NIO實現(xiàn)簡單的群聊基本已經(jīng)實現(xiàn),大家看一下自己的與我這里提供的有什么差別。當(dāng)然有些功能可能不完善,這里只是作為一個簡單的案例,太復(fù)雜的功能可以學(xué)習(xí)完以后在進(jìn)行添加。下一章我們將介紹一下NIO的零拷貝。
猜你感興趣:
Netty專題-(1)初識Netty
Netty專題-(2)NIO三大核心
Netty專題-(3)NIO網(wǎng)絡(luò)編程
相關(guān)專題持續(xù)更新中,敬請期待…
更多文章請點擊:更多…
總結(jié)
以上是生活随笔為你收集整理的Netty专题-(3)NIO网络编程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Netty专题-(2)NIO三大核心
- 下一篇: ZooKeeper概述与原理