I had developed a server channel socket using below code:
public class EchoServer {
private static final int BUFFER_SIZE = 1024;
private final static int DEFAULT_PORT = 9090;
private long numMessages = 0;
private long loopTime;
private InetAddress hostAddress = null;
private int port;
private Selector selector;
// The buffer into which we'll read data when it's available
private ByteBuffer readBuffer = ByteBuffer.allocate(BUFFER_SIZE);
int timestamp=0;
public EchoServer() throws IOException {
this(DEFAULT_PORT);
}
public EchoServer(int port) throws IOException {
this.port = port;
hostAddress = InetAddress.getByName("127.0.0.1");
selector = initSelector();
loop();
}
private Selector initSelector() throws IOException {
Selector socketSelector = SelectorProvider.provider().openSelector();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
InetSocketAddress isa = new InetSocketAddress(hostAddress, port);
serverChannel.socket().bind(isa);
serverChannel.register(socketSelector, SelectionKey.OP_ACCEPT);
return socketSelector;
}
private void loop() {
for (;true;) {
try {
selector.select();
Iterator<SelectionKey> selectedKeys = selector.selectedKeys()
.iterator();
while (selectedKeys.hasNext()) {
SelectionKey key = selectedKeys.next();
selectedKeys.remove();
if (!key.isValid()) {
continue;
}
// Check what event is available and deal with it
if (key.isAcceptable()) {
accept(key);
} else if (key.isWritable()) {
write(key);
}
}
Thread.sleep(3000);
timestamp+=3;
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}
private void accept(SelectionKey key) throws IOException {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
socketChannel.setOption(StandardSocketOptions.TCP_NODELAY, true);
// socketChannel.register(selector, SelectionKey.OP_READ);
socketChannel.register(selector, SelectionKey.OP_WRITE);
System.out.println("Client is connected");
}
private void write(SelectionKey key) throws IOException {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer dummyResponse = ByteBuffer.wrap(("ok:" + String.valueOf(timestamp)) .getBytes("UTF-8"));
socketChannel.write(dummyResponse);
if (dummyResponse.remaining() > 0) {
System.err.print("Filled UP");
}
System.out.println("Message Sent");
// key.interestOps(SelectionKey.OP_READ);
}
}
As you can see I run it on localhost port 9090. In order to testing the code for heavy connections I developed a test app that each second run a new thread and connect to the server. This is the code of my test app:
public class Main {
/**
* #param args
*/
public static void main(String[] args) {
int i = 0;
try {
while (i < 10000) {
RunnableDemo temp = new RunnableDemo("Thread-"
+ String.valueOf(i));
temp.start();
i++;
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class RunnableDemo implements Runnable {
private Thread t;
private String threadName;
InetAddress host = null;
int port = 9090;
RunnableDemo(String name) {
threadName = name;
System.err.println("Creating " + threadName);
}
public void run() {
System.err.println("Running " + threadName);
try {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress(host, port));
while (!socketChannel.finishConnect())
;
System.out.println("Thread " + threadName + " Connected");
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
if (socketChannel.read(buffer) > 0) {
buffer.flip();
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes);
System.out.println(threadName+ ":" + new String(bytes));
buffer.clear();
}
}
} catch (Exception e) {
System.out.println("Thread " + threadName + " interrupted.");
e.printStackTrace();
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start() {
System.out.println("Starting " + threadName);
try {
host = InetAddress.getByName("127.0.0.1");
if (t == null) {
t = new Thread(this, threadName);
t.start();
}
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
The test application runs but only 75 threads can connect to the server and all thread after 75th shows below exception:
java.net.ConnectException: Connection refused: connect
at sun.nio.ch.Net.connect0(Native Method)
at sun.nio.ch.Net.connect(Unknown Source)
at sun.nio.ch.Net.connect(Unknown Source)
at sun.nio.ch.SocketChannelImpl.connect(Unknown Source)
at net.behboodi.client.RunnableDemo.run(Main.java:48)
at java.lang.Thread.run(Unknown Source)
Is there any limits for number of concurrent connections to the socket? Or is there any limits on using port 127.0.0.1 and local host? The other idea is that maybe a java app or JVM can not create more than 75 thread.
I searched about them all, but do not find any answer that show me what of the above reasons is my main problem and how can I fixed the code so that I can test the app with more than 10000 concurrent thread?
Get rid of the sleep. The Selector already blocks. Sleeping in network code is just literally a waste of time. NB (1) You don't call finishConnect() in blocking mode, (2) you aren't connecting threads to a ServerChannel, you are connecting client sockets to a server socket, (3) your client code doesn't handle end of stream, and (4) this is not an echo server.
– EJP
Related
This is a simple server, I used nc as clients to connect to the server, the first client went through and entered the acceptHandler. However, the second client cannot trigger the select to return a number greater than 0 (the return value of select is 0). I see that removing the processed key will resolve the issue, what I don't understand is that why a new connection cannot trigger an event when the serverSocket is still registered with the selector
static ServerSocketChannel server;
static Selector selector;
public static void main(String[] args) throws IOException {
server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(9090));
server.configureBlocking(false);
selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
int num;
while ((num = selector.select()) > 0) {
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = selectionKeys.iterator();
while (iter.hasNext()) {
SelectionKey sk = iter.next();
// iter.remove();
if (sk.isAcceptable()) {
acceptHandler(sk);
} else if (sk.isReadable()) {
readHandler(sk);
}
}
}
}
}
public static void acceptHandler(SelectionKey sk) {
System.out.println("accept handle");
ServerSocketChannel server = (ServerSocketChannel) sk.channel();
SocketChannel client = null;
try {
client = server.accept(); // return null if no pending connections
if (client != null) {
client.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(65535);
client.register(selector, SelectionKey.OP_READ, buffer);
}
} catch (IOException e) {
e.printStackTrace();
}
}
I have the following issue connecting to a AccessGard (newnet) solution that forwards TPC messages to my application from a POS.
Basically the AG automatically connects from an ip "X.X.X.2" to my pooled server but it never sends any data.
When the POS send the message for some reason the AG sends the TPC request from another IP "X.X.X.132" but it never triggers the serverSocket.accept()
With wiresharck I can see Keep Alive messages from the X.X.X.2 to my server every second. Also I can see the request incoming from ip "X.X.X.132" but it never reaches the server. All the incoming transmissions come to the same port.
here is my server :
public class Server2 {
protected int serverPort = 8005;
protected ServerSocket serverSocket = null;
protected boolean isStopped = false;
protected Thread runningThread= null;
protected ExecutorService threadPool = Executors.newFixedThreadPool(10);
public Server2()
{}
public void run(){
openServerSocket();
while(! isStopped()){
Socket clientSocket = null;
try {
clientSocket = this.serverSocket.accept();
} catch (IOException e) {
if(isStopped()) {
System.out.println("Server Stopped.") ;
break;
}
throw new RuntimeException(
"Error accepting client connection", e);
}
this.threadPool.execute(
new WorkerRunnable(clientSocket,
"Thread Pooled Server"));
}
this.threadPool.shutdown();
System.out.println("Server Stopped.") ;
}
private void openServerSocket() {
try {
this.serverSocket = new ServerSocket(this.serverPort);
} catch (IOException e) {
throw new RuntimeException("Cannot open port 8005", e);
}
}
private synchronized boolean isStopped() {
return this.isStopped;
}
}
here the worker:
public class WorkerRunnable implements Runnable
{
private static final Logger logger = Logger.getLogger(WorkerRunnable.class);
protected Socket connectionSocket = null;
protected String serverText = null;
public WorkerRunnable(Socket connectionSocket, String serverText) {
this.connectionSocket = connectionSocket;
this.serverText = serverText;
}
public void run() {
try {
System.out.println(read());
} catch (IOException e) {
//report exception somewhere.
e.printStackTrace();
}
}
private String read() throws IOException
{
InputStream in = connectionSocket.getInputStream();
byte[] m = new byte[2];
in.read(m,0,2);
ByteBuffer wrapped = ByteBuffer.wrap(m);
short num = wrapped.getShort();
logger.info("IN message length:" + num +" Hexa:" + String.format("%02x", m[0]) + String.format("%02x", m[1])); System.out.println("IN message length:" + num +" Hexa:" + String.format("%02x", m[0]) + String.format("%02x", m[1]));
byte[] message = new byte[num];
in.read(message,0,num);
String inMessage = Util.bytesToHex(message);
logger.info("Full message:" + inMessage); System.out.println("Full message:" + inMessage );
return inMessage;
}
}
Update
I tried the same implementation, but this time with ServerSocketChannel/SocketChannel. This works on Android. It seems that I am somehow having 100% packet loss over UDP on Android. Does anyone have thoughts on the cause for this? (It is not a WiFi-issue. The UDP client works from my notebook.)
Original
I am having trouble receiving packets in an Android application. The Android application is able to send data to the desktop server, who replies immediately, but always receives zero bytes. The same code works when I use it as a desktop client application.
My Android application has the INTERNET permission. I also tried it with NETWORK, CHANGE_WIFI_MULTICAST_STATE, ACCESS_WIFI_STATE and ACCESS_NETWORK_STATE. That made no difference, sadly.
Tried it both on a device and in an emulator.
Below is the code and a sample of the output.
MainActivity:
private Thread thread;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
thread = new Thread(new Client());
thread.start();
}
Client:
public class Client implements Runnable {
private static final String LOG_TAG = "NET";
private static final String IP = "--.---.---.--";
private static final int PORT = 6543;
private final ByteBuffer byteBuffer = ByteBuffer.allocate(256);
private boolean running;
public Client() {
running = false;
}
#Override
public void run() {
try {
final Selector selector = Selector.open();
final DatagramChannel datagramChannel = DatagramChannel.open();
datagramChannel.configureBlocking(false);
datagramChannel.register(selector, SelectionKey.OP_WRITE);
datagramChannel.connect(new InetSocketAddress(IP, PORT));
running = true;
while (running) {
selector.select();
final Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
final SelectionKey key = keys.next();
if (key.isReadable()) {
handleRead(key);
}
if (key.isValid() && key.isWritable()) {
handleWrite(key);
}
keys.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void handleRead(final SelectionKey key) throws IOException {
final DatagramChannel channel = (DatagramChannel) key.channel();
byteBuffer.clear();
final SocketAddress from = channel.receive(byteBuffer);
byteBuffer.flip();
Log.i(LOG_TAG, String.format("Received %d bytes from %s", byteBuffer.limit(), from));
key.interestOps(SelectionKey.OP_WRITE);
}
private void handleWrite(final SelectionKey key) throws IOException {
final DatagramChannel channel = (DatagramChannel) key.channel();
byteBuffer.clear();
byteBuffer.putInt(1234);
byteBuffer.flip();
final SocketAddress to = new InetSocketAddress(IP, PORT);
final int bytes = channel.send(byteBuffer, to);
Log.i(LOG_TAG, String.format("Send %d bytes to %s", bytes, to));
key.interestOps(SelectionKey.OP_READ);
}
}
Server:
public class Server implements Runnable {
private static final int PORT = 6543;
private final ByteBuffer byteBuffer = ByteBuffer.allocate(256);
private SocketAddress from;
private boolean running;
public Server() {
from = null;
running = false;
}
#Override
public void run() {
try {
final Selector selector = Selector.open();
final DatagramChannel datagramChannel = DatagramChannel.open();
datagramChannel.configureBlocking(false);
datagramChannel.socket().setReuseAddress(true);
datagramChannel.register(selector, SelectionKey.OP_READ);
datagramChannel.bind(new InetSocketAddress(PORT));
running = true;
while (running) {
selector.selectNow();
final Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
final SelectionKey key = keys.next();
if (key.isReadable()) {
handleRead(key);
}
if (key.isValid() && key.isWritable()) {
handleWrite(key);
}
keys.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void handleRead(final SelectionKey key) throws IOException {
final DatagramChannel channel = (DatagramChannel) key.channel();
byteBuffer.clear();
from = channel.receive(byteBuffer);
byteBuffer.flip();
System.out.println(String.format("Received %d bytes from %s", byteBuffer.limit(), from));
key.interestOps(SelectionKey.OP_WRITE);
}
private void handleWrite(final SelectionKey key) throws IOException {
final DatagramChannel channel = (DatagramChannel) key.channel();
if (from != null) {
byteBuffer.clear();
byteBuffer.putInt(1234);
byteBuffer.flip();
final int bytes = channel.send(byteBuffer, from);
System.out.println(String.format("Send %d bytes to %s", bytes, from));
}
key.interestOps(SelectionKey.OP_READ);
}
public static void main(String args[]) {
new Thread(new Server()).start();
}
}
Client output:
Send 4 bytes to /--.---.---.--:6543
Received 0 bytes from null
Send 4 bytes to /--.---.---.--:6543
Received 0 bytes from null
Server output:
Received 4 bytes from /--.---.---.--:52974
Send 4 bytes to /--.---.---.--:52974
Received 4 bytes from /--.---.---.--:52974
Send 4 bytes to /--.---.---.--:52974
I recently started to make a 2d java game, now I began the TCP server, though the server runs insanely slow (Average of 2 seconds) and I can't figure out how to stop the input stream from metering all the data into one string. I would greatly appreciate it if someone is able to help me.
ServerCode:
package com.diedericksclan.main.network;
import java.io.*;
import java.net.*;
import java.util.ArrayList;
public class ServerThread extends Thread {
private ServerHandler server;
private ServerSocket dataSocket;
private Socket socket;
private InetSocketAddress address;
private int megabyte = 1024 * 1024;
private int dedicated = 1024;
public int RAM = megabyte * dedicated;
private WriteData send;
private ReadData read;
public ServerThread(ServerHandler server, String serverIP, int ram, int backlog) throws Exception {
this.server = server;
this.dedicated = ram;
//System.out.println(serverIP);
String ip = "localhost";
int port = 2048;
if(serverIP.contains(":")) {
ip = serverIP.split(":")[0];
port = Integer.parseInt(serverIP.split(":")[1]);
} else {
ip = serverIP;
port = 2048;
}
//System.out.println("Makin' the server");
this.dataSocket = new ServerSocket(port, backlog, InetAddress.getByName(ip));
this.address = new InetSocketAddress(dataSocket.getInetAddress(), port);
this.send = new WriteData();
this.read = new ReadData();
//System.out.println("Makin' the data handlers");
//System.out.println("Server has been made, details: " + address.getAddress() + ":" + address.getPort());
}
public ServerThread(ServerHandler server, String ip) throws Exception {
this(server, ip, 1024, 0);
}
public void run() {
//System.out.println("made");
this.send.start();
this.read.start();
while(true) {
try {
socket = dataSocket.accept();
socket.setReceiveBufferSize(megabyte);
socket.setSendBufferSize(megabyte);
socket.setTcpNoDelay(true);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void sendData(byte[] data, InetAddress IPaddress, int port) {
this.send.sendData(data, IPaddress, port);
}
public void serverShutdown() {
try {
this.dataSocket.close();
if(this.socket != null) this.socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public class WriteData extends Thread {
public WriteData() {}
public void sendData(byte[] data, InetAddress IPaddress, int port) {
try {
System.out.println("[" + System.currentTimeMillis() + "] Sending... " + new String(data));
socket.getOutputStream().write(data);
socket.getOutputStream().flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class ReadData extends Thread {
public ReadData() {}
public void run() {
try {
this.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
byte[] data;
while(true) {
try {
data = new byte[megabyte];
socket.getInputStream().read(data);
System.out.println("[" + System.currentTimeMillis() + "] Server has read, " + new String(data) + ", details: " + socket.getLocalAddress().getHostName() + ":" + socket.getLocalPort());
server.parsePacket(data, socket.getInetAddress(), socket.getPort());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
ClientCode:
package com.diedericksclan.main.network;
import java.io.*;
import java.net.*;
public class ClientThread extends Thread {
private ClientHandler client;
private Socket socket;
private InetSocketAddress address;
private int megabyte = 1024 * 1024;
private WriteData send;
private ReadData read;
public ClientThread(ClientHandler client) {
this.client = client;
this.address = new InetSocketAddress("192.168.1.2", 2048);
socket = new Socket();
try {
socket.setSendBufferSize(megabyte);
socket.setSendBufferSize(megabyte);
socket.setTcpNoDelay(true);
socket.connect(address);
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//System.out.println("Made client");
this.send = new WriteData();
this.read = new ReadData();
//System.out.println("Client has been made, details: " + socket.getLocalAddress() + ":" + socket.getLocalPort());
}
public void run() {
//System.out.println("made");
this.send.start();
this.read.start();
}
public void sendData(byte[] data) {
this.send.sendData(data);
}
public void serverShutdown() {
try {
this.socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public class WriteData extends Thread {
public WriteData() {}
public void sendData(byte[] data) {
try {
//System.out.println("[" + System.currentTimeMillis() + "] Sending... " + new String(data) + " to: " + socket.getInetAddress() + ":" + socket.getPort());
socket.getOutputStream().write(data);
socket.getOutputStream().flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class ReadData extends Thread {
public ReadData() {}
public void run() {
byte[] data;
while(true) {
try {
data = new byte[megabyte];
socket.getInputStream().read(data);
System.out.println("[" + System.currentTimeMillis() + "] Server data recived, " + new String(data).trim());
client.parsePacket(data, socket.getInetAddress(), socket.getPort());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
I did try to improve speed by making 2 separate threads for reading and writing data, in both the client and server, yet there was no improvement,
You have a few problems.
you allow any number of threads to write to the same socket at the same time. This makes developing a protocol very hard.
you need a protocol so you know where a message starts and end. e.g. you send the length first.
you ignore how many bytes where read. The minimum will be 1 and you can get any number of messages up to the size of the buffer at once. TCP is a stream protocol, not a messaging protocol.
If you have a reader and writer process on the same machine you should be able to get the latency to around 10 micro-seconds. (0.000010 seconds)
EDIT here is a simple example
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class PlainIOSample {
static final int RUNS = 1000000;
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(0);
DataSocket ds = new DataSocket(new Socket("localhost", ss.getLocalPort()));
DataSocket ds2 = new DataSocket(ss.accept());
long start = System.nanoTime();
for (int i = 0; i < RUNS; i++) {
// send a small message
ds.write(new byte[64]);
// receive the same message
byte[] bytes = ds2.read();
if (bytes.length != 64)
throw new AssertionError();
}
long time = System.nanoTime() - start;
System.out.printf("Average time to send/recv was %.1f micro-seconds%n",
time / RUNS / 1e3);
ds.close();
ds2.close();
}
static class DataSocket implements Closeable {
private final DataOutputStream dos;
private final DataInputStream dis;
private final Socket socket;
public DataSocket(Socket socket) throws IOException {
dos = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
dis = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
this.socket = socket;
}
public void write(byte[] message) throws IOException {
synchronized (dos) {
dos.writeInt(message.length);
dos.write(message);
dos.flush();
}
}
public byte[] read() throws IOException {
synchronized (dis) {
int length = dis.readInt();
byte[] bytes = new byte[length];
dis.readFully(bytes);
return bytes;
}
}
#Override
public void close() throws IOException {
socket.close();
}
}
}
prints
Average time to send/recv was 3.3 micro-seconds
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
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.*;
public class EchoServer {
private InetAddress addr;
private int port;
private Selector selector;
private Map<SocketChannel,List<byte[]>> dataMap;
public EchoServer(InetAddress addr, int port) throws IOException {
this.addr = addr;
this.port = port;
dataMap = new HashMap<SocketChannel,List<byte[]>>();
startServer();
}
private void startServer() throws IOException {
// create selector and channel
this.selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
// bind to port
InetSocketAddress listenAddr = new InetSocketAddress(this.addr, 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.doConnect(key);
}
}
}
}
private void doConnect(SelectionKey key) {
SocketChannel channel = (SocketChannel) key.channel();
if (channel.finishConnect()) {
/* success */
System.out.println("Connected");
} else {
/* failure */
System.out.println("failure");
}
}
public void connect(String hostname, int port) throws IOException {
SocketChannel clientChannel = SocketChannel.open();
clientChannel.configureBlocking(false);
clientChannel.connect(new InetSocketAddress(hostname,port));
clientChannel.register(selector,SelectionKey.OP_CONNECT);
clientChannel.write(ByteBuffer.wrap(("$Hello "+UserInfo[0]+"|").getBytes("US-ASCII")));
}
private void accept(SelectionKey key) throws IOException {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel channel = serverChannel.accept();
channel.configureBlocking(false);
// write welcome message
channel.write(ByteBuffer.wrap("Welcome, this is the echo server\r\n".getBytes("US-ASCII")));
Socket socket = channel.socket();
SocketAddress remoteAddr = socket.getRemoteSocketAddress();
log("Connected to: " + remoteAddr);
dataMap.put(channel, new ArrayList<byte[]>()); // register channel with selector for further IO
channel.register(this.selector, SelectionKey.OP_READ);
}
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.remove(channel);
Socket socket = channel.socket();
SocketAddress remoteAddr = socket.getRemoteSocketAddress();
log("Connection closed by client: " + remoteAddr);
channel.close();
key.cancel();
return;
}
byte[] data = new byte[numRead];
System.arraycopy(buffer.array(), 0, data, 0, numRead);
log("Got: " + new String(data, "US-ASCII"));
doEcho(key, data); // write back to client
}
private void write(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
List<byte[]> pendingData = this.dataMap.get(channel);
Iterator<byte[]> items = pendingData.iterator();
while (items.hasNext()) {
byte[] item = items.next();
items.remove();
channel.write(ByteBuffer.wrap(item));
}
key.interestOps(SelectionKey.OP_READ);
}
private void doEcho(SelectionKey key, byte[] data) {
SocketChannel channel = (SocketChannel) key.channel();
List<byte[]> pendingData = this.dataMap.get(channel);
pendingData.add(data);
key.interestOps(SelectionKey.OP_WRITE);
}
private static void log(String s) {
System.out.println(s);
}
public static void main(String[] args) throws Exception {
new EchoServer(null, 8989);
}
}
The program works with incoming connections. But when I make an outgoing connection, the program does not work. I need to make some connections in a row through the connect (String hostname, int port) and receive data in a method read(). The program stops working on the line clientChannel.register(...)
You need to check for a connectable key, e.g.
if (key.isConnectable()) {
this.doConnect(key);
}
...
private void doConnect(SelectionKey key) {
SocketChannel channel = (SocketChannel) key.channel();
if (channel.finishConnect()) {
/* success */
} else {
/* failure */
}
}
Use SocketChannel.finishConnect to determine whether the connection was established successfully.
This is my NIO Client example Ive been using. It gives a timed out open/write/read functions and is suitable for request-response messaging.
Tricky part is always how do parties recognize a packet is fully received. This example assumes
Server gets one_line_command+newline (client->server packet)
Client receives 1..n lines with ">>" terminator line without trailing newline in a terminator line (server->client packet)
you could specify terminator to be ">>\n" but readUntil needs small fix in a newline parser
You could write 4-byte length header, fixed size packet splitter or delimiter 0x27 byte but make sure it cannot be a value of data payload. NIO read() or write() never assumes you receive a full packet in one call. Or may read two or more packet bytes in one read() buffer. It is up to us make a packet parser not loosing bytes.
import java.util.*;
import java.io.*;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
public class DPSocket {
private boolean debug;
private String host;
private int port;
private String charset;
private ByteArrayOutputStream inBuffer;
private ByteBuffer buf;
private Selector selector;
private SocketChannel channel;
public DPSocket(String host, int port, String charset) {
this.charset = charset==null || charset.equals("") ? "UTF-8" : charset;
this.host = host;
this.port = port;
}
public boolean isDebug() { return debug; }
public void setDebug(boolean b) { debug=b; }
public void open(long timeout) throws IOException {
selector = Selector.open();
channel = SocketChannel.open();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_CONNECT);
channel.connect(new InetSocketAddress(host, port));
inBuffer = new ByteArrayOutputStream(1024);
buf = ByteBuffer.allocate(1*1024);
long sleep = Math.min(timeout, 1000);
while(timeout > 0) {
if (selector.select(sleep) < 1) {
timeout-=sleep;
continue;
}
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while(keys.hasNext()) {
SelectionKey key = keys.next();
keys.remove();
if (!key.isValid() || !key.isConnectable()) continue;
SocketChannel channel = (SocketChannel)key.channel();
if (channel.isConnectionPending()) {
channel.finishConnect();
channel.configureBlocking(false);
if (debug) System.out.println("finishConnect");
return; // we are ready to receive bytes
}
}
}
throw new IOException("Connection timed out");
}
public void close() {
try { if(channel!=null) channel.close(); } catch(Exception ex) { }
try { if(selector!=null) selector.close(); } catch(Exception ex) { }
inBuffer=null;
buf=null;
}
public void write(String data, long timeout) throws IOException {
write(data.getBytes(charset), timeout);
}
public void write(byte[] bytes, long timeout) throws IOException {
ByteBuffer outBuffer = ByteBuffer.wrap(bytes);
channel.register(selector, SelectionKey.OP_WRITE);
long sleep = Math.min(timeout, 1000);
while(timeout > 0) {
if (selector.select(sleep) < 1) {
timeout-=sleep;
continue;
}
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while(keys.hasNext()) {
SelectionKey key = keys.next();
keys.remove();
if (!key.isValid() || !key.isWritable()) continue;
SocketChannel channel = (SocketChannel)key.channel();
if (debug) System.out.println("write remaining="+outBuffer.remaining());
channel.write(outBuffer);
if (debug) System.out.println("write remaining="+outBuffer.remaining());
if (outBuffer.remaining()<1)
return;
}
}
throw new IOException("Write timed out");
}
public List<String> readUntil(String terminator, long timeout, boolean trimLines) throws IOException {
return readUntil(new String[]{terminator}, timeout, trimLines);
}
public List<String> readUntil(String[] terminators, long timeout, boolean trimLines) throws IOException {
List<String> lines = new ArrayList<String>(12);
inBuffer.reset();
// End of packet terminator strings, line startsWith "aabbcc" string.
byte[][] arrTerminators = new byte[terminators.length][];
int[] idxTerminators = new int[terminators.length];
for(int idx=0; idx < terminators.length; idx++) {
arrTerminators[idx] = terminators[idx].getBytes(charset);
idxTerminators[idx] = 0;
}
int idxLineByte=-1;
channel.register(selector, SelectionKey.OP_READ);
long sleep = Math.min(timeout, 1000);
while(timeout>0) {
if (selector.select(sleep) < 1) {
timeout-=sleep;
continue;
}
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while(keys.hasNext()) {
SelectionKey key = keys.next();
keys.remove();
if (!key.isValid() || !key.isReadable()) continue;
SocketChannel channel = (SocketChannel)key.channel();
buf.clear();
int len = channel.read(buf);
if (len == -1) throw new IOException("Socket disconnected");
buf.flip();
for(int idx=0; idx<len; idx++) {
byte cb = buf.get(idx);
if (cb!='\n') {
idxLineByte++;
inBuffer.write(cb);
for(int idxter=0; idxter < arrTerminators.length; idxter++) {
byte[] arrTerminator = arrTerminators[idxter];
if (idxLineByte==idxTerminators[idxter]
&& arrTerminator[ idxTerminators[idxter] ]==cb) {
idxTerminators[idxter]++;
if (idxTerminators[idxter]==arrTerminator.length)
return lines;
} else idxTerminators[idxter]=0;
}
} else {
String line = inBuffer.toString(charset);
lines.add(trimLines ? line.trim() : line);
inBuffer.reset();
idxLineByte=-1;
for(int idxter=0; idxter<arrTerminators.length; idxter++)
idxTerminators[idxter]=0;
}
}
}
}
throw new IOException("Read timed out");
}
// **************************
// *** test socket client ***
// **************************
public static void main(String[] args) throws Exception {
String NEWLINE = "\n";
int TIMEOUT=5000;
DPSocket dps = new DPSocket("myserver.com", 1234, "UTF-8");
dps.setDebug(true);
try {
List<String> lines;
dps.open(15000);
dps.write("Command1 arg1 arg2"+NEWLINE, TIMEOUT);
lines = dps.readUntil(">>", TIMEOUT, true);
dps.write("Command2 arg1 arg2"+NEWLINE, TIMEOUT);
lines = dps.readUntil(">>", TIMEOUT, true);
} catch (Exception ex) {
String msg = ex.getMessage();
if (msg==null) msg = ex.getClass().getName();
if (msg.contains("timed out") || msg.contains("Invalid command ")) {
System.out.println("ERROR: " + ex.getMessage());
} else {
System.out.print("ERROR: ");
ex.printStackTrace();
}
} finally {
dps.close();
}
}
}