Skip to main content
gamedev.net
🔒 Locked

Input/Output Processing From Sockets

Started by redshift86 Apr 13, 2006 at 5:35 PM 13 replies 1.7k views
Original Post
redshift86
redshift86
I'm interested in tutorials or helpful sites on what to do with input/output. I can send/recv just fine, but I'm unsure on how to go about keeping the input buffers from overflowing. What I've got so far is to look through the whole input buffer from the beginning until I find a '\n' or '\r' and sending that out for processing. How would I go about moving the rest of the stuff inside the buffer forward so that I can keep from getting overflow. My other problem is, what happens if the user enters more text than the input buffer can handle at once? As far as handling output, what do I need to do here to insure that the socket gets all the output for one command if its bigger than the output buffer? I'm not sure that that would come up, but in case it does? Oh, and this is for a MUD server I'm trying to write. For me, this is the hardest stage. All the output/input is text.
_winterdyne_
_winterdyne_
The classic approach to prevent overflow is to split the message into packets of a size less than the transmission unit, and reassemble it at the other end.
Winterdyne Solutions Ltd is recruiting - this thread for details!
hplus0603
hplus0603
Computers have finite amounts of memory. No matter how big you make a buffer, it is technically possible to attempt to overflow it. Here are your options:

1) block everything until the buffer drains
2) throw away the extra data

I would recommend 2) on the server, because 1) would allow a user to lock up your server. I would recommend 1) on the client, because it makes correctness easier.
enum Bool { True, False, FileNotFound };
Kylotan
Kylotan
Quote:
Original post by redshift86
I'm interested in tutorials or helpful sites on what to do with input/output. I can send/recv just fine, but I'm unsure on how to go about keeping the input buffers from overflowing.

What I've got so far is to look through the whole input buffer from the beginning until I find a '\n' or '\r' and sending that out for processing. How would I go about moving the rest of the stuff inside the buffer forward so that I can keep from getting overflow.


Once you find your end of line character, use memmove to shift the rest along to the start. Or just loop through moving a character at a time. Make sure you keep track of where the new end of the buffer is (it will be wherever it was before minus whatever you just removed, obviously).

Quote:
As far as handling output, what do I need to do here to insure that the socket gets all the output for one command if its bigger than the output buffer? I'm not sure that that would come up, but in case it does?


Put as much as you can into the buffer, send it, then get the next bit, send that, until you have no more to send. It's trivial when you think about it! :)
redshift86
redshift86
Thanks for the help guys. I decided to go with moving each character, and its working great.
_winterdyne_
_winterdyne_
Quote:
Original post by Kylotan
Quote:
Original post by redshift86
I'm interested in tutorials or helpful sites on what to do with input/output. I can send/recv just fine, but I'm unsure on how to go about keeping the input buffers from overflowing.

What I've got so far is to look through the whole input buffer from the beginning until I find a '\n' or '\r' and sending that out for processing. How would I go about moving the rest of the stuff inside the buffer forward so that I can keep from getting overflow.


Once you find your end of line character, use memmove to shift the rest along to the start. Or just loop through moving a character at a time. Make sure you keep track of where the new end of the buffer is (it will be wherever it was before minus whatever you just removed, obviously).



This can be horribly inefficient. Instead of moving, track the start point of the 'allocated' block in the buffer and move it forward. You will also need a 'wrap flag', a read offset and a write offset. When a block to be queued is added to the buffer, it is, if possible added contiguously to the existing block (at the end). If the block will not fit, it should be added to the start of the block but should not overwrite the read offset. This is a typical 'circular buffer' trick. Providing you can retrieve the data fast enough (incrementing the read) before the write offset catches up this provides a fast, efficient, reduced-overflow buffer. It is however susceptible to failed adds when numerous large blocks are being added. This is alleviated by increasing the buffer size.

For a MUD server, you do really want to look at a packet queue system, especially if you're planning on expanding the codebase to a custom client, rather than telnet.
Winterdyne Solutions Ltd is recruiting - this thread for details!
Kylotan
Kylotan
Quote:
Original post by _winterdyne_
This can be horribly inefficient.

Oh, of course, but he did say this was for a MUD server. The incoming bandwidth is unlikely to exceed 1K a second, if that. :) I'd prefer not to give out the complex but efficient advice to begin with.
Kylotan
Kylotan
Quote:
Original post by Anonymous Poster
and instead of a buffer overflowing in a dangerous way that overwrites the stack, the worst that can happen with a ring buffer is that unread data is overwritten.


That's simply not true. There's no difference between reading into a ring buffer or any other buffer. You still have to know exactly how many bytes it is safe for you to place in the buffer, or you'll trash whatever comes after it, being the rest of the buffer, and/or anything else.
_winterdyne_
_winterdyne_
Yes, if you're using a ring buffer it's a really good idea to wrap reads and writes to/from it into functions that perform proper bounds checking.


Winterdyne Solutions Ltd is recruiting - this thread for details!
xrazybud
xrazybud
Quote:
Original post by _winterdyne_
For a MUD server, you do really want to look at a packet queue system, especially if you're planning on expanding the codebase to a custom client, rather than telnet.


Would that just be like polling all sockets first, adding complete packets to the que, and then processing the packets in the que?
_winterdyne_
_winterdyne_
Basically, yes.

You check to see which sockets have data waiting to be recv()'d on them by using select(). For each of these you first identify the client (by means of an ID for the IP address, usually) and mark the packet appropriately before queuing.

I identify packets with message sequence number, packet offset into message, and client ID. This is enough information to reassemble the packets into valid messages, under either UDP or TCP. For a MUD talking on telnet you can't do this (but you should really think about it for custom clients later).

Note that telnet (the usual way MUDs talk) is basically a text stream. Your packet structure doesn't exist - it's simply chunks of the text stream with terminators in. Because TCP guarantees sequence this is fine.

You therefore:
1) Get all sockets with a pending recv() from select(). Stuff em in a vector.
2) Identify the client associated with the socket (store IP address / client ID with the socket / connection object)
3) Add incoming data to the buffer for that client.
4) Move on to the next socket (iterate vector)
5) Repeat 2-4 until timeout or no sockets left
6) If not timed out, repeat 1, then do 2-4 until timeout.

Meanwhile, you can have another thread harvesting the buffers (remember to mutex them because a ring buffer is effectively an iterated structure and you DON'T want those iterators invalidated), or you can simply process the buffers after your recv() step times out.

You still want to send and recv in chunks though, TCP can and will truncate your message if it exceeds the transmission unit. Turning off nagling (ENABLE TCP_NODELAY - I often get on/off confused at this point) on the sockets (causes the send to occur immediately rather than wait for a full buffer) can help a bit.
Winterdyne Solutions Ltd is recruiting - this thread for details!
pcxmac
pcxmac
here are some sites I have found so far for winsock2

http://www.sockets.com/
WinSock Development Information

http://tangentsoft.net/wskfaq/
Winsock Programmer's FAQ: Winsock Programmer's FAQ

I heard turning off nagling isnt a good idea, but i know next to nothing on this stuff, good solid links, the later is supremely nice, better than Win32 SDK documentation, as far as substance.
sig
_winterdyne_
_winterdyne_
The problem with Nagle's algorithm is that it's opaque to your application, you can't tell when it's going to send or cache. For the brief reader, Nagle's algorithm takes packets you've sent and merges them to make better use of the hardware buffer on the network before transmission, in effect sending fewer larger packets rather than many small.

This makes debugging packet exchanges tricky - and can make send buffer overflow more common (truncated sends). If you need the nagle style functionality, it's a good idea to construct aggregate packets yourself - these at least can be tracked and recognised as such, although the extra work does increase overhead per packet.
Winterdyne Solutions Ltd is recruiting - this thread for details!

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.