/IceNet

A low-level C++ networking library for Windows and Linux

Primary LanguageC++MIT LicenseMIT

IceNet

IceNet is a statically-linked low-level networking library for Windows and Linux. It is currently in early development stages.

Current issues

Beware of asynchronous packet handling in the Linux build. At this point in time (2/21/2014), using asynchronous packet handling can break IceNet. I am working on a fix.

Features

IceNet provides both server and client contexts. The library is low-level, meaning that, beyond basic operations, users are expected to handle data in packets manually through an opcode system. IceNet is designed to be suitable for single-purpose servers; all clients exists within the same context, meaning this library will not be suitable for server applications which provide a large amount of services. IceNet is more suitable for server applications such as those for video games, voice-over-ip and chat.

Feature sheet:

  • TCP and UDP support.
  • Flexible; packets can convey any type of information and are handled by user code.
  • Fully multithreaded and scalable. Each client connected to the server is given it's own threads which send, receive and handle packets. IceNet is asynchronous by nature.*
  • Compact. Initializing the server/client takes only a few lines of code.

Caveats:

  • Encryption of packets in whole is not possible as of yet.
  • Users are expected to anticipate connection success or failure.
  • No direct P2P.

*Packet handling can be done synchronously.

History

The first version of IceNet was created in September 2012. It was used in a multiplayer first-person-shooter game. This version was very buggy.

The second version of IceNet was created from December 2012 to January 2013. It was to be used as part of a networking assignment. A crippling bug caused suspension of development on IceNet.

Development was resumed in January 2014. The crippling bug was fixed, which allowed for the first public release of IceNet.

Acknowledgements

Juul Joosten, for commenting on the code (back in 2012/2013).

Rick van Miltenburg, for suggesting synchronous packet handling.

Members of the programming section of Facepunch for making suggestions.

How-to

In IceNet, each client has it's own public and private ID. The public ID is used to identify the client everywhere (on other clients) while the private ID is used by the server to find out the origin and handle packet destinations.

The usage of IceNet in an application is very easy. The project should be linked with the appropiate .lib or .a file (IceNetLib.lib/libIceNetLib.a for release and IceNetLib_d.lib/libIceNetLib_d.a for debug). For development on Linux, one should also link with -pthread and -lrt. Then, the IceNet headers should be included in the project.

For a server application:

#include "IceNetServer.h"
using namespace IceNet::ServerSide;

For a client application:

#include "IceNetClient.h"
using namespace IceNet::ClientSide;

Before initializing/connecting, you should link opcode numbers to functions. Some functions are linked internally. Example;

// PRINT_NUMBER_OPCODE is an enum
LinkOpCodeFunction( PRINT_NUMBER_OPCODE, PrintNumber );

Some functions are called internally, here is an example;

// This function is called on all clients when another client connects to the server.
ClientSide::SetOnAddRemoteClient( AddRemoteClient );

// These functions are called depending on the result of the connection.
ClientSide::SetOnConnectionSucceed( ConnectionSuccessful );
ClientSide::SetOnConnectionFail( ConnectionFailed );

// These functions are called when a client joins or parts.
ServerSide::SetOnAddClient( OnJoin );
ServerSide::SetOnRemoveClient( OnPart );

After the functions have been linked, one may initialize the server or connect to it;

// Initialize on port 346366. You must set flags as well.
ServerSide::Initialize( "346366", NetworkControl::PROTOCOL_UDP | NetworkControl::HANDLER_ASYNC );

// Connect to IP on port 34366 (localhost).
ClientSide::Connect( "346366", "127.0.0.1", NetworkControl::PROTOCOL_UDP | NetworkControl::HANDLER_SYNC )

Here's an example of a packet handling function;

void PrintNumber( Packet& packet, void* userData )
{
  // Retrieve the data from the packet and increment the streaming pointer.
  int number = packet.RetrieveDataStreaming< int >();
  
  printf( "%d\n", number );
}

When using NetworkControl::HANDLER_ASYNC in the initialization, packets are handled by the default client threads. This means that thread safety has to be ensured. If you wish to handle packets in a thread of choice, you may do so;

// Use the NetworkControl::HANDLER_SYNC flag
ServerSide::Initialize( "346366", NetworkControl::PROTOCOL_UDP | NetworkControl::HANDLER_SYNC );

// for clients
ClientSide::Connect( "346366", "127.0.0.1", NetworkControl::PROTOCOL_UDP | NetworkControl::HANDLER_SYNC )

....

// Then, in a thread of choice. (This function will return 0 if HANDLER_SYNC isn't used.)
unsigned int packetsHandled = HandlePackets();

// Packets are deleted automatically!

By default, every client has information about the other clients on the server. This can be disabled;

// Use the VENDOR_MODE flag. This flag has no effect on ClientSide::Connect()
ServerSide::Initialize( "346366", NetworkControl::PROTOCOL_UDP | NetworkControl::VENDOR_MODE );

To send packets from the client (example);

Packet* newPacket = new Packet():

// Setting the public ID is optional. This is useful if the packet is bounced back to other clients. 
newPacket->SetClientPublicId( ClientSide::GetLocalClient()->m_PublicId );

// Setting the private ID is useful if a packet is sent back with info from this packet.
newPacket->SetClientPrivateId( ClientSide::GetLocalClient()->m_PrivateId );

// AddDataStreaming can be used to add any type of data to the packet in a streaming manner.
newPacket->AddDataStreaming< int >( 42 );

// Opcode determines which linked function to call.
newPacket->SetOpcode( 5 );

// Send a package with TCP OR UDP.
ClientSide::SendTCP( newPacket );
ClientSide::SendUDP( newPacket );

// Do not delete newPacket!

To send packets from the server (example);

Packet* newPacket = new Packet():

// Setting the private ID is required if the packet needs to go to a specific destination.
newPacket->SetClientPrivateId( someClient->m_PrivateId );

....

// Send a package with TCP OR UDP to a specific client...
ServerSide::SendTCP( newPacket );
ServerSide::SendUDP( newPacket );

// Broadcasting options
newPacket->SetUDPEnabled( true ); // Enable UDP broadcast.
newPacket->SetFlag( Packet::PF_FREE ); // Default, send to all.
newPacket->SetFlag( Packet::PF_SPECIFIC ); // Send only to client with the private Id specified by the packet.
newPacket->SetFlag( Packet::PF_EXCLUDEORIGIN ); // Send to all but the client with the private Id specified by the packet.

// ... or broadcast the packet.
ServerSide::Broadcast( newPacket );

// Do not delete newPacket!

More examples can be found within the examples folder (psuedocode).

Future

  • Improve connection handling.
  • Add custom protocol support.
  • Add encryption.