Explaining BubbleChat & Network Programming Part 1 2023-03-03
Hello There! So this is a series of posts I am going to make discussing and documenting my current project at the time of writing this which is BubbleChat. BubbleChat is just a simple chat program I am making for a few reasons.
- I wanted to practice using Sockets by building something bigger than I usually make with them
- The logic for the messaging wouldnt be incredibly hard
- It will help me later down the line if I have to implement it in another project.
- But most importantly it just sounded like a fun project
Im not going to lie to you though I had originally planned on talking about the basic functionality in this part but I wanted to discuss how sockets work for those that dont know how they work, and as I was writing it out it was getting long enough to just call it its own post, but in my experience sockets can be really hard to find good resources on and not many really go into detail about how to really improve at using them. For me I learned from a friend I met in a discord who offered to show me how they work and what I can do with them, so I completely understand if you are one of those people that are confused on how this all works too.
I feel like dedicating this part to covering the basics will ensure everyone is on the same page as we go along, and provide a much needed baseline for starting out with sockets, and it will let me go into a bit of depth about everything. Since this project is written in C# so will the examples but that doesn't mean that you cant follow along with another language.
All of the code for my project is going to be on my GitHub. Just in case someone wanted to use it or fork it.
Introduction to Sockets
A socket is the node that binds an IP address to a port that ends up representing one of the endpoints in a two-way communication between computers. We use sockets in many applications such as multiplayer video games and HTTP/HTTPS clients and servers aka whenever you use your browser.
Introduction to Servers
A server can be defined in a few different ways in the world of IT and Programming. The first being a physical computer, and the second being software that handles packets coming from clients. If you really want a deeper dive into this LiveOverflow created an AMAZING video on this exact subject you can use this link, so lets look at the code for a basic server.
// server.cs
using System; // Console.WriteLine()
using System.Net; // IPHostEntry IPAddress IPEndPoint Dns
using System.Text; // Encoding.ASCII.GetBytes() Encoding.ASCII.GetString()
using System.Net.Sockets; // Socket
namespace Server;
class Program
{
static void Main(string[] args)
{
IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName()); // Gets ip address
IPAddress ServerIp = host.AddressList[1]; // Selects the private IPv4 address from the list
IPEndPoint ServerEndPoint = new(ServerIp, 12345); // Creates the EndPoint for the server
// Creates a socket.
// AddressFamily.InterNetwork tells it to use IPv4
// SocketType.Stream and ProtocolType.Tcp tell it to use TCP
Socket socket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(ServerEndPoint); // Binds that socket to the network endpoint we created
socket.Listen(); //Listens for connections
Socket clientConn = socket.Accept(); // Accepts the connection and creates a new socket to handle the transmissions with the client
byte[] buffer; //where we store our data
while(true)
{
buffer = new byte[1024];
int bytesReceived = clientConn.Receive(buffer); // put bytes into buffer and store byte count in bytesReceived
if (bytesReceived == 0) break;
string message = Encoding.ASCII.GetString(buffer);
Console.WriteLine(message);
message = "[SERVER] HELLO CLIENT!";
clientConn.Send(Encoding.ASCII.GetBytes(message));
}
socket.Close();
socket.Dispose();
}
}
This might seem complicated at first but theres a basic proceedure for this to work. First we need to get the IP address of the machine so we can create an endpoint which is covered in the section below.
IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName()); // Gets ip address
IPAddress ServerIp = host.AddressList[1]; // Selects the private IPv4 address from the list
IPEndPoint ServerEndPoint = new(ServerIp, 12345); // Creates the EndPoint for the server
Just something I wanted to mention was why I chose host.AddressList[1]
. HostAddress is a list containing
the IP addresses of the host object we created when we used Dns.GetHostEntry(Dns.GetHostName());
, and its specifically how to get the private IPv4 address of the computer.
And as mentioned before the Endpoint is just the combination of the IP and Port which we create for the next step
// Creates a socket.
// AddressFamily.InterNetwork tells it to use IPv4
// SocketType.Stream and ProtocolType.Tcp tell it to use TCP
Socket socket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(ServerEndPoint); // Binds that socket to the network endpoint we created
socket.Listen(); //Listens for connections
Socket clientConn = socket.Accept(); // Accepts the connection and creates a new socket to handle the transmissions with the client
We create a socket that will act as the primary connection for the server.
I know that these parameters SocketType.Stream, ProtocolType.Tcp
kinda seem redundant to have next to each other,
however SocketType
tells the socket to use Tcp ProtocolType
and this sets the protocol type.
Then we use the endpoint we created earlier and bind the socket to it so its using that IP and port to send and receive data over.
Next we start listening for connections on the socket, and accept any incoming connections that we get,
but when we accept we are also creating a new socket which is then used to handle the connection with that specific client that connected and we will refer to that as clientConn
.
That socket is whats used in the main server loop.
byte[] buffer; //where we store our data
while(true)
{
buffer = new byte[1024];
int bytesReceived = clientConn.Receive(buffer); // put bytes into buffer and store byte count in bytesReceived
if (bytesReceived == 0) break;
string message = Encoding.ASCII.GetString(buffer);
Console.WriteLine(message);
message = "[SERVER] HELLO CLIENT!";
clientConn.Send(Encoding.ASCII.GetBytes(message));
}
A buffer is a term for a temporary place we store data. With our buffer were going to be storing the data we received from the client.
so when we call clientConn.Recieve()
we tell it to store the data in buffer
and the count of bytes it received in bytesReceived
,
but just to make sure we still have an active connection we want to check if it received anything and if it didnt receive a single byte we break out of the loop
To get the string the client sent all we have to do is call Encoding.ASCII.GetString(buffer)
assuming we KNOW its a string but we know it is so this is fine.
the final section of the loop is just us making a string for the client to receive, converting it to byte[]
with Encoding.ASCII.GetBytes(message)
and sending it.
When we break out of the loop we close the connection and call dispose to release the unmanaged resources back to the GC.
socket.Close();
socket.Dispose();
In the server code we are introduced to all of the basic concepts that sockets use to function properly, so in the client explaination Im not going to be rehashing the explainations with syntax but I will still leave comments.
Introduction to Clients
The client is the program the user runs in order to interact with the server.
// client.cs
using System; // Console.WriteLine()
using System.Net; // IPAddress IPEndPoint
using System.Text; // Encoding.ASCII.GetBytes() Encoding.ASCII.GetString()
using System.Net.Sockets; // Socket
namespace Client;
class Program
{
static void Main(string[] args)
{
IPAddress ip = IPAddress.Parse(""); // the server IP here
IPEndPoint serverEndPoint = new(ip, 12345);
Socket socket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(serverEndPoint);
string message = "[CLIENT] HELLO SERVER!";
socket.Send(Encoding.ASCII.GetBytes(message)); // Converts message to bytes and sends it
byte[] buffer = new byte[1024];
int bytesReceived = socket.Receive(buffer); // Receives data and stores it in the buffer
Console.WriteLine(Encoding.ASCII.GetString(buffer));
socket.Close();
socket.Dispose();
}
}
Already the client is looking much simpler than the server, and that typically is the case.
This is the basic layout of the logic for the client.
Create the socket
Socket socket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Connect to the server
socket.Connect(serverEndPoint);
And from there you can client.Send()
, client.Receive()
when appropriate
and when you are done you can call client.Close()
& client.Dispose()
just as we did in the server its the same concepts so its self explanatory.
One of the main reasons being that the client only needs to worry about one connection with one socket because the server is the only node its connected to while the server is meant to handle many connections with many clients. Now you might be thinking, Bubbles these examples are alright and you covered most of the syntax but I have questions,
How does the client know what to do with the servers data??
How does the server know what data to send to the client??
How do I begin to send data back and forth that matters instead of the HELLO CLIENT/SERVER message????
well those are some good questions if you had those. The way BubbleChat, HTTP, FTP, and even the video games you play all know how to talk to each other is because they have established a protocol on how to talk to one another. If the client or server doesnt follow the protocol then it will not function properly.
But this is another rabbit hole we can dive down so I am going to talk about that when in the next part. I hope you enjoyed reading this and it helped you understand sockets more or maybe even answer a question for you and next we can actually take a look at BubbleChat Server/Client logic and how it handles defining a protocol and creating packets!