🔒 Locked
Input/Output Processing From Sockets
Advertisement
Original Post
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.
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.
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.
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.
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! :)
Thanks for the help guys. I decided to go with moving each character, and its working great.
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.
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.
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.
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?
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.
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.
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.
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.
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.
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.
Topic Locked
This topic has been locked by a moderator. New replies are not allowed.
Sign in to reply to this topic.
Advertisement