By now we have a program that obediently sits at the command prompt, waiting for us to press CTRL-C
so that it can shutdown in a nice way. It all works, but it’s not very exciting.
In this part we will begin writing one of the interesting parts of a MUD server, the part that actually puts the Multi User in Multi User Dungeon: Networking.
Networking is a very broad subject, and can be quite advanced as well. Actually, we will spend the next several parts of this tutorial with implementing the basic networking code. It will not do complete Telnet negotiation, but will hopefully be open enough so it will be easy to add in the future.
For a more general tutorial to networking, see for example Beej’s Guide to Network Programming.
First lets start with some terminology:
- A
user
is someone connected to the MUD server. - A
player
is a user who is logged in and playing the MUD. - A
socket
is the interface to the network stream, kind of like a file. - A
passive socket
, orlistening socket
, is a socket that only listens for connections from users. - When a user connects to a listening socket, we
accept
that connection to create a new socket. - All passive sockets are listening on connections on a specific
address
, andport number
. - All connected sockets are connected from an
address
.
As the networking code will be a large part of the server, we will place it not only in a separate namespace, but also in a separate folder. This means that we have to modify the Makefile
we created in the first part. After that we will create a simple base class to be used by both the listening socket and the user sockets.
New folder and Makefile
In the source folder, src
, we will now create a new network folder, lets call int net
. We also need a Makefile
here, to build the networking code.
[codesyntax lang=”bash”]
cd src mkdir net cp Makefile net/Makefile
[/codesyntax]
To simply things we re-use the Makefile in the source folder. But we need to edit it somewhat:
[codesyntax lang=”make”]
TARGET = net.a OBJFILES = CXXFLAGS = -Wall -Wextra -std=c++0x -pipe -g CXX = g++ .PHONY: default default: all .PHONY: all all: $(TARGET) $(TARGET): $(OBJFILES) ar crv $(TARGET) $(OBJFILES) ranlib $(TARGET) .PHONY: clean clean: -rm -f $(OBJFILES) -rm -f $(TARGET) ##################################################################### # Object file targets
[/codesyntax]
This Makefile
doesn’t produce a program, instead it create a library that will be linked to the program in the src
Makefile
. This has to be modified to add the new library:
[codesyntax lang=”make”]
NETLIB = net/net.a LIBS = $(NETLIB) $(TARGET): $(OBJFILES) $(NETLIB) $(LD) $(LDFLAGS) $(OBJFILES) -o $(TARGET) $(LIBS) .PHONY: $(NETLIB) $(NETLIB): $(MAKE) -C net
[/codesyntax]
The TARGET
, i.e. our final program, now depends on the network library. It has been marked as PHONY
to make sure we will always try to build it. The variable MAKE
is set automatically to the make
program, and the argument -C
tells make
to use the supplied argument (net
) as base folder.
Addresses
Before we can create a passive socket, or receive connections, we need to have an address
structure in place. An address, also known as ip-address (or dotted decimal number), is what is used when you connect to a server. There are a lot of system functions to handle ip-addresses, so we will make the use of addresses simpler by creating a wrapper class to handle those.
First the header file, address.h
:
[codesyntax lang=”cpp”]
#include <netinet/in.h> namespace net { class address4 { public: static address4 any; static address4 none; static address4 loopback; address4(); address4(const in_addr_t addr); address4(const in_addr &addr); address4(const address4 &addr); const in_addr &get() const; const std::string str() const; address4 &operator=(const in_addr_t addr); address4 &operator=(const in_addr &addr); address4 &operator=(const address4 &addr); private: in_addr m_addr; // The actual address }; typedef address4 address; } // namespace net
[/codesyntax]
Then the source file, address.cpp
:
[codesyntax lang=”cpp”]
#include <arpa/inet.h> #include <string> #include "address.h" namespace net { address4 address4::any = INADDR_ANY; address4 address4::none = INADDR_NONE; address4 address4::loopback = INADDR_LOOPBACK; address4::address4() { } address4::address4(const in_addr_t addr) { m_addr.s_addr = addr; } address4::address4(const in_addr &addr) : m_addr(addr) { } address4::address4(const address4 &addr) : m_addr(addr.m_addr) { } const in_addr &address4::get() const { return m_addr; } const std::string address4::str() const { char addr_buffer[INET_ADDRSTRLEN]; const char *ret = inet_ntop(AF_INET, &m_addr, addr_buffer, sizeof(addr_buffer)); return (ret ? ret : ""); } address4 &address4::operator=(const in_addr_t addr) { m_addr.s_addr = addr; return *this; } address4 &address4::operator=(const in_addr &addr) { m_addr = addr; return *this; } address4 &address4::operator=(const address4 &addr) { m_addr = addr.m_addr; return *this; } } // namespace net
[/codesyntax]
You might have noticed that instead of calling the class just address
, we have opted for address4
. This is because there are actually two version of the addresses used on the Internet: Version 4 (which we implement here) and version 6. We named the class address4
so it will be easy to add an address6
in the future. We also have a typedef to address
, also so it will be easier to use in the future when we implement version 6 addresses.
Don’t forget to add the object file, and its dependencies, to the new net
Makefile
.
Socket base class
Now it’s time for the base socket class. This doesn’t really do anything special, but acts more of a holder of the actual socket. Therefore it will be quite simple.
The header file, base_socket.h
:
[codesyntax lang=”cpp”]
namespace net { class base_socket { public: typedef int socket_type; virtual ~base_socket() { if (is_open()) close(); } const socket_type &get() const { return m_socket; } bool is_open() const { return m_is_open; } void close() { close(m_socket); m_is_open = false; } void set_blocking(const bool blocking) { int flags; flags = fcntl(m_socket, F_GETFL, 0); if (blocking) flags &= ~O_NONBLOCK; // Make socket blocking else flags |= O_NONBLOCK; // Make socket non-blocking fcntl(m_sicket, F_SETFL, flags); } protected: base_socket() : m_is_open(false) { } base_socket(const socket_type &socket) : m_socket(socket), m_is_open(true) { } void set(const socket_type &socket) { m_socket = socket; } void is_open(const bool status) { m_is_open = status; } private: socket_type m_socket; bool m_is_open; base_socket(const base_socket &) = delete; base_socket &operator=(const base_socket &) = delete; }; } // namespace net
[/codesyntax]
If you are not so used to C++ you might notice a couple of odd things about the class. First that the constructor is protected
, not public
. This means that one can not declare an instance of base_socket
directly, it can only be inherited. The other is that the copy-constructor and assignment operator are marked as deleted. This is a new C++11 feature that makes these functions not available. Having them marked as deleted means that sockets can not be copied.
The methods in base_socket
has all been implemented inline, so it doesn’t need any source file.
All code can be found in the github repository, under the part-5
tag.