[英]Simple non-blocking server
為了編寫即時消息程序,我試圖組成一個簡單的服務器類,它將在自己的線程中運行。
Map<Integer, SelectionKey> keys
的連接的Map<Integer, SelectionKey> keys
與ID相關聯,以便信使線程可以通過ID訪問連接 send_message(int id, String msg)
我目前的方法主要基於這個例子: 一個帶有Java nio的簡單非阻塞Echo服務器 。
我還使用了使用選擇器來管理非阻塞套接字和已編輯的頁面,以了解非阻塞套接字和選擇器。
package snserver;
/* imports */
//class SNServer (Simple non-blocking Server)
public class SNServer extends Thread {
private int port;
private Selector selector;
private ConcurrentMap<Integer, SelectionKey> keys; // ID -> associated key
private ConcurrentMap<SocketChannel,List<byte[]>> dataMap_out;
ConcurrentLinkedQueue<String> in_msg; //incoming messages to be fetched by messenger thread
public SNServer(int port) {
this.port = port;
dataMap_out = new ConcurrentHashMap<SocketChannel, List<byte[]>>();
keys = new ConcurrentHashMap<Integer, SelectionKey>();
}
public void start_server() throws IOException {
// create selector and channel
this.selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
// bind to port
InetSocketAddress listenAddr = new InetSocketAddress((InetAddress)null, this.port);
serverChannel.socket().bind(listenAddr);
serverChannel.register(this.selector, SelectionKey.OP_ACCEPT);
log("Echo server ready. Ctrl-C to stop.");
// processing
while (true) {
// wait for events
this.selector.select();
// wakeup to work on selected keys
Iterator keys = this.selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = (SelectionKey) keys.next();
// this is necessary to prevent the same key from coming up
// again the next time around.
keys.remove();
if (! key.isValid()) {
continue;
}
if (key.isAcceptable()) {
this.accept(key);
}
else if (key.isReadable()) {
this.read(key);
}
else if (key.isWritable()) {
this.write(key);
}
else if(key.isConnectable()) {
this.connect(key);
}
}
}
}
private void accept(SelectionKey key) throws IOException {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel channel = serverChannel.accept();
channel.configureBlocking(false);
send_message(key, "Welcome."); //DEBUG
Socket socket = channel.socket();
SocketAddress remoteAddr = socket.getRemoteSocketAddress();
log("Connected to: " + remoteAddr);
// register channel with selector for further IO
dataMap_out.put(channel, new ArrayList<byte[]>());
channel.register(this.selector, SelectionKey.OP_READ);
//store key in 'keys' to be accessable by ID from messenger thread //TODO first get ID
keys.put(0, key);
}
//TODO verify, test
public void init_connect(String addr, int port){
try {
SocketChannel channel = createSocketChannel(addr, port);
channel.register(this.selector, channel.validOps()/*, SelectionKey.OP_?*/);
}
catch (IOException e) {
//TODO handle
}
}
//TODO verify, test
private void connect(SelectionKey key) {
SocketChannel channel = (SocketChannel) key.channel();
try {
channel.finishConnect(); //try to finish connection - if 'false' is returned keep 'OP_CONNECT' registered
//store key in 'keys' to be accessable by ID from messenger thread //TODO first get ID
keys.put(0, key);
}
catch (IOException e0) {
try {
//TODO handle ok?
channel.close();
}
catch (IOException e1) {
//TODO handle
}
}
}
private void read(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(8192);
int numRead = -1;
try {
numRead = channel.read(buffer);
}
catch (IOException e) {
e.printStackTrace();
}
if (numRead == -1) {
this.dataMap_out.remove(channel);
Socket socket = channel.socket();
SocketAddress remoteAddr = socket.getRemoteSocketAddress();
log("Connection closed by client: " + remoteAddr); //TODO handle
channel.close();
return;
}
byte[] data = new byte[numRead];
System.arraycopy(buffer.array(), 0, data, 0, numRead);
in_msg.add(new String(data, "utf-8"));
}
private void write(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
List<byte[]> pendingData = this.dataMap_out.get(channel);
Iterator<byte[]> items = pendingData.iterator();
while (items.hasNext()) {
byte[] item = items.next();
items.remove();
//TODO is this correct? -> re-doing write in loop with same buffer object
ByteBuffer buffer = ByteBuffer.wrap(item);
int bytes_to_write = buffer.capacity();
while (bytes_to_write > 0) {
bytes_to_write -= channel.write(buffer);
}
}
key.interestOps(SelectionKey.OP_READ);
}
public void queue_data(SelectionKey key, byte[] data) {
SocketChannel channel = (SocketChannel) key.channel();
List<byte[]> pendingData = this.dataMap_out.get(channel);
key.interestOps(SelectionKey.OP_WRITE);
pendingData.add(data);
}
public void send_message(int id, String msg) {
SelectionKey key = keys.get(id);
if (key != null)
send_message(key, msg);
//else
//TODO handle
}
public void send_message(SelectionKey key, String msg) {
try {
queue_data(key, msg.getBytes("utf-8"));
}
catch (UnsupportedEncodingException ex) {
//is not thrown: utf-8 is always defined
}
}
public String get_message() {
return in_msg.poll();
}
private static void log(String s) {
System.out.println(s);
}
@Override
public void run() {
try {
start_server();
}
catch (IOException e) {
System.out.println("IOException: " + e);
//TODO handle exception
}
}
// Creates a non-blocking socket channel for the specified host name and port.
// connect() is called on the new channel before it is returned.
public static SocketChannel createSocketChannel(String hostName, int port) throws IOException {
// Create a non-blocking socket channel
SocketChannel sChannel = SocketChannel.open();
sChannel.configureBlocking(false);
// Send a connection request to the server; this method is non-blocking
sChannel.connect(new InetSocketAddress(hostName, port));
return sChannel;
}
}
我的問題:上面的代碼是正確的還是好的,或者我應該改變什么? 如何正確實現上面提到的要求? 另請注意我的“TODO”。
感謝您的任何幫助!
這里有幾個問題。
您沒有檢查write()的結果。 它可以從零開始返回任何內容。 您可能不得不多次重做。
如果finishConnect()返回false則不是錯誤,它還沒有完成,所以只需保持OP_CONNECT注冊並等待它(再次)觸發。 您剛剛通過SocketChannel.open()創建的SocketChannel的唯一validOps()是OP_CONNECT。 如果finishConnect()拋出異常,那就是錯誤,您應該關閉該通道。
關閉頻道取消密鑰,您不必自己取消密鑰。
通常,綁定時應使用null作為本地InetAddress。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.