A couple more questions!

Started by
5 comments, last by UNiSOL 17 years, 1 month ago
Some of these questions apply to general programming/c++, but overall i feel it applies to multiplayer programming. First off, my serialization class, BitStream. This has to do with function template specialization.

class BitStream
{
	std::size_t		size;
	unsigned char		data[2048];
public:
	class iterator
	{
		const BitStream*	stream;
		std::size_t	index;
	public:
		iterator( const BitStream& bs ) : stream(&bs), index(0) {}
		virtual ~iterator() {}
		// Set the iterator to the contents of the iterator on the rhs
		iterator&	operator=( iterator& rhs )
		{
			stream = rhs.stream;
			index = rhs.index;
		}
		// iterator comparison
		bool	operator==( const iterator& rhs )
		{
			if ( rhs.stream == stream )
			{
				if ( rhs.index == index )
				{
					return true;
				}
			}

			return false;
		}
		// iterator inequality
		bool operator!=( const iterator& rhs )
		{
			if ( rhs.stream != stream || rhs.index != index )
			{
				return true;
			}

			return false;
		}
		// increment the iterator
		void operator++()
		{
			++index;

			if ( index > stream->Size() )
			{
				index = stream->Size();
			}
		}
		// Read a numeric value from the stream
		template < typename T >
		void	Read( T& v )
		{
			char parts[sizeof(T)];

			for ( int i=0; i<sizeof(T); i++ )
			{
				Read(parts);
			}

			Endian< T >	en(parts);
			
			v = en.Value();
		}
		template < >
			void Read< std::string > ( std::string& v )
		{
			// First clear the string
			v.clear();
			//
			std::size_t length;
			// First get the length
			Read( length );
			// Set the length
			v.resize(length);
			for ( std::size_t i=0; i<length; i++ )
			{
				char c;
				// Read in the character
				Read(c);
				// Set the characters as we go!
				v = c;
			}
		}
		template < >
			void Read< unsigned char >( unsigned char& v )
		{
			v = stream->data[index];
			++index;
		}
		template < >
			void Read< char >( char& v )
		{
			v = stream->data[index];
			++index;
		}

		friend class BitStream;
	};
	friend class iterator;
public:
	BitStream();
	virtual ~BitStream();

	template < typename T >
	void Write( const T& v)
	{
		Endian< T >	en(v);
		// Push the endian modified value
		*this << en;
	}

	template<>
		void Write< std::string >( const std::string& s)
	{
		// first write the length
		Write( s.length() );
		// now write the string
		for ( std::size_t i=0; i<s.length(); i++ )
		{
			Write( s.at(i) );
		}
	}
	template<>
		void Write< char >( const char& c)
	{
		data[size] = c;
		++size;
	}
	template<>
		void Write< unsigned char >( const unsigned char& c)
	{
		data[size] = c;
		++size;
	}

	std::size_t		Size() const { return size; }

	iterator Begin() const;
	iterator End() const;
};

#endif // BITSTREAM_H




As you can see i specialized the char/unsigned char + string write & read types in the class, originally they were in they were in the cpp file like so... ( the current way works without any problems, the original dosn't )

template<>
void BitStream::Write< unsigned char >( const unsigned char& c )
{
	data[size] = c;
	++size;
}

template<>
void BitStream::Write< char >( const char& c )
{
	data[size] = c;
	++size;
}

template<>
void BitStream::Write< std::string >( const std::string& s )
{
	// first write the length
	Write( s.length() );
	// now write the string
	for ( std::size_t i=0; i<s.length(); i++ )
	{
		Write( s.at(i) );
	}
}

//
// BitStream iterator
//

template<>
void BitStream::iterator::Read< char >( char& v )
{
	v = stream->data[index];
	++index;
}

template<>
void BitStream::iterator::Read< unsigned char >( unsigned char& v )
{
	v = stream->data[index];
	++index;
}

template<>
void BitStream::iterator::Read< std::string >( std::string& v )
{
	// First clear the string
	v.clear();
	//
	std::size_t length;
	// First get the length
	Read( length );
	// Set the length
	v.resize(length);
	for ( std::size_t i=0; i<length; i++ )
	{
		char c;
		// Read in the character
		Read(c);
		// Set the characters as we go!
		v = c;
	}
}




With the second code block in a c++ file without the definitions for those specialized functions in the first codeblock it ended up in unresolved externals. I have a feeling this is because i'm a scrooge and have not invested in a copy of the c++ iso standards, and would appreciate being pointed out where wrong. It will end up using std::queue, but for now it is a static 2k block of memory. Ok, the NEXT question. Something that i realized while compiling, yet forgot while running the loginserver, is that the client was still running the old serilisation & message system. In the old system a string length was an unsigned char, not std::size_t. So when the client returned any messages it would crash the login server for obvious reasons. What this made me realise is that clients with malicious intent would try to exploit certain bugs like this, and now i had to litter my code with tests to make sure that packets or streams from client/server or server/client were valid. Suggestions? I have some ideas of my own, just thought while asking about the former i could ask about the latter. Ideas are appreciated! EDIT: source tags, not code tags. [Edited by - UNiSOL on March 3, 2007 8:33:12 AM]
Advertisement
Another thing..

I'm using enet 1.0, which uses udp rather than sockets. I was trying to test login sessions, on the same machine, two clients connecting to the login server. The second instance of the client would not receive the challenge message from the loginsever, confused at first, then realised because it was a connectionless session ( udp ), that the second client was not receiving the packet. I tested this, and the first client was receiving the packet that the second should have receieved.

I was wondering, if there is anyway around this with udp, i have a feeling there isn't, otherwise it would have been implemented with enet, and also it dons't seem possible with udp.
The compiler is free to require that a template specialization be visible in the translation unit where it is used. Most popular compilers (GCC and MSVC) make this requirement.

Yes, clients will send bad data. Wrap all your parsing in very paranoid code that checks that lengths don't exceed the available size of data, etc. Throw an exception when bad data is found, and disconnect the client (or mark the client as ignore-all) -- there's obviously something wrong with it.

The second client not receiving the challenge has nothing to do with UDP. UDP aims packets at specific ports on specific machines, just like TCP. If someone else gets it, there is a bug -- and chances are, the bug is in your code, rather than in Enet (though of course, bugs could exist anywhere).
enum Bool { True, False, FileNotFound };
It works perfectly when the clients are on seperate machines ( i have tested seven simultaneous clients on seven machines so far ), i'll do more testing and try to isolate the problem.
A complete guess: you're keying on the client's IP and not on the full sockaddr (IP + port) combination. That would cause the symptoms you describe.
I'm going to prod around enet's source to see if i have missed an option/flag somewhere.
Ok.. I have no idea what i did, but it works now. *shrug*

I didn't actually change anything, just re-compiled, and voila. I had already done a complete rebuild on the current sources earlier, so nothing had changed at all.

Heisenbug?

On another note, is there anyway to get my username changed?

This topic is closed to new replies.

Advertisement