Peer to Peer Networking Example Using the Lidgren.Network Framework

Brief topic switch: Peer to peer networking. I had wanted to get this example in a simple movement game in IceCream but found I needed to start with a console app first. So here it is. I’ve cleverly named it NetConsoles, and the purpose is to teach myself how to link multiple nodes in a peer to peer fashion using the Lidgren.Network framework. While I had some initial issues, it wasn’t all that hard.

Nearly every example I could find using Lidgren.Network was a client/server architecture, so I had to do quite a bit of experimenting to get peer to peer going. Finding sample code for it was completely fruitless. But I did see a post indicating it is possible and relatively easy, so I pressed on. It turns out that the base object of NetClient and NetServer is NetPeer, and it’s all you need to connect p2p (I’m now tired of typing it out).

Two things stand out from this experience: the first is that I tried to create a semi-contained and reusable networking object. It’s far from robust, but I think it will be a sufficient base to carry into my next networking endeavor. The second is that I don’t typically need to do much threading, but it was necessary for this application. The reason is that we need to be constantly polling for incoming network data while also waiting for the user to type commands (like to send a test message). Console.WriteLine is blocking, so my main networking object is written so that when Listen() is called, it spins off a thread that continuously asks Lidgren.Network, “do we have messages to process?” If yes, parse and handle. While basic, I enjoyed the exercise because threads are not in my day to day toolbox (but maybe they should be!).

You can download all the code here, which includes binaries, in case people have trouble building: NetConsoles

Note that the folder structure expects the lidgren-network-gen3 folder to be just one-level above the solution folder. I’d rather include the code library than just a DLL to make debugging and stepping through easier. While learning, it was very handle to be able to visually scan the internal workings of the library rather than be stuck with just IntelliSense (even though IntelliSense is very nice).

Some highlights of the code include:

Construction of the NetPeerConfiguration object (only the first two NetIncomingMessageType are required for basic functionality, but I wanted to see the other 2 in action).

Config = new NetPeerConfiguration("test_console");
Config.LocalAddress = NetUtility.Resolve("localhost");

The initialization of the NetPeer object (extremely simple) and starting up the listener thread:

Peer = new NetPeer(Config);
Console.WriteLine("listening on " + Config.Port.ToString());
NetWorker = new MyNetWorker(Peer, this);
NetThread = new Thread(NetWorker.ProcessNet);

Sending a basic string message (the MessageType enum is my own, not part of the framework):

public void SendMessage(string message) {
    if (Peer.Connections == null || Peer.Connections.Count == 0) {
        Console.WriteLine("No connections to send to.");
    NetOutgoingMessage msg = Peer.CreateMessage();
    Peer.SendMessage(msg, Peer.Connections, NetDeliveryMethod.ReliableOrdered, 0);

Sending peer info to all connected peers (by passing Peer.Connections to Peer.SendMessage):

public void SendPeerInfo(IPAddress ip, int port) {
    Console.WriteLine(string.Format("Broadcasting {0}:{1} to all (count: {2})", ip.ToString(),
        port.ToString(), Peer.ConnectionsCount));
    NetOutgoingMessage msg = Peer.CreateMessage();
    byte[] addressBytes = ip.GetAddressBytes();
    Peer.SendMessage(msg, Peer.Connections, NetDeliveryMethod.ReliableOrdered, 0);

And finally–huge code dump–the listen thread class:

public class MyNetWorker
    NetPeer peer = null;
    NetManager netManager = null;
    public volatile bool shouldQuit = false;
    public MyNetWorker(NetPeer inPeer, NetManager inNetManager) {
        peer = inPeer;
        netManager = inNetManager;
    public void ProcessNet() {
        // read messages
        while (!shouldQuit) {
            if (peer == null)
            NetIncomingMessage msg;
            while ((msg = peer.ReadMessage()) != null) {
                switch (msg.MessageType) {
                    case NetIncomingMessageType.DiscoveryRequest:
                        Console.WriteLine("ReceivePeersData DiscoveryRequest");
                        peer.SendDiscoveryResponse(null, msg.SenderEndpoint);
                    case NetIncomingMessageType.DiscoveryResponse:
                        // just connect to first server discovered
                        Console.WriteLine("ReceivePeersData DiscoveryResponse CONNECT");
                    case NetIncomingMessageType.ConnectionApproval:
                        Console.WriteLine("ReceivePeersData ConnectionApproval");
                        //broadcast this to all connected clients
                        //msg.SenderEndpoint.Address, msg.SenderEndpoint.Port
                        netManager.SendPeerInfo(msg.SenderEndpoint.Address, msg.SenderEndpoint.Port);
                    case NetIncomingMessageType.Data:
                        //another client sent us data
                        Console.WriteLine("BEGIN ReceivePeersData Data");
                        MessageType mType = (MessageType)msg.ReadInt32();
                        if (mType == MessageType.StringMessage) {
                            Console.WriteLine("Message received: " + msg.ReadString());
                        else if (mType == MessageType.PeerInformation) {
                            Console.WriteLine("Data::PeerInfo BEGIN");
                            int byteLenth = msg.ReadInt32();
                            byte[] addressBytes = msg.ReadBytes(byteLenth);
                            IPAddress ip = new IPAddress(addressBytes);
                            int port = msg.ReadInt32();
                            IPEndPoint endPoint = new IPEndPoint(ip, port);
                            Console.WriteLine("Data::PeerInfo::Detecting if we're connected");
                            if (peer.GetConnection(endPoint) == null) {//are we already connected?
                                //Don't try to connect to ourself!
                                if (peer.Configuration.LocalAddress.GetHashCode() != endPoint.Address.GetHashCode()
                                        || peer.Configuration.Port.GetHashCode() != endPoint.Port.GetHashCode()) {
                                    Console.WriteLine(string.Format("Data::PeerInfo::Initiate new connection to: {0}:{1}",
                                        endPoint.Address.ToString(), endPoint.Port.ToString()));
                            Console.WriteLine("Data::PeerInfo END");
                        Console.WriteLine("END ReceivePeersData Data");
                    case NetIncomingMessageType.UnconnectedData:
                        string orphanData = msg.ReadString();
                        Console.WriteLine("UnconnectedData: " + orphanData);
                        Console.WriteLine("ReceivePeersData Unknown type: " + msg.MessageType.ToString());
                        try {
                        catch {
                            Console.WriteLine("Couldn't parse unknown to string.");
        Console.WriteLine("Exiting net thread!");

Whew! It’s a lot, but p2p is pretty cool! Good luck!

8 thoughts on “Peer to Peer Networking Example Using the Lidgren.Network Framework”

  1. I am looking at this code and not sure how are u RESOLVING any public IP associated you are currently using “NetUtility.Resolve(“localhost”);” which will bind it to self not to any unknown IP

  2. This post is helpful to get started on this concept, but I’m fairly confused.

    Starting up multiple consoles is fine and I get that in order to process messages, the listen port of one must correspond to the client port of another (and vice versa).
    For this reason, you can use setup1 and setup2 in separate processes to communicate with one another.

    However, how can configuration be performed programatically to ensure that a new peer will be able to communicate with all peers?
    For instance, two consoles with setup1 will throw an exception when trying to call Peer.Start -> only one usage of each socket permitted.

    Essentially, I want to be able to start a new console and immediately be able to communicate with other peers, without have to set ports manually (because users won’t know what ports to look for)

    1. Hi Felipe,

      Frankly, it’s been a long time since I looked at this project. The actual implementation of how you choose ports and initially get connected to a swarm is not covered here. I had originally prototyped it with the thinking that a central server would act as a lobby (like for a game) and tell each client who everyone is, then those clients could communicate freely without waiting for trips to a central server.

      1. Ah, interesting. Well the post was still helpful, and has put me on the right track, since there is very sparse documentation on P2P with Lidgren.
        Very much appreciated!

  3. Thank you for collecting your information and experimentation here. It is really difficult to find much information about building peer to peer networking software. It seems like most people consider it to be inferior to client server architectures… especially in games. However, I think it’s strictly superior, especially for real time simulations, especially if you can implement a robust trust solution.

    I really enjoyed reading your readme in the it made me feel validated heading down this path because it sounded like something I was thinking recently lol:
    “This is a very simple example. But it demonstrates that connecting to a single peer will get you connected to the entire swarm,
    and each peer can broadcast to all other peers. This allows for the lowest latency possible because there is no intermediary server,
    and it’s fault tolerant because if someone drops, all other peers still see each other. There is no server to host, or that can crash
    or be compromised, or require hosting fees.”‘

    This was the exact goal that lead me to your post. So, thank you.

Leave a Reply

Your email address will not be published. Required fields are marked *