-
Notifications
You must be signed in to change notification settings - Fork 0
/
NioEchoServer.java
112 lines (102 loc) · 4.52 KB
/
NioEchoServer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package org.evgeniy.ua.server;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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.nio.charset.StandardCharsets;
import java.util.Iterator;
public class NioEchoServer implements Server {
// Use the same byte buffer for all channels. A single thread is
// servicing all the channels, so no danger of concurrent access.
private static ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
private final int port;
public NioEchoServer(int port) {
this.port = port;
}
@Override
public void start() throws Exception {
//create socket channel, selector, bind server on port and register in selector
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverSocketChannel.socket();
Selector selector = Selector.open();
serverSocket.bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//consume requests
while (true) {
//blocking call
int readyChannels = selector.select();
//still need to do check as another thread can invoke Selector#wakeup
if (readyChannels > 0) {
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isValid()) {
try {
if (key.isAcceptable()) {
//safe cast as only ServerSocketChannel support OP_ACCEPT operation
//also for this sample channel is our serverSocketChannel reference
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel client = channel.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
writeWelcomeMessage(client);
}
if (key.isReadable()) {
SocketChannel channel = (SocketChannel) key.channel();
writeReadDataMessage(channel);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
private void writeWelcomeMessage(SocketChannel channel) throws Exception {
//clear buffer
buffer.clear();
//put content
buffer.put(String.format(WELCOME_MESSAGE_TEMPLATE, channel.getRemoteAddress()).getBytes());
//make buffer readable
buffer.flip();
//write to channel
channel.write(buffer);
}
private void writeReadDataMessage(SocketChannel channel) throws Exception {
int count;
buffer.clear();
// Loop while data is available; channel is nonblocking
// We can also remove loop and store data in local map
// than when we read all bytes (for example we read end-line
// special symbol for our custom protocol), then we write
// data to client
while ((count = channel.read(buffer)) > 0) {
buffer.flip();
String clientMessage = StandardCharsets.UTF_8.decode(buffer).toString();
String response = String.format(READ_DATA_MESSAGE_TEMPLATE, clientMessage);
ByteBuffer bb = ByteBuffer.wrap(response.getBytes());
// Send the data; don't assume it goes all at once
while (bb.hasRemaining()) {
channel.write(bb);
}
// WARNING: the above loop is evil. You
// should put in local map (for example)
// and continue loop over selection keys,
// than you will again be here and can
// write chunk of data to channel,
// if hasRemaining = false, than you wrote
// all data to client.
buffer.clear();
}
if (count < 0) {
// Close channel on EOF, invalidates the key
channel.close();
}
}
}