Net39 DLL
#21
Posted 19 October 2010 - 03:55 PM
It talks about every side of online concepts (tcp hole punching, udp hole punching, tcp hairpin and udp hairpin). It also includes an interesting section to determine the NAT type of certain routers and the success of routers to be hole punched, etc. All in all, VERY useful informations.
#22
Posted 20 October 2010 - 06:18 AM
#23
Posted 20 October 2010 - 09:03 AM
#24
Posted 20 October 2010 - 02:52 PM
@39ster: I had planned on making it very different, but a lot of people have already invested in your scheme, so I didn't want to deter them. The extensions will far-outweigh the original design and hopefully people will stop using those so that I can start deprecating them. Not saying that your program is garbage, but I read awhile ago that you had no plans on upgrading it further, so I figured I'd give you a break
@jon: I will be releasing a set of scripts for GM that EXACTLY match (by name) the ones you get with the original 39dll. Any work that you have already done using those scripts will still accomplish roughly the same thing (so you simply delete those, and add mine). The plans I have for extending this will hopefully make everyone's life easier when it comes to network programming....heck, the grouping system and filtering system alone are quite needed.
@all: I have edited the original post, another update has been uploaded. I have now reached alpha-stage with the material, I am tired and I'm not sure what else to say, so good night guys....and let me know what you think!
Please give suggestions if you got them...would love to read them!
#25
Posted 21 October 2010 - 12:45 PM
The "problem" is that in GM, readstring() copies the string from the returned char* in the runner, but in C/C++, it doesn't. readstring() always returns the same value (except in this DLL where it may change when resized), the data stored at that location changes. Hopefully everybody can understand what's going on.That looks like how it is suppose to be done, so I'm not sure I follow. What have you tried to do and it failed to work?When you receive said message in a c++ server, you have to do something like below:
#26
Posted 21 October 2010 - 01:39 PM
Oh, I see what you're saying now. Yes, GM will not properly free the char* pointer, instead it strcpy's it and expects the dll to take care of the memory management on the pointer...this is why it uses the same memory location for the pointer. The best thing you can do is either strcpy the return value into a new string, or simply call the "read" function directly (instead of the "read***" functions, which all call the "read" parent-type function).The "problem" is that in GM, readstring() copies the string from the returned char* in the runner, but in C/C++, it doesn't. readstring() always returns the same value (except in this DLL where it may change when resized), the data stored at that location changes. Hopefully everybody can understand what's going on.
void Buffer::read(void* dst, int size)
{
if ((readpos + size) > count)
size = count - readpos;
if (size <= 0)
return;
memcpy(dst, data + readpos, size);
readpos += size;
}That's the code for 'read'....so if you want to read a string off the buffer using c++ tacked into these functions, do this instead:
int len = 1 + (int)strlen(mybuffer->data); char* mystring = new char[len]; mybuffer->read(mystring, len);
Now "mystring" contains what you want, and you can pass the pointer around all day and not have to worry about the Buffer class destroying the data. You will be responsible for the memory management of that pointer though, just as a heads up.
If you are planning to use this AS a dll rather than link directly into the open-source files themselves, then you will need to unfortunately strcpy the return in order to preserve the data....I will not be doing a DLLEXPORT for functions outside the realm of GM possibility of use, sorry.
#27
Posted 21 October 2010 - 07:32 PM
Edited by TheMagicNumber, 21 October 2010 - 07:40 PM.
#28
Posted 22 October 2010 - 12:21 AM
Why wouldn't GM be responsible? Last time I checked, when you call a function that returns a pointer, then you should call a function with that pointer to free it....whether that function is in the dll, or just the memory manager itself, _you_ are responsible for the pointers on the caller side. GM just uses the pointer to copy data, then chucks it, this is (imo) irresponsible -- which also requires us dll programmers to use this stupid workaround of static memory.GM isn't supposed to free the char* pointer. The location is the same because the value returned by readchars and readstring is always the same, the static buffer of 20000 chars.
I have given the code for the c++ bypass to GM's workaround...all good now
#29
Posted 22 October 2010 - 11:29 AM
Whatever allocated it should free it, it makes the most sense.
#30
Posted 22 October 2010 - 12:05 PM
The point is GM _doesn't_ free the pointer, it does a strcpy of the return and ignores the pointer (so if the DLL doesn't take care of the memory management of the string, the string will dangle in memory for the lifetime of the program....imagine a GM server reading 10 strings per second...the memory will max out pretty quickly). This is a flaw (imo) in GM, so if you want to use the functions provided as-is, you will need to use strcpy as well.So, if we want to keep this data, we should make a copy of it since GM needs to free one?
The stack has a smaller boundary than the heap, it will run out far sooner than regular memory allocation. I could have used a "hack" to return the pointer in the stack for the string as a return to GM....but that's extremely bad programming because that space is considered frame_unknown the moment the DLL returns to GM...for all I know, GM could do several recursive calls in order to "read" this so-called string, at which point the string will be overwritten with new stack data. Cannot be trusted to use the stack in this way.You are also saying nobody should use stack allocated strings, it's not bad.
Not quite....if that were the case, then "alloc" function should free it's own memory, since it's the function that actually "creates" the space of memory for your program to use. If you go *IN* to a function knowing that it returns a memory location, then that calling function _IS_ responsible for that pointer to be dealt with. Normally you would have a matching "free" function for every "alloc" type function, but GM doesn't pass anything by-reference, so I can't do that either. This is, for now, the best case for GM.Whatever allocated it should free it, it makes the most sense.
@All: I am almost complete with the GM scripts file, I should be finished today and can start getting to work on testing, thank you all for support/comments...please let me know your thoughts in the meantime!
#31
Posted 22 October 2010 - 09:10 PM
*NOTE* This is still in Alpha, please take caution in use of this product, it has yet to be tested by myself, let alone anyone I know of. Feel free to browse through and see the inside of the project and comment/thoughts/ideas/etc. are welcome.
#32
Posted 22 October 2010 - 09:41 PM
From line 200:
size = recv(id, dbuff, 65536, 0);
Your receiving 65536 bytes of data. What happens if two "32768 byte" messages were stored on the sockets data queue? You would essentially be "receiving" them all as one packet which could be very bad for a game.
Your method also requires a constant 65536 byte buffer allocated.
I didn't really look at your sending/buffer set up, but the best way would be to have a 2 byte header at the beginning of every packet containing the size of the packet. When you receive a packet, you would read the 2 byte header, then read the rest of the data. That way sending multiple messages won't all be received as one giant message. With this method, you don't have to keep a 65536 buffer allocated, you simply allocate enough space for the packet. You could also implement a system where it expands/directly writes to the buffer without creating a second buffer.
Edited by Revel, 22 October 2010 - 09:42 PM.
#33
Posted 22 October 2010 - 10:34 PM
I had 2 choices to go with...either read 1 byte at a time until I got a WSAEWOULDBLOCK error, or attempt to grab as much as I could in 1 shot and deal with whatever size returns. Reading 1 byte at a time (or even XXX byte-blocks at a time) in succession until a WSAEWOULDBLOCK _could_ possibly cause a stall (constantly reallocating memory for each new block on top of the successive reads and calling WSAGetLast each roll). I opted the choice to read in 64k (the maximum packet size) in 1 shot and be done. This is not set in stone, I can easily reduce the limits, or even make the dllInit function use an argument for the size of the backbuffer.I took a glance at your socket code, and I've already found a massive problem.
From line 200:size = recv(id, dbuff, 65536, 0);
Your receiving 65536 bytes of data.
Depends on what protocol it is...for UDP, each packet is separate entities and each "receive" of 64k will only grab the 32k each. TCP is a stream protocol, you are not guaranteed to receive the same size you sent...which means you could get 16k followed by 48k, or 32k/32k, or 64k/0k...this was the other _slight_ error that 39ster had not accounted for in his original.What happens if two "32768 byte" messages were stored on the sockets data queue?
What you see in the send/receive functions are the _raw_ options format, which buffers the data without caring about size of actual messages. I have plans for other formats, including filtering which I have gone over.
Your game is _suppose_ to be programmed as a continued read of data ANYWAY...something like:You would essentially be "receiving" them all as one packet which could be very bad for a game.
while(bytesleft())
{
switch(readbyte())
{
case whatever:
}
}Why else do you think 39ster put that function in there for? What do you think nagle does? These are no coincidences.Again, this can be reduced....figured 64k was no biggy, but I'll keep this on the front of my mind to change in the next update, 16k should be a fair size.Your method also requires a constant 65536 byte buffer allocated.
39ster tried this as well, which was the problem I had with his TCP scheme. Let me show you a hypothetical example:I didn't really look at your sending/buffer set up, but the best way would be to have a 2 byte header at the beginning of every packet containing the size of the packet.
Johnny decides to send a message, "hello brian", to brian
This message is packaged as .25. (the byte code for "sending message to someone"), .31881. (the int code for _brian_s ID from the server), and "hello brian\0" (the text)
You "send" this to the 39dll, which you think it will just send it.....nooooo
1. it allocates space for the size of the buffer + 2 bytes (takes some time)
2. it writes 2 bytes to this new buffer denoting it's payload size
3. it memcopies the sent buffer to it (takes some time)
4. then it sends it
That's not that bad right? What if, for some reason (because in hypotheticals, things happen), the connection between 2 points of the cloud that Johnny and Brian both "communicate" over, can only take 48 byte payloads. This becomes a problem because our package is:
.17.25.31881."hello brian\0" + 40 bytes IP header
59 bytes total
IP is fragmentable, so guess what happens when this reaches our dead zone? In order for IP to fragment, it must add it's IP header to every piece of data in the split (along with some other things, but I'll keep it simple for now). This means our package has just turned into 3 packages:
40 bytes IP header + .17.25.31881."h"
40 bytes IP header + "ello bri"
40 bytes IP header + "an\0"
Let us now reach even further and say that these packages get to Brian's computer in this fashion, and GM "reads" them like so:
1. read in 2 bytes (at this point, it only has the first message) = .17.
2. read in 17 bytes of data = .25.31881."h" (recv function returns 6, because only those 6 bytes are currently in the window)
3. GM is given this message, which is not even finished
4. next call to "receivemessage" will read 2 bytes (which is the next message "el" is a pretty big number of bytes to read)
5. read in "el" bytes of data = "lo bri" (only 6 were available at this moment in the window)
etc. etc.
Obviously the internet doesn't have a 48 byte MTU anywhere...but there are places that do have a 1k MTU, and UDP is sometimes limited to even 572 bytes MTU (which is truncated, not fragmented in most cases). This might not be a problem in a tiny game with a few updates...but the server might send 4k data chunks per second per person just to update what OTHER people are doing, it adds up. When IP starts to fragment these huge chunks, your clients will start destroying data without even knowing, and GM will get back junk code messages.
I _do_ have plans to have a size format of "wait to receive" which will send back a "do not have a message yet" (0) until the 'bin' buffer for that socket has reached the amount given by its 2-byte header, then it will memmove up to the next position and read the next 2-byte header. This would severely dampen the speed of your communications, so it should only be used on things like chat channels and other not-time-crucial things, but it would be MUCH better than the current system.
Edited by sabriath, 22 October 2010 - 10:39 PM.
#34
Posted 22 October 2010 - 10:56 PM
Any packet being sent over the Internet Protocol can be fragmented. All fragmentation is done "behind the scenes" by Winsock. Any fragmentation is done transparently and is only passed to the sockets data buffer after the full packet has been reassembled on the receiving end. You shouldn't be worrying about fragmentation since it's all handled automatically. So yes, when you send a TCP packet, after the receiving end is done its "back end" processing, the packet will be identical to what was sent, same size and everything.Depends on what protocol it is...for UDP, each packet is separate entities and each "receive" of 64k will only grab the 32k each. TCP is a stream protocol, you are not guaranteed to receive the same size you sent...which means you could get 16k followed by 48k, or 32k/32k, or 64k/0k...this was the other _slight_ error that 39ster had not accounted for in his original.
The fragmented data won't be added to the sockets data buffer until the whole message has been received and reassembled. This means your entire message will be on the data buffer regardless of how much fragmentation was done to the packet.1. read in 2 bytes (at this point, it only has the first message) = .17.
2. read in 17 bytes of data = .25.31881. (recv function returns 6, because only those 6 bytes are currently in the window)
3. GM is given this message, which is not even finished
4. next call to "receivemessage" will read 2 bytes (which is the next message "he" is a pretty big number of bytes to read)
5. read in "he" bytes of data = "llo br" (only 6 were available at this moment in the window)
1. it allocates space for the size of the buffer + 2 bytes (takes some time)
2. it writes 2 bytes to this new buffer denoting it's payload size
3. it memcopies the sent buffer to it (takes some time)
4. then it sends it
Yes, 39dll does it terribly.
The best way to do it would be to have the 2 byte header allocated in the buffer the entire time, so you don't have to go prepend it when you go to send the message. The easiest way would be to write everything at writepos+2, so you keep a "blank" 2 bytes at the beginning.
But now I understand that your receive method was just for the "Raw" method and you haven't added the packet header yet.
Also, are you planning on adding IPv6 support?
Edited by Revel, 22 October 2010 - 11:00 PM.
#35
Posted 22 October 2010 - 11:59 PM
http://www.rhyshaden.com/tcp.htm *check sliding window*So yes, when you send a TCP packet, after the receiving end is done its "back end" processing, the packet will be identical to what was sent, same size and everything.
http://www.osischool.com/protocol/Tcp/slidingWindow/index.php *nice little demo of the sliding window*
http://en.wikipedia.org/wiki/Nagle%27s_algorithm
Sooooo...not exactly. Unless there is another message band signal that I don't know about other than SYN, ACK, and FIN....is there a MSG_BLOCK_END signal?
I don't see how you figure this....messages have only 1 thing associated with them, and that's the sliding window SEQ value which is usually modded to some value based on the total size of the window buffer. When you "receive", you are grabbing what is on the left side of the MSS that has been flagged as "acknowledged," after which it will then be flagged as "sent to application."The fragmented data won't be added to the sockets data buffer until the whole message has been received and reassembled. This means your entire message will be on the data buffer regardless of how much fragmentation was done to the packet.
When you "send" XXX bytes using the function, it appends that data into the buffer directly behind the last byte that was sent (and wrapping around back to the front, if you reach the LAR pointer, the rest of the message is truncated)...."send" function returns how many bytes actually made it onto this buffer. The throttle/congestion module will take care of sending the next set, flagging them as sent but not ACKed, and also resizing the window.
By the way, SEQ is a byte pointer indexed from the start of the buffer in memory...not the actual packets themselves, they are just appended to the end as I mentioned. Have a look at that java thing and imagine that each of those numbers are MTU sized blocks.
That does seem the best method on _sending_ the packets....and the method I roughly stated above is best for _receiving_ them, at least for reliability issues.Yes, 39dll does it terribly.
The best way to do it would be to have the 2 byte header allocated in the buffer the entire time, so you don't have to go prepend it when you go to send the message. The easiest way would be to write everything at writepos+2, so you keep a "blank" 2 bytes at the beginning.
Exactly....the options could be endless..."options" of a socket currently is assigned these:But now I understand that your receive method was just for the "Raw" method and you haven't added the packet header yet.
bit 0 = whether it is a listening socket, quick check for send/recv on these, quick returns an error
bits 1-3 = receiving options, giving 8 possibilities
bits 4-7 = sending options, giving 8 possibilities
bits 8-32 = unused for now
The options check is in a switch statement:
switch(options & 112) <-- located in 'Socket::Send' switch(options & 14) <-- located in 'Socket::Recv'In each of those, you can find the "case 0:" for RAW formatting of the buffer data.
I am not a very good IPv6 guy, but I _do_ want to support it...it will have to go on the backburner though. There is really no point right now in using it because IPv6 is STILL being encapsulated in IPv4 headers for transmission, it's slow, and cumbersome. I would gladly switch....if I knew more about it lol. Going from 4 byte address to a 16 byte address _might_ be a little overkill? The header is HUGE as well, and they keep putting bandaids on a system that should be overhauled completely....but hey, no one owns the internets right lolAlso, are you planning on adding IPv6 support?
#36
Posted 23 October 2010 - 01:05 AM
My original question was how you were going to separate the packets from eachother because you were simply reading 65k bytes off the data queue which provided no way of separating the packets. You have since mentioned that you have not yet added any method to do so, which has cleared some things up. I also missed the option checking code you had in the Send/Recv.
#37
Posted 23 October 2010 - 06:51 AM
The next method I have in mind is exactly how you describe it. There is a 'Buffer*' (bin) already assigned to a socket when it is created (although this won't happen normally, it'll be created when the right option is set). When GM calls "receivemessage", it will check your options and see that you want perdetermined sized datablocks...so it will expand the size of 'bin' 16k (or whatever the default will be) from it's 'writepos', and attempt a 'recv' from the socket to this location. It will then update the 'writepos' based on how many bytes actually got read. If the size header has not been read yet, and the buffer is at least 2 bytes, grab the 2 bytes off and store in the size header. It will then check the 2 byte size header against the count of the buffer, if the buffer is not big enough, it will return with a "no message on queue" signal. If it is big enough, it will:
1. memcpy the size of the block to the targetted buffer
2. take the data following that block and memmove it up to the front '0' position
3. reduce the count by the size of the block, along with the readpos and writepos
4. return the size of the block to GM to let it know it has a full message block ready to read
When sending data, it's easier...whenever clearing the buffer in this mode, it will automatically start the offset at byte 2, the count will still show 0, and any attempts to setpos the pointers to it will not work. When "sendmessage" is called, it writes the size of the buffer in that 2 byte space and sends it.
There are plenty of other options to cover, this is just the second one that will be in the works when I'm finished testing all the functions and fixing the bugs. I start my real world job again tomorrow, so I won't have much time to spend on it as much, but if anyone would like to help, I would greatly appreciate it.
#38
Posted 23 October 2010 - 07:11 AM
could you please explain that further?Well the primary #1 reason for me doing this is because:looks like 39dll is pretty much the same, but more efficient internally i guess?
a = createbuffer(); b = createbuffer(); c = createbuffer(); freebuffer(b);
'c' now points to a NULL indexed address as far as 39dll is concerned, and whatever you "wrote" in 'c' buffer, will now be in addressed by 'b' even though it was freed.
#39
Posted 23 October 2010 - 07:52 AM
Had 39ster used the CList "Remove" functions, it would have done what I explained, but I retracted my argument because 39ster bypassed those functions altogether. The 39dll does not do what I stated, sorry for the panic.could you please explain that further?
The CList "Remove" function would compact the list of pointers, that's why I stated what I did. It's like when you "createbuffer" it puts a book on your desk, and do it again and another book goes on top, and this continues on and on. When you "freebuffer" I _thought_ he called the 'remove' function, which would remove your book from the table, but all the other books would fall down 1 slot. So when the guy goes looking for his book in the 10th position, it's not there.
Again, 39ster bypassed this, create/free buffer do what they are suppose to.
#40
Posted 23 October 2010 - 08:17 AM
Example:
SendMessage(sockID, buffID); //Send a data message (two bytes at the front of the packet with is the length over TCP)
SendText(sockID, string); //Send a string (no formatting)
SendBytes(sockID, buffID); //Send a buffer(no formatting)
ReceiveMessage(sockID, buffID); //Receives a data message (using the two bytes for the length over TCP)
ReceiveText(sockID, delimiter); //Receives a string. Use delimiter to determine end of string (same as format mode 1)
ReceiveBytes(sockID, buffID, count); //Receives count amount of bytes from the socket
Easier than always having to switch format modes. You could also leave in the original scripts for compatibility.
Although the only problem i see is with ReceiveText() as it would return a string, it would be more difficult to check for socket errors.
Edited by 39ster, 23 October 2010 - 08:18 AM.
0 user(s) are reading this topic
0 members, 0 guests, 0 anonymous users











