Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Picture Chat for .NET

0.00/5 (No votes)
10 Jul 2014 3  
A chat program that supports Unicode input and picture transfer.

Sample Image - PictureChat2.JPG.

1.0 Introduction

Most chat programs are text based, and do not support multi languages. In this article, I would like to share with the reader, the techniques I used to implement multilingual support and picture/media transfer in a chat program.

2.0 Streams and Protocols

A stream is a continuous flow of bytes. For a chat server and a chat client to communicate over the network, each would read and write to the network stream. The network stream is duplex, and bytes transmitted and read are always in FIFO order.

When a chat client is connected to a chat server, they would have established a common network stream to use.

However, for any meaningful communication, there must be some rules and order. These rules and order would be known as the communication protocols. There are at least a few layers of protocols.

At the lowest level, the communicating parties would need to know how to break a continuous flow of bytes into packets/frames. These rules would be referred to as the Network Protocol.

At the next level, the parties would need to interpret the packets/frames. These rules would be known as the Application Protocol(s).

3.0 Network Protocol

I would like to make a distinction between a text stream and a binary stream. In a text stream, only text characters are allowed. In a binary stream, all byte values (0x00 - 0xFF) are allowed.

One way to set markers in a text stream is to use a non-text byte as a marker. For example, the traditional C string is terminated by 0x00, which serves as an end marker.

For a binary stream, there is no way to set a marker because all byte values are legal. Thus, one way to break a binary stream to packets is for the parties to communicate to one another about the size of the binary bytes to follow, before actually sending the bytes.

In ChatStream.cs (and ChatStream.vb), the ChatStream class is implemented with methods for reading and writing text data (Read() and Write()), and also methods for reading and writing binary data (ReadBinary() and WriteBinary()).

Note that in the Write() method, we set a 0x03 as the marker, and Read() will read until the marker is encountered. For the WriteBinary() method, no marker is set, and ReadBinary() requires an input parameter to indicate the number of bytes to read.

public class ChatStream:IChatStream 
{
 //..
 public string Read(NetworkStream n)
 {
   //maximum buffer size is 256 double byte character
   byte[] bytes=new byte[512];
   //total number of bytes read
   int totalbytes=0;
   while(true){
     //reading 2 byte at a time
     int i=n.Read(bytes,totalbytes,2);

     //small endian first byte is lsb
     if(i==2)
     {
       //the special end byte if found
       if(bytes[totalbytes]==(byte)0x03)
         if(bytes[totalbytes+1]==(byte)0x00)
           break;
     }
     else
     {
       //end of stream
       return "";
     }
     //advance to the next position to store read byte(s)
     totalbytes +=i;
   }

   //convert the bytes to a unicode string and return
   UnicodeEncoding Unicode = new UnicodeEncoding();
   int charCount = Unicode.GetCharCount(bytes, 0, totalbytes);
   char[] chars = new Char[charCount];
   Unicode.GetChars(bytes, 0, totalbytes, chars, 0);
   string s=new string(chars);
   return s;
 }

 public void Write(NetworkStream n,string s)
 {
   //Append char 0x03 to end of text string
   s=s+new string((char)0x03,1);
   //byte[] bytes=System.Text.Encoding.ASCII.GetBytes(s);
   UnicodeEncoding Unicode = new UnicodeEncoding();
   byte[] bytes=Unicode.GetBytes(s);
   n.Write(bytes,0,bytes.Length );
   n.Flush();
 }

 public byte[] ReadBinary(NetworkStream n,int numbytes)
 {
   //total bytes read
   int totalbytes=0;

   byte[] readbytes=new byte[numbytes];

   while(totalbytes<numbytes)
   {
     //read as much as possible
     int i=n.Read(readbytes,totalbytes,numbytes-totalbytes);

     //End of stream
     if(i==0)
        return null;

     //advence to the next position to store read byte(s)
     totalbytes +=i;
   }
   return readbytes;
 }

 public void WriteBinary(NetworkStream n,byte[] b)
 {
   n.Write(b,0,b.Length);
   n.Flush();
 }
}
	Public Class ChatStream
		Implements IChatStream

        '''

        Public Function Read(ByVal n As NetworkStream) As String Implements IChatStream.Read
            'maximum buffer size is 256 bytes
            Dim bytes As Byte() = New Byte(511) {}
            'total number of bytes read
            Dim totalbytes As Integer = 0
            While True
                'reading 1 byte at a time
                Dim i As Integer = n.Read(bytes, totalbytes, 2)
                If i = 2 Then
                    'the special end byte if found
                    If bytes(totalbytes) = CByte(&H3) Then
                        If bytes(totalbytes + 1) = CByte(&H0) Then
                            Exit While
                        End If
                    End If
                Else
                    'end of stream
                    Return ""
                End If
                'advance to the next position to store read byte(s)
                totalbytes += i
            End While
            'convert the bytes to a string and return
            Dim Unicode As New UnicodeEncoding()
            Dim charCount As Integer = Unicode.GetCharCount(bytes, 0, totalbytes)
            Dim chars As Char() = New [Char](charCount - 1) {}
            Unicode.GetChars(bytes, 0, totalbytes, chars, 0)
            Dim s As New String(chars)
            Return s
            'System.Text.Encoding.ASCII.GetString(bytes,0,totalbytes);

        End Function

        Public Function Read() As String Implements IChatStream.Read
            Return Read(m_stream)
        End Function

        Public Sub Write(ByVal s As String) Implements IChatStream.Write
            Write(m_stream, s)

        End Sub

        Public Sub Write(ByVal n As NetworkStream, ByVal s As String) Implements IChatStream.Write
            'Append char 0x03 to end of text string
            s = s & New String(Chr(&H3), 1)
            'byte[] bytes=System.Text.Encoding.ASCII.GetBytes(s);
            Dim Unicode As New UnicodeEncoding()
            Dim bytes As Byte() = Unicode.GetBytes(s)
            n.Write(bytes, 0, bytes.Length)
            n.Flush()
        End Sub

        Public Function ReadBinary(ByVal numbytes As Integer) As Byte() Implements IChatStream.ReadBinary
            Return ReadBinary(m_stream, numbytes)
        End Function

        Public Function ReadBinary(ByVal n As NetworkStream, ByVal numbytes As Integer) As Byte() Implements IChatStream.ReadBinary
            'total bytes read
            Dim totalbytes As Integer = 0

            Dim readbytes As Byte() = New Byte(numbytes - 1) {}

            While totalbytes < numbytes
                'read as much as possible
                Dim i As Integer = n.Read(readbytes, totalbytes, numbytes - totalbytes)

                'End of stream
                If i = 0 Then
                    Return Nothing
                End If

                'advence to the next position to store read byte(s)
                totalbytes += i
            End While
            Return readbytes
        End Function


        Public Sub WriteBinary(ByVal b As Byte()) Implements IChatStream.WriteBinary
            WriteBinary(m_stream, b)
        End Sub

        Public Sub WriteBinary(ByVal n As NetworkStream, ByVal b As Byte()) Implements IChatStream.WriteBinary
            n.Write(b, 0, b.Length)
            n.Flush()
        End Sub
		#End Region
	End Class

4.0 Unicode

The traditional C string is a single-byte character string. It is adequate for representing all ASCII characters. However, for languages where the character set has more than 256 characters, a single-byte character representation is no longer adequate.

In .NET as in VB6, strings are all internally double-byte.

To manipulate the double-byte characters in the String class, we can make use of the UnicodeEncoding class.

This code fragment shows how to extract out all the double-byte bytes from a string:

UnicodeEncoding Unicode = new UnicodeEncoding();
byte[] bytes=Unicode.GetBytes(s);
Dim Unicode As New UnicodeEncoding()
Dim bytes As Byte() = Unicode.GetBytes(s)

Similarly, to construct a Unicode string from a byte array:

UnicodeEncoding Unicode = new UnicodeEncoding();
int charCount = Unicode.GetCharCount(bytes, 0, totalbytes);
char[] chars = new Char[charCount];
Unicode.GetChars(bytes, 0, totalbytes, chars, 0);
string s=new string(chars);
Dim Unicode As New UnicodeEncoding()
Dim charCount As Integer = Unicode.GetCharCount(bytes, 0, totalbytes)
Dim chars As Char() = New [Char](charCount - 1) {}
Unicode.GetChars(bytes, 0, totalbytes, chars, 0)
Dim s As New String(chars)

Although the TextBox and RichTextBox controls in .NET support Unicode, a Unicode supported font is needed for display of Unicode characters, and an appropriate IME (Input Method Editor) is needed for Unicode input.

For this program, I make use of Arial Unicode MS which comes with Windows XP. 

You can also use Symbola Fonts from the Ancient Script Fonts web site.

5.0 Sending and Receiving Pictures

If you ever use a binary editor to view a picture file (JPG or BMP), you will know that all byte values are possible in a picture file. To transfer picture binary data from a file or memory stream, we would not be able to set a marker to break the stream as what we can do for text data.

For this program:

The protocol for sending a picture is as follows:

  • The client sends a command: send pic:<target>.
  • When the server receives the command, it will check if <target> has an active connection. It then replies with “<server> send pic”.
  • When the client receives this special message, it will send a text message to indicate to the server the number of bytes of binary data that will be sent over. Following that, the binary data are then sent.
  • The server uses the ChatStream ReadBinay method to get the binary data, and then saves the data to a file marked with the sender and target names.
  • The server will then send a message to the <target> indicating that there is a picture ready for it to retrieve.

The protocol for getting a picture is as follows:

  • The client sends a command: get pic:<sender>.
  • When the server receives the command, it will first check if there is a file with the <sender> and the client name. If so, it will send the reply “<server> get pic”. It will then send a text message to indicate to the client the number of bytes to read. Then the binary data will be sent over.
  • The client uses the common ChatStream ReadBinary method to get the binary data and display the image in the RichTextBox.

The server code for acting on the send_pic and get_pic commands from the client:

//SEND PIC
private void action_send_pic()
{
  string[] s=readdata.Split(':');
  string name="";
  //format is
  //:send pic:<target>
  if (s.Length==3)name=s[2];

  //Locate the target
  TcpClient t=null;
  if (chatserver.FindUserRoom(name)!=0)
    t=(TcpClient)chatserver.ClientConnections[name.ToUpper()];

  //If target is found
  if((t!=null))
  {
    //Inform the sender(client) to send the picture
    chatserver.Write(client.GetStream(), 
                  ChatProtocolValues.SEND_PIC_MSG);

    //Find out the number of byte to read from sender
    string snumbytes=chatserver.Read(client.GetStream());
    int numbytes=int.Parse(snumbytes);

    //read the bytes
    byte[] b=chatserver.ReadBinary(client.GetStream(),numbytes);
    if (b==null)
    {
      chatserver.Write(client.GetStream(),
         "server> Transmission Error");
      return;
    }

    //To store the data in a jpg file
    //name convention is <sender>_<target>.jpg
    FileStream f=new FileStream(nickname+"_"+name+".jpg",FileMode.Create);
    f.Write(b,0,b.Length);
    f.Close();
    //Inform the target that there is a picture from sender
    chatserver.Write (t.GetStream(),
      ChatProtocolValues.PIC_FROM_MSG(nickname,name));
    //Inform the sender that server had received the picture
    chatserver.Write(client.GetStream(),
       ChatProtocolValues.PIC_SEND_MSG(nickname));
  }
  else
  {
    //If target is not found inform sender
    chatserver.Write(client.GetStream(),
      ChatProtocolValues.USER_NOT_FOUND_MSG(name));
  }
}

//GET PIC
private void action_get_pic()
{
  string[] s=readdata.Split(':');
  string sender="";
  string picname="";
  //format is
  //:get pic:<sender>
  if(s.Length==3)sender=s[2];

  //format of saved jpg file is
  //<sender>_<target>.jpg
  //In this case the current user is the target
  picname=sender + "_" + nickname + ".jpg";

  //Check for existence of file
  if(!File.Exists(picname))
    chatserver.Write(client.GetStream(),
      ChatProtocolValues.PIC_NOT_FOUND_MSG(picname));
  else
  {
    //Create a file stream
    FileStream f=new FileStream(picname,FileMode.Open);
    //To get the size of the file for purpose of memory allocation
    FileInfo fi=new FileInfo(picname);
    byte[] b=new byte[fi.Length];
    //Read the content of the file and close
    f.Read(b,0,b.Length);
    f.Close();
    //Inform the client to get the pic
    chatserver.Write (client.GetStream(),
      ChatProtocolValues.GET_PIC_MSG);
    //Inform the client of number of bytes
    chatserver.Write(client.GetStream(),""+b.Length);
    //Send the binary data
    chatserver.WriteBinary(client.GetStream(),b);
    //Inform the client that all binary data has been send
    chatserver.Write(client.GetStream(),
      ChatProtocolValues.PIC_SEND_ACK_MSG);

    //Locate the sender of the picture
    TcpClient t=null;
    if (chatserver.FindUserRoom(sender)!=0)
      t=(TcpClient)chatserver.ClientConnections[sender.ToUpper()];

    //Inform the sender that the target has gotten the picture
    if(t!=null)
      chatserver.Write(t.GetStream(),
        ChatProtocolValues.GOTTEN_PIC_MSG(nickname));
  }
}
'SEND PIC
Private Sub action_send_pic()
    Dim s As String() = readdata.Split(":"C)
    Dim name As String = ""
    'format is
    ':send pic:<target>
    If s.Length = 3 Then
        name = s(2)
    End If

    'Locate the target
    Dim t As TcpClient = Nothing
    If chatserver.FindUserRoom(name) <> 0 Then
        t = DirectCast(chatserver.ClientConnections(name.ToUpper()), TcpClient)
    End If

    'If target is found
    If (t IsNot Nothing) Then
        'Inform the sender(client) to send the picture
        chatserver.Write(client.GetStream(), ChatProtocolValues.SEND_PIC_MSG)

        'Find out the number of byte to read from sender
        Dim snumbytes As String = chatserver.Read(client.GetStream())
        Dim numbytes As Integer = Integer.Parse(snumbytes)

        'read the bytes
        Dim b As Byte() = chatserver.ReadBinary(client.GetStream(), numbytes)
        If b Is Nothing Then
            chatserver.Write(client.GetStream(), "server> Transmission Error")
            Return
        End If

        'To store the data in a jpg file
        'name convention is <sender>_<target>.jpg
        Dim f As New FileStream(nickname & "_" & name & ".jpg", FileMode.Create)
        f.Write(b, 0, b.Length)
        f.Close()
        'Inform the target that there is a picture from sender
        chatserver.Write(t.GetStream(), ChatProtocolValues.PIC_FROM_MSG(nickname, name))
        'Inform the sender that server had received the picture
        chatserver.Write(client.GetStream(), ChatProtocolValues.PIC_SEND_MSG(nickname))
    Else
        'If target is not found inform sender
        chatserver.Write(client.GetStream(), ChatProtocolValues.USER_NOT_FOUND_MSG(name))
    End If
End Sub

'GET PIC
Private Sub action_get_pic()
    Dim s As String() = readdata.Split(":"C)
    Dim sender As String = ""
    Dim picname As String = ""
    'format is
    ':get pic:<sender>
    If s.Length = 3 Then
        sender = s(2)
    End If

    'format of saved jpg file is
    '<sender>_<target>.jpg
    'In this case the current user is the target
    picname = (sender & "_" & nickname &".jpg"

    'Check for existence of file
    If Not File.Exists(picname) Then
        chatserver.Write(client.GetStream(), ChatProtocolValues.PIC_NOT_FOUND_MSG(picname))
    Else
        'Create a file stream
        Dim f As New FileStream(picname, FileMode.Open)
        'To get the size of the file for purpose of memory allocation
        Dim fi As New FileInfo(picname)
        Dim b As Byte() = New Byte(fi.Length - 1) {}
        'Read the content of the file and close
        f.Read(b, 0, b.Length)
        f.Close()
        'Inform the client to get the pic
        chatserver.Write(client.GetStream(), ChatProtocolValues.GET_PIC_MSG)
        'Inform the client of number of bytes
        chatserver.Write(client.GetStream(), "" & b.Length)
        'Send the binary data
        chatserver.WriteBinary(client.GetStream(), b)
        'Inform the client that all binary data has been send
        chatserver.Write(client.GetStream(), ChatProtocolValues.PIC_SEND_ACK_MSG)

        'Locate the sender of the picture
        Dim t As TcpClient = Nothing
        If chatserver.FindUserRoom(sender) <> 0 Then
            t = DirectCast(chatserver.ClientConnections(sender.ToUpper()), TcpClient)
        End If

        'Inform the sender that the target has gotten the picture
        If t IsNot Nothing Then
            chatserver.Write(t.GetStream(), ChatProtocolValues.GOTTEN_PIC_MSG(nickname))
        End If
    End If
End Sub

The client responses to the server's messages:

//SEND PIC
//Sending picture to the server
private void action_server_send_pic()
{
  //Console.WriteLine("server> send pic");
  //Save the picture box image to the memory
  MemoryStream ms=new MemoryStream();
  pic.Image.Save(ms,ImageFormat.Jpeg);
  //Get the memory buffer
  byte[] buf=ms.GetBuffer();
  ms.Close();

  Write("" + buf.Length);
  //Thread.Sleep(500);
  WriteBinary(buf);

  Console.WriteLine("Send: {0} bytes", buf.Length);
}

//GET PIC
//Geting picture from server
private void action_server_get_pic()
{
  string snumbytes=Read();
  int numbytes=int.Parse(snumbytes);
  //int numbytes=0;
  byte[] readbytes=ReadBinary(numbytes);

  if(readbytes==null)
  {
    Console.WriteLine("Error getting picture");
    responseData="server> Error getting picture";
    action_message();
    return;
  }

  //Create the image from memory
  MemoryStream ms=new MemoryStream(readbytes);
  Image img=Image.FromStream(ms);
  ms.Close();

  //Paste picture to the rich text box
  PastePictureToRTB(img);
}
'SEND PIC
'Sending picture to the server
Private Sub action_server_send_pic()
    'Console.WriteLine("server> send pic");
    'Save the picture box image to the memory
    Dim ms As New MemoryStream()
    pic.Image.Save(ms, ImageFormat.Jpeg)
    'Get the memory buffer
    Dim buf As Byte() = ms.GetBuffer()
    ms.Close()

    Write("" + buf.Length)
    'Thread.Sleep(500);
    WriteBinary(buf)

    Console.WriteLine("Send: {0} bytes", buf.Length)
End Sub

'GET PIC
'Geting picture from server
Private Sub action_server_get_pic()
    Dim snumbytes As String = Read()
    Dim numbytes As Integer = Integer.Parse(snumbytes)
    'int numbytes=0;
    Dim readbytes As Byte() = ReadBinary(numbytes)

    If readbytes Is Nothing Then
        Console.WriteLine("Error getting picture")
        responseData = "server> Error getting picture"
        action_message()
        Return
    End If

    'Create the image from memory
    Dim ms As New MemoryStream(readbytes)
    Dim img As Image = Image.FromStream(ms)
    ms.Close()

    'Paste picture to the rich text box
    PastePictureToRTB(img)
End Sub

6.0 Sending, Receiving, and Playing Media Clips

The protocol for transferring media clips is very similar to that for picture transfer. The main difference is that unlike a picture which is basically copied from a picture box in the UI and saved to a file with a fixed jpg extension, the media clips are just files that are tagged and stored by the program, and can have various different extensions. The extensions for these files have to be maintained as the media player relies on the extension to play the files.

When sending the binary data of a media clip to the server, the extension of the clip must be sent. And when the receiver retrieves the binary data from the server, the extension must also be made known so that the media clip file can be recreated with the correct extension.

To resolve this problem, there is a slight change in the protocol. When sending media clip data to the server, the sender first sends a three-character extension, followed by the number of bytes of binary data, and then finally, the binary data. The server first reads the extension, saves the extension to a file named <sender>_<target> (without the extension), and then saves the binary data to a file named <sender>_<target>.<ext>.

private void action_server_send_media()
{
    //To store the data in a media file
    //name convention is <sender>_<target>.ext
    
    if(shp1.Text.Equals("Empty")){
       Write(""+0);
       return;
    }

       String ext=shp1.Text.Substring(shp1.Text.Length-3);
    Write(ext);

    FileInfo fi=new FileInfo(_currentpath +"\\"+nickname+"."+ext);
    FileStream f=new FileStream(_currentpath  +"\\"+nickname+"."+ext,
    FileMode.Open);
    byte[] b=new byte[fi.Length];

    f.Read(b,0,b.Length);
    f.Close();
    Write("" + b.Length);
    //Thread.Sleep(500);
    WriteBinary(b);
    //Console.WriteLine("Send: {0} bytes", b.Length);
}
Private Sub action_server_send_media()
    'To store the data in a media file
    'name convention is <sender>_<target>.ext

    If shp1.Text.Equals("Empty") Then
        Write("" + 0)
        Return
    End If

    Dim ext As [String] = shp1.Text.Substring(shp1.Text.Length - 3)
    Write(ext)

    Dim fi As New FileInfo(_currentpath & "\" + nickname & "." & ext)
    Dim f As New FileStream(_currentpath & "\" & nickname + "." & ext, FileMode.Open)
    Dim b As Byte() = New Byte(fi.Length - 1) {}

    f.Read(b, 0, b.Length)
    f.Close()
    Write("" & b.Length)
    'Thread.Sleep(500);
    WriteBinary(b)
    'Console.WriteLine("Send: {0} bytes", b.Length);
End Sub

Similarly, when the receiver retrieves the media clip, the server first locates the file that stores the extension, retrieves the extension, and the retrieves the file <sender>_<target>.<ext>, and then sends the extension followed by the media clip binary data to the receiver.

private void action_server_get_media()
{
    string ext=Read();

    string snumbytes=Read();
    int numbytes=int.Parse(snumbytes);
    //int numbytes=0;
    byte[] readbytes=ReadBinary(numbytes);
    
    if(readbytes==null)
    {
        //Console.WriteLine("Error getting picture");
        responseData="server> Error getting picture";
        action_message();
        return;
    }

    FileStream f=new FileStream(_currentpath + 
        "\\"+nickname+"_received."+ext,
        FileMode.Create);
    f.Write(readbytes,0,numbytes);
    f.Close();

    // shpR.Text=""+(numbytes/1000)+"KB";

    rtb.SelectionStart=rtb.Text.Length;
    _media_id++;

    string c_currentpath=_currentpath.Replace(" ","@");

    rtb.SelectedText="\nfile:///" + c_currentpath + 
        "\\"+nickname+"_received" +
        _media_id+"."+ext;

    File.Copy(_currentpath +"\\"+nickname+"_received."+ext,
    _currentpath +"\\"+nickname+"_received" + 
    _media_id+"."+ext,true);

}
Private Sub action_server_get_media()
    Dim ext As String = Read()

    Dim snumbytes As String = Read()
    Dim numbytes As Integer = Integer.Parse(snumbytes)
    'int numbytes=0;
    Dim readbytes As Byte() = ReadBinary(numbytes)

    If readbytes Is Nothing Then
        'Console.WriteLine("Error getting picture");
        responseData = "server> Error getting picture"
        action_message()
        Return
    End If

    Dim f As New FileStream(_currentpath & "\" & nickname & "_received." & ext, FileMode.Create)
    f.Write(readbytes, 0, numbytes)
    f.Close()

    ' shpR.Text=""&(numbytes/1000)&"KB";

    rtb.SelectionStart = rtb.Text.Length
    _media_id += 1

    Dim c_currentpath As String = _currentpath.Replace(" ", "@")

    rtb.SelectedText = vbLf & "file:///" & c_currentpath & "\" & nickname & "_received" & _media_id & "." & ext

    File.Copy(_currentpath & "\" & nickname & "_received." & ext, _currentpath &"\" & nickname & "_received" & _media_id +&"." & ext, True)

End Sub

To play the media clip, the system must have the media player installed. The chat client locates the media player from the Windows registry and sends the clip to be played by the media player.

public class WinMediaPlayer
{
    public static string GetMediaPlayerDirectory()
    {

        try
        {
            Microsoft.Win32.RegistryKey localmachineregkey=
                Microsoft.Win32.Registry.LocalMachine;
            Microsoft.Win32.RegistryKey mediaplayerkey=
                localmachineregkey.OpenSubKey(@"SOFTWARE\Microsoft\MediaPlayer");
            return (string)mediaplayerkey.GetValue("Installation Directory");
        }
        catch
        {
           return "";
        }
    }

    public static void Play(IntPtr hwnd,string strFileName)
    {
      if(!ChatClient.MediaPlayerDirectory.Equals(""))
        Helpers.ShellExecute(hwnd,"open","wmplayer",
                "\""+strFileName+"\"",ChatClient.MediaPlayerDirectory ,
                Helpers.SW_NORMAL);
    }
}
Public Class WinMediaPlayer
    Public Shared Function GetMediaPlayerDirectory() As String

        Try
            Dim localmachineregkey As Microsoft.Win32.RegistryKey = Microsoft.Win32.Registry.LocalMachine
            Dim mediaplayerkey As Microsoft.Win32.RegistryKey = localmachineregkey.OpenSubKey("SOFTWARE\Microsoft\MediaPlayer")
            Return DirectCast(mediaplayerkey.GetValue("Installation Directory"), String)
        Catch
            Return ""
        End Try
    End Function

    Public Shared Sub Play(hwnd As IntPtr, strFileName As String)
        If Not ChatClient.MediaPlayerDirectory.Equals("") Then
            Helpers.ShellExecute(hwnd, "open", "wmplayer", """" & strFileName & """", ChatClient.MediaPlayerDirectory, Helpers.SW_NORMAL)
        End If
    End Sub
End Class

 

7.0 Server Program

The server program starts by creating the requested number of chat rooms, and reads out the users by deserializing the users.bin file. Then, it continues to listen for connections. Once a connection is established, it sprouts a new SocketHelper object to manage the communication with the connected client.

public class ChatServer:ChatStream,IChatServer
{
  //...
  //2 parameters Constructor
  public ChatServer(int port_no,int num_room)
  {
    this.port_no =port_no;
    this.num_room=num_room;

    //init num per room used for room assignment
    num_per_room=DEFAULT_NUM_PER_ROOM;

    //instantiate the connection listener
    listener=new TcpListener(IPAddress.Any,this.port_no);

    //get all the registered users from file
    DeserializeChatUsers("users.bin");

    //init all the rooms
    roomusers=new Hashtable[num_room];
    for(int i=0;i<num_room;i++)
      roomusers[i]=new Hashtable();

    //init connections
    connections=new Hashtable();

    //start listening for connection
    Listener.Start();

    //Loop forever
    //The only way to break is to use clt break key
    while(true)
    {
      Console.WriteLine("Waiting for connection...");
      //get the connected client
      TcpClient client=Listener.AcceptTcpClient();
      //create a new socket helper to manage the connection
      SocketHelper sh=new SocketHelper(this,client);
      Console.WriteLine("Connected");
    }
  }
}
Public Class ChatServer
    Inherits ChatStream
    Implements IChatServer
    '...
    '2 parameters Constructor
    Public Sub New(port_no As Integer, num_room As Integer)
        Me.port_no = port_no
        Me.num_room = num_room

        'init num per room used for room assignment
        num_per_room = DEFAULT_NUM_PER_ROOM

        'instantiate the connection listener
        listener = New TcpListener(IPAddress.Any, Me.port_no)

        'get all the registered users from file
        DeserializeChatUsers("users.bin")

        'init all the rooms
        roomusers = New Hashtable(num_room - 1) {}
        For i As Integer = 0 To num_room - 1
            roomusers(i) = New Hashtable()
        Next

        'init connections
        connections = New Hashtable()

        'start listening for connection
        Listener.Start()

        'Loop forever
        'The only way to break is to use clt break key
        While True
            Console.WriteLine("Waiting for connection...")
            'get the connected client
            Dim client As TcpClient = Listener.AcceptTcpClient()
            'create a new socket helper to manage the connection
            Dim sh As New SocketHelper(Me, client)
            Console.WriteLine("Connected")
        End While
    End Sub
End Class

To run the server:

chatserver <port_number> <num_room>

E.g.: chatserver 1300 10 uses port 1300, and creates 10 chat rooms.

8.0 Client Program

The client program starts by connecting to the server. It then attempts to perform authentication. If successful, a thread is started to listen for the server's messages. A form is then loaded to show the UI and handle user's interaction.

public ChatClient(string _host,int _port)
{
    
    _currentpath=Path.GetDirectoryName(
      Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName);
            
    //store the host string
    host =_host;

    //store the port number
    port=_port;

    //Get the connection
    try
    {    
        tcpc=Connect(host,port);
        stream=tcpc.GetStream();
        Stream=stream;
    }
    catch//(Exception e)
    {
        //Console.WriteLine(e);
        Environment.Exit(0);    
    }


    //Initialize the GUI
    init_components();    
    
    //Start Listening
    tl=new Thread(new ThreadStart(Listen));
    tl.Start();
 
}
Public Sub New(_host As String, _port As Integer)

    _currentpath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetModules()(0).FullyQualifiedName)

    'store the host string
    host = _host

    'store the port number
    port = _port

    'Get the connection
    Try
        tcpc = Connect(host, port)
        stream = tcpc.GetStream()
        Stream = stream
    Catch
        '(Exception e)
        'Console.WriteLine(e);
        Environment.[Exit](0)
    End Try

    'Initialize the GUI
    init_components()

    'Start Listening
    tl = New Thread(New ThreadStart(Listen))

    tl.Start()
End Sub

To run the client program:

chatclient <server_name_or_ip> <port_no>

E.g.: chatclient localhost 1300, or chatclient 127.0.0.1 1300

9.0 Chat Client User Interface

The user can either key in commands at the text-box, or use the menu system.

For chatting, just type into the chat box and hit enter. Your message will be broadcast to all the users in the same room.

These are the commands:

Command Description
:help list out all the valid command
:change room change to another chat room
:list  list all the users in the current chat room
:list all list all the users in all the chat rooms
:list <room_no> list all the user in <room no>
:which room find out which room you are in
:private:<target>:<message> send a private message to a user regardless of room number
:send pic:<target> send the picturebox image to a user
:get pic:<sender> get the picture from the sender
:send media:<target> send the loaded media file to a user. Media files are all files that can be played by the Media Player, eg wma, wmv, avi, jpg..
:get media:<sender> get the media file from the sender
:quit quit chatting

I have also added a feature for direct typing of Unicode characters to the textbox. Type \u####<space>

For example to enter the character ☺ (simling face), type \u263a follow by <space>

See my article for more details:

Fun with Unicode

10.0 Testing

Testing the chat server and multiple chat clients all from the same computer. 

1. Start a command prompt windows, cd to the directory containing chatserver.exe. Run chatserver 1300 10

2. Start another command prompt, cd to the directory containing chatclient.exe. Run chatclient 127.0.0.1 1300. Register: Userid: user1, Password user1

3. Start another command prompt, cd to the directory containing chatclient.exe. Run chatclient 127.0.0.1 1300. Register: Userid: user2, Password:user2

4. Send a message from user1 to user2.

5. For user2, using the mouse draw on the picture box. In the text box, type   :send pic:user1 , then press the <enter> key

6. For user1, right click on the green rounded button, select <load media file> to load a media file from the file selection dialog box.

7. For user1, type :send media:user2

Testing over the LAN

1. Select a port number that is not blocked for the computer hosting chatserver and computers running chatclient

2. By default we have choosen port number 1300. You can choose any port number that is not used by other application, for example 9192

3. For the computer running chatserver, start a command prompt windows, cd to the directory containing chatserver.exe. Run chatserver 1300 10

4. For another computer running chatclient, start a command prompt, cd to the directory containing chatclient.exe. Run chatclient <server_name_or_ip> <port_no>. Register: Userid: user3 Password user3

5. For another computer running chatclient, start a command prompt, cd to the directory containing chatclient.exe. Run chatclient <server_name_or_ip> <port_no>. Register: Userid: user4 Password user4

6. For user4, using the mouse draw on the picture box. In the text box, type   :send pic:user3 , then press the <enter> key

7. For user3, right click on the green rounded button, select <load media file> to load a media file from the file selection dialog box.

8. For user3, type :send media:user4

11.0 Conclusion

I hope that the readers would benefit from this article and its associated code. I would welcome any comments and contributions.

Happy picture chatting.

History

  • Version 1.0: Sep 2004.
  • Version 2.0: Jun 2006.
  • Version 3.0: Jul 2008.
  • Version 4.0: May 2014 - ChatClient.cs can now be edited by the Visual Designer in Visual Studio
  • Version 4.1: 19 May 2014 - Direct uncode typing into the textbox
  • 7 July 2014: Include source code for VB.Net

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here