Writing a multi-user chat in C # in 15 minutes
C # is as stupid as it is simple. And it is so simple that a chat for several people with minimal protection is written in it for fifteen, well, a maximum of thirty minutes. It took us a little more than two days, but here is the problem of fools, not tools.
Let’s complete the task: we want to make a decentralized group chat with some protection. For such a “large” task, we need nothing at all: C # (you can even use the non-Orthodox MonoDevelop) with its beautiful .NET Framework.
First, write the part of sending and receiving messages. The problem is that you need to somehow deal with sending messages to multiple clients. This is quite difficult to do, all sorts of dirty thoughts a la immediately come to the unfamiliar with the networks: to store all IPs that they ever sent to messages, or to organize some tricky graph between users there. As usual, the problem is solved if you look at it from the side: why do we need to use TCP when there is UDP?
After completely futile attempts to establish at least some kind of multi-user interaction via TCP, I chose the second option, and everything turned out to be very simple - there are separate groups in UDP, and the participants cannot send a message to any particular group member so simply. If a message is sent, it is sent to all members of the group - what is needed for us. Let's create the Chat class, in which there will be the following fields and methods:
View code
private UdpClient udpclient;
private IPAddress multicastaddress;
private IPEndPoint remoteep;
public void SendOpenMessage(string data);
public void Listen();
For the fields UdpClient, IPAddress and IPEndPoint, we will connect the System.Net.Sockets and System.Net libraries
Well, the constructor-destructors by itself. In the constructor, we will initialize the udpclient field:
View code
public Chat()
{
multicastaddress = IPAddress.Parse("239.0.0.222"); // один из зарезервированных для локальных нужд UDP адресов
udpclient = new UdpClient();
udpclient.JoinMulticastGroup(multicastaddress);
remoteep = new IPEndPoint(multicastaddress, 2222);
}
We will not do anything in the destructor yet - the Garbage collector is the same.
Now the main thing is SendMessage and Listen. SendMessage will send UTF8 a string representation, and here again C # comes to the rescue, in which you can get a byte representation in one line:
View code
public void SendMessage(string data)
{
Byte[] buffer = Encoding.UTF8.GetBytes(data);
udpclient.Send(buffer, buffer.Length, remoteep);
}
In the Listen method, we simply start a separate thread on the same address and port through which we send messages, which will receive bytes, decrypt them, and write them to the common console:
View code
public void Listen()
{
UdpClient client = new UdpClient();
client.ExclusiveAddressUse = false;
IPEndPoint localEp = new IPEndPoint(IPAddress.Any, 2222);
client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
client.ExclusiveAddressUse = false;
client.Client.Bind(localEp);
client.JoinMulticastGroup(multicastaddress);
Console.WriteLine("\tListening started");
string formatted_data;
while (true)
{
Byte[] data = client.Receive(ref localEp);
formatted_data = Encoding.UTF8.GetString(data);
Console.WriteLine(formatted_data);
}
}
Messaging is now over. Encryption is screwed even easier: for it, we will have to ask the user for a key when creating a chat object, add decryption encryption methods, send a string to the group after processing by the encryption method, and output it after decryption. Business then.
View code
private byte[] Encrypt(string clearText, string EncryptionKey = "123")
{
byte[] clearBytes = Encoding.UTF8.GetBytes(clearText);
byte[] encrypted;
using (Aes encryptor = Aes.Create())
{
Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 }); // еще один плюс шарпа в наличие таких вот костылей.
encryptor.Key = pdb.GetBytes(32);
encryptor.IV = pdb.GetBytes(16);
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(clearBytes, 0, clearBytes.Length);
cs.Close();
}
encrypted =ms.ToArray();
}
}
return encrypted;
}
private string Decrypt(byte[] cipherBytes, string EncryptionKey = "123")
{
string cipherText = "";
using (Aes encryptor = Aes.Create())
{
Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
encryptor.Key = pdb.GetBytes(32);
encryptor.IV = pdb.GetBytes(16);
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(cipherBytes, 0, cipherBytes.Length);
cs.Close();
}
cipherText = Encoding.UTF8.GetString(ms.ToArray());
}
}
return cipherText;
}
Now you need to slightly modify the SendMessage and Listen methods, adding encryption and decryption there. Pretty trivial in my opinion.
View code
// в SendMessage
public void SendMessage(string data)
{
Byte[] buffer = Encoding.UTF8.GetBytes(data);
Byte[] encrypted = Encrypt(data);
udpclient.Send(encrypted, encrypted.Length, remoteep);
}
// в Listen
while (true)
{
Byte[] data = client.Receive(ref localEp);
formatted_data = Decrypt(data);
Console.WriteLine(formatted_data);
}
Now the final step is the main function. In it, we will run one thread, so we need System.Threading;
With an additional stream, everything is implemented in just four lines:
View code
static void Main(string[] args)
{
Chat chat = new Chat();
Thread ListenThread = new Thread(new ThreadStart(chat.Listen));
ListenThread.Start();
string data = Console.ReadLine();
chat.SendMessage(data);
}
All the simplest messaging we wrote. It can be supplemented with messaging in an endless loop, nicknames, message history, settings, windows - a lot of things, but this can already be attributed to another article.
PS bitbucket.org/AnAverageGuy/termprojectc - here you can find all that gloomy horror, which is shown on the top of the second picture. Someday I will comb all the code and the master branch will turn from pumpkin to carriage.