[Intermediate] - C# AsyncSockets(Server)

08/16/2008 14:39 tanelipe#1
Hello everyone! In this tutorial I will teach you how to do simple ServerSocket and ClientSocket implementation using Asynchronous Sockets (non blocking, doesn't wait for data & doesn't freeze the current thread)


Requirement(s)
- Microsoft Visual C# 2008 Express Edition
- .NET Framework 3.5 (I'm not sure wether this comes with the above program or not.)

I will be using that program on all my C# tutorials so get ready to download it. :D

I'll start off with Async ServerSocket; I'll add the ClientSocket part later on.
I'll show how to implement it step by step. Starting from scratch.

I'll be numbering the steps (1, 2, 3...)

Alright, lets begin! I hope you got all the required stuff already.

1. Create a new Project (File -> New Project) There should open a list of the project types you can create

2. Pick the "Windows Forms Application". Give it what ever name you like (A little bit down from the project list). And then click OK.

3. Add a class to the project (Project -> Add Class & Give it a name) I'll be naming it SocketSystem.cs

3.5 Now go to Project > <Namespace> Properties > Build > Check the "allow unsafe code"

4. Now the window should look like this
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WindowsFormsApplication1
{
    class SocketSystem
    {
    }
}
5. Change the code to look like this :
(When it doesn't have a namespace it's globally usable in your project; Thanks Infamous for this information)
Code:
using System;
using System.Net;
using System.Net.Sockets;
using System.Collections;

public unsafe class Native
{
    [DllImport("msvcrt.dll")]
    public static extern unsafe void* memcpy(void* dest, void* src, uint size);
}
class BSocket
{

}
class ServerSocket
{

}
6. You might wonder why did we create the BSocket. Well it's because we need instance to handle the Sockets Buffer, Packet, ID and so on.

7. I've also added those "using System.Net;" and so on, these are required to create a wrapper around sockets. And the System.Collections is used to access Hashtable in the code.

8. Now we need to add all the variables needed to the BSocket class, to identify each sockets properly.

9. After the adding all the variables; The BSocket class should look like

Code:
class BSocket
{
    public Socket wSocket;
    public int ID = -1, BufferLength = 0;
    public byte[] Buffer;
    public unsafe byte[] Packet
    {
        get
        {
            byte[] ret = new byte[BufferLength];
            fixed (byte* src = Buffer, des = ret)
                Native.memcpy(des, src, (uint)BufferLength);
            return ret;
        }
    }
    public BSocket(uint BufferSize)
    {
        Buffer = new byte[BufferSize];
    }
}
10. Now that we have the BSocket ready, We have to add delegates to handle the correct events (OnConnect, OnReceive etc)

11. Add these lines
Code:
delegate void SocketEvent(object sender, BSocket Socket);
delegate void SocketErrorEvent(object Sender, BSocket Socket, string Error);
So the code looks like :

Code:
delegate void SocketEvent(object sender, BSocket Socket);
delegate void SocketErrorEvent(object Sender, BSocket Socket, string Error);
class ServerSocket
{

}
12. Now we need all the variables set for ServerSocket.
13. What we need is a Socket, NextID (For client identifying), something to hold the clients and ofcourse events based on those delegates.

14. This is how I did it :
Code:
public event SocketEvent OnClientConnect,
                             OnReceivePacket;
    public event SocketErrorEvent OnClientDisconnect;
    public Hashtable Connections = new Hashtable(200);
    public Socket Server;
    private int NextID = 0;
15. Now we need to add functions to start listening and closing the server

16. Lets make the Listen void first, add this to the code
Code:
   public void Listen(ushort Port)
    {
        if (Port == 0) return;
        IPEndPoint Bind = new IPEndPoint(IPAddress.Any, Port);
        Server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        Server.Bind(Bind);
        Server.Listen(100);
        Server.BeginAccept(new AsyncCallback(AcceptConnection), new BSocket(0xFFFF));
    }
17. This will start the ServerSocket to listen on specified port. Server.Listen(100); Indicates how many user there will be queue

18. Now we need to add that AcceptConnection void that the above code exaomple takes as a param for AsyncCallback.

19. So add this to the code
Code:
 private void AcceptConnection(IAsyncResult res)
    {
        try
        {
            BSocket Socket = (BSocket)res.AsyncState;
            Socket.wSocket = Server.EndAccept(res);
            Socket.ID = NextID++;
            Connections.Add(Socket.ID, Socket);
            if (OnClientConnect != null)
                OnClientConnect(this, Socket);
            Server.BeginAccept(new AsyncCallback(AcceptConnection), new BSocket(0xFFFF));
            Socket.wSocket.BeginReceive(Socket.Buffer, 0, 0xFFFF, SocketFlags.None, new AsyncCallback(ReceivePacket), Socket);
        }
        catch (Exception) { Server.BeginAccept(new AsyncCallback(AcceptConnection), new BSocket(0xFFFF)); }
    }
20. I will later on explain how to access the events; (OnClientConnect etc) Did you notice that this started new AsyncCallback for receiving?

21. Now we need to add a function to handle the receiving packet so add this to your code.

Code:
private void ReceivePacket(IAsyncResult res)
    {
        BSocket Socket = (BSocket)res.AsyncState;
        SocketError Error;
        try
        {
            if (Socket.wSocket.Connected)
            {
                Socket.BufferLength = Socket.wSocket.EndReceive(res, out Error);
                if (Error == SocketError.Success && Socket.BufferLength > 0)
                {
                    if (OnReceivePacket != null)
                        OnReceivePacket(this, Socket);
                    Socket.wSocket.BeginReceive(Socket.Buffer, 0, 0xFFFF, SocketFlags.None, new AsyncCallback(ReceivePacket), Socket);
                }
                else
                {
                    InvokeDC(Socket, "Error == " + Error + "");
                }
            }
            else { InvokeDC(Socket, "Lost connection."); }
        }
        catch (Exception E)
        {
            InvokeDC(Socket, E.ToString());
        }
    }
22. You might have noticed that the InvokeDC hasn't been implemented yet, lets get on that.

23. Add this to your code

Code:
 private void InvokeDC(BSocket Socket, string Reason)
    {
        if (Socket == null)
            return;
        if (OnClientDisconnect != null)
            OnClientDisconnect(this, Socket, Reason);
        Socket.Buffer = null;
        Socket.BufferLength = -1;
        Socket.ID = -1;
        Socket.IsAlive = false;
        Socket.wSocket = null;
        Socket = null;
    }
24. Basically it takes the BSocket as a param and resets it, and invokes the Disconnect.

25. We're still missing the the Closing down so lets add that now.
Code:
public void Close()
    {
        foreach (DictionaryEntry DE in Connections)
        {
            BSocket BSocket = (BSocket)DE.Value;
            InvokeDC(BSocket, "User shutdown server");
        }
        Server.Close();
        Connections.Clear();
    }
26. WE HAVE IT DONE! Lets take a look how our code should look now

Code:
using System;
using System.Net;
using System.Net.Sockets;
using System.Collections;
using System.Runtime.InteropServices;

public unsafe class Native
{
    [DllImport("msvcrt.dll")]
    public static extern unsafe void* memcpy(void* dest, void* src, uint size);
}
class BSocket
{
    public Socket wSocket;
    public int ID = -1, BufferLength = 0;
    public byte[] Buffer;
    public unsafe byte[] Packet
    {
        get
        {
            byte[] ret = new byte[BufferLength];
            fixed (byte* src = Buffer, des = ret)
                Native.memcpy(des, src, (uint)BufferLength);
            return ret;
        }
    }
    public BSocket(uint BufferSize)
    {
        Buffer = new byte[BufferSize];
    }
}

delegate void SocketEvent(object sender, BSocket Socket);
delegate void SocketErrorEvent(object Sender, BSocket Socket, string Error);
class ServerSocket
{
    public event SocketEvent OnClientConnect,
                             OnReceivePacket;
    public event SocketErrorEvent OnClientDisconnect;
    public Hashtable Connections = new Hashtable(200);
    public Socket Server;
    private int NextID = 0;

    public void Listen(ushort Port)
    {
        if (Port == 0) return;
        IPEndPoint Bind = new IPEndPoint(IPAddress.Any, Port);
        Server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        Server.Bind(Bind);
        Server.Listen(100);
        Server.BeginAccept(new AsyncCallback(AcceptConnection), new BSocket(0xFFFF));
    }
    public void Close()
    {
        foreach (DictionaryEntry DE in Connections)
        {
            BSocket BSocket = (BSocket)DE.Value;
            InvokeDC(BSocket, "User shutdown server");
        }
        Server.Close();
        Connections.Clear();
    }
    private void InvokeDC(BSocket Socket, string Reason)
    {
        if (Socket == null)
            return;
        if (OnClientDisconnect != null)
            OnClientDisconnect(this, Socket, Reason);
        Socket.Buffer = null;
        Socket.BufferLength = -1;
        Socket.ID = -1;
        Socket.wSocket = null;
        Socket = null;
    }
    private void AcceptConnection(IAsyncResult res)
    {
        try
        {
            BSocket Socket = (BSocket)res.AsyncState;
            Socket.wSocket = Server.EndAccept(res);
            Socket.ID = NextID++;
            Connections.Add(Socket.ID, Socket);
            if (OnClientConnect != null)
                OnClientConnect(this, Socket);
            Server.BeginAccept(new AsyncCallback(AcceptConnection), new BSocket(0xFFFF));
            Socket.wSocket.BeginReceive(Socket.Buffer, 0, 0xFFFF, SocketFlags.None, new AsyncCallback(ReceivePacket), Socket);
        }
        catch (Exception) { Server.BeginAccept(new AsyncCallback(AcceptConnection), new BSocket(0xFFFF)); }
    }
    private void ReceivePacket(IAsyncResult res)
    {
        BSocket Socket = (BSocket)res.AsyncState;
        SocketError Error;
        try
        {
            if (Socket.wSocket.Connected)
            {
                Socket.BufferLength = Socket.wSocket.EndReceive(res, out Error);
                if (Error == SocketError.Success && Socket.BufferLength > 0)
                {
                    if (OnReceivePacket != null)
                        OnReceivePacket(this, Socket);
                    Socket.wSocket.BeginReceive(Socket.Buffer, 0, 0xFFFF, SocketFlags.None, new AsyncCallback(ReceivePacket), Socket);
                }
                else
                {
                    InvokeDC(Socket, "Error == " + Error + "");
                }
            }
            else { InvokeDC(Socket, "Lost connection."); }
        }
        catch (Exception E)
        {
            InvokeDC(Socket, E.ToString());
        }
    }
}
27. How to use this code now, You can go to you Main form and add this to the code.

Code:
 public Form1()
        {
            InitializeComponent();
            ServerSocket Server = new ServerSocket();
            Server.Listen(12345);
            Server.OnClientConnect += new SocketEvent(Server_OnClientConnect);
            Server.OnClientDisconnect += new SocketErrorEvent(Server_OnClientDisconnect);
            Server.OnReceivePacket += new SocketEvent(Server_OnReceivePacket);
        }
        void Server_OnReceivePacket(object sender, BSocket Socket)
        {
            byte[] Packet = Socket.Packet;
        }
        void Server_OnClientDisconnect(object Sender, BSocket Socket, string Error)
        {
            MessageBox.Show("Client Disconnected");
        }
        void Server_OnClientConnect(object sender, BSocket Socket)
        {
           MessageBox.Show("Client Connected");
        }


If you need any help with the following code, please post it here. I might have missed something.

Any feedback is greatly appreciated.
08/16/2008 14:53 © Haydz#2
Very Very Well Done Tane!...

This will help many people :)... real good job:D
08/17/2008 03:19 Hiyoal#3
Really nice job. Very descriptive and good code :p

Hiyoal
08/17/2008 05:20 plasma-hand#4
wow very nice took alot of work and time
09/21/2008 11:45 StarCelli1#5
If i try to debug this
im getting an error on Connection...

NullReferenceExeption

cause Socket.Buffer is NULL

how to fix this?
09/22/2008 08:21 masterjoc#6
whats this for? what is its use? what are the functions?
12/29/2009 23:14 gabrola#7
This needs a good bump, helped me a lot.
12/30/2009 02:29 ChrisLoopy#8
Quote:
Originally Posted by tanelipe View Post
A LOT OF STUFF
[/B]
WOW never thought that i would ever get such a descriptive tutorial on this subject, will help be in creating my own-type of proxy for when jproxy runs out thanks!

Ive been with elite pvpers since the beginning lurking for a good year, its nice to see posts like this that can help the community that actually gives you the information to learn, and from that experience expand on your own knowledge, its a lot more rewarding having something you create than what others do.
12/30/2009 03:36 kinshi88#9
Very, very nice!
12/30/2009 14:55 gabrola#10
Code:
class ClientSocket
{
    public event SocketEvent OnClientConnect,
                             OnReceivePacket;
    public event SocketErrorEvent OnClientDisconnect;
    public Hashtable Connections = new Hashtable(200);
    public Socket Client;

    public void Connect(string IP, ushort Port)
    {
        if (Port == 0) return;
        Client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        Client.BeginConnect(IPAddress.Parse(IP), Port, new AsyncCallback(Connected), Client);
    }

    public void Close()
    {
        Client.Close();
        Connections.Clear();
    }

    private void Connected(IAsyncResult res)
    {
        Client.EndConnect(res);
        BSocket Socket = new BSocket(0xFFFF);
        Socket.wSocket = Client;
        if (OnClientConnect != null)
            OnClientConnect(this, Socket);
        Socket.wSocket.BeginReceive(Socket.Buffer, 0, 0xFFFF, SocketFlags.None, new AsyncCallback(ReceivePacket), Socket);
    }

    private void InvokeDC(BSocket Socket, string Reason)
    {
        if (Socket == null)
            return;
        if (OnClientDisconnect != null)
            OnClientDisconnect(this, Socket, Reason);
        Socket.Buffer = null;
        Socket.BufferLength = -1;
        Socket.ID = -1;
        Socket.wSocket = null;
        Socket = null;
    }
    private void ReceivePacket(IAsyncResult res)
    {
        BSocket Socket = (BSocket)res.AsyncState;
        SocketError Error;
        try
        {
            if (Socket.wSocket.Connected)
            {
                Socket.BufferLength = Socket.wSocket.EndReceive(res, out Error);
                if (Error == SocketError.Success && Socket.BufferLength > 0)
                {
                    if (OnReceivePacket != null)
                        OnReceivePacket(this, Socket);
                    Socket.wSocket.BeginReceive(Socket.Buffer, 0, 0xFFFF, SocketFlags.None, new AsyncCallback(ReceivePacket), Socket);
                }
                else
                {
                    InvokeDC(Socket, "Error == " + Error + "");
                }
            }
            else { InvokeDC(Socket, "Lost connection."); }
        }
        catch (Exception E)
        {
            InvokeDC(Socket, E.ToString());
        }
    }
}
Made a quick ClientSocket class since tane seems to have forgotten to.
12/30/2009 17:43 tanelipe#11
[Only registered and activated users can see links. Click Here To Register...]
[Only registered and activated users can see links. Click Here To Register...]

Yah.. :p
12/30/2009 19:24 gabrola#12
Ok I fail lmao
01/01/2010 16:09 Nullable#13
Quote:
Originally Posted by gabrola View Post
Code:
class ClientSocket
{
    public Hashtable Connections = new Hashtable(200);
Made a quick ClientSocket class since tane seems to have forgotten to.
You don't need a generic container for connections or even an array since this is just a client socket :p
01/04/2010 03:04 gabrola#14
Quote:
Originally Posted by Nullable View Post
You don't need a generic container for connections or even an array since this is just a client socket :p
Yeah, I know that, as I said it was just a quick thing I made by copying the server socket class and doing the necessary changes.
01/26/2010 16:09 Basser#15
BUMP. I'm sure there are a lot of people that find this useful, or would if they took the time to read it.