在看到本文之前,如果讀者沒看過筆者的前文Java實(shí)現(xiàn)Socket網(wǎng)絡(luò)編程(三) ,請(qǐng)先翻閱。
下面,我們來實(shí)現(xiàn)服務(wù)器單體發(fā)送和廣播發(fā)送:
我們?yōu)镴List客戶端列表設(shè)置監(jiān)聽,在每次點(diǎn)擊觸發(fā)時(shí),先默認(rèn)設(shè)置所有Socket未選中,避免錯(cuò)亂,然后獲取全部已選中下標(biāo),并做好標(biāo)記。筆者此處采用HashMap存儲(chǔ)Socket,如Socket被選中,其對(duì)應(yīng)Value為true,反之為false。
// 添加監(jiān)聽
clientList.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
// 先默認(rèn)把所有Socket未選中
resetSocket(ListenThread.clientSockets);
// 獲取全部已選中下標(biāo)
int[] selecteds = clientList.getSelectedIndices();
for (int i = 0; i < selecteds.length; i++) {
// 獲取所選中項(xiàng)的HashMap
HashMap<Socket, Boolean> map = ListenThread.clientSockets
.get(selecteds[i]);
// 用迭代器獲取HashMap的Key,即所選中的Socket
Iterator iter = map.entrySet().iterator();
Map.Entry<Socket, Boolean> entry = (Entry<Socket, Boolean>) iter
.next();
Socket key = (Socket) entry.getKey();
// 把所選中的Socket設(shè)置為true
map.replace(key, true);
}
}
});
然后,在點(diǎn)擊發(fā)送消息的按鈕時(shí),根據(jù)之前在JList中標(biāo)記的Socket,向指定客戶端發(fā)送消息。
// 設(shè)置發(fā)送消息監(jiān)聽
jbSendMessage.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (jtaSendMessage.getText().equals("")) {
JOptionPane.showMessageDialog(null, "發(fā)送內(nèi)容不能為空!");
return;
}
if (jtaSendMessage.getText().length() > 5000) {
JOptionPane.showMessageDialog(null, "發(fā)送數(shù)據(jù)過大,最大不能超過5000字!");
return;
}
// 取得要發(fā)送的消息
// 代表服務(wù)器正常連接
String message = Common.OK;
String t = "server " + Common.IP + ":" + Common.PORT + " "
+ jtaSendMessage.getText();
OutputStreamWriter outstream = null;
// 將信息發(fā)送給每個(gè)選中的客戶端
for (int i = 0; i < ListenThread.clientSockets.size(); i++) {
try {
HashMap<Socket, Boolean> map = ListenThread.clientSockets
.get(i);
// 用迭代器獲取HashMap的Key,即所選中的Socket
Iterator iter = map.entrySet().iterator();
Map.Entry<Socket, Boolean> entry = (Entry<Socket, Boolean>) iter
.next();
// 如果Socket已選中
if ((Boolean) entry.getValue()) {
Socket key = (Socket) entry.getKey();
outstream = new OutputStreamWriter(key
.getOutputStream(), "GBK");
outstream.write(message);
outstream.flush();
}
} catch (IOException e1) {
if (outstream != null)
try {
outstream.close();
} catch (IOException e2) {
e2.printStackTrace();
}
e1.printStackTrace();
}
}
// 清空文本
jtaSendMessage.setText(null);
}
});
為了實(shí)現(xiàn)客戶端自動(dòng)連接服務(wù)器,我們要為客戶端實(shí)現(xiàn)一個(gè)子線程,用于輪詢檢測(cè)服務(wù)器是否已啟動(dòng):
public void run() {
while (true) {
// 窗口關(guān)閉則直接退出循環(huán),避免在關(guān)閉過程中再次連接服務(wù)器
if (ClientMain.isWindowClosing)
break;
try {
if (ClientMain.mSocket == null || ClientMain.mSocket.isClosed()) {
ClientMain.isConnected = false;
ClientMain.jlConnect.setText("Out Of Connect.");
}
if (!ClientMain.isConnected) {
Socket socket = new Socket(Common.IP, Common.PORT);
ClientMain.mSocket = socket;
// 啟動(dòng)客戶端連接子線程
new Thread(new ClientReceivedThread(socket)).start();
ClientMain.isConnected = true;
ClientMain.jlConnect.setText("Success Connect.");
}
} catch (Exception e) {
// 無法連接服務(wù)器
ClientMain.isConnected = false;
ClientMain.jlConnect.setText("Out Of Connect.");
e.printStackTrace();
}
}
}
在這里,讀者可能會(huì)遇到一個(gè)問題,客戶端是如何檢測(cè)到服務(wù)器斷開,而服務(wù)器又如何檢測(cè)到客戶端斷開呢?
筆者為讀者提供兩種方法:
1、檢測(cè)讀寫錯(cuò)誤,在BufferedReader的read()方法中,如果發(fā)生讀寫錯(cuò)誤,便能捕獲到。
2、發(fā)送心跳包檢測(cè)
對(duì)于方法1,服務(wù)器和客戶端實(shí)現(xiàn)的方法一樣
對(duì)于方法2,服務(wù)器應(yīng)發(fā)送心跳包0,客戶端應(yīng)發(fā)送心跳到0xFF
mSocket.sendUrgentData(0);//服務(wù)器檢測(cè)客戶端斷開
mSocket.sendUrgentData(0xFF);//客戶端檢測(cè)服務(wù)器斷開
當(dāng)捕獲到異常時(shí),便能得知對(duì)方斷開。