基於 java nio 長連接實現的聊天室,如果併發量大的話,可能會有線程問題。
服務端代碼
package com.lp.io.socket;
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.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class LpNioServerSocket {
//線程安全
private static List<SocketChannel> channels = Collections.synchronizedList( new ArrayList<SocketChannel>() );
public static void main(String[] args) {
HandlerSelectionKey handler = new HandlerHandlerSelectionKeyImpl();
try {
//創建 ServerSocketChannel
ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(false);
server.bind(new InetSocketAddress("localhost", 12345));
//創建 Selector
Selector selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);
//死循環,持續接收 客戶端連接
while(true) {
//selector.select(); 是阻塞方法
int keys = selector.select();
if(keys > 0) {
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while(it.hasNext()) {
SelectionKey key = it.next();
it.remove();
//處理 SelectionKey
handler.handler(key, selector);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* SelectionKey 處理接口
*
*/
public static interface HandlerSelectionKey {
public void handler(SelectionKey key, Selector selector) throws IOException;
}
/**
* SelectionKey 接口 實現類
*
*/
public static class HandlerHandlerSelectionKeyImpl implements HandlerSelectionKey {
@Override
public void handler(SelectionKey key, Selector selector) throws IOException {
int keyState = selectionKeyState(key);
switch (keyState) {
case SelectionKey.OP_ACCEPT:
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
accept(serverSocketChannel, selector);
break;
case SelectionKey.OP_READ:
SocketChannel readSocketChannel = (SocketChannel) key.channel();
read(readSocketChannel, selector);
break;
}
}
/**
* 獲取 SelectionKey 是什麼事件
* @param key
* @return
*/
private int selectionKeyState(SelectionKey key) {
if(key.isAcceptable()) {
return SelectionKey.OP_ACCEPT;
} else if(key.isReadable()) {
return SelectionKey.OP_READ;
}
return -1;
}
/**
* 接口客戶端請求
* @param serverSocketChannel
* @param selector
* @throws IOException
*/
private void accept(ServerSocketChannel serverSocketChannel, Selector selector) throws IOException {
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
channels.add(socketChannel);
//將 channel 註冊到 Selector
socketChannel.register(selector, SelectionKey.OP_READ);
}
/**
* 讀取客戶端發送過來的信息
* @param socketChannel
* @param selector
* @throws IOException
*/
private void read(SocketChannel socketChannel, Selector selector) throws IOException {
ByteBuffer readBuffer = ByteBuffer.allocate(8192);
int readBytes = socketChannel.read(readBuffer);
String msg = "";//客戶端發送來的消息
if(readBytes > 0) {
msg = new String(readBuffer.array(), 0, readBytes);
System.out.println("客戶端發送來的消息");
System.out.println(msg);
}
write(socketChannel, msg);
}
/**
* 響應客戶端請求
* @param socketChannel
* @param selector
* @throws IOException
*/
private void write(SocketChannel socketChannel, String msg) throws IOException {
msg = "遊客" + socketChannel.hashCode()+ "\r\n " + msg;
//響應消息
byte[] responseByte = msg.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(responseByte.length);
writeBuffer.put(responseByte);
writeBuffer.flip();
//響應客戶端
for(int i=0; i<channels.size(); i++) {
if(!socketChannel.equals(channels.get(i))) {
channels.get(i).write(writeBuffer);
}
}
}
}
}
客戶端代碼
package com.lp.io.socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
public class LpSocketClient1 {
/**
* @param args
* @throws IOException
* @throws UnknownHostException
* @throws InterruptedException
*/
public static void main(String[] args) throws UnknownHostException, IOException, InterruptedException {
final Socket socket = new Socket("localhost", 12345);
Thread inT = new Thread(new Runnable() {
@Override
public void run() {
try {
while(true) {
InputStream inputStream = socket.getInputStream();
byte[] b = new byte[8192];
int readSize = inputStream.read(b);
System.out.println(new String(b,0,readSize));
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
inT.start();
while(true) {
InputStreamReader stdin = new InputStreamReader(System.in);//鍵盤輸入
BufferedReader bufin = new BufferedReader(stdin);
String str = bufin.readLine();
System.out.println(str);
OutputStream outStream = socket.getOutputStream();
outStream.write(str.getBytes());
outStream.flush();
}
}
}
啓動服務端代碼,然後啓動多個客戶端。在某個客戶端代碼控制檯輸入英文(輸入中文亂碼),點擊回車,可以看到其他客戶端控制檯有信息輸出。