亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > C# > 正文

詳解C# 網絡編程系列:實現類似QQ的即時通信程序

2020-01-24 00:51:54
字體:
來源:轉載
供稿:網友

引言:

前面專題中介紹了UDP、TCP和P2P編程,并且通過一些小的示例來讓大家更好的理解它們的工作原理以及怎樣.Net類庫去實現它們的。為了讓大家更好的理解我們平常中常見的軟件QQ的工作原理,所以在本專題中將利用前面專題介紹的知識來實現一個類似QQ的聊天程序。

 一、即時通信系統

在我們的生活中經常使用即時通信的軟件,我們經常接觸到的有:QQ、阿里旺旺、MSN等等。這些都是屬于即時通信(Instant Messenger,IM)軟件,IM是指所有能夠即時發送和接收互聯網消息的軟件。

在前面專題P2P編程中介紹過P2P系統分兩種類型――單純型P2P和混合型P2P(QQ就是屬于混合型的應用),混合型P2P系統中的服務器(也叫索引服務器)起到協調的作用。在文件共享類應用中,如果采用混合型P2P技術的話,索引服務器就保存著文件信息,這樣就可能會造成版權的問題,然而在即時通信類的軟件中, 因為客戶端傳遞的都是簡單的聊天文本而不是網絡媒體資源,這樣就不存在版權問題了,在這種情況下,就可以采用混合型P2P技術來實現我們的即時通信軟件。前面已經講了,騰訊的QQ就是屬于混合型P2P的軟件。

因此本專題要實現一個類似QQ的聊天程序,其中用到的P2P技術是屬于混合型P2P,而不是前一專題中的采用的單純型P2P技術,同時本程序的實現也會用到TCP、UDP編程技術。

二、程序實現的詳細設計

本程序采用P2P方式,各個客戶端之間直接發消息進行聊天,服務器在其中只是起到協調的作用,下面先理清下程序的流程:

2.1 程序流程設計

當一個新用戶通過客戶端登陸系統后,從服務器獲取當在線的用戶信息列表,列表信息包括系統中每個用戶的地址,然后用戶就可以單獨向其他發消息。如果有用戶加入或者在線用戶退出時,服務器就會及時發消息通知系統中的所有其他客戶端,達到它們即時地更新用戶信息列表。

根據上面大致的描述,我們可以把系統的流程分為下面幾步來更好的理解(大家可以參考QQ程序將會更好的理解本程序的流程):

1.用戶通過客戶端進入系統,向服務器發出消息,請求登陸

2.服務器收到請求后,向客戶端返回回應消息,表示同意接受該用戶加入,并把自己(指的是服務器)所在監聽的端口發送給客戶端

3.客戶端根據服務器發送過來的端口號和服務器建立連接

4.服務器通過該連接 把在線用戶的列表信息發送給新加入的客戶端。

5.客戶端獲得了在線用戶列表后就可以自己選擇在線用戶聊天。(程序中另外設計一個類似QQ的聊天窗口來進行聊天)

6.當用戶退出系統時也要及時通知服務器,服務器再把這個消息轉發給每個在線的用戶,使客戶端及時更新本地的用戶信息列表。 

2.2 通信協議設計

所謂協議就是約定,即服務器和客戶端之間會話信息的內容格式進行約定,使雙方都可以識別,達到更好的通信。

下面就具體介紹下協議的設計:

1. 客戶端和服務器之間的對話

(1)登陸過程

① 客戶端用匿名UDP的方式向服務器發出下面的信息:

login, username, localIPEndPoint

 消息內容包括三個字段,每個字段用 “,”分割,login表示的是請求登陸;username表示用戶名;localIPEndPint表示客戶端本地地址。

② 服務器收到后以匿名UDP返回下面的回應:

Accept, port

其中Accept表示服務器接受請求,port表示服務器所在的端口號,服務器監聽著這個端口的客戶端連接

③ 連接服務器,獲取用戶列表

客戶端從上一步獲得了端口號,然后向該端口發起TCP連接,向服務器索取在線用戶列表,服務器接受連接后將用戶列表傳輸到客戶端。用戶列表信息格式如下:

 username1,IPEndPoint1;username2,IPEndPoint2;...;end

username1、username2表示用戶名,IPEndPoint1,IPEndPoint2表示對應的端點,每個用戶信息都是由"用戶名+端點"組成,用戶信息以“;”隔開,整個用戶列表以“end”結尾。

(2)注銷過程

用戶退出時,向服務器發送如下消息:

logout,username,localIPEndPoint

這條消息看字面意思大家都知道就是告訴服務器 username+localIPEndPoint這個用戶要退出了。

2. 服務器管理用戶

(1)新用戶加入通知

  因為系統中在線的每個用戶都有一份當前在線用戶表,因此當有新用戶登錄時,服務器不需要重復地給系統中的每個用戶再發送所有用戶信息,只需要將新加入用戶的信息通知其他用戶,其他用戶再更新自己的用戶列表。

服務器向系統中每個用戶廣播如下信息:login,username,remoteIPEndPoint

在這個過程中服務器只是負責將收到的"login"信息轉發出去。

(2)用戶退出

  與新用戶加入一樣,服務器將用戶退出的消息進行廣播轉發:logout,username,remoteIPEndPoint

3. 客戶端之間聊天

用戶進行聊天時,各自的客戶端之間是以P2P方式進行工作的,不與服務器有直接聯系,這也是P2P技術的特點。

聊天發送的消息格式如下:talk, longtime, selfUserName, message

其中,talk表明這是聊天內容的消息;longtime是長時間格式的當前系統時間;selfUserName為發送發的用戶名;message表示消息的內容。

協議設計介紹完后,下面就進入本程序的具體實現的介紹的。

注:協議是本程序的核心,也是所有軟件的核心,每個軟件產品的協議都是不一樣的,QQ有自己的一套協議,MSN又有另一套協議,所以使用的QQ的用戶無法和用MSN的朋友進行聊天。 

三、程序的實現

 服務器端核心代碼:

// 啟動服務器    // 根據博客中協議的設計部分    // 客戶端先向服務器發送登錄請求,然后通過服務器返回的端口號    // 再與服務器建立連接    // 所以啟動服務按鈕事件中有兩個套接字:一個是接收客戶端信息套接字和    // 監聽客戶端連接套接字    private void btnStart_Click(object sender, EventArgs e)    {      // 創建接收套接字      serverIp = IPAddress.Parse(txbServerIP.Text);      serverIPEndPoint = new IPEndPoint(serverIp, int.Parse(txbServerport.Text));      receiveUdpClient = new UdpClient(serverIPEndPoint);      // 啟動接收線程      Thread receiveThread = new Thread(ReceiveMessage);      receiveThread.Start();      btnStart.Enabled = false;      btnStop.Enabled = true;      // 隨機指定監聽端口      Random random = new Random();      tcpPort = random.Next(port + 1, 65536);      // 創建監聽套接字      tcpListener = new TcpListener(serverIp, tcpPort);      tcpListener.Start();      // 啟動監聽線程      Thread listenThread = new Thread(ListenClientConnect);      listenThread.Start();      AddItemToListBox(string.Format("服務器線程{0}啟動,監聽端口{1}",serverIPEndPoint,tcpPort));    }    // 接收客戶端發來的信息    private void ReceiveMessage()    {      IPEndPoint remoteIPEndPoint = new IPEndPoint(IPAddress.Any, 0);      while (true)      {        try        {          // 關閉receiveUdpClient時下面一行代碼會產生異常          byte[] receiveBytes = receiveUdpClient.Receive(ref remoteIPEndPoint);          string message = Encoding.Unicode.GetString(receiveBytes, 0, receiveBytes.Length);          // 顯示消息內容          AddItemToListBox(string.Format("{0}:{1}",remoteIPEndPoint,message));          // 處理消息數據          // 根據協議的設計部分,從客戶端發送來的消息是具有一定格式的          // 服務器接收消息后要對消息做處理          string[] splitstring = message.Split(',');          // 解析用戶端地址          string[] splitsubstring = splitstring[2].Split(':');          IPEndPoint clientIPEndPoint = new IPEndPoint(IPAddress.Parse(splitsubstring[0]), int.Parse(splitsubstring[1]));          switch (splitstring[0])          {            // 如果是登錄信息,向客戶端發送應答消息和廣播有新用戶登錄消息            case "login":              User user = new User(splitstring[1], clientIPEndPoint);              // 往在線的用戶列表添加新成員              userList.Add(user);              AddItemToListBox(string.Format("用戶{0}({1})加入", user.GetName(), user.GetIPEndPoint()));              string sendString = "Accept," + tcpPort.ToString();              // 向客戶端發送應答消息              SendtoClient(user, sendString);              AddItemToListBox(string.Format("向{0}({1})發出:[{2}]", user.GetName(), user.GetIPEndPoint(), sendString));              for (int i = 0; i < userList.Count; i++)              {                if (userList[i].GetName() != user.GetName())                {                  // 給在線的其他用戶發送廣播消息                  // 通知有新用戶加入                  SendtoClient(userList[i], message);                }              }              AddItemToListBox(string.Format("廣播:[{0}]", message));              break;            case "logout":              for (int i = 0; i < userList.Count; i++)              {                if (userList[i].GetName() == splitstring[1])                {                  AddItemToListBox(string.Format("用戶{0}({1})退出",userList[i].GetName(),userList[i].GetIPEndPoint()));                  userList.RemoveAt(i); // 移除用戶                }              }              for (int i = 0; i < userList.Count; i++)              {                // 廣播注銷消息                SendtoClient(userList[i], message);              }              AddItemToListBox(string.Format("廣播:[{0}]", message));              break;          }        }        catch        {          // 發送異常退出循環          break;        }      }      AddItemToListBox(string.Format("服務線程{0}終止", serverIPEndPoint));    }    // 向客戶端發送消息    private void SendtoClient(User user, string message)    {      // 匿名方式發送      sendUdpClient = new UdpClient(0);      byte[] sendBytes = Encoding.Unicode.GetBytes(message);      IPEndPoint remoteIPEndPoint =user.GetIPEndPoint();      sendUdpClient.Send(sendBytes,sendBytes.Length,remoteIPEndPoint);      sendUdpClient.Close();    }        // 接受客戶端的連接    private void ListenClientConnect()    {      TcpClient newClient = null;      while (true)      {        try        {          newClient = tcpListener.AcceptTcpClient();          AddItemToListBox(string.Format("接受客戶端{0}的TCP請求",newClient.Client.RemoteEndPoint));        }        catch        {          AddItemToListBox(string.Format("監聽線程({0}:{1})", serverIp, tcpPort));          break;        }        Thread sendThread = new Thread(SendData);        sendThread.Start(newClient);      }    }    // 向客戶端發送在線用戶列表信息    // 服務器通過TCP連接把在線用戶列表信息發送給客戶端    private void SendData(object userClient)    {      TcpClient newUserClient = (TcpClient)userClient;      userListstring = null;      for (int i = 0; i < userList.Count; i++)      {        userListstring += userList[i].GetName() + ","          + userList[i].GetIPEndPoint().ToString() + ";";      }      userListstring += "end";      networkStream = newUserClient.GetStream();      binaryWriter = new BinaryWriter(networkStream);      binaryWriter.Write(userListstring);      binaryWriter.Flush();      AddItemToListBox(string.Format("向{0}發送[{1}]", newUserClient.Client.RemoteEndPoint, userListstring));      binaryWriter.Close();      newUserClient.Close();    }

客戶端核心代碼:

// 登錄服務器    private void btnlogin_Click(object sender, EventArgs e)    {      // 創建接受套接字      IPAddress clientIP = IPAddress.Parse(txtLocalIP.Text);      clientIPEndPoint = new IPEndPoint(clientIP, int.Parse(txtlocalport.Text));      receiveUdpClient = new UdpClient(clientIPEndPoint);      // 啟動接收線程      Thread receiveThread = new Thread(ReceiveMessage);      receiveThread.Start();      // 匿名發送      sendUdpClient = new UdpClient(0);      // 啟動發送線程      Thread sendThread = new Thread(SendMessage);      sendThread.Start(string.Format("login,{0},{1}", txtusername.Text, clientIPEndPoint));      btnlogin.Enabled = false;      btnLogout.Enabled = true;      this.Text = txtusername.Text;    }    // 客戶端接受服務器回應消息     private void ReceiveMessage()    {      IPEndPoint remoteIPEndPoint = new IPEndPoint(IPAddress.Any,0);      while (true)      {        try        {          // 關閉receiveUdpClient時會產生異常          byte[] receiveBytes = receiveUdpClient.Receive(ref remoteIPEndPoint);          string message = Encoding.Unicode.GetString(receiveBytes,0,receiveBytes.Length);          // 處理消息          string[] splitstring = message.Split(',');          switch (splitstring[0])          {            case "Accept":              try              {                tcpClient = new TcpClient();                tcpClient.Connect(remoteIPEndPoint.Address, int.Parse(splitstring[1]));                if (tcpClient != null)                {                  // 表示連接成功                  networkStream = tcpClient.GetStream();                  binaryReader = new BinaryReader(networkStream);                }              }              catch              {                MessageBox.Show("連接失敗", "異常");              }              Thread getUserListThread = new Thread(GetUserList);              getUserListThread.Start();              break;            case "login":              string userItem = splitstring[1] + "," + splitstring[2];              AddItemToListView(userItem);              break;            case "logout":              RemoveItemFromListView(splitstring[1]);              break;            case "talk":              for (int i = 0; i < chatFormList.Count; i++)              {                if (chatFormList[i].Text == splitstring[2])                {                  chatFormList[i].ShowTalkInfo(splitstring[2], splitstring[1], splitstring[3]);                }              }              break;          }        }        catch        {          break;        }      }    }    // 從服務器獲取在線用戶列表    private void GetUserList()    {      while (true)      {        userListstring = null;        try        {          userListstring = binaryReader.ReadString();          if (userListstring.EndsWith("end"))          {            string[] splitstring = userListstring.Split(';');            for (int i = 0; i < splitstring.Length - 1; i++)            {              AddItemToListView(splitstring[i]);            }            binaryReader.Close();            tcpClient.Close();            break;          }        }        catch        {          break;        }      }    }  // 發送登錄請求    private void SendMessage(object obj)    {      string message = (string)obj;      byte[] sendbytes = Encoding.Unicode.GetBytes(message);      IPAddress remoteIp = IPAddress.Parse(txtserverIP.Text);      IPEndPoint remoteIPEndPoint = new IPEndPoint(remoteIp, int.Parse(txtServerport.Text));      sendUdpClient.Send(sendbytes, sendbytes.Length, remoteIPEndPoint);      sendUdpClient.Close();    }

程序的運行結果:

首先先運行服務器窗口,在服務器窗口點擊“啟動”按鈕來啟動服務器,然后客戶端首先指定服務器的端口號,修改用戶名(這里也可以不修改,使用默認的也可以),然后點擊“登錄”按鈕來登陸服務器(也就是告訴服務器本地的客戶端地址),然后從服務器端獲得在線用戶列表,界面演示如下:

然后用戶可以雙擊在線用戶進行聊天(此程序支持與多人進行聊天),下面是功能的演示圖片:

雙方進行聊天時,這里沒有實現像QQ一樣,有人發信息來在對應的客戶端就有消息提醒的功能的, 所以雙方進行聊天的過程中,每個客戶端都需要在在線用戶列表中點擊聊天的對象來激活聊天對話框(意思就是從圖片中可以看出“天涯”客戶端想和劍癡聊天的話,就在“在線用戶”列表雙擊劍癡來激活聊天窗口,同時“劍癡”客戶端也必須雙擊“天涯”來激活聊天窗口,這樣雙方就看到對方發來的信息了,(不激活窗口,也是發送了信息,只是沒有一個窗口來進行顯示)),而且從圖片中也可以看出――此程序支持與多人聊天,即天涯同時與“劍癡”和"大地"同時聊天。

本程序的源代碼鏈接:demo

四、總結

 本專題介紹了如何去實現一個類似QQ的聊天程序,一方面讓大家可以鞏固前面專題的內容,另一方面讓大家更好的理解即時通信軟件(騰訊QQ)的工作原理和軟件協議的設計。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美成人精品xxx| 尤物九九久久国产精品的分类| 国产精品视频导航| 国产成人aa精品一区在线播放| 奇米4444一区二区三区| 亚洲欧洲偷拍精品| 久久久www成人免费精品张筱雨| 日韩av毛片网| 久久久久国产精品免费网站| 日本精品久久久久影院| 亚洲日本中文字幕| 欧美日韩国产成人| 精品国偷自产在线视频| 久久久爽爽爽美女图片| 日本欧美中文字幕| 欧美午夜电影在线| 97视频在线观看播放| 2019亚洲日韩新视频| 国产精品自拍视频| 91极品视频在线| 欧美性理论片在线观看片免费| 国产精品久久电影观看| 国产成人综合一区二区三区| 国产亚洲欧美aaaa| 亚洲国产成人在线播放| 欧美黑人狂野猛交老妇| 午夜精品福利视频| 欧美在线视频免费观看| 亚洲国产精品小视频| 久久亚洲精品一区| 5278欧美一区二区三区| 在线观看视频99| 亚洲成色www8888| 成人免费网站在线看| 成人午夜一级二级三级| 欧美成人精品三级在线观看| 国产精品久久久久aaaa九色| 一区二区三区四区在线观看视频| 日韩资源在线观看| 国产va免费精品高清在线观看| 91久久久久久久久久| 久久久国产精品视频| 国产一区二区动漫| 久久视频国产精品免费视频在线| 成人亚洲欧美一区二区三区| 欧美大学生性色视频| 一区二区三区久久精品| 国产女同一区二区| 中文字幕亚洲欧美一区二区三区| 久久精品91久久久久久再现| 日韩免费黄色av| 欧美激情视频网| 91精品视频在线| 97精品视频在线| 国产一区二区三区在线| 亚洲免费视频网站| 少妇av一区二区三区| 欧美精品激情在线观看| 日韩精品免费视频| 欧美黑人一区二区三区| 亚洲欧美日韩在线一区| 91麻豆国产语对白在线观看| 98精品国产高清在线xxxx天堂| 26uuu日韩精品一区二区| 精品国产精品三级精品av网址| 亚洲精品国产品国语在线| 国产精品久久久久国产a级| 中文字幕成人精品久久不卡| 久久久欧美精品| 欧美成人精品激情在线观看| 国产精品久久久久久久久久新婚| 欧美激情欧美狂野欧美精品| 欧美激情xxxx性bbbb| 高清亚洲成在人网站天堂| 国产精品免费网站| 国产99久久精品一区二区| 久久天天躁狠狠躁夜夜躁2014| 久久亚洲国产精品成人av秋霞| 亚洲一区久久久| 国产免费一区二区三区在线观看| 亚洲另类xxxx| 中文字幕最新精品| 国产精品久久婷婷六月丁香| 精品亚洲精品福利线在观看| 亚洲国产成人久久综合| 亚洲亚裔videos黑人hd| 国产精品成人在线| 国产日本欧美一区二区三区在线| 中文字幕亚洲综合久久| 在线观看免费高清视频97| 在线视频日韩精品| 日韩欧美精品在线观看| 日韩网站免费观看| 欧美性色视频在线| 91国偷自产一区二区三区的观看方式| 欧美日韩第一页| 91精品久久久久久久久久久久久久| 在线成人中文字幕| 亚洲一区二区三区视频| 成人动漫网站在线观看| 亚洲人成在线观| 国产成人aa精品一区在线播放| 上原亚衣av一区二区三区| 亚洲欧洲成视频免费观看| 国产99久久精品一区二区永久免费| 国内精品美女av在线播放| 青青久久aⅴ北条麻妃| 自拍偷拍免费精品| 日韩精品免费在线视频| 亚洲一区中文字幕在线观看| 久久久久久久久久久人体| 久久久久久久香蕉网| 日韩高清欧美高清| 91高清在线免费观看| 国产亚洲精品久久久| 国产视频在线观看一区二区| 久久久精品2019中文字幕神马| 2025国产精品视频| 亚洲国产精品悠悠久久琪琪| 日韩毛片在线看| 久久久久久久激情视频| 久久国产加勒比精品无码| 国产精品久久久久久久久免费看| 亚洲欧美国产一区二区三区| 久久久久久久久久久亚洲| 5278欧美一区二区三区| 欧美性xxxxx| 欧美性开放视频| 97视频在线看| 中文字幕久久久av一区| 九九热精品视频| 这里只有精品在线播放| 一区二区三区四区精品| 久久99精品久久久久久青青91| 久久九九精品99国产精品| 国产精品久久久久999| 欧美电影免费观看电视剧大全| 亚洲成色www8888| 色樱桃影院亚洲精品影院| 国产精品久久网| 亚洲国产成人久久综合| 国产精品av在线播放| 久久国产天堂福利天堂| 国产一区二区三区高清在线观看| 国产精品av免费在线观看| 国产91精品在线播放| 亚洲精品成a人在线观看| 欧美精品videossex88| 亚洲一区二区三区成人在线视频精品| 国产精品吊钟奶在线| 国产精品狼人色视频一区| 日韩精品中文字幕在线| 精品国产91久久久久久| 国产精品一区二区久久久| 国产98色在线| 77777少妇光屁股久久一区| 精品久久久久久久久久国产| 久久久久久噜噜噜久久久精品| 国产情人节一区| 欧美在线一级视频| 在线观看日韩视频| 欧美亚洲在线视频| 亚洲偷熟乱区亚洲香蕉av| 亚洲精品日产aⅴ|