本機要講到的ServerSocketChannel、SocketChannel,與Java網絡編程中的ServerSocket、Socket是非常相識,至少從使用方式上來看是這樣,本質上都是TCP網絡套接字,只是多了“channel”;
一、SocketChanel
1、創建SocketChannel
有兩種方法可以獲取一個SocketChannel實例
1. 通過靜態方法open打開一個
SocketChannel socketChannel = SocketChannel.open();
- ServerSocketChannel接受一個連接請求後等到
SocketChannel socketChannel = serverSocketChannel.accept();
獲取到實例後可通過connect方法與服務端建立連接:
socketChannel.connect(new InetSocketAddress(1234));
2、讀寫數據
SocketChannel的讀寫數據與其他通道沒有區別,讀數據使用多個read方法,將數據讀取如一個buffer中,返回一個int值,表示成功讀取的字節數,返回-1表示讀取到了數據流的末尾了;
sizeBuffer.clear();
int read = socketChannel.read(sizeBuffer);
使用write方法將buffer中的數據寫入到通道中,同樣需要循環寫入;
buffer.flip();
while (buffer.hasRemaining()) {
socketChannel.write(dest);
}
3、非阻塞模式
非阻塞就是普通套接字的區別了,SocketChannel在非阻塞模式下,許多方法都是可能直接返回不等待的:
1. connect方法,可能沒有完成連接建立就已經返回,需要使用finishConnect,判斷是否完成了連接;
2. read方法,可能沒有讀取任何就返回了(不是到數據末尾),需要通過返回值判斷;
3. write方法,與阻塞模式一樣,需要在循環中寫入;
二、ServerSocketChannel
1、創建ServerSocketC
通過靜態方法open獲取一個實例,並使用bind方法綁定地址:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(1234));
完成綁定後,可以通過accept方法,接受客戶端連接請求,與客戶端建立連接後,獲取到一個SocketChannel實例,通過這個實例與建立連接的客戶端進行通信;
2、非阻塞模式
ServerSocketChannel同樣有非阻塞模式,此時,accept方法在沒有連接請求是,可能返回Null,需要做判斷處理;
三、結合實例
通過ServerSocketChannel 和 SocketChannel實現一個簡單的消息發送例子:
1、服務端
服務端在循環中等待客戶端連接請求(阻塞模式下),對每一個請求使用一個單獨的線程進行通信處理
public class ServerSocketChannelServer {
public static void main(String[] args) throws IOException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 10, 1000, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(100));
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(1234));
while (true) {
SocketChannel socketChannel = serverSocketChannel.accept();
//每個連接使用一個單獨線程處理
if (socketChannel != null) {
executor.submit(new SocketChannelThread(socketChannel));
}
}
}
}
2、處理線程
public class SocketChannelThread implements Runnable {
private SocketChannel socketChannel;
private String remoteName;
public SocketChannelThread(SocketChannel socketChannel) throws IOException {
this.socketChannel = socketChannel;
this.remoteName = socketChannel.getRemoteAddress().toString();
System.out.println("客戶:" + remoteName + " 連接成功!");
}
@Override
public void run() {
ByteBuffer buffer = ByteBuffer.allocate(1024);
ByteBuffer sizeBuffer = ByteBuffer.allocate(4);
StringBuilder sb = new StringBuilder();
byte b[];
while(true) {
try {
sizeBuffer.clear();
int read = socketChannel.read(sizeBuffer);
if (read != -1) {
sb.setLength(0);
sizeBuffer.flip();
int size = sizeBuffer.getInt();
int readCount = 0;
b = new byte[1024];
//讀取已知長度消息內容
while (readCount < size) {
buffer.clear();
read = socketChannel.read(buffer);
if (read != -1) {
readCount += read;
buffer.flip();
int index = 0 ;
while(buffer.hasRemaining()) {
b[index++] = buffer.get();
if (index >= b.length) {
index = 0;
sb.append(new String(b,"UTF-8"));
}
}
if (index > 0) {
sb.append(new String(b,"UTF-8"));
}
}
}
System.out.println(remoteName + ":" + sb.toString());
}
} catch (Exception e) {
System.out.println(remoteName + " 斷線了,連接關閉");
try {
socketChannel.close();
} catch (IOException ex) {
}
break;
}
}
}
}
消息的發送和接收,在消息的前4個字節固定發送後面消息的內容長度,接收時,按照長度接收消息內容,使用這個方式的來解決“消息無邊界”問題;
3、客戶端
使用“分散(Scatter)”方式寫入
public class SocketChanneClient {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress(1234));
while (true) {
Scanner sc = new Scanner(System.in);
String next = sc.next();
sendMessage(socketChannel, next);
}
}
public static void sendMessage(SocketChannel socketChannel, String mes) throws IOException {
if (mes == null || mes.isEmpty()) {
return;
}
byte[] bytes = mes.getBytes("UTF-8");
int size = bytes.length;
ByteBuffer buffer = ByteBuffer.allocate(size);
ByteBuffer sizeBuffer = ByteBuffer.allocate(4);
sizeBuffer.putInt(size);
buffer.put(bytes);
buffer.flip();
sizeBuffer.flip();
ByteBuffer dest[] = {sizeBuffer,buffer};
while (sizeBuffer.hasRemaining() || buffer.hasRemaining()) {
socketChannel.write(dest);
}
}
}
總最後的例子來看,確實與平常的網絡套接字沒有什麼區別,可以看出的最大區別應該就是非阻塞模式了;