發(fā)布時(shí)間:2023-11-27 21:58:17 瀏覽量:144次
前言:Android開發(fā)中可以使用Java API提供的Socket和ServerSocket類來實(shí)現(xiàn)Socket通信。但是,通過這兩個(gè)類實(shí)現(xiàn)的Socket通信是阻塞式的,當(dāng)程序執(zhí)行輸入/輸出操作后,在這些操作返回之前會一直阻塞線程。當(dāng)有大量任務(wù)需要處理時(shí),這種方式會降低性能。在Java中提供了另一種NIO API,可以實(shí)現(xiàn)非阻塞的Socket通信,該NIO API主要提供了以下兩種特殊類:Selector和SelectableChannel。
Selector是SelectableChannel對象的多路復(fù)用器,可以同時(shí)監(jiān)控多個(gè)SelectableChannel的IO狀況,是非阻塞IO的核心。所有希望采用非阻塞方式進(jìn)行通信的Channel都應(yīng)該注冊到Selector對象。可通過調(diào)用Selector類的靜態(tài)open()方法來創(chuàng)建Selector實(shí)例。一個(gè)Selector實(shí)例包含3個(gè)SelectionKey集合:
(1)所有SelectionKey集合:通過keys()方法返回,表示注冊在該Selector上的所有Channel。
(2)被選擇的SelectionKey集合:通過selectedKeys()返回,表示所有可通過select()方法監(jiān)測到、需要進(jìn)行IO處理的Channel。
(3)被取消的SelectionKey集合:表示所有被取消注冊關(guān)系的Channel,在下一次執(zhí)行select()方法時(shí),這些Channel對應(yīng)的SelectionKey會被徹底刪除。程序通常無需直接訪問該集合。
除了3個(gè)SelectionKey集合,Selector還提供了和select()相關(guān)的方法:
(1)int select():監(jiān)控所有注冊的Channel,當(dāng)有Channel需要處理IO操作時(shí),該方法返回,并將對應(yīng)的SelectionKey加入被選擇的SelectionKey集合中。該方法返回這些Channel的數(shù)量。
(2)int select(long timeout):可以設(shè)置超時(shí)時(shí)長的select()操作。
(3)int selectNow():執(zhí)行一個(gè)立即返回的select()操作,相對于無參數(shù)的select()方法,該方法不會阻塞線程。
(4)Selector wakeup():使一個(gè)還未返回的select()方法立即返回。
SelectableChannel代表了可以支持非阻塞IO操作的Channel對象,可以將其注冊到Selector上,這種注冊的關(guān)系由SelectionKey實(shí)例表示。Java中可以調(diào)用SelectableChannel實(shí)例的register()方法將其注冊到指定的Selector上。當(dāng)注冊到Selector中的Channel當(dāng)中有需要處理IO操作時(shí),可以調(diào)用Selector的select()方法獲取它們的數(shù)量,并通過selectedKeys()方法返回它們對應(yīng)的SelectionKey集合。通過這個(gè)集合,可以獲取所需要處理IO操作的SelectableChannel集。
SelectableChannel支持阻塞和非阻塞兩種模式,默認(rèn)是阻塞的,必須通過非阻塞模式才能實(shí)現(xiàn)非阻塞IO操作??梢酝ㄟ^以下兩個(gè)方法來設(shè)置和返回Channel的模式:
(1)SelectionKey configureBlocking(boolean block):設(shè)置是否采用阻塞模式
(2)boolean isBlocking():返回該Channel是否阻塞模式。
不同的SelectableChannel所支持的操作不一樣。在SelectableChannel中提供了如下方法來返回不同SelectableSocketChannel所支持的操作:
int validOps():返回一個(gè)bit mask,表示這個(gè)Channel上支持的IO操作。
此外,SelectableChannel還提供了如下方法獲取它的注冊狀態(tài):
boolean isRegistered():返回該Channel是否已注冊在一個(gè)或多個(gè)Selector上。
selectionKey keyFor(Selector sel):返回該Channel和Selector之間的注冊關(guān)系,如不存在注冊關(guān)系,則返回null。
下面介紹兩種SelectableSocketChannel,分別是對應(yīng)于java.net.ServerSocket的ServerSocketChannel和對應(yīng)于java.net.Socket的SocketChannel。
ServerSocketChannel代表一個(gè)ServerSocket,提供了一個(gè)TCP協(xié)議的IO接口,對應(yīng)于java.net.ServerSocket,支持非阻塞模式。它只支持OP_ACCEPT操作。同時(shí),該類也提供了accept()方法,功能相當(dāng)于ServerSocket的accept()方法。
SocketChannel對應(yīng)于java.net.Socket,同樣提供一個(gè)TCP協(xié)議的IO接口,支持非阻塞模式。它支持OP_CONNECT、OP_READ和OP_WRITE操作。此外,該類還實(shí)現(xiàn)了ByteChannel接口、ScatteringByteChannel接口和GatheringByteChannel接口,可以直接通過SocketChannel來讀寫B(tài)yteBuffer對象。
服務(wù)器上所有的Channel都需要向Selector注冊,包括ServerSocketChannel和SocketChannel。該Selector負(fù)責(zé)監(jiān)控這些Channel的IO狀態(tài),當(dāng)有Channel需要IO操作時(shí),Selector的select()方法將會返回具有IO操作的Channel的數(shù)量。并且,可通過selectedKeys()方法獲取這些Channel對應(yīng)的SelectionKey集合,進(jìn)而獲取到具有IO操作的SelectableChannel集。下面以一個(gè)實(shí)例來說明Java中NIO Socket的使用。直接上代碼加注釋
public class NIOSocketServer implements ActionListener { private String ip = "192.168.1.132"; private Window mWindow; private JButton mBtnSend; private JButton mBtnSendAll; private JTextField mTextFiled; private JTextArea mTextArea; private JButton mBtnClear; // 用于檢測所有Channel狀態(tài)的Selector private Selector mSelector = null; // 定義實(shí)現(xiàn)編碼、解碼的字符集對象 private Charset mCharset = Charset.forName("UTF-8"); private SocketChannel mSocketChannel = null; // private static ExecutorService mThreadPool; // private static SocketRun mSocketRun; public NIOSocketServer() { mWindow = new Window("服務(wù)端"); mBtnSend = mWindow.getSendButton(); mBtnSend.setName("發(fā)送"); mBtnSendAll = mWindow.getSendAllButton(); mBtnSendAll.setName("群發(fā)"); mBtnClear = mWindow.getClearButton(); mBtnClear.setName("clear"); mTextFiled = mWindow.getTextField(); mTextArea = mWindow.getJTextArea(); mBtnSend.addActionListener(this); mBtnSendAll.addActionListener(this); mBtnClear.addActionListener(this); // mSocketRun = new SocketRun(); // mThreadPool = Executors.newCachedThreadPool(); } public static void main(String[] args) throws IOException { System.out.println("服務(wù)端已啟動..."); new NIOSocketServer().init(); } @Override public void actionPerformed(ActionEvent event) { JButton source = (JButton) event.getSource(); String name = source.getName(); if (mBtnSend.getName().equals(name)) { // 向單個(gè)客戶端發(fā)送消息 String content = mTextFiled.getText().toString(); if (mSocketChannel != null) { try { mSocketChannel.write(mCharset.encode(content)); } catch (IOException e) { e.printStackTrace(); } } } else if (mBtnSendAll.getName().equals(name)) { // 向所有客戶端發(fā)送消息 String content = mTextFiled.getText().toString(); sendToAll(content); } else if (mBtnClear.getName().equals(name)) { mTextArea.setText(""); } } private void sendToAll(String message) { for (SelectionKey sk : mSelector.keys()) { Channel channel = sk.channel(); if (channel instanceof SocketChannel) { SocketChannel dest = (SocketChannel) channel; if (dest.isOpen()) { try { dest.write(mCharset.encode(message)); } catch (IOException e) { e.printStackTrace(); } } } } } public void init() throws IOException { mSelector = Selector.open(); // 通過open方法打開一個(gè)未綁定的ServerSocketChannel實(shí)例 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); InetSocketAddress inetSocketAddress = new InetSocketAddress(ip, 30000); // 將該ServerSocketChannel綁定到指定的IP地址 serverSocketChannel.socket().bind(inetSocketAddress); // 設(shè)置ServerSocket以非阻塞方式工作 serverSocketChannel.configureBlocking(false); // 將serverSocketChannel注冊到指定Selector對象 serverSocketChannel.register(mSelector, SelectionKey.OP_ACCEPT); while (mSelector.select() > 0) { // 依次處理selector上的每個(gè)已選擇的SelectionKey Set<SelectionKey> selectedKeys = mSelector.selectedKeys(); //這里必須用iterator,如果用for遍歷Set程序會報(bào)錯(cuò) Iterator<SelectionKey> iterator = selectedKeys.iterator(); while (iterator.hasNext()) { SelectionKey sk = iterator.next(); // 從selector上的已選擇的SelectionKey集合中刪除正在處理的SelectionKey iterator.remove(); // 如果sk對應(yīng)的通道包含客戶端的連接請求 if (sk.isAcceptable()) { // 調(diào)用accept方法接受連接,產(chǎn)生服務(wù)端對應(yīng)的SocketChannel SocketChannel socketChannel = serverSocketChannel.accept(); mTextArea.append("客戶端接入,IP:"+socketChannel.getLocalAddress()+"\n"); // 設(shè)置采用非阻塞模式 socketChannel.configureBlocking(false); mSocketChannel = socketChannel; // 將該SocketChannel也注冊到selector socketChannel.register(mSelector, SelectionKey.OP_READ); // 將sk對應(yīng)的Channel設(shè)置成準(zhǔn)備接受其他請求 sk.interestOps(SelectionKey.OP_ACCEPT); } // 如果sk對應(yīng)的通道有數(shù)據(jù)需要讀取 if (sk.isReadable()) { // 獲取該SelectionKey對應(yīng)的Channel,該Channel中有可讀的數(shù)據(jù) SocketChannel socketChannel = (SocketChannel) sk.channel(); mSocketChannel = socketChannel; // 定義準(zhǔn)備執(zhí)行讀取數(shù)據(jù)的ByteBuffer ByteBuffer buffer = ByteBuffer.allocate(1024); String content = ""; // 開始讀取數(shù)據(jù) try { while (socketChannel.read(buffer) > 0) { buffer.flip(); content += mCharset.decode(buffer); } if ("shutdown".equals(content)) { sk.cancel(); if (sk.channel() != null) { sk.channel().close(); } sendToAll("reconnect"); } else { // 打印從該sk對應(yīng)的Channel里讀取到的數(shù)據(jù) mTextArea.append("來自客戶端的消息:" + content + "\n"); // 將sk對應(yīng)的Channel設(shè)置成準(zhǔn)備下一次讀取 sk.interestOps(SelectionKey.OP_READ); } } // 如果捕捉到該sk對應(yīng)的Channel出現(xiàn)了異常,即表明該Channel // 對應(yīng)的Client出現(xiàn)了問題,所以從Selector中取消sk的注冊 catch (IOException ex) { // 從Selector中刪除指定的SelectionKey sk.cancel(); if (sk.channel() != null) { sk.channel().close(); } sendToAll("reconnect"); } } } } } }
Server端創(chuàng)建GUI界面輔助測試的Window類
public class Window extends JFrame {
/**
* 窗口類 定義客戶端和服務(wù)器端的窗口
*/
private static final long serialVersionUID = 2L;
private String windowName;
private JFrame myWindow;
private JTextArea area;
private JTextField field;
private JButton btnSend;
private JButton btnSendAll;
private JButton btnClear;
public Window(String windowName) {
this.windowName = windowName;
myWindow = new JFrame(windowName);
myWindow.setLayout(new FlowLayout());
myWindow.setSize(new Dimension(600, 300));
// 不能改變窗口大小
myWindow.setResizable(false);
area = new JTextArea();
field = new JTextField();
btnSend = new JButton("發(fā)送");
btnSendAll = new JButton("群發(fā)");
btnClear = new JButton("clear");
// 設(shè)置field的大小
field.setPreferredSize(new Dimension(300, 30));
myWindow.add(field);
myWindow.add(btnSend);
myWindow.add(btnSendAll);
myWindow.add(btnClear);
myWindow.add(area);
// 改變area的大小
area.setPreferredSize(new Dimension(470, 210));
area.setBackground(Color.PINK);
area.setEditable(false);
// 設(shè)置窗口顯示在電腦屏幕的某區(qū)域
myWindow.setLocation(400, 200);
myWindow.setVisible(true);
// 點(diǎn)擊關(guān)閉按鈕時(shí)觸發(fā)該方法
closeMyWindow();
}
/**
* 方法名:closeMyWindow()
*
* @param
* @return 功能:當(dāng)用戶點(diǎn)擊關(guān)閉按鈕時(shí),退出并且關(guān)閉該窗口
*/
private void closeMyWindow() {
myWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
/**
* 方法名:getFieldText()
*
* @param
* @return string 功能:獲取窗口的TextField中的字符串
*/
public String getFieldText() {
return field.getText().toString();
}
/**
* 方法名:getSendButton()
*
* @param
* @return JButton 功能:獲得該窗口中的按鈕
*/
public JButton getSendButton() {
return btnSend;
}
/**
* 方法名:getSendAllButton()
*
* @param
* @return JButton 功能:獲得該窗口中的按鈕
*/
public JButton getSendAllButton() {
return btnSendAll;
}
/**
* 方法名:getClearButton()
*
* @param
* @return JButton 功能:獲得該窗口中的按鈕
*/
public JButton getClearButton() {
return btnClear;
}
/**
* 方法名:getJTextArea()
*
* @param
* @return JTextArea 功能:返回窗口中的JTextArea
*/
public JTextArea getJTextArea() {
return area;
}
/**
* 方法名:getTextField()
*
* @param
* @return JTextField 功能:獲得窗口中的textfield
*/
public JTextField getTextField() {
return field;
}
}
MainActivity代碼:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MainActivity";
private String ip = "192.168.1.132";
private TextView contentTv;
// 定義檢測SocketChannel的Selector對象
private Selector mSelector;
// 客戶端SocketChannel
private SocketChannel mSocketChannel;
// 定義處理編碼、解碼的字符集
private Charset mCharset = Charset.forName("UTF-8");
private String mData = "";
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message message) {
switch (message.what) {
case 0:
contentTv.append("連接成功...\n");
break;
case 1:
contentTv.append("來自服務(wù)端端:" + mData + "\n");
break;
}
return false;
}
});
private EditText mContentEt;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
contentTv = (TextView) findViewById(R.id.content_tv);
mContentEt = (EditText) findViewById(R.id.content_et);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_conn:
//Android里面網(wǎng)絡(luò)操作不能放在UI線程,
// 所以開啟一個(gè)線程來測試
new connectThread().start();
break;
case R.id.btn_send:
new sendMsgThread().start();
break;
}
}
private class sendMsgThread extends Thread {
@Override
public void run() {
super.run();
try {
mSocketChannel.write(mCharset.encode(mContentEt.getText().toString()));
} catch (IOException e) {
e.printStackTrace();
}
}
}
private class connectThread extends Thread {
@Override
public void run() {
super.run();
try {
mSelector = Selector.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(ip, 30000);
// 調(diào)用open方法創(chuàng)建連接到指定主機(jī)的SocketChannel
mSocketChannel = SocketChannel.open(inetSocketAddress);
mHandler.sendEmptyMessage(0);
// 設(shè)置該SocketChannel以非阻塞方式工作
mSocketChannel.configureBlocking(false);
// 將該SocketChannle對象注冊到指定的Selector
mSocketChannel.register(mSelector, SelectionKey.OP_READ);
// 讀取服務(wù)端消息
while (mSelector != null && mSelector.select() > 0) {
Set<SelectionKey> selectionKeys = mSelector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey sk = iterator.next();
iterator.remove();
if (sk != null && sk.isReadable()) {
mSocketChannel = (SocketChannel) sk.channel();
ByteBuffer buff = ByteBuffer.allocate(1024);
String data = "";
while (mSocketChannel.read(buff) > 0) {
mSocketChannel.read(buff);
buff.flip();
data += mCharset.decode(buff);
buff.clear();
}
Log.e(TAG, "run: data:" + data);
mData = data;
mHandler.sendEmptyMessage(1);
sk.interestOps(SelectionKey.OP_READ);
}
}
}
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "run: e:" + e.getMessage());
}
}
}
}
activity_main.xml代碼
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.niosocketdemo.MainActivity">
<Button
android:id="@+id/btn_conn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="連接" />
<Button
android:id="@+id/btn_send"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="發(fā)送消息" />
<EditText
android:id="@+id/content_et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="請輸入要發(fā)送的消息" />
<TextView
android:id="@+id/content_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="消息:\n" />
</LinearLayout>
熱門資訊
探討游戲引擎的文章,介紹了10款游戲引擎及其代表作品,涵蓋了RAGE Engine、Naughty Dog Game Engine、The Dead Engine、Cry Engine、Avalanche Engine、Anvil Engine、IW Engine、Frostbite Engine、Creation引擎、Unreal Engine等引擎。借此分析引出了游戲設(shè)計(jì)領(lǐng)域和數(shù)字藝術(shù)教育的重要性,歡迎點(diǎn)擊咨詢報(bào)名。
2. 手機(jī)游戲如何開發(fā)(如何制作傳奇手游,都需要準(zhǔn)備些什么?)
?如何制作傳奇手游,都需要準(zhǔn)備些什么?提到傳奇手游相信大家都不陌生,他是許多80、90后的回憶;從起初的端游到現(xiàn)在的手游,說明時(shí)代在進(jìn)步游戲在更新,更趨于方便化移動化。而如果我們想要制作一款傳奇手游的
3. B站視頻剪輯軟件「必剪」:免費(fèi)、炫酷特效,小白必備工具
B站視頻剪輯軟件「必剪」,完全免費(fèi)、一鍵制作炫酷特效,適合新手小白??靵碓囋?!
4. Steam值得入手的武俠游戲盤點(diǎn),各具特色的快意江湖
游戲中玩家將面臨武俠人生的掙扎抉擇,戰(zhàn)或降?殺或放?每個(gè)抉定都將觸發(fā)更多愛恨糾葛的精彩奇遇。《天命奇御》具有多線劇情多結(jié)局,不限主線發(fā)展,高自由...
5. Bigtime加密游戲經(jīng)濟(jì)體系揭秘,不同玩家角色的經(jīng)濟(jì)活動
Bigtime加密游戲經(jīng)濟(jì)模型分析,探討游戲經(jīng)濟(jì)特點(diǎn),幫助玩家更全面了解這款GameFi產(chǎn)品。
6. 3D動漫建模全過程,不是一般人能學(xué)的會的,會的多不是人?
步驟01:面部,頸部,身體在一起這次我不準(zhǔn)備設(shè)計(jì)圖片,我從雕刻進(jìn)入。這一次,它將是一種純粹關(guān)注建模而非整體繪畫的形式。像往常一樣,我從Sphere創(chuàng)建它...
7. 3D動畫軟件你知道幾個(gè)?3ds Max、Blender、Maya、Houdini大比拼
當(dāng)提到3D動畫軟件或動畫工具時(shí),指的是數(shù)字內(nèi)容創(chuàng)建工具。它是用于造型、建模以及繪制3D美術(shù)動畫的軟件程序。但是,在3D動畫軟件中還包含了其他類型的...
?三昧動漫對于著名ARPG游戲《巫師》系列,最近CD Projekt 的高層回應(yīng)并不會推出《巫師4》。因?yàn)椤段讕煛废盗性诓邉澋臅r(shí)候一直定位在“三部曲”的故事框架,所以在游戲的出品上不可能出現(xiàn)《巫師4》
9. 3D打印技巧揭秘!Cura設(shè)置讓你的模型更堅(jiān)固
想讓你的3D打印模型更堅(jiān)固?不妨嘗試一下Cura參數(shù)設(shè)置和設(shè)計(jì)技巧,讓你輕松掌握!
10. Unity3D入門:手把手帶你開發(fā)一款坦克大戰(zhàn)的游戲
Unity工程創(chuàng)建完成后如圖所示: 接下來應(yīng)該導(dǎo)入此項(xiàng)目所需的Unity Package文件,要用到的Unity package文件大家可以去Unity3D的官方網(wǎng)站下載(地址:ht...
最新文章
同學(xué)您好!