Java network game: How to list available servers? - java

I'm working on a game that uses local area network. Like most of multiplayer games, there is a server-client system. Computer A runs an instance of program, creates a server and wait; Computer B do the same thing. Now Computer C runs the program, what I want is that he can see computer A and B listed there as game servers. How can I do this?
In order to list all of the servers available, a simple solution might be this: I need to check all of the IP addresses in a particular range and see if they respond via my specific port or not. If yes, therefor an instance of game is running on it and should be listed in the servers list.
Is the solution described above a good one?
I've searched and get this piece of code:
public void checkHosts(String subnet){
int timeout=1000;
for (int i=1;i<254;i++){
String host=subnet + "." + i;
if (InetAddress.getByName(host).isReachable(timeout)){
System.out.println(host + " is reachable");
}
}
}
but is takes so much time and is useless.
If it's not the right solution, what are some other ways?

If you are running on a local network, your method might take a huge amount of time and is definitely not the best solution.
You can solve it by having your servers periodically broadcast their addresses in the network, and have all the clients listen for it. A good example can be found in the Java Tutorials.

Send a discover message using either:
a multicast (use java.netMulticast socket)
broadcast (use java.net.DatagramSocket) to the networks broadcast address
Have all servers listen for that and reply saying "I'm here" and possibly more information for further connection setup (server name, version, use port x, udp or tcp etc)

The best way to do this is with something like ZeroConf ( also known as Bonjour ).
This is what Apple uses for all its network discovery in iTunes and iOS devices so that they can find each other.
I have implemented it Linux, Windows and OSX in server side applications with great success.
And there is great support in all the major relevant languages as well.
There is no need to re-invent this wheel.

you could use udp for this; send out a broadcast if a server is up and let al nodes listen for udp packets.
As requested, here is some example code on utp; theses are 2 classes, one is the heart (wich beats) and the other is the listener.
public class Heart extends Observable implements Runnable {
private String groupName = "229.5.38.17";
private int port = 4567;
MulticastSocket multicastSocket;
DatagramPacket datagramPacket;
public Heart(int connectionListenerPort, Observer...observers) {
for(Observer observer : observers) {
this.addObserver(observer);
}
try {
multicastSocket = new MulticastSocket();
InetAddress group = InetAddress.getByName(groupName);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(new Beat(connectionListenerPort));
objectOutputStream.flush();
objectOutputStream.close();
byte[] buf = byteArrayOutputStream.toByteArray();
datagramPacket = new DatagramPacket(buf, buf.length, group, port);
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void run() {
while(true) {
beat();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void beat() {
try {
multicastSocket.send(datagramPacket);
message(new Message(TYPE.INFO, KEY.MESSAGE, "Heart beat sent."));
} catch (IOException e) {
e.printStackTrace();
}
}
private void message(Message message) {
setChanged();
notifyObservers(message);
}
}
public class BeatListener extends Observable implements Runnable {
private boolean run = true;
private String groupName = "229.5.38.17";
MulticastSocket multicastSocket;
private Network network;
public BeatListener(Network network, Observer... observers) {
for(Observer observer : observers) {
addObserver(observer);
}
try {
multicastSocket = new MulticastSocket(4567);
multicastSocket.joinGroup(InetAddress.getByName(groupName));
} catch (IOException e) {
error(e);
e.printStackTrace();
}
this.network = network;
}
#Override
public void run() {
while(run) {
DatagramPacket datagramPacket = new DatagramPacket(new byte[1500], 1500);
try {
multicastSocket.receive(datagramPacket);
if(!isLocalhost(datagramPacket.getAddress().getHostAddress())) {
Beat beat = getBeat(datagramPacket);
if(beat != null) {
network.setPeer(new Peer(datagramPacket.getAddress(), beat.getConnectionListenerPort()));
message(new Message(TYPE.NETWORK, KEY.NETWORK, network));
}
}
} catch (IOException e) {
error(e);
e.printStackTrace();
}
}
}
private void message(Message message) {
setChanged();
notifyObservers(message);
}
private void error(Exception e) {
message(new Message(TYPE.ERROR, KEY.MESSAGE, e.getClass().getSimpleName()));
}
public void stop() {
run = false;
}
private boolean isLocalhost(String hostAddress) {
boolean isLocalhost = false;
Enumeration<NetworkInterface> networkInterfaces;
try {
networkInterfaces = NetworkInterface.getNetworkInterfaces();
if(networkInterfaces != null) {
OUTER:
while(networkInterfaces.hasMoreElements()) {
NetworkInterface networkInterface = networkInterfaces.nextElement();
Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
if(inetAddresses != null) {
while(inetAddresses.hasMoreElements()) {
InetAddress inetAddress = inetAddresses.nextElement();
if(hostAddress.equals(inetAddress.getHostAddress())) {
isLocalhost = true;
break OUTER;
}
}
}
}
}
} catch (SocketException e) {
error(e);
e.printStackTrace();
}
return isLocalhost;
}
private Beat getBeat(DatagramPacket datagramPacket) {
Beat beat = null;
byte[] data = datagramPacket.getData();
if(data != null) {
try {
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(data));
beat = (Beat)objectInputStream.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return beat;
}
}

Related

Best way to handle many clients (with threads?)

So my question goes here. Now if my Server has over 20 clients, it also has 20 threads and my desktop with an ryzen CPU goes to 100% at usage at 30 Threads. Now I'd like to handle a mass-amount of clients by one server, but the CPU is just getting over-used. My wise is very simple how I do it, but there must be a better way; because I saw many good java servers so far yet. I don't know what I do wrong though. In the following I share my code, how I do it in principle.
while(this.isRunning()) {
ServerSocket server = new ServerSocket(8081);
Socket s = server.accept();
new Thread(new WorkerRunnable(s)).start();
//now here if e.g. over 25 users connect there are 25 threads. CPU is at 100%. Is there a better way to handle this?
The worker runnable is identifing the clients. After that they will get into a chat-room. Its like a group chat for e.g.
Edit: Relevant parts of my very unfinished code which is still very WIP
private boolean state;
private ServerSocket socket;
#Override
public void run() {
while(this.isRunning()==true) {
try {
if(this.socket==null) this.socket = new ServerSocket(this.getPort());
Socket connection = this.socket.accept();
IntroductionSession session = new IntroductionSession(this, connection);
new Thread(session).start();
//register timeout task for 3 secs and handle it async
System.out.println(ManagementFactory.getThreadMXBean().getThreadCount());
//this.handleIncomingConnection(connection);
} catch(Exception e) {
e.printStackTrace();
//System.exit(1);
}
}
}
private class IntroductionSession implements Runnable {
private boolean alive = true;
private BaseServer server;
private Socket socket;
private boolean introduced = false;
public IntroductionSession(BaseServer server, Socket socket) {
this.server = server;
this.socket = socket;
}
private void interrupt() {
System.out.println("Not mroe alive");
this.alive = false;
}
private void killConnection() {
this.killConnection("no_reason");
}
private void killConnection(String reason) {
try {
if(this.from_client!=null) this.from_client.close();
if(this.to_client!=null) this.to_client.close();
this.socket.close();
switch(reason) {
case "didnt_introduce":
System.out.println("Kicked connection, cause it didn't introduce itself");
break;
case "unknown_type":
System.out.println("Kicked unknown connection-type.");
break;
case "no_reason":
default:
//ignore
break;
}
} catch (IOException e) {
switch(reason) {
case "didnt_introduce":
System.out.println("Error at kicking connection, which didn't introduce itself");
break;
case "unknown_type":
System.out.println("Error at kicking unknown connection-type.");
break;
case "no_reason":
default:
System.out.println("Error occured at kicking connection");
break;
}
e.printStackTrace();
}
}
private ObjectInputStream from_client;
private ObjectOutputStream to_client;
#Override
public void run() {
while(this.alive==true) {
try {
if(this.to_client==null) {
this.to_client = new ObjectOutputStream(this.socket.getOutputStream());
//this.to_client.flush();
}
if(this.from_client==null) this.from_client = new ObjectInputStream(this.socket.getInputStream());
//Time runs now, if socket is inactive its getting kicked
new Timer().schedule(new java.util.TimerTask() {
#Override
public void run() {
if(IntroductionSession.this.introduced==false) {
IntroductionSession.this.killConnection("didnt_introduce");
Thread.currentThread().interrupt();
IntroductionSession.this.interrupt();
}
}
}, 5000
);
Object obj = this.from_client.readObject();
while(obj!=null) {
if(obj instanceof IntroductionPacket) {
IntroductionPacket pk = (IntroductionPacket) obj;
introduced = true;
if(isCompatible(pk)==false) {
try {
this.to_client.writeObject(new DifferentVersionKickPacket(BaseServer.version));
this.to_client.close();
this.from_client.close();
IntroductionSession.this.socket.close();
System.out.println("Kicked socket, which uses another version.");
} catch(Exception e) {
Thread.currentThread().interrupt();
//ignore
System.out.println("Error at kicking incompatible socket.");
e.printStackTrace();
}
} else {
this.server.handleIncomingConnection(this.socket, this.from_client, this.to_client);
}
Thread.currentThread().interrupt();
}
}
} catch(StreamCorruptedException e) {
//unknown client-type = kick
this.killConnection("unknown_type");
} catch (IOException|ClassNotFoundException e) {
e.printStackTrace();
this.killConnection("no_reason");
}/* catch(SocketException e) {
}*/
}
Thread.currentThread().interrupt();
}
}
Extending class, which is an actual server:
#Override
public void handleIncomingConnection(Socket connection, ObjectInputStream from_client, ObjectOutputStream to_client) {
new AuthenticationSession(connection, from_client, to_client).run();
}
private class AuthenticationSession implements Runnable {
private Socket socket;
private ObjectInputStream from_client;
private ObjectOutputStream to_client;
public AuthenticationSession(Socket socket, ObjectInputStream from_client, ObjectOutputStream to_client) {
this.socket = socket;
this.to_client = to_client;
this.from_client = from_client;
}
//TODO: Implement app id for access tokens
#Override
public void run() {
try {
while(this.socket.isConnected()==true) {
/*ObjectOutputStream to_client = new ObjectOutputStream(socket.getOutputStream()); //maybe cause problems, do it later if it does
ObjectInputStream from_client = new ObjectInputStream(socket.getInputStream());*/
Object object = from_client.readObject();
while(object!=null) {
if(object instanceof RegisterPacket) {
RegisterPacket regPacket = (RegisterPacket) object;
System.out.println("Username:" + regPacket + ", password: " + regPacket.password + ", APP-ID: " + regPacket.appId);
} else {
System.out.println("IP " + this.socket.getInetAddress().getHostAddress() + ":" + this.socket.getPort() + " tried to send an unknown packet.");
this.socket.close();
}
}
}
}/* catch(EOFException eofe) {
//unexpected disconnect
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}*/
catch(Exception e) {
e.printStackTrace();
System.exit(1);
}
/*catch(Exception e) {
//e.printStackTrace();
Thread.currentThread().interrupt();
}*/
}
}
Please dont look at its very bad formatting and stuff I did in hope to fix it, the tasks dont die whyever though.
Generally, in production grade server code, we don't work with direct creation of socket and handling of requests. It's a nightmare to work with low level sockets, close connections and prevent leaks. Rather, we rely on production grade frameworks such as Java Spring Framework or Play Framework.
My question is, why aren't you using any server-side frameworks such as the ones I listed above?
If you're wondering how these frameworks handle thousands of concurrent requests, look into design patterns such as Thread Pool. These frameworks abstract away the complexities and handle the thread pool for you.
If the clients aren't expected to receive an immediate response, you could also look into introducing messaging queue such as Kafka. The server will pick the messages one by one from the queue and process them. However, bear in mind that this is asynchronous and may not meet your requirements.
If you're not just restricted to one server, you could look into deploying your server code to Azure or AWS VMSS (Virtual machine scale set). Based on CPU load rules you configure, the system will autoscale and dynamically manage resources for you.
I would suggest reading upon system design principles related to servers to reinforce your understanding.
Don't reinvent the wheel.
Since you are doing a Chat Application you need to think of doing a Single Threaded Event Loop.
You can Keep a Map of String (Client id) and Socket (Client socket).
Map<String, Socket> clientSockets;
You Server thread will accept new Client Sockets and will just put it in the above map. Then there will be another Thread which will do the Event Loop and whenever there is data in any of the Client Socket in InputStream it should send that data to all other Client Sockets (Group Chat). This should happen infinitely with a Sleep interval.

Simple UDP link

Hallo I need to write a very simple app that sends and retrieve data trough an UDP link (on Android OS, API level > 18):
This's a piece of code I used in an application I wrote sometime ago to connect trough TCP/IP (and doing the same thing):
class LinkReader implements Runnable
{
private final int mPort;
private final String mAddress;
private final Socket mLinkSocket;
public LinkReader(String address, int port, Socket link_socket)
{
mLinkSocket = link_socket;
mPort = port;
mAddress = address;
}
#Override
public void run()
{
try {
BufferedReader input = new BufferedReader(
new InputStreamReader(
mLinkSocket.getInputStream()
)
);
while (true)
{
Thread.sleep(100);
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
class LinkThread implements Runnable {
private final int mPort;
private final String mAddress;
private Socket mLinkSocket;
public LinkThread(String address, int port, Socket socket)
{
mLinkSocket = socket;
mPort = port;
mAddress = address;
}
#Override
public void run()
{
try
{
mLinkSocket = new java.net.Socket(
InetAddress.getByName(mAddress),
mPort
);
new Thread(new LinkReader(mAddress,mPort,mLinkSocket)).start();
}
catch (UnknownHostException e) {
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
private void sendCommand(String cmd, Socket socket)
{
try
{
PrintWriter _out = new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream()
)
),
false
);
_out.print(cmd);
_out.flush();
}
catch (UnknownHostException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
catch (Exception e)
{
e.printStackTrace();
}
}
I've not been able to find an example about UDP connection on Android OS and I tought to reuse this code mainly because it was working pretty good (but I have to admint that I had not a very deep knowlede of Android neither I had very hight performances expectations from this piece of code).
Besides it was working in my past application my question is it correct now, in terms of code structure and aging?
How can I modify this to establish an UDP connection?
BTW Up to now I don't need really a R/W connection (I just need to send commands over there).

why it didn't go through the thread?

I was checking if the sign Up button is clicked it do something in another thread but it didn't go to the end of thread ?
if(!(email.isEmpty() ||username.isEmpty() || password.isEmpty())){
Main.users.add(new User(username, password, email));
new Thread(new SocketSender(Main.socket, Main.users)).start();
Thread.sleep(1000);
System.out.println("here");
new Thread(new ServerReciver(Main.socket)).start();
Thread.sleep(2000);
System.out.println("here");
Main.pstage.setScene(new Scene(FXMLLoader.load(getClass().getResource("/sample/logIn.fxml")),700,700))}
Socket Sender
public class SocketSender implements Runnable{
Socket socket;
Object object;
public SocketSender(Socket socket, Object object) {
this.socket = socket;
this.object = object;
}
#Override
public void run() {
try {
ObjectOutputStream write = new ObjectOutputStream(socket.getOutputStream());
write.writeObject(object);
write.flush();
} catch (IOException e) {
e.printStackTrace();
}
}}
Server Reciver
public class ServerReciver implements Runnable {
Socket socket;
public ServerReciver(Socket socket) {
this.socket = socket;
}
#Override
public void run() {
System.out.println("log1");
try {
System.out.println("log2");
ObjectInputStream read = new ObjectInputStream(socket.getInputStream());
System.out.println("log3");
Object o = read.readObject();
System.out.println("ma");
if(o instanceof User){
System.out.println(log4);
Server.getUsers().add((User) o);
}
else if(o instanceof Artist){
Server.getArtists().add((Artist) o);
}
else if(o instanceof PlayList){
Server.getPlayLists().add((PlayList) o);
}
else if(o instanceof Song){
Server.getSongs().add((Song) o);
}
else if(o instanceof Album){
Server.getAlbums().add((Album) o);
}
System.out.println(log6);
Server.write();
System.out.println(log7);
Thread.sleep(100);
System.out.println(Server.getUsers().get(0));
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
in the Server receiver it prints the 2 first log but it didn't print all the logs!?
Your problem is simply: you can't use the same socket object to send and read data at the same time.
What you are trying is like: using your own mobile phone to call your own number and then talk to you (in a way that works).
In other words: instead of reusing some Main.socket you simply have to create two distinct sockets in the first place.
But of course, there is zero point in doing things in this order. Don't build a JavaFX application and then try to add in client/server connectivity to a remote system.
Start by learning how to do that client/server thing, for example by reading the corresponding Oracle tutorial. And then, when you have written small command line tools that nicely communicate via sockets; even to a remote system; then take that knowledge forward and build it into your JavaFX application. Instead of trying to learn juggling while learning to use the unicycle.
To create server's client use java.net.Socket:
Socket s = new Socket(serverAddress, 9090);
BufferedReader input =
new BufferedReader(new InputStreamReader(s.getInputStream()));
String answer = input.readLine();
To create server use java.net.ServerSocket :
ServerSocket listener = new ServerSocket(9090);
try {
while (true) {
Socket socket = listener.accept();
try {
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println(new Date().toString());
} finally {
socket.close();
}
}
} finally {
listener.close();
}

Handling multi Java TCP clients with Threads

I have been working with TCP server/client stuff for a while. I am actully good at UDP programming when it comes to connecting more than one user that is multiple clients. I tried to do the same on a TCP server that i made using Threads but whenever the Thread gets to this piece of code
String reader = (String)in.readObject();
an error is generated and the thread stops executing the code but the thread still runs the program keeping it alive.
Anyway here is the entire source code :
public class TestServer implements Runnable {
private Thread run, streams, connect, receive, send;
private ServerSocket socket;
private Socket conn;
private ObjectInputStream in;
private ObjectOutputStream out;
private boolean running, incomingMessage = false;
private int port;
public TestServer(int port) throws IOException {
this.port = port;
socket = new ServerSocket(port);
console("Server stated on : " + InetAddress.getLocalHost() + " : " + port);
run = new Thread(this, "Run");
run.start();
}
public void run() {
running = true;
connect();
receive();
}
private void connect() {
connect = new Thread("Connect") {
public void run() {
while(running) {
try {
conn = socket.accept();
} catch (IOException e) {
e.printStackTrace();
}
console("You are now connected" + conn.getInetAddress().toString() + " : " + conn.getPort());
try {
setupStreams();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}; connect.start();
}
private void setupStreams() throws IOException {
streams = new Thread("Streams") {
public void run() {
try {
console("Setting up Streams");
out = new ObjectOutputStream(conn.getOutputStream());
out.flush();
in = new ObjectInputStream(conn.getInputStream());
console("Streams are now setup");
incomingMessage = true;
receive.start();
} catch(IOException e) {
e.printStackTrace();
}
}
}; streams.start();
}
private void receive() {
receive = new Thread("Receive") {
public void run() {
while(incomingMessage) {
String message = "";
try {
message = (String) in.readObject();
//This is the only flaw the program
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
}
console("Client : " + message);
}
}
};
}
private void console(String message) {
System.out.println(message);
}
public static void main(String[] args) {
try {
new TestServer(1234);
} catch (IOException e) {
e.printStackTrace();
}
}
}
FYI am not new to this. The error is caused because the server starts receiving packets even when there are no packets to be received. But because the thread forces it to receive it, i generates the error in the thread and dont know any other way to counter this. So please help. Thanks in Advance.
You shouldn't need 2 threads per connection. One thread is all that's required. After the connection is accepted, pass it to a worker thread to start reading. This can be done in a while loop in the worker thread.
Even though the socket's input stream can be read, the ObjectInputStream() class is more sensitive. If there is any error, its state is corrupted and it can't be used.
while (true) {
try {
Object input = in.readObject();
message = (String) input;
} catch (IOException e) {
e.printStackTrace();
break; //unrecoverable
} catch (ClassNotFoundException e) {
e.printStackTrace();
break; //unrecoverable
}
console("Client : " + message);
}
It's a better design to use a specific message protocol instead of sending serialized Java objects. For example if you are sending Strings like your sample, an InputStreamReader can be used to convert bytes to characters more easily and with less error handling.
These resources would be helpful to you:
https://docs.oracle.com/javase/tutorial/networking/sockets/clientServer.html#later
Java - Listening to a socket with ObjectInputStream
ObjectInputStream(socket.getInputStream()); does not work

how can I have a linked list that multiple clients can read write to?

I have a multithreaded server and can have multiple clients at once connected. These threads call a class that has multiple linked lists and the clients can add and remove information to it.
For example
This is the server
public class ShareServer {
public static void main(String[] args) throws IOException {
//if (args.length != 1) {
//System.err.println("Usage: java ShareServer <port number>");
//System.exit(1);
//}
//int portNumber = 2000;
boolean listening = true;
try (ServerSocket serverSocket = new ServerSocket(2000)) {
while (listening) {
new ClientThread(serverSocket.accept()).start();
}
} catch (IOException e) {
System.err.println("Could not listen on port " + 2000);
System.exit(-1);
}
}
}
This is the clientsthread
public class ClientThread extends Thread {
private Socket socket = null;
private ObjectOutputStream out;
private ObjectInputStream in;
FindMatch look= new FindMatch();
string fruit;
public ClientThread(Socket socket) {
super("ClientThread");
this.socket = socket;
}
public void run() {
try {
out = new ObjectOutputStream (socket.getOutputStream());
in = new ObjectInputStream (socket.getInputStream());
int count=0;
boolean flag = false;
try{
fruit = (Double)in.readObject();
flag = look.checkForMatch(string fruit);
if(flag==true)
sendMessage("found a match")
}
catch(ClassNotFoundException classnot){
System.err.println("Data received in unknown format");
}
socket.close();
}catch (IOException e) {
e.printStackTrace();
}
}
void sendMessage(string fuit)
{
try{
out.writeObject(msg);
out.flush();
System.out.println("server>" + msg);
}
catch(IOException ioException){
ioException.printStackTrace();
}
}
}
public LinkedList<String> fruitEntries = new LinkedList<Integer>();
public LinkedList<?> clientID = new LinkedList <?>();
this is the code that it calls
boolean checkFormatch(string fruit){
for(int i = 0; i< fruitEntries.length();i++){
if(fruit == fruitEntries.get(i)){
tell client at clientID(i);
fruitEntries.remove(i);
clientID.remove(i);
retutn true;
}
}
}
This code is far from perfect I just threw this together. the general idea is right though. I will have maybe 6 linked lists of info in mine.
I'm not sure how to keep track of what thread a client has either so I would appreciate help with that.
Personally I would use RMI instead of sockets. RMI handles all the messy listening threading etc.
Consider using one of the java.util.concurrent classes -- ConcurrentSkipListMap
ConcurrentHashMap
Also, when you compare Strings you need fruit.compareTo(...) which is rather slow so creating a hash is probably better.

Categories