SSH port fowarding with SSHj - java

I am trying to create a tunnel to use a service behind a firewall, that supports SSH. I wanted a complete solution in java, but I cannot seem to get it to work. I found this github snip and based on that I created the following code to keep a background thread giving me the tunnel:
// property on surrounding class
// static final SSHClient sshclient = new SSHClient();
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
try {
String host = "10.0.3.96";
sshclient.useCompression();
sshclient.addHostKeyVerifier("30:68:2a:20:21:9f:c8:e8:ac:b4:a7:fc:2d:a7:d0:26");
sshclient.connect(host);
sshclient.authPassword("messy", "messy");
if (!sshclient.isAuthenticated()) {
throw new RuntimeException(String.format("Unable to authenticate against '%s'", host));
}
Forward forward = new Forward(8111);
InetSocketAddress addr = new InetSocketAddress("google.com", 80);
SocketForwardingConnectListener listener = new SocketForwardingConnectListener(addr);
sshclient.getRemotePortForwarder().bind(forward, listener);
sshclient.getTransport().setHeartbeatInterval(30);
HttpURLConnection con = (HttpURLConnection) new URL("http://localhost:8111").openConnection();
con.setRequestMethod("GET");
BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
String line = null;
while( (line = reader.readLine()) != null) {
System.out.println(line);
}
sshclient.getTransport().join();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (sshclient != null && sshclient.isConnected()) {
sshclient.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
thread.setDaemon(true);
thread.start();
The problem is that if I connect to a remote SSH server like '10.0.3.96' it does not work. If I use the local SSH server on my localhost it will work. I have been going thru different configurations with any luck and tried debugging, but I cannot grasp what is going on inside the SSHj package.
Now it does not have to be a SSHj solution, but that would be preferred since other parts of the code are fully implemented and using SSHj and I do not want to mix two SSH packages in one project.
Thanks for any help.

Try something like this. It takes in a list of servers to connect to. It will tunnel each intermediate connection to the last server. I have not tested with more than 2 servers, but it should work. This answer was adapted from the overthere project and written in groovy. You should only need imports to get it working in groovyconsole.
#Grab(group='net.schmizz', module='sshj', version='0.8.1')
#Grab(group='org.bouncycastle', module='bcprov-jdk16', version='1.46')
//the sequence of hosts that the connections will be made through
def hosts = ["server1", "server2"]
//the starting port for local port forwarding
def startPort = 2222
//username for connecting to all servers
def username = 'user'
def pw = 'pass'
//--------------------------------------------------------------------------//
final TunnelPortManager PORT_MANAGER = new TunnelPortManager()
//list of all active port forwarders
List<PortForwarder> portForwarders = []
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
/**
* Established the actual port forwarder
*/
class PortForwarder extends Thread implements Closeable {
private final SSHClient sshClient;
private final InetSocketAddress remoteAddress;
private final ServerSocket localSocket;
private CountDownLatch latch = new CountDownLatch(1);
public PortForwarder(SSHClient sshClient, InetSocketAddress remoteAddress, ServerSocket localSocket) {
this.sshClient = sshClient;
this.remoteAddress = remoteAddress;
this.localSocket = localSocket;
}
private static String buildName(InetSocketAddress remoteAddress, Integer localPort) {
return "SSH local port forward thread [${localPort}:${remoteAddress.toString()}]"
}
#Override
public void run() {
LocalPortForwarder.Parameters params = new LocalPortForwarder.Parameters("127.0.0.1", localSocket.getLocalPort(),
remoteAddress.getHostName(), remoteAddress.getPort());
LocalPortForwarder forwarder = sshClient.newLocalPortForwarder(params, localSocket);
try {
latch.countDown();
forwarder.listen();
} catch (IOException ignore) {/* OK. */}
}
#Override
public void close() throws IOException {
localSocket.close();
try {
this.join();
} catch (InterruptedException e) {/* OK.*/}
}
}
/**
* Will hand out local ports available for port forwarding
*/
class TunnelPortManager {
final int MAX_PORT = 65536
Set<Integer> portsHandedOut = new HashSet()
ServerSocket leaseNewPort(Integer startFrom) {
for (int port = startFrom; port < MAX_PORT; port++) {
if (isLeased(port)) {
continue;
}
ServerSocket socket = tryBind(port);
if (socket != null) {
portsHandedOut.add(port);
println "handing out port ${port} for local binding"
return socket;
}
}
throw new IllegalStateException("Could not find a single free port in the range [${startFrom}-${MAX_PORT}]...");
}
synchronized void returnPort(ServerSocket socket) {
portsHandedOut.remove(socket.getLocalPort());
}
private boolean isLeased(int port) {
return portsHandedOut.contains(port);
}
protected ServerSocket tryBind(int localPort) {
try {
ServerSocket ss = new ServerSocket();
ss.setReuseAddress(true);
ss.bind(new InetSocketAddress("localhost", localPort));
return ss;
} catch (IOException e) {
return null;
}
}
}
PortForwarder startForwarder(PortForwarder forwarderThread) {
forwarderThread.start();
try {
forwarderThread.latch.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return forwarderThread;
}
SSHClient getSSHClient(username, pw, String hostname, int port=22){
SSHClient client = new SSHClient()
client.addHostKeyVerifier(new PromiscuousVerifier())
client.connect(hostname, port)
client.authPassword(username, pw)
return client
}
int hostCount = hosts.size()
String hostname = hosts[0]
SSHClient client = getSSHClient(username, pw, hostname)
println "making initial connection to ${hostname}"
Session session
//create port forwards up until the final
for (int i=1; i<hostCount; i++){
hostname = hosts[i]
println "creating connection to ${hostname}"
ServerSocket ss = PORT_MANAGER.leaseNewPort(startPort)
InetSocketAddress remoteAddress = new InetSocketAddress(hostname, 22)
PortForwarder forwarderThread = new PortForwarder(client, remoteAddress, ss)
forwarderThread = startForwarder(forwarderThread)
session = client.startSession()
println "adding port forward from local port ${ss.getLocalPort()} to ${remoteAddress.toString()}"
portForwarders.add(forwarderThread)
client = getSSHClient(username, pw, "127.0.0.1", ss.getLocalPort())
}
session = client.startSession()
//shut down the running jboss using the script
Command cmd = session.exec("hostname")
String response = IOUtils.readFully(cmd.getInputStream()).toString()
cmd.join(5, TimeUnit.SECONDS)
println "response -> ${response}"
portForwarders.each { pf ->
pf.close()
}
session.close()
client.disconnect()

Related

java http server implementation and JMEter testing

the control is reaching the put,get,post requests but i am unable to get the reply back from server, the http://localhost:8080 is throwing invalid ip error and jmeter test case is showing error status
I have configured a server as below : public class HttpServer {
private static final int MAX_THREADS = 64;
private static final Map<Integer, AtomicInteger> threadCounts = new ConcurrentHashMap<>();
private static final ExecutorService threadPool = Executors.newFixedThreadPool(MAX_THREADS);
public static void main(String[] args) {
ArrayList<Integer> portNodes;
ArrayList<Integer> timeout;
XMLFileHandler xmlFileHandler = new XMLFileHandler();
xmlFileHandler.readFromXML("config.xml");
portNodes = xmlFileHandler.getPortNodes();
timeout = xmlFileHandler.getTimeout();
for (int i = 0; i < portNodes.size(); i++) {
int port = portNodes.get(i);
int timeouts = timeout.get(i);
if (!threadCounts.containsKey(port)) {
threadCounts.put(port, new AtomicInteger(0));
}
try {
ServerSocket serverSocket = new ServerSocket(port);
serverSocket.setSoTimeout(timeouts);
threadPool.submit(() -> {
while (true) {
try {
Socket socket = serverSocket.accept();
OutputStream os = socket.getOutputStream();
os.write("welcome to server".getBytes());
System.out.println("connected successfully" + port);
threadCounts.get(port).incrementAndGet();
threadPool.submit(new RequestHandler(socket));
}
catch (SocketTimeoutException e) {
System.out.println("Timeout occurred on port: " + port);
}
catch (IOException e) {
e.printStackTrace();
}
}
});
}
catch (BindException e) {
System.out.println("Port " + port + " is already in use. Please choose a different port.");
}catch(IOException e) {
System.out.println("erro"+port);
}
}
}
private static class RequestHandler implements Runnable {
private final Socket socket;
public RequestHandler(Socket socket) {
this.socket = socket;
}
public void run() {
try {
System.out.println("in request handler");
// Handle HTTP request
InputStream input = socket.getInputStream();
OutputStream output = socket.getOutputStream();
// Parse the request
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
String[] requestLine = reader.readLine().split(" ");
String method = requestLine[0];
String url = requestLine[1];
String httpVersion = requestLine[2];
// Handle GET request
if (method.equals("GET")) {
//TODO: Implement handling of GET request
}
// Handle POST request
else if (method.equals("POST")) {
//TODO: Implement handling of POST request
}
// Handle PUT request
else if (method.equals("PUT")) {
//TODO: Implement handling of PUT request
}
else {
// Send error message for unsupported method
output.write("HTTP/1.1 400 Bad Request\r\n\r\n".getBytes());
}
// Close socket and release resources
input.close();
output.close();
socket.close();
threadCounts.get(socket.getPort()).decrementAndGet();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Your "http server" doesn't implement HTTP protocol hence you won't be able to use HTTP Request sampler for conducting the load.
Consider moving either to TCP Sampler or HTTP Raw Request sampler (can be installed using JMeter Plugins Manager)

How to refer to server class located in a different folder/package

I'm trying to implement a multi client server program. I decided to implement each client program in a different folder/package. Similarly, the server program will be in a different package. So, when I'm creating a socket on the client side and need to refer to the server, how do i go about it? If the server was on a different computer, I'd provide the IP address.
Socket clientscoket = new Socket("server.java", 2100);
This works when the server program resides in the same directory. What if the server program resides in a different directory?
EDIT:
import java.io.*;
import java.net.*;
import java.util.HashMap;
class ServerSide {
static private HashMap<String, Socket> clientList = new HashMap<String, Socket>();
public static void main(String argv[]) throws Exception {
ServerSide ss = new ServerSide();
new Listener(argv[0]);
}
}
class Listener implements Runnable {
ServerSocket listenSocket;
HashMap<String, Socket> hm = new HashMap<String, Socket>();
Listener(String prt) throws NumberFormatException, IOException {
String output;
listenSocket = new ServerSocket(Integer.parseInt(prt));
Thread t = new Thread(this);
t.start();
}
public Listener() {
// TODO Auto-generated constructor stub
}
// List<Socket> cList = new ArrayList<Socket>();
// Create a new Thread. One for listening. Another for comms
public void run() {
boolean cont = true;
while (cont) {
Socket connectionSocket;
try {
connectionSocket = listenSocket.accept();
new ClientThread(connectionSocket);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/*
* BufferedReader inFromClient = new BufferedReader(new
* InputStreamReader(connectionSocket.getInputStream()));
* System.out.println("Input Stream created"); String cid =
* inFromClient.readLine(); System.out.println(cid+" connected");
* PrintWriter outToClient = new
* PrintWriter(connectionSocket.getOutputStream());
* outToClient.println("Hello "+cid); clientSentence =
* inFromClient.readLine(); welcomeSocket.close(); cont = false;
*
* System.out.println("Received: " + clientSentence);
* capitalizedSentence = clientSentence.toUpperCase() + '\n';
* outToClient.writeBytes(capitalizedSentence);
*/
}
public HashMap<String, Socket> getMap() {
return hm;
}
}
class ClientThread implements Runnable {
String msg;
Thread t;
BufferedReader ipStream;
boolean flag = true;
Listener l = new Listener();
ClientThread(Socket connectionSocket) throws IOException {
System.out.println("We're here");
t = new Thread(this);
ipStream = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));
if (ipStream.ready()) {
String cid = ipStream.readLine();
t.setName(cid);
System.out.println(t.getName() + " connected");
l.getMap().put(cid, connectionSocket);
t.start();
}
}
public void run() {
try {
while (flag) {
if (ipStream.ready()) {
System.out.println("Stream ready");
msg = ipStream.readLine();
if (msg.equals("close")) {
System.out.println(t.getName() + " disconnected");
flag = false;
break;
} else {
String[] cmd = msg.split("\\s+");
System.out.println(msg);
if (cmd[0].equals("broadcast")) {
broadcast(cmd[1]);
} else if (cmd[0].equals("unicast")) {
unicast(cmd[1], cmd[2]);
} else if (cmd[0].equals("blockcast"))
blockcast(cmd[1], cmd[2]);
else
continue;
}
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void broadcast(String msg) throws IOException {
for (String k : l.getMap().keySet()) {
if (k.equals(t.getName()))
continue;
Socket receiverSocket = new Socket(l.getMap().get(k).getInetAddress(), l.getMap().get(k).getPort());
PrintWriter outToServer = new PrintWriter(receiverSocket.getOutputStream(), true);
outToServer.println(msg);
}
System.out.println(msg);
}
public void unicast(String cID, String msg) {
System.out.println(msg);
}
public void blockcast(String cID, String msg) {
System.out.println(msg);
}
}
So, when I'm creating a socket on the client side and need to refer to
the server, how do i go about it? If the server was on a different
computer, I'd provide the IP address?
You would need to provide the IP address anyhow, you cannot specify a server using it's class name. You always must specify the host address.
Socket clientscoket = new Socket("server.java", 2100);
What you are doing here, is creating a Socket that binds to the host specified by server.java.
If you need to refer to the server, you should try to connect to the port on which the server listens instead of trying to access it using the class name...
How do you implement it.
You can have port numbers assigned to the type of server in a factory class, that can give you [Server]Sockets when you need them.
Class ConnectionFactory
private static final Map<String, Integer> ports = new HashMap<String, Integer>();
static {
// These Servers would listen to these ports.
ports.put("com.stackoverflow.multiconnect.servers.Server1", 5001);
ports.put("com.stackoverflow.multiconnect.servers.Server2", 5502);
ports.put("com.stackoverflow.multiconnect.servers.Server3", 8080);
}
And you can have instances of both your server and client sockets returned from this class.
public static ServerSocket createServerSocket(Class<?> serverClass)
throws IOException {
return createServerSocket(serverClass.getName());
}
public static ServerSocket createServerSocket(String fqcn)
throws IOException {
Integer port = ports.get(fqcn);
if (port == null)
throw new IllegalArgumentException("No server registery found for " + fqcn);
return new ServerSocket(port);
}
public static Socket createClientSocket(Class<?> serverClass)
throws UnknownHostException, IOException {
return createClientSocket(serverClass.getName());
}
public static Socket createClientSocket(String fqcn)
throws UnknownHostException, IOException {
Integer port = ports.get(fqcn);
if (port == null)
throw new IllegalArgumentException("No server registery found for " + fqcn);
return new Socket("localhost", port);
}
Now with this, you can:
//Open a server Socket
ServerSocket server1 = ConnectionFactory.createServerSocket(Server1.class);
// Create a client to connect to a server
Socket client1 = ConnectionFactory.createClientSocket(Server1.class);
A more elaborate and full example could be found here.
Try instead of server.java making use of the whole path of the directory to server.java
So home/user/class/server/server.java

Convert Single Threaded Server into Multi Threaded in JAVA

I need to convert this single threaded Server in Multi Threaded one, so i'm able to handle multiple request from a server:
public class YASGP {
public static void main(String args[]) throws IOException {
ServerSocket server;
try{
server = new ServerSocket(5559);
System.out.println("Listening for connection on port 5559 ....");
while (true) {
Socket clientSocket = server.accept();
InputStreamReader isr = new InputStreamReader(clientSocket.getInputStream());
BufferedReader reader = new BufferedReader(isr);
String line = reader.readLine();
new Thread(new WorkerRunnable(clientSocket)).start();
while (!line.isEmpty()) {
System.out.println("----- " + line);
if (!line.contains("OPTIONS")) {
// System.out.println("Non c'รจ nulla!!!");
} else {
timeS = line.substring(line.indexOf("timeS=") + 6, line.indexOf("&url"));
url = line.substring(line.indexOf("url=") + 4, line.lastIndexOf("&param"));
param = line.substring(line.indexOf("&param=") + 7, line.indexOf("HTTP"));
}
line = reader.readLine();
}
}
}
}catch (IOException e) {
System.out.println("Could not listen on port: 4001");
}
}
private static class RequestHandlingClass {
public RequestHandlingClass(Socket clientSocket) {
}
}
}
How i can convert it? Thanks to all
remove the 'client processing' code from the server as follows
public static void main(String args[]) throws IOException {
ServerSocket server;
try{
server = new ServerSocket(5559);
System.out.println("Listening for connection on port 5559 ....");
while (true) {
Socket clientSocket = server.accept();
new Thread(new WorkerRunnable(clientSocket)).start();
}
}catch (IOException e) {
System.out.println("Could not listen on port: 4001");
}
}
then the code that you lifted out goes in to the run method of the workerRunnable class .
I recommend u use the excutorService api.Since it will manager all your threading issue for u behind the scene
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
//Your code here
});

Java Socketing: My server class constantly takes in input but my client doesn't?

Try to do some concurrent messaging between the server and the client. When they first connect to eachother and the Server sends the test string, the client gets it perfectly fine the first time. And the client can SEND messages just fine to the Server. But my Client class cant constantly check for messages like my Server can and idk what's wrong. Any suggestions?
Server class code:
import java.lang.*;
import java.io.*;
import java.net.*;
import java.util.Random;
import java.util.concurrent.*;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Server {
String testMessage = "You are now connected and can begin chatting!";
boolean connected = false;
int port;
public Server(int port) {
this.port = port;
}
public void Open() {
//creates Threadpool for multiple instances of chatting
final ExecutorService clientProcessingPool = Executors.newFixedThreadPool(10);
Runnable serverTask = new Runnable() {
#Override
public void run() {
try {
System.out.println("Opening...");
ServerSocket srvr = new ServerSocket(port);
while (true) {
Socket skt = srvr.accept();
clientProcessingPool.submit(new ClientTask(skt));
}
} catch (Exception e) {
try {
System.out.println(e);
System.out.print("You're opening too many servers in the same location, fool!\n");
ServerSocket srvr = new ServerSocket(port);
while (true) {
Socket skt = srvr.accept();
clientProcessingPool.submit(new ClientTask(skt));
}
} catch (IOException ex) {
Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
};
Thread serverThread = new Thread(serverTask);
serverThread.start();
}
private class ClientTask implements Runnable {
private final Socket skt;
private ClientTask(Socket skt) {
this.skt = skt;
}
#Override
public void run() {
//for sending messages
if (connected == false) {
System.out.println("======================");
System.out.println("Server has connected!");
processMessage(testMessage);
}
//for receiving messages
while (true) {
try {
// Read one line and output it
BufferedReader br = new BufferedReader(new InputStreamReader(skt.getInputStream()));
String incomingMessage = br.readLine();
if (incomingMessage != null) {
System.out.println("Server: Received message: " + incomingMessage);
processMessage(incomingMessage);
}
//br.close();
//skt.close(); //maybe delete
} catch (Exception e) {
System.out.println("Server had error receiving message.");
System.out.println("Error: " + e);
}
}
}
//for processing a message once it is received
public void processMessage(String message) {
PrintWriter out = null;
try {
out = new PrintWriter(skt.getOutputStream(), true);
} catch (IOException ex) {
System.out.println(ex);
System.out.println("Server had error sending message.");
}
System.out.print("Server: Sending message: " + message + "\n");
out.print(message);
out.flush();
connected = true;
try {
skt.shutdownOutput();
//out.close();
} catch (IOException ex) {
Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
Client class code:
import java.lang.*;
import java.io.*;
import java.net.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
class Client {
public String message;
Socket skt;
public int port;
public Client(int port) {
this.port = port;
}
//for receiving messages from Server
public void receiveMessage() {
final ExecutorService clientProcessingPool = Executors.newFixedThreadPool(10);
Runnable serverTask = new Runnable() {
#Override
public void run() {
try {
skt = new Socket(InetAddress.getLocalHost().getHostName(), port);
while (true) {
clientProcessingPool.submit(new Client.ClientTask(skt));
}
} catch (IOException ex) {
Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
}
}
};
Thread serverThread = new Thread(serverTask);
serverThread.start();
}
//for sending messages to Server
public void sendMessage(String outgoingMessage) throws IOException {
try {
skt = new Socket(InetAddress.getLocalHost().getHostName(), port);
PrintWriter pw = new PrintWriter(skt.getOutputStream());
System.out.println("Client: Sending message: " + outgoingMessage);
pw.print(outgoingMessage);
pw.flush();
skt.shutdownOutput();
//skt.close(); //maybe delete
} catch (Exception e) {
System.out.println(e);
System.out.print("Client had error sending message.\n");
JOptionPane.showMessageDialog(null, "That User is not currently online.", "ERROR!!", JOptionPane.INFORMATION_MESSAGE);
}
}
private class ClientTask implements Runnable {
private final Socket skt;
private ClientTask(Socket skt) {
this.skt = skt;
}
#Override
public void run() {
while (true) {
try {
BufferedReader in = new BufferedReader(new InputStreamReader(skt.getInputStream()));
//while (!in.ready()) {}
String incomingMessage = in.readLine();
if (incomingMessage != null) {
System.out.println("Client: Received message: " + incomingMessage); // Read one line and output it
message = incomingMessage;
}
//skt.shutdownInput();
//in.close();
//skt.close(); //maybe delete
} catch (Exception e) {
System.out.print("Client had error receiving message.\n");
}
}
}
}
}
Streams cannot be re-wrapped. Once assigned to a wrapper, they must use that wrapper for the entire life-cycle of the stream. You also shouldn't close a stream until you are done using it, which in this case isn't until your client and server are done communicating.
In your current code, there are a few times where you re-initialize streams:
while (true) {
try {
//Each loop, this reader will attempt to re-wrap the input stream
BufferedReader br = new BufferedReader(new InputStreamReader(skt.getInputStream()));
String incomingMessage = br.readLine();
if (incomingMessage != null) {
System.out.println("Server: Received message: " + incomingMessage);
processMessage(incomingMessage);
}
//don't close your stream and socket so early!
br.close();
skt.close();
} catch (Exception e) {
//...
}
You get the idea; you can use this knowledge to find the stream problems in your client code as well.
With that said, servers are the middle-man between multiple clients. If you want to be able to type in the server's console to send a message to clients, it shouldn't go to only 1 client (unless you had a system that allowed you to specify a name). You need to store every connection in some kind of collection so when you type in the server's console, it goes to every client that's connected. This also helps when a client wants to send a message to every other client (global message). The server's main thread is primarily for accepting clients; I created another thread to allow you to type in the console.
As for your streams, you should create them whenever you start the ClientTask, both server side and client side:
public class Server {
private ExecutorService executor = Executors.newFixedThreadPool(10);
private Set<User> users = new HashSet<>();
private boolean running;
private int port;
public Server(int port) {
this.port = port;
}
public void start() {
running = true;
Runnable acceptor = () -> {
try(ServerSocket ss = new ServerSocket(port)) {
while(running) {
User client = new User(ss.accept());
users.add(client);
executor.execute(client);
}
} catch(IOException e) {
//if a server is already running on this port;
//if the port is not open;
e.printStackTrace();
}
};
Runnable userInputReader = () -> {
try(Scanner scanner = new Scanner(System.in)) {
while(running) {
String input = scanner.nextLine();
for(User user : users) {
user.send(input);
}
}
} catch(IOException e) {
//problem sending data;
e.printStackTrace();
}
};
Thread acceptorThread = new Thread(acceptor);
Thread userThread = new Thread(userInputReader);
acceptorThread.start();
userThread.start();
}
public void stop() {
running = false;
}
public static void main(String[] args) {
new Server(15180).start();
System.out.println("Server started!");
}
}
In the run() method is where the streams should be wrapped.
class User implements Runnable {
private Socket socket;
private boolean connected;
private DataOutputStream out; //so we can access from the #send(String) method
public User(Socket socket) {
this.socket = socket;
}
public void run() {
connected = true;
try(DataInputStream in = new DataInputStream(socket.getInputStream())) {
out = new DataOutputStream(socket.getOutputStream());
while(connected) {
String data = in.readUTF();
System.out.println("From client: "+data);
//send to all clients
}
} catch(IOException e) {
//if there's a problem initializing streams;
//if socket closes while attempting to read from it;
e.printStackTrace();
}
}
public void send(String message) throws IOException {
if(connected) {
out.writeUTF(message);
out.flush();
}
}
}
It's pretty much the same idea with the client:
1. Connect to Server
2. Create "communication" thread
3. Create "user input" thread (to receive input from console)
4. Start threads
public class Client {
private final String host;
private final int port;
private boolean connected;
private Socket socket;
public Client(String host, int port) {
this.host = host;
this.port = port;
}
public void start() throws IOException {
connected = true;
socket = new Socket(host, port);
Runnable serverInputReader = () -> {
try (DataInputStream in = new DataInputStream(socket.getInputStream())) {
while (connected) {
String data = in.readUTF();
System.out.println(data);
}
} catch (IOException e) {
// problem connecting to server; problem wrapping stream; problem receiving data from server;
e.printStackTrace();
}
};
Runnable userInputReader = () -> {
try (DataOutputStream out = new DataOutputStream(socket.getOutputStream());
Scanner scanner = new Scanner(System.in)) {
while (connected) {
String input = scanner.nextLine();
out.writeUTF(input);
}
} catch (IOException e) {
//problem wrapping stream; problem sending data;
e.printStackTrace();
}
};
Thread communicateThread = new Thread(serverInputReader);
Thread userThread = new Thread(userInputReader);
communicateThread.start();
userThread.start();
}
public static void main(String[] args) throws IOException {
new Client("localhost", 15180).start();
}
}
There are a few things I used in the code above that you may not be familiar with. They help simplify the syntax for your code:
Lambda Expressions - Prevents the need to create an anonymous class (or subclass) to declare a method
Try-With-Resources - Closes the resources specified automatically once the try block as ended
EDIT
When a user connects, you should store their connection by name or id. That way, you can send data to specific users. Even if your client is running on the same machine as the server, it's still the same idea: client connects to server, server sends message to client based on name or id:
while(running) {
User client = new User(ss.accept());
users.add(client); //add to set
executor.execute(client);
}
Right now, you are simply adding users to a Set. There is currently no way to grab a specific value from this set. What you need to do is give it some kind of "key". To give you an idea, here's an old algorithm I used to use. I have an array full of empty slots. When someone connects, I look for the first empty slot. Once an empty slot is found, I pass the user the index of the array it's being stored at (that will be the user's id), then store the user in the array at the specified index. When you need to send a message to someone, you can use the id to access that specific array index, grab the user you want and send a message:
class Server {
private int maxConnections = 10;
private ExecutorService executor = Executors.newFixedThreadPool(maxConnections);
private User[] users = new User[maxConnections];
//...
while(running) {
Socket socket = ss.accept();
for(int i = 0; i < users.length; i++) {
if(users[i] == null) {
users[i] = new User(socket, i);
executor.execute(users[i]);
break;
}
}
}
//...
public static void sendGlobalMessage(String message) throws IOException {
for(User user : users)
if(user != null)
user.send(message);
}
public static void sendPrivateMessage(String message, int id) {
User user = users[id];
if(user != null) {
user.send(message);
}
}
}
class User {
private Socket socket;
private int id;
private DataOutputStream out;
public User(Socket socket, int id) {
this.socket = socket;
this.id = id;
}
public void send(String message) throws IOException {
out.writeUTF(message);
out.flush();
}
public void run() {
DataInputStream in;
//wrap in and out streams
while(connected) {
String data = in.readUTF();
//Server.sendGlobalMessage(data);
//Server.sendPrivateMessage(data, ...);
sendMessage(data); //sends message back to client
}
}
}

Java TCP client-server NAT Connection Refused

I recently started making a 2D java game, and started making a tcp server 2 days ago, now that all my other issues are fixed, I would like to address the fact that the client can only connect to the server when it is on the same device and using the devices static ip, though it cannot connect using my wan ip address, I have port forwarded and I have checked on many port-checker websites and they all can see my server, my firewall is also disabled, yet the client can't see the server using the wan ip, I made a mine craft server on the same port (Minecraft servers are also tcp) and others were able to connect using my wan ip. When a client does try to connect using my wan ip, it gives a Connection Refused exception. What am i doing wrong?
Client (Can't connect to server over nat):
package com.diedericksclan.main.network;
import java.io.*;
import java.net.*;
import java.util.Enumeration;
public class ClientThread extends Thread {
private ClientHandler client;
private Socket socket;
private InetSocketAddress address;
private int megabyte = 1024 * 1024;
private DataInputStream in;
private DataOutputStream out;
public ClientThread(ClientHandler client, InetSocketAddress address) {
this.client = client;
this.address = address;
socket = new Socket();
try {
socket.setSendBufferSize(megabyte);
socket.setSendBufferSize(megabyte);
socket.setTcpNoDelay(true);
socket.setReuseAddress(true);
socket.bind(new InetSocketAddress(this.getIP(), 0));
System.out.println(socket.getLocalAddress().getHostAddress() + ":" + socket.getLocalPort() + " is a new client!");
socket.connect(address);
in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private InetAddress getIP() throws SocketException {
Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
NetworkInterface ni;
while (nis.hasMoreElements()) {
ni = nis.nextElement();
if (!ni.isLoopback() && ni.isUp()) {
for (InterfaceAddress ia : ni.getInterfaceAddresses()) {
if (ia.getAddress().getAddress().length == 4) {
return ia.getAddress();
}
}
}
}
return null;
}
long starttime;
long endtime;
long overall;
public void run() {
byte[] data;
while(true) {
try {
in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
data = new byte[in.readInt() - 4];
in.read(data);
client.parsePacket(data, socket.getInetAddress(), socket.getPort());
endtime = System.nanoTime();
overall = endtime - starttime;
//System.out.println("CLIENT >> SERVER >> CLIENT - Time was: " + overall + " nano seconds!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
public synchronized void sendData(byte[] data) {
try {
try { socket.connect(address); } catch (IOException e) {}
out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
starttime = System.nanoTime();
out.writeInt(data.length + 4);
out.write(data);
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public void serverShutdown() {
try {
this.socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Server (Can be seen by everything but client):
package com.diedericksclan.main.network;
import java.io.*;
import java.net.*;
import java.util.ArrayList;
import com.diedericksclan.main.network.handling.PlayerMP;
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 ArrayList<Client> clients = new ArrayList<Client>();
public ServerThread(ServerHandler server, String serverIP, int ram, int backlog) throws Exception {
super(serverIP);
this.server = server;
this.dedicated = ram;
String ip = "localhost";
int port = 2048;
if(serverIP.contains(":")) {
ip = serverIP.split(":")[0];
port = Integer.parseInt(serverIP.split(":")[1]);
} else {
ip = serverIP;
port = 2048;
}
this.dataSocket = new ServerSocket();
this.dataSocket.setReuseAddress(true);
this.address = new InetSocketAddress(InetAddress.getByName(ip), port);
this.dataSocket.bind(address, 0);
}
public ServerThread(ServerHandler server, String ip) throws Exception {
this(server, ip, 1024, 0);
}
public void run() {
while(true) {
try {
socket = dataSocket.accept();
socket.setKeepAlive(true);
socket.setSendBufferSize(megabyte);
socket.setSendBufferSize(megabyte);
socket.setTcpNoDelay(true);
socket.setReuseAddress(true);
InetSocketAddress clientAddress = new InetSocketAddress(socket.getInetAddress(), socket.getPort());
System.out.println("Starting");
if(getClients().size() > 0) {
for(Client c : getClients()) {
if(clientAddress != c.socket.getLocalSocketAddress()) {
Client client = new Client(socket, clientAddress);
getClients().add(client);
client.start();
System.out.println("Added new client!");
break;
}
}
} else {
Client client = new Client(socket, clientAddress);
getClients().add(client);
client.start();
System.out.println("Added new client!");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public synchronized void sendData(byte[] data, InetAddress IPaddress, int port) {
if(this.getClient(new InetSocketAddress(IPaddress, port)) != null) {
this.getClient(new InetSocketAddress(IPaddress, port)).sendData(data);
}
}
public void serverShutdown() {
try {
this.dataSocket.close();
if(this.socket != null) this.socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public int getClientIndex(InetSocketAddress address) {
int index = 0;
for(Client c : getClients()) {
if(c.socket.getRemoteSocketAddress().equals(address)) {
break;
}
index++;
}
System.out.println("Getting client index...");
return index;
}
public synchronized ArrayList<Client> getClients() {
return this.clients;
}
private Client getClient(InetSocketAddress address) {
for(Client c : getClients()) {
if(c.socket.getRemoteSocketAddress().equals(address)) {
return c;
}
}
return null;
}
public class Client extends Thread {
DataInputStream in;
DataOutputStream out;
Socket socket;
public Client(Socket sock, InetSocketAddress IPaddress) {
try {
socket = sock;
in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
} catch (IOException e) {
e.printStackTrace();
}
}
public void run() {
while(true) {
try {
byte[] data = new byte[in.readInt() - 4];
in.read(data);
server.parsePacket(data, socket.getInetAddress(), socket.getPort());
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void sendData(byte[] data) {
try {
out.writeInt(data.length + 4);
out.write(data);
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
I would really appreciate it if someone could help.
-Regards
user.....
Port forwarding does not work when both machines are on the same LAN. Here's why:
The client sends a packet to the server's WAN address. It goes to the router. (Because machines on a LAN use the router to reach any WAN address.)
The router port forwards the packet to the server's LAN address. The source IP address is not changed, it's still the client's LAN address. (That's what port forwarding does.)
The server accepts the TCP connection by sending packets to the address it saw as the source address of the packets it received -- the client's LAN address. And, of course it gives them the only source IP address it can, it's LAN address.
Packets sent from the server to the client's LAN address go directly to the client, the router has no opportunity to NAT them because they are sent to the client's LAN address. (Here's where things go awry. If the client had been on another network, the packets would have gone to the router, since that's how the server reaches other networks.)
The client receives packets from the server's LAN address, but it was expecting packets from the server's WAN address (since that's what it sent them to), so the connection cannot work.
If you want to connect to the server from other machines on the LAN you must either connect to the server's LAN address or use some form of dual-NAT such as hairpin NAT -- port forwarding won't work.

Categories