65.9K
CodeProject 正在变化。 阅读更多。
Home

.NET 图片聊天

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (73投票s)

2004年9月2日

CPOL

10分钟阅读

viewsIcon

434823

downloadIcon

11037

一个支持 Unicode 输入和图片传输的聊天程序。

Sample Image - PictureChat2.JPG.

1.0 简介

大多数聊天程序都是基于文本的,不支持多语言。在本文中,我将与读者分享我在聊天程序中实现多语言支持和图片/媒体传输的技术。

2.0 流和协议

流是连续的字节流。对于聊天服务器和聊天客户端在网络上进行通信,它们各自会读写网络流。网络流是双工的,传输和读取的字节总是按先进先出 (FIFO) 的顺序。

当聊天客户端连接到聊天服务器时,它们就建立了一个共同的网络流供使用。

然而,为了进行任何有意义的通信,必须有一些规则和顺序。这些规则和顺序被称为通信协议。至少有几层协议。

在最低层,通信双方需要知道如何将连续的字节流分解成数据包/帧。这些规则被称为网络协议。

在下一层,双方需要解释数据包/帧。这些规则被称为应用协议。

3.0 网络协议

我想区分文本流和二进制流。在文本流中,只允许文本字符。在二进制流中,允许所有字节值(0x00 - 0xFF)。

在文本流中设置标记的一种方法是使用非文本字节作为标记。例如,传统的 C 字符串以 0x00 终止,它作为结束标记。

对于二进制流,无法设置标记,因为所有字节值都是合法的。因此,将二进制流分解成数据包的一种方法是,双方在实际发送字节之前相互告知接下来要发送的二进制字节的大小。

ChatStream.cs(和 ChatStream.vb)中,ChatStream 类实现了用于读写文本数据的方法(Read()Write()),以及用于读写二进制数据的方法(ReadBinary()WriteBinary())。

请注意,在 Write() 方法中,我们将 0x03 设置为标记,Read() 将一直读取直到遇到标记。对于 WriteBinary() 方法,没有设置标记,并且 ReadBinary() 需要一个输入参数来指示要读取的字节数。

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

传统的 C 字符串是单字节字符字符串。它足以表示所有 ASCII 字符。然而,对于字符集超过 256 个字符的语言,单字节字符表示就不再足够了。

在 .NET 中,与 VB6 一样,字符串在内部都是双字节的。

要操作 String 类中的双字节字符,我们可以使用 UnicodeEncoding 类。

此代码片段展示了如何从字符串中提取所有双字节

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

同样,要从字节数组构造 Unicode 字符串

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)

尽管 .NET 中的 TextBoxRichTextBox 控件支持 Unicode,但显示 Unicode 字符需要支持 Unicode 的字体,并且 Unicode 输入需要适当的 IME(输入法编辑器)。

对于这个程序,我使用了 Windows XP 自带的 Arial Unicode MS

您还可以使用来自 Ancient Script Fonts 网站的 Symbola 字体

5.0 发送和接收图片

如果您曾经使用二进制编辑器查看图片文件(JPG 或 BMP),您会知道图片文件中可能存在所有字节值。要从文件或内存流传输图片二进制数据,我们将无法设置标记来中断流,就像我们对文本数据所做的那样。

对于这个程序

发送图片的协议如下:

  • 客户端发送命令:send pic:<目标>
  • 当服务器收到命令时,它会检查 <目标> 是否有活动连接。然后它回复“<服务器> send pic”。
  • 当客户端收到此特殊消息时,它会发送一条文本消息告知服务器将要发送的二进制数据字节数。随后,发送二进制数据。
  • 服务器使用 ChatStreamReadBinary 方法获取二进制数据,然后将数据保存到以发送者和目标名称标记的文件中。
  • 服务器随后会向 <目标> 发送一条消息,指示有图片可供其检索。

获取图片的协议如下:

  • 客户端发送命令:get pic:<发送者>
  • 当服务器收到命令时,它会首先检查是否存在带有 <发送者> 和客户端名称的文件。如果存在,它将发送回复“<服务器> get pic”。然后它会发送一条文本消息告知客户端要读取的字节数。然后发送二进制数据。
  • 客户端使用通用的 ChatStreamReadBinary 方法获取二进制数据并在 RichTextBox 中显示图像。

服务器代码用于处理客户端的 send_picget_pic 命令

//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

客户端对服务器消息的响应

//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 发送、接收和播放媒体剪辑

传输媒体剪辑的协议与图片传输非常相似。主要区别在于,与图片(基本上是从 UI 中的图片框复制并保存到具有固定 jpg 扩展名的文件)不同,媒体剪辑只是被程序标记和存储的文件,并且可以有各种不同的扩展名。这些文件的扩展名必须保留,因为媒体播放器依靠扩展名来播放文件。

向服务器发送媒体剪辑的二进制数据时,必须发送剪辑的扩展名。当接收方从服务器检索二进制数据时,也必须告知扩展名,以便可以使用正确的扩展名重新创建媒体剪辑文件。

为了解决这个问题,协议略有改变。当向服务器发送媒体剪辑数据时,发送方首先发送一个三字符扩展名,然后是二进制数据的字节数,最后是二进制数据。服务器首先读取扩展名,将扩展名保存到名为 <发送方>_<目标方> 的文件中(不带扩展名),然后将二进制数据保存到名为 <发送方>_<目标方>.<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

类似地,当接收方检索媒体剪辑时,服务器首先找到存储扩展名的文件,检索扩展名,然后检索文件 <发送方>_<目标方>.<ext>,然后将扩展名连同媒体剪辑二进制数据发送给接收方。

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

要播放媒体剪辑,系统必须安装媒体播放器。聊天客户端从 Windows 注册表中找到媒体播放器,并将剪辑发送给媒体播放器播放。

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 服务器程序

服务器程序通过创建所需数量的聊天室开始,并通过反序列化 users.bin 文件读取用户。然后,它继续监听连接。一旦建立连接,它就会生成一个新的 SocketHelper 对象来管理与连接客户端的通信。

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

运行服务器

chatserver <port_number> <num_room>

例如:chatserver 1300 10 使用端口 1300,并创建 10 个聊天室。

8.0 客户端程序

客户端程序首先连接到服务器。然后它尝试执行身份验证。如果成功,将启动一个线程来监听服务器的消息。然后加载一个表单以显示 UI 并处理用户的交互。

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

运行客户端程序

chatclient <server_name_or_ip> <port_no>

例如:chatclient localhost 1300,或 chatclient 127.0.0.1 1300

9.0 聊天客户端用户界面

用户可以在文本框中输入命令,或者使用菜单系统。

聊天时,只需在聊天框中输入并按回车键。您的消息将广播给同一房间中的所有用户。

以下是命令

命令 描述
:help 列出所有有效命令
:change room 切换到另一个聊天室
:list 列出当前聊天室中的所有用户
:list all 列出所有聊天室中的所有用户
:list <room_no> 列出 <room no> 中的所有用户
:which room 找出您所在的房间
:private:<目标>:<消息> 向用户发送私信,无论房间号如何
:send pic:<目标> 将图片框图像发送给用户
:get pic:<发送者> 从发送者处获取图片
:send media:<目标> 将加载的媒体文件发送给用户。媒体文件是所有可以被媒体播放器播放的文件,例如 wma、wmv、avi、jpg 等。
:get media:<发送者> 从发送者处获取媒体文件
:quit 退出聊天

我还添加了一个功能,可以直接在文本框中输入 Unicode 字符。输入 \u####<空格>

例如,要输入字符 ☺(笑脸),输入 \u263a,然后按 <空格>

请参阅我的文章了解更多详细信息

玩转 Unicode

10.0 测试

在同一台计算机上测试聊天服务器和多个聊天客户端。

1. 启动一个命令提示符窗口,cd 到包含 chatserver.exe 的目录。运行 chatserver 1300 10

2. 启动另一个命令提示符,cd 到包含 chatclient.exe 的目录。运行 chatclient 127.0.0.1 1300。注册:用户 ID:user1,密码:user1

3. 启动另一个命令提示符,cd 到包含 chatclient.exe 的目录。运行 chatclient 127.0.0.1 1300。注册:用户 ID:user2,密码:user2

4. 从 user1 向 user2 发送消息。

5. 对于 user2,用鼠标在图片框上绘制。在文本框中输入: :send pic:user1 ,然后按 <回车> 键

6. 对于 user1,右键单击绿色圆形按钮,选择 <加载媒体文件> 从文件选择对话框中加载媒体文件。

7. 对于 user1,输入 :send media:user2

在局域网中测试

1. 为承载 chatserver 的计算机和运行 chatclient 的计算机选择一个未被阻止的端口号

2. 默认情况下,我们选择了端口号 1300。您可以选择任何未被其他应用程序使用的端口号,例如 9192

3. 对于运行 chatserver 的计算机,启动一个命令提示符窗口,cd 到包含 chatserver.exe 的目录。运行 chatserver 1300 10

4. 对于另一台运行 chatclient 的计算机,启动一个命令提示符,cd 到包含 chatclient.exe 的目录。运行 chatclient <服务器名称或 IP> <端口号>。注册:用户 ID:user3 密码:user3

5. 对于另一台运行 chatclient 的计算机,启动一个命令提示符,cd 到包含 chatclient.exe 的目录。运行 chatclient <服务器名称或 IP> <端口号>。注册:用户 ID:user4 密码:user4

6. 对于 user4,用鼠标在图片框上绘制。在文本框中输入: :send pic:user3 ,然后按 <回车> 键

7. 对于 user3,右键单击绿色圆形按钮,选择 <加载媒体文件> 从文件选择对话框中加载媒体文件。

8. 对于 user3,输入 :send media:user4

11.0 结论

我希望读者能从本文及其相关代码中受益。我欢迎任何评论和贡献。

祝您图片聊天愉快。

历史

  • 版本 1.0:2004 年 9 月。
  • 版本 2.0:2006 年 6 月。
  • 版本 3.0:2008 年 7 月。
  • 版本 4.0:2014 年 5 月 - ChatClient.cs 现在可以通过 Visual Studio 中的可视化设计器进行编辑
  • 版本 4.1:2014 年 5 月 19 日 - 直接在文本框中输入 Unicode
  • 2014 年 7 月 7 日:包含 VB.Net 源代码
© . All rights reserved.