Thread: Porting to c++ need advice

  1. #1
    Registered User
    Join Date
    Dec 2011
    Posts
    795

    Porting to c++ need advice

    I'm porting a (mainly) finished C proxy for Minecraft over to C++, for a variety of reasons, but I can't figure out the best way to approach the issue. The only definite requirements are that two threads must be running concurrently, one using select and monitoring the actual proxy part, and the other dealing with commands that use loops and would lag the network communication.

    I've came up with a temporarily working solution using a bunch of nested classes, although this expanded the once-simple network function to this abomination:
    Code:
    void * proxy::proxy_main (void *empty)
    {
    	struct timeval tv = {DEFAULT_TIMEOUT, 0};
    	fd_set copy = master_set;
    	ssize_t size;
    	unsigned char packet[1028];
    	
    	while (select(max_fd + 1, &copy, NULL, NULL, &tv) > 0) {
    		if (FD_ISSET(server_fd, &copy)) {
    			out->lock_server();
    			if ((size = out->parser.packet_len (server_fd)) < 0)
    				break;
    			if (out->inet.recv_all(server_fd, packet, size) != size)
    				break;
    			out->unlock_server();
    			
    			out->lock_client();
    			if (out->parser.server_data_handler(packet))
    				if (out->inet.send_all(client_fd, packet, size) != size)
    					break;
    			out->unlock_client();
    			
    			FD_CLR(server_fd, &copy);
    		}
    		if (FD_ISSET(client_fd, &copy)) {
    			out->lock_client();
    			if ((size = out->parser.packet_len (client_fd)) < 0)
    				break;
    			if (out->inet.recv_all(client_fd, packet, size) != size)
    				break;
    			out->unlock_client();
    			
    			out->lock_server();
    			if (out->parser.client_data_handler(packet))
    				if (out->inet.send_all(server_fd, packet, size) != size)
    					break;
    			out->unlock_server();
    			
    			FD_CLR(client_fd, &copy);
    		}
    		
    		copy = master_set;
    	}
    
    
    	return NULL;
    }
    I already tried a much simpler solution with no annoying "out->", but that failed as there were too many classes to link together to make the "class <class name> public: <other class>" trick work effectively. I'm new to C++, however, so does anybody have ideas as to how to simplify the project?

  2. #2
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,660
    > The only definite requirements are that two threads must be running concurrently, one using select and monitoring the actual proxy part,
    > and the other dealing with commands that use loops and would lag the network communication.
    Why?

    If you're using a sane operating system with a Unix heritage, then you can add fd0 (ie, stdin) to the select descriptor set, and everthing can run in a nice simple single thread.
    You just add
    Code:
    if (FD_ISSET(0, &copy)) {
      doSomeUserInput();
    }
    > I'm porting a (mainly) finished C proxy for Minecraft over to C++, for a variety of reasons, but I can't figure out the best way to approach the issue.
    1. Start with a clean sheet of paper, and think about an actual design of classes (what each is for).
    To use C++ well, you need to do some design work up front to get an idea of what classes you need.

    2. Don't rely on your old code too much. A rather literal placing of class:: in front of all your C code will result in a mess.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  3. #3
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    I'm new to C++, however, so does anybody have ideas as to how to simplify the project?
    The same way you would start doing the same for C.

    C++ is unusual even in a world that has a several different languages derived from C, but it is not such a unique snowflake that switching the language improves design by virtue.

    Excellent design doesn't really change from language to language. It all just comes down to the gory details: the sin and syntax. For example, in C++ you'd have to deal with the possibility of exceptions which may mean a few changes to the implementation of the design without really changing any semantics of the design.

    You really only have to deal with the extras of C++ if you find that they benefit the expression of the design.

    Certainly, don't cram what is a natural expression of a procedure into a family of classes just because C++ has native offerings of "OOP". I think you've done exactly this; why is "using a bunch of nested classes" a thing for your design? Did this conversion buy you anything other than "Oh. My. God. OOPS!"? I doubt it.

    You don't have to add templates, operator overloading, or function overloading either if it doesn't actually give a you cleaner implementation.

    What you can do is add a little error handling to communication classes (so you can avoid duplicating certain `read' or `write' code), add "RAII" facilities for anything that constitutes a scope level "push/pop" style mechanism (so you can't forget to "pop" what you "push"), and change a few places you'd use `assert' and conditional `exit' to using exceptions (so you can add prefix/postfix code to these locations without ugly macros).

    Soma

  4. #4
    Registered User
    Join Date
    Dec 2011
    Posts
    795
    Thanks for the advice, I think you're right that I should step away from the older code and do a cleaner redesign before moving forwards.

    > but it is not such a unique snowflake that switching the language improves design by virtue.
    I understand this, I'm mainly switching because I kept seeing myself use things such as giant nested structs and function pointers that C++ offers somewhat of a solution to. Of course the design has to be changed, but I thought that C++ would offer a cleaner way to implement the new design.

    If you're using a sane operating system with a Unix heritage, then you can add fd0 (ie, stdin) to the select descriptor set, and everthing can run in a nice simple single thread.
    > and the other dealing with commands that use loops
    I have functions inside the proxy that handle commands that the user give it, and some of those commands use large loops with delays (mass block-placing or whatnot). The cleanest solution I've found for allowing both the listener and the commands run concurrently is probably threading.

    Edit: A few more questions that I'd like a few opinions on
    • One of the classes will be a proxy internet handler using select and mutexes to prevent incomplete packets from the user. In general, is it a better idea to have many small classes (one handing the mutexes, one handling the sockets, one handling the actual proxy part, etc.) that are better organized but have to be linked together? Or, is it a better idea to lump all of this into one large proxy class, making any internal functions unusable to other things in the program?
    • If nested classes have to be used, is it a better idea to create instances of the classes and use initialization lists? Or would it be better design to not nest at all and instead use public members and friend classes?
    • What is the best way to share data between classes? Network data from the server needs to be passed to some sort of parser (which should probably be in another class), and the parser needs to send/store its data *somewhere* that is accessible to other classes. This is important as the parser function gives level data, player data, boundaries, and other things that will change often and the user commands will rely on.


    The proxy class:
    Code:
    class proxy: public inet, parser {
    public:
    	proxy (const char *ip, in_port_t port);
    	~proxy ();
    	
    	ssize_t relay_server (unsigned char *buf, size_t len);
    	ssize_t relay_client (unsigned char *buf, size_t len);
    private:
    	pthread_mutex_t client_lock;
    	pthread_mutex_t server_lock;
    
    
    	int client_fd;
    	int server_fd;
    	
    	fd_set master_set;
    	int max_fd; 
    	
    	int proxy_main (void);
    };
    
    
    proxy::proxy (const char *ip, in_port_t port)
    {
    	pthread_mutex_init(&client_lock, NULL);
    	pthread_mutex_init(&server_lock, NULL);
    	
    	if ((this->server_fd = server_connect(ip, port)) < 0)
    		throw runtime_error("Server connection failed");
    	if ((this->client_fd = client_connect(PROXY_PORT)) < 0)
    		throw runtime_error("Client connection failed");
    	
    	FD_ZERO(&this->master_set);
    	
    	FD_SET(this->server_fd, &this->master_set);
    	FD_SET(this->client_fd, &this->master_set);
    	
    	this->max_fd = (this->server_fd > this->client_fd ? 
    			this->server_fd : this->client_fd);
    	
    	proxy_main(NULL);
    }
    
    
    proxy::~proxy ()
    {
    	close(server_fd);
    	close(client_fd);
    	
    	pthread_mutex_destroy(&client_lock);
    	pthread_mutex_destroy(&server_lock);
    }
    
    
    
    
    ssize_t proxy::relay_client (unsigned char *buf, size_t len)
    {
    	ssize_t ret;
    	
    	pthread_mutex_lock(&client_lock);
    	ret = inet::send_all(client_fd, buf, len);
    	pthread_mutex_unlock(&client_lock);
    	
    	return ret;
    }
    
    
    ssize_t proxy::relay_server (unsigned char *buf, size_t len)
    {
    	ssize_t ret;
    	
    	pthread_mutex_lock(&client_lock);
    	ret = inet::send_all(server_fd, buf, len);
    	pthread_mutex_unlock(&client_lock);
    	
    	return ret;
    }
    
    
    int proxy::proxy_main ()
    {
    	struct timeval tv = {DEFAULT_TIMEOUT, 0};
    	fd_set copy = master_set;
    	ssize_t size;
    	unsigned char packet[1028];
    	
    	while (select(max_fd + 1, &copy, NULL, NULL, &tv) > 0) {
    		if (FD_ISSET(server_fd, &copy)) {
    			if ((size = packet_len (server_fd)) < 0)
    				break;
    			if (recv_all(server_fd, packet, size) != size)
    				break;
    			if (server_data_handler(packet))
    				if (relay_client(packet, size) != size)
    					break;
    			FD_CLR(server_fd, &copy);
    		}
    		if (FD_ISSET(client_fd, &copy)) {
    			if ((size = packet_len (client_fd)) < 0)
    				break;
    			if (recv_all(client_fd, packet, size) != size)
    				break;
    			if (client_data_handler(packet))
    				if (relay_server(packet, size) != size)
    					break;
    			FD_CLR(client_fd, &copy);
    		}
    		
    		copy = master_set;
    	}
    
    
    	return 0;
    }
    Last edited by memcpy; 07-08-2012 at 09:33 AM.

  5. #5
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    What is the best way to share data between classes?
    It depends on the situation.

    For the situation that you describe, it sounds like the game has a state that is mutated and accessed by many different clients. That kind of implies that it may need to be a class. That game state could be viewed as an abstraction shared by multiple other components. The game state could be viewed as an owned "value" by a higher level view of the game. You might model the game state as a facility of which the parser and game are both clients. You might also model the game state as a "value" owned by the game where the parser is a client of the game state.

    There is no universal best fit.

    I would probably model the stream itself as part of the game state where the parser would be a facility of the state that operates on an abstract stream as consumed from a socket.

    Soma

  6. #6
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    >> small classes (one handing the mutexes ...
    Yes. To take advantage of RAII at least, with a very minimal wrapper interface at most. Here's a Win32 example: ReadDirectoryChangesW question

    gg

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Porting to Linux?
    By cpjust in forum C Programming
    Replies: 3
    Last Post: 10-18-2007, 05:46 PM
  2. porting and monitoring
    By xxxrugby in forum C Programming
    Replies: 3
    Last Post: 04-18-2005, 01:46 PM
  3. Porting
    By lrusso in forum Tech Board
    Replies: 4
    Last Post: 08-13-2004, 11:30 PM
  4. Porting from g++ to VC6
    By Hubas in forum Windows Programming
    Replies: 4
    Last Post: 03-22-2003, 09:30 PM
  5. Porting app from c to c++
    By GUI_XP in forum C++ Programming
    Replies: 1
    Last Post: 11-29-2002, 03:31 PM