Okay time for a second socket tutorial! This one will be far better as I've learned new ways of doing sockets!
You can find my old tutorial here:
If you haven't read any of the tutorials and just skipped here then if anything is referred to old material then it's up to you to figure it out! I'd strongly suggest to read ALL the parts before jumping into sockets.
I will cover both server and client sockets.
First of all it's important that you understand what a socket is.
A socket is basically an endpoint that is bound to a specific ip and port. You can send/receive data through sockets.
The way we're going to design our sockets will be the following:
The reason I wrote linked is that they'll both be using the same wrapper, but we'll set it up to work for both and the reason is that there is no point in coding the same thing two times.
I will start out by explaining the server part.
A server is an application which is located either on a local machine or an extern machine. You connect to the server through a socket, the servers IP and Port would be the end point.
A server does not necessary has to be something that you connect to on an extern machine, an example could be sql server. If you're hosting the sql server and wants to use it ex. through your application then you'll most likely connect to it local. (It's possible to connect remote to a database server as well, but NEVER, I repeat NEVER do it through your client application, because it'll leave it open to security issues and I have seen a lot of people do that (Usually in the VB section, ironic LOL.)!!)
For this we'll need to use two namespaces:
System.Net and System.Net.Sockets.
Well start by making a class for our socket server.
Code:
public class SocketServer
{
}
We'll have to add one variable to it as a Socket.
Code:
private Socket _socket;
Now we need a constructor in which we'll create our socket.
Code:
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Let me explain to you why I've chosen to use the following things.
The address family is InterNetwork because we want to connect to the internet, there is also options for Firefox and such, but I won't go into that stuff.
The socket type is Stream, because we want to stream data over the network. Raw is also used a lot, but we don't want that now.
Now for the protocol type. I've chosen Tcp for a simple reason. Tcp will never have its data lost and if it loses any data it will always report back with an error. If we were to choose Udp then we'd be risking a chance that we might lose data, because Udp is about speed. It's faster to use Udp, but they might lose data and it won't report back if any data was lost or anything. Tcp on the other side is about being effecient and that it actually works like it should. Usually Tcp is used for real servers, games and such where Udp is usually used for streaming (Ex. streaming movies) because they'll deliever the data faster, ut it might get lost on the way and such!)
Now we need to create 3 methods.
2 public methods called Start and Accept. The start method should take two parameters, one as a string (IP) and one as an int (Port). The accept method should take no parameters at all.
Code:
public void Start(string IP, int Port)
{
}
public void Accept()
{
}
Let's look at the Start method first. In the start method we'll bind the server and start to listen for connections.
To bind our socket to an end point we'll have to create an IPEndPoint from the IP and Port passed as parameters. IPAddress.Parse will parse a string to an IP (If the string is a valid IP lol.)
Code:
_socket.Bind(new IPEndPoint(IPAddress.Parse(IP), Port));
When we call the Listen method we'll have to pass an int as the parameter. That is the pending queue for our connections. I usually use 100, but you can use any value you want, I've seen some use 500 and such as well, but I'll just use 100.
Code:
_socket.Listen(100);
Now let's make a new class and call it SocketClient, because we're going to use that now (We'll not do anything in it yet!)
Now go back to the server.
We'll have to make a new method now which should be private and take one parameter as IAsyncResult. The method itself is a callback from an async callback.
Code:
private void Accept_Callback(IAsyncResult asyncResult)
{
}
Now in the Accept method we'll call the BeginAccept method in our socket class and it takes 2 parameters. the async call back (A delegate) and an object (Our SocketClient). We'll create a new instance of the SocketClient here.
Code:
public void Accept()
{
_socket.BeginAccept(new AsyncCallback(Accept_Callback), new SocketClient());
}
Now we need to handle the call back. In the end of the call back call the Accept method again and every thing that we do from now on should be above that!
Code:
private void Accept_Callback(IAsyncResult asyncResult)
{
// do things here
Accept();
}
Now we'll go into our SocketClient class.
First thing we'll do is making a few variables.
Code:
private byte[] dataHolder;
private Socket _socket;
The dataHolder is a byte array which should hold all the data that's received.
The _socket is the connection of the client.
Now we'll make a new method returning a bool called EndAccept which should take 2 parameters as a Socket (as server) and IAsyncResult. Basically this should call the Socket.EndAccept method.
Code:
public bool EndAccept(Socket server, IAsyncResult asyncResult)
{
}
We should create a try/catch and on catch we'll return false and in try we'll return true.
Code:
try
{
return true;
}
catch
{
return false;
}
What we'll do now is making the socket in our class equal to the end accept method and pass the async result as parameter.
Code:
public bool EndAccept(Socket server, IAsyncResult asyncResult)
{
try
{
_socket = server.EndAccept(asyncResult);
return true;
}
catch
{
return false;
}
}
And that's basically it. What we'll do now is creating 3 delegates (Do it in a new file if you want or wherver you prefer, but not in any class!)
Code:
public delegate void ConnecionEvent(SocketClient Client);
public delegate void BufferEvent(SocketClient Client, byte[] Buffer);
In the server class we'll have to define the 2 delegates (Make them static!)
2 for connection (OnConnect and OnDisconnect) and 1 for receive (OnReceive)
Code:
public static ConnecionEvent OnConnection, OnDisconnection;
public static BufferEvent OnReceive;
Now let's return to the accept callback.
First of all we'll check if the connection was accepted and if it's accepted we'll invoke the OnConnection delegate.
However we'll need to invoke the SocketClient and to do that we'll need to get it and we can do that from our async result.
Code:
private void Accept_Callback(IAsyncResult asyncResult)
{
SocketClient Client = asyncResult.AsyncState as SocketClient;
if (Client.EndAccept(_socket, asyncResult))
{
OnConnection.Invoke(Client);
}
Accept();
}
Now in the SocketClient class make a method called Receive and it should take no parameters. Call that method after the OnConnection has been invoked.
Code:
private void Accept_Callback(IAsyncResult asyncResult)
{
SocketClient Client = asyncResult.AsyncState as SocketClient;
if (Client.EndAccept(_socket, asyncResult))
{
OnConnection.Invoke(Client);
Client.Receive();
}
Accept();
}
Now our server socket is done. The only thing we need is to receive/send data which is in the SocketClient we'll handle that.
First of all make a private method called ReceiveData which should have one parameter as an int which is the size of the data we want to receive.
Code:
private void ReceiveData(int size)
{
}
Now we need to make another call back, this time it's for receive.
Code:
private void Receive_Callback(IAsyncResult asyncResult)
{
}
Okay now add two other variables to the class which should be for the data size received and the required data size.
Code:
private int requiredSize, currentSize;
And now another variable as a bool which should be indicating whether or not we're recieving the header and it should be true as a start.
Code:
private bool isHeader = true;
Now call ReceiveData from Receive with a size of 4, because we want that to get the size of the packet.
Code:
public void Receive()
{
ReceiveData(4);
}
Now in ReceiveData we have to call the BeginReceive method from our socket.
Code:
private void ReceiveData(int size)
{
dataHolder = new byte[size];
_socket.BeginReceive(dataHolder, 0, size, SocketFlags.None, new AsyncCallback(Receive_Callback), null);
}
Now in the receive callback we have to put a try catch, 2 variables for size and socket-error, but we also have to make a check if it's in header or not.
We also have to check if the receive was a success.
Code:
private void Receive_Callback(IAsyncResult asyncResult)
{
try
{
SocketError err;
int receiveSize = _socket.EndReceive(asyncResult, out err);
if (err != SocketError.Success)
{
return;
}
if (isHeader)
{
}
else
{
}
}
catch
{
}
}
Now implemented the data packet class that we used in the previous tutorial.
Before we continue we have to make a disconnect method.
Basically it should just call the Socket.Disconnect method with the parameter as false so the user can connect again! It should also check if the user is already disconnected, because we don't want to invoke the event two times.
Code:
public void Disconnect()
{
if (alreadyDisconnected)
return;
alreadyDisconnected = true;
_socket.Disconnect(false);
SocketServer.OnDisconnection.Invoke(this);
}
Okay back to the callback.
At this block:
Code:
if (err != SocketError.Success)
{
return;
}
It should disconnect.
Same goes here:
Result:
Code:
private void Receive_Callback(IAsyncResult asyncResult)
{
try
{
SocketError err;
int receiveSize = _socket.EndReceive(asyncResult, out err);
if (err != SocketError.Success)
{
Disconnect();
return;
}
if (isHeader)
{
}
else
{
}
}
catch
{
Disconnect();
}
}
Now we first have to handle the header which is a simple process.
We'll create a new datapacket as the header and it should have the dataholder as parameter. Then the required size should be the headers data (as int) - 4, because we do not want to receive the size again.
Then we'll call ReceiveData with the required size and at last we'll set isHeader to false.
Code:
DataPacket header = new DataPacket(dataHolder);
requiredSize = (header.ReadInt32(0) - 4);
ReceiveData(requiredSize);
isHeader = false;
Now to handle the body.
First of all the currentSize should have added the receivedSize to it.
Code:
currentSize += receiveSize;
Then we'll check if the currentSize is equal to the receivedSize.
Code:
if (currentSize == requiredSize)
And else we'll check if the currentSize is above the requiredSize and if it is then we'll disconnect, because that means we've received an invalid buffer.
Code:
else if (currentSize > requiredSize)
{
Disconnect();
}
This should be the callback as of now:
Code:
private void Receive_Callback(IAsyncResult asyncResult)
{
try
{
SocketError err;
int receiveSize = _socket.EndReceive(asyncResult, out err);
if (err != SocketError.Success)
{
Disconnect();
return;
}
if (isHeader)
{
DataPacket header = new DataPacket(dataHolder);
requiredSize = (header.ReadInt32(0) - 4);
ReceiveData(requiredSize);
isHeader = false;
}
else
{
currentSize += receiveSize;
if (currentSize == requiredSize)
{
}
else if (currentSize > requiredSize)
{
Disconnect();
}
}
}
catch
{
Disconnect();
}
}
Now if the sizes match we'll reset the currentSize and tell it that it's not in the header anymore.
Code:
currentSize = 0;
isHeader = true;
Then we'll make a new byte array with the requiredSize and copy the data from dataholder into it.
Code:
byte[] rBuffer = new byte[requiredSize];
System.Buffer.BlockCopy(dataHolder, 0, rBuffer, 0, requiredSize);
At last we'll invoke the OnReceive event and then start to receive the next header.
Code:
SocketServer.OnReceive.Invoke(this, rBuffer);
ReceiveData(4);
Now the last thing to do for the socket client is to make a send method as we also want to send data. This should just take one parameter as a DataPacket and within the method we'll copy the buffer from the data packet into a new byte array.
Code:
public void Send(DataPacket packet)
{
int size = packet.DataBuffer.Length;
byte[] sendBuffer = new byte[size];
System.Buffer.BlockCopy(packet.DataBuffer, 0, sendBuffer, 0, size);
}
We'll also call the BeginSend method here, but before that we'll have to check if the client is connected and also lock the client in case of cross-threading problems!
Code:
try
{
System.Threading.Monitor.Enter(this);
if (_socket.Connected)
{
_socket.BeginSend(sendBuffer, 0, size, SocketFlags.None, new AsyncCallback(Send_Callback), null);
}
}
finally
{
System.Threading.Monitor.Exit(this);
}
We also need a callback, but I don't think I need to explain this callback...
Code:
private void Send_Callback(IAsyncResult asyncResult)
{
try
{
int size = _socket.EndSend(asyncResult);
if (size < 8) // if the size/id is not included in the packet
Disconnect();
}
catch
{
Disconnect();
}
}