Advertisement

Java Server Q&A

Started by July 14, 2017 07:19 AM
27 comments, last by Kylotan 7 years, 4 months ago
2 hours ago, Xer0botXer0 said:

as an alternative to sending the size, Instead I could send a unique Short value to say 'this is the end of this packet'. With a unique short value as the ID for the start of a packet.

That's not likely to work, because a "unique short value" is just 2 bytes next to each other - and how will you guarantee that nothing else you ever send will ever include those 2 bytes next to each other?

2 hours ago, Xer0botXer0 said:

I'm a bit worried that I might lose packets if I were to use udp instead of tcp

So use TCP.

2 hours ago, Xer0botXer0 said:

something I haven't wrapped my head around is that even the 'checks' could become lost packets

Welcome to the complex world of implementing reliable connections. TCP has a system where it makes a guarantee that either what you send gets acknowledged as being received, or it will drop the connection. This works in tandem with its packet ordering system that keeps messages in order, because it uses the message number to communicate with the other side over what has been successfully received, and to coordinate resending of anything that has been lost. Don't try and implement this yourself yet.

Ah ordered messages sounds useful, anything else would break my programs. 

well I'm trying to go with sending the packet size, here's my gml code

 


///Run once say hello

if run_once == false
{
buffer_seek(buff, buffer_seek_start, 0);//Go to the start of the buffer
buffer_write(buff, buffer_u8, 51);//MSG_TYPE

buffer_write(buff, buffer_u8, buffer_get_size(buff));//Buffer Size
buffer_write(buff, buffer_string, "Hello Server!");//Message


network_send_raw(client, buff, buffer_get_size(buff));//Send the package to the server

run_once = true;
}

So first message tells the server the message type, second is suppose to tell the server the size of the buffer, the third and so on being the messages and then I send the packet.

 

So I don't get how I would send the packet size without first writing everything into the buffer, I'm on the gml forum now because network_send_raw for some reason has the third parameter that gets the buffer size too, so I'm hoping that it sends the buffer size anyway. 

if it were a java client how would you approach this ?

I have an idea that what I can do is get the bytes of everything that I send to the server for example


 


///Run once say hello

if run_once == false
{

buffer_seek(buff, buffer_seek_start, 0);//Go to the start of the buffer

var msg_type = string_byte_length(String(51)); 
var msg_1 = string_byte_length("Hello Server!");

var get_buff_size = msg_type + msg_1;

buffer_write(buff, buffer_u8, msg_type);//MSG_TYPE
buffer_write(buff, buffer_u8, get_buff_size);//Buffer Size
buffer_write(buff, buffer_string, msg_1);//Message

network_send_raw(client, buff, buffer_get_size(buff));//Send the package to the server

run_once = true;
}

 

This could work I believe but now I still wonder why network_send_raw uses buffer_get_size, I'll have to see what people say on the GML forum, if all is okay then I'd have to make scripts/methods to convert these things more easier. 
 


 

Advertisement
15 minutes ago, Xer0botXer0 said:

So I don't get how I would send the packet size without first writing everything into the buffer

The standard approach is: write placeholder data where the size is going to be (e.g. zero), write the rest of the message, then seek back and overwrite the size with the real size.

15 minutes ago, Xer0botXer0 said:

I still wonder why network_send_raw uses buffer_get_size

Because you don't always want to send a whole buffer.

Thanks that's actually pretty good.

I've never used buffer_seek to move around a buffer, I'm actually asking about that on the forum..

I know we've gone through a lot of bits and bytes stuff but what came to me now is knowing what set of bits/bytes belong to which variable and read as what data type.

 

Here's what I posted there

 

Code:

///SEND

buffer_seek(buff, buffer_seek_start, 0);

buffer_write(buff, buffer_u16, 5);

buffer_write(buff, buffer_u8, 51);

buffer_write(buff, buffer_string, "Hello Server!");

network_send_raw(client, buff, buffer_get_size(buff));

Code:

///RECEIVE var val1 = buffer_read(buff,buffer_u16);

var val2 = buffer_read(buff,buffer_u8);

var val3 = buffer_read(buff,buffer_string);

 

 

So my question is, how do the reading buffers know what part of the written buffers to read, which should be read, so how does val1 know not to read the value "5" and the value"51, and that only "5" belongs to val1 ?

My assumption is the way bits work, 8 bits in a byte, since 1 byte is used to represent the english language on what I'm assuming GML uses the UTF-8 Table, then only 1 byte has a value, where the other byte since it's a u16 should be empty ? or am I missing something.

buffer_u16 can represent values from 0-65,535 but at the moment it only represents the value 5, so there should be a whole lot of 0s there.. are those 0s being sent with ? 

so buffer_write sends this

00000000 00000101

and buffer_read (in order since TCP) will get those bits as they are there ?

I brought this up because on the server I will be able to get the total amount of incoming data, I then use the BIS method which stores a length or received bytes from a set position into a byte array, and then from there I would read bytes from say 9-16 and read it as a 8 bit integer, 17-24 as an 8 bit, but now what about strings.. All I got was the length of all the bytes in total, but I never got the length for each individual message that needs to be read, for stacked integers it's fine but what about a message.

I need to see strings the way I see integers, if I'm sending an 8 bit integer, and I'm expecting an 8 bit integer, then I need to send a predefined sized string too so that I can expect that same size in return. But this all depends on the question. 

 

So I found out that in GML the buffer_string data type has a null terminating character 0x0. 

I see that's the ASCII reference to null. 

 

This is where it brings up high bits and so on.. Time for lunch. 

When you read from a stream, you read all of it, in order. Remember it's just a list of bytes, nothing more. If the sender has written out a u16 (2 bytes) followed by a u8 (one byte), then that's exactly what the receiver should do - read in 2 bytes to treat as a u16, and 1 byte to treat as a u8. If for some reason the sender is transmitting data you don't care about, you just read it and then ignore or forget it - but it must be read.

The English language and UTF-8 are not at all relevant here. UTF-8 is just a convention for treating bytes as text - here, we are talking about bytes as numbers. Text doesn't come into it.

Yes, if you have a u16 (often known as 'unsigned short' in some languages), that is represented in binary as 0000000000000101, and that will (typically) be sent as a byte of 00000000 followed by a byte of 00000101. The other side, if it reads a u16, will get the number 5. (Unless they are sending the bytes in different order... which you might encounter, since you're using 2 different languages. Let's hope not.)

When we say "message" in networking we don't mean text, we mean "all the data being sent together as one package to deliver some information".

When you want to write a string, you have 2 standard methods, as I mentioned before: either send a length value followed by the text (similar to how you'd handle a whole message), or send the text followed by a delimiter (safe if you know that your text never contains that delimiter). There is the possibility of using a 3rd method, of sending a fixed size string, but that just wastes space.

So I was able to receive the information I wanted,

One thing that's come up is the client disconnecting, from what I gather, with the following code I would need to close all four streams and backwards in the way they were created, I would then close the socket, and then since I'm not using the class object anymore I could destroy that too ? 

 


package LearningProject;

import java.io.*;
import java.net.*;


public class Player 
{
	
	Socket my_Sock;
	
	
	private DataInputStream dis;
	private DataOutputStream dos;
	
	private BufferedInputStream bis;
	private BufferedOutputStream bos;
	
	
	boolean keep_Alive = true;
		
	byte buffer_u8; //8bit integer 0 - 255
	byte[] buffer_u16 = new byte[2]; //16bit 0 - 65,535
	byte[] buffer_u32 = new byte[4]; //32bit 0 - 4,294,967,295.
	byte[] buffer_get_all = new byte[1000]; //1kb with 1000 ASCII characters
	
	Integer[] msg_Types = new Integer[5];
	
	
	
	
	
	public Player (Socket sck)
	{
		my_Sock = sck;
		Setup_Create_Streams();
		Setup_Message_Types();
		Monitor_Incoming();
		
	}
	
	void Setup_Create_Streams()

	
	{
		try{
			bis = new BufferedInputStream(my_Sock.getInputStream());
			bos = new BufferedOutputStream(my_Sock.getOutputStream());
			dis = new DataInputStream(bis);
			dos = new DataOutputStream(bos);
		}catch(IOException e) 
		{System.out.println("Unable to create Input/output Streams.");}
	}
		
	void Setup_Message_Types()
	{
		msg_Types[0] = 51; // Client says hello
		msg_Types[1] = 20; //CLient says good bye
	}
	
	void Monitor_Incoming()
	{
		Integer receive_msg_id = null;
		
		Integer buffer_Size = 0;
		
			try{
				receive_msg_id = dis.readUnsignedByte();
				System.out.println("Message_ID: " + receive_msg_id);
				
				
			for (int i = 0; i < msg_Types.length; i ++)
			{
				if (receive_msg_id == msg_Types[i])
				{	
						buffer_Size = dis.readUnsignedByte();
						System.out.println("Buffer size: " +buffer_Size);
						Handle_Data(buffer_Size,receive_msg_id);
				}  
			}
			
			
			
		}catch (IOException e){System.out.println("Error receiving bytes."); }	
		
				
	}
	
	void Handle_Data(Integer buff_size,Integer msg_id)
	{
		switch(msg_id)
		{
			case 51:
			{
				try {
					
						dis.read(buffer_get_all,2,buff_size);
						String Receive_greeting = new String(buffer_get_all, "UTF-8");
					
					
						System.out.println("Greeting: " + Receive_greeting);
					} catch (IOException e) {System.out.println("Failed to handle data, see Msg_ID: " + msg_id);
				}
			}
		}
	}
	
}

 

Output:

Quote

 

Server Online!
Looking for new clients..
GML Client connected..


Message_ID: 51
Buffer size: 32
Greeting: Hello Server!

 

 

So I'm quite happy with this so far. I just need to go over it because I have uncertainties, I haven't tested this with multiple incoming packets so I don't know what errors I may get, I haven't worked out how to handle disconnecting clients yet, maybe even a keep-alive ping kinda thing too, and am I taking the right approach to reading the incoming data, do I even need DataInputStream when I can just read the bytes from the BIS.. my gml server is pretty simple, it just looks at the data type then reads what ever comes next into their respective variables with specified data types and then makes what ever calculations. I want this to be pretty much the same.

Tomorrow I'll carry on with this and then go on with responding so sending packets from the server to the client. There's still a lot to do like I'm sure I'd like to use multithreading later on, I plan on using a sql data base for storing all game info, ah yeah the gml server only has one sql extension for it. I was previously using ini files to save information but was limiting and ugly. gml servers also cant be run on dedicated servers due to the gui without spending extra. I think Java has a lot of benefits over a gm server. Oh and then there's actually making stuff happen in the gm game.. don't want to think about that for now. 

 

Advertisement

Here you go for some ideas on oo


class Message {
public:
	enum MSG_IDS :int { NOID, EXAMPLE1, EXAMPLE2, EXAMPLE3 }; 
protected:
	const MSG_IDS mID;

public:
	Message(MSG_IDS id= MSG_IDS::NOID)
		:mID(id)
	{}

	MSG_IDS getID() { return mID; }

	virtual void readMsg(ByteStream& bs) {}
	virtual void writeMsg(ByteStream& bs) {}
	virtual void dispatch() {}
};

class MessageExample1 : public Message {
	int mX{ 0 }, mY{ 0 };
	double mZ{ 0 };
	std::string mName;
public:	

	MessageExample1( int x=0, int y=0, double z = 0, std::string name = "")
		: Message(Message::EXAMPLE1)
		, mX(x), mY(y), mZ(z)
		, mName(name )
		{}

	void writeMsg(ByteStream& bs) {
		bs << mID;	//first write the ID
		bs << mX << mY << mName;
	}
	void readMsg(ByteStream& bs) {
		//ID read earlier
		bs >> mX >> mY >> mName;
		dispatch();
	}
};

class MessageExample2 : public Message {
	double mD{ 0 };
public:
	MessageExample2( double d=0)
		: Message(Message::EXAMPLE2)
		, mD(0)
	{}

	void writeMsg(ByteStream& bs) {
		bs << mID;	//first write the ID
		bs << mD;
	}
	void readMsg(ByteStream& bs) {
		//ID read earlier 
		bs >> mD;
		dispatch();
	}
};


int main() {
		


	std::map< Message::MSG_IDS, std::unique_ptr<Message> > messages;
	
	auto addMessage = [&](std::unique_ptr<Message> msg) {
		messages[msg->getID()] = std::move(msg);
	};
	
	addMessage(std::make_unique<MessageExample1>());
	addMessage(std::make_unique<MessageExample2>());

	
	//READING MESSAGES
	ByteStream data = Network->read();
		
	Message::MSG_IDS messageID = Message::MSG_IDS::NOID;
	data >> messageID;

	auto msg = messages.find(messageID);
	if (msg != messages.end()) {
		msg->second->readMsg( data );
	}

	//SENDING MESSAGE
	MessageExample1 msgEx1(x,y,z,name);
	msgEx1.writedMsg(data);

	Network->send(data);

	std::cin.get();
}

 

15 hours ago, Xer0botXer0 said:

with the following code I would need to close all four streams and backwards in the way they were created, I would then close the socket, and then since I'm not using the class object anymore I could destroy that too ? 

If the socket has disconnected then it's already closed. If you have finished with a socket that isn't closed yet, it's a good idea to close it.

Most likely you want to release these stream wrappers in the reverse order to how you create them.

And pretty much everything in Java is a "class object" so you probably want to be more specific there.

Things to consider:

  • are you making sure you never read past the end of a buffer?
  • what will happen if you start reading a message but it's not all available yet?
  • how to avoid the unnecessary loop through message types, and how to define message types generally (hint: the post above mine, although that is C++)
  • handling the errors properly, rather than just catch-and-log

This topic is closed to new replies.

Advertisement