Socket實現仿QQ聊天(可部署於廣域網)附源碼(2)-伺服器搭建

来源:http://www.cnblogs.com/ATtuing/archive/2016/04/22/5422628.html
-Advertisement-
Play Games

1.前言 這是本系列的第二篇文章,第一篇文章得到了很多朋友們的支持,在這裡表示非常的感謝。對於這一系列文章需要補充的是這隻是一篇入門級別的Socket通信文章,對於專業人員來說完全可以跳過。本文只介紹一些基本TCP通信技術並使用該技術實現聊天功能。本篇文章實現聊天伺服器搭建,我會把聊天伺服器部署到廣 ...


1.前言

     這是本系列的第二篇文章,第一篇文章得到了很多朋友們的支持,在這裡表示非常的感謝。對於這一系列文章需要補充的是這隻是一篇入門級別的Socket通信文章,對於專業人員來說完全可以跳過。本文只介紹一些基本TCP通信技術並使用該技術實現聊天功能。本篇文章實現聊天伺服器搭建,我會把聊天伺服器部署到廣域網伺服器上,到時候大家就可以可以在源碼裡面打開客戶端與我聊天啦!(這隻是一個初級版功能簡單不支持離線消息,所以聊天的前提是我線上(用戶名為張三的就是我,Q我吧)……),也可以自己打開兩個客戶端測試一下(除張三以外賬戶)。

2.本篇實現功能

1. 聊天室伺服器端的創建。

2. 聊天室客戶端的創建。

3. 實現客戶與伺服器的連接通訊。

4. 實現客戶之間的私聊。

3.具體實現

(1)客戶端搭建

1)運行過程 與服務端建立連接—>首次連接向伺服器發送登錄用戶信息(格式例如 張三| )—>聊天:先將聊天消息發送到伺服器,然後由伺服器解析發給好友(發往伺服器的消息如下 張三|李四|你好呀李四?),如圖

QQ截圖20160422200750

客戶端代碼實現:

  1 //客戶端通信套接字
  2      private Socket clientSocket;
  3      //新線程
  4      private Thread thread;
  5      //當前登錄的用戶
  6      private string userName = "";
  7      public Client()
  8      {
  9          InitializeComponent();
 10          //防止新線程調用主線程卡死
 11          CheckForIllegalCrossThreadCalls = false;
 12      }
 13 
 14      //通過IP地址與埠號與服務端建立鏈接      
 15      private void btnToServer_Click(object sender, EventArgs e)
 16      {
 17          //連接伺服器前先選擇用戶
 18          if (cmbUser.SelectedItem == null)
 19          {
 20              MessageBox.Show("請選擇登錄用戶");
 21              return;
 22          }
 23          userName = cmbUser.SelectedItem.ToString();
 24          this.Text = "當前用戶:" + userName;
 25          //登錄後禁止切換用戶
 26          cmbUser.Enabled = false;
 27          //載入好友列表
 28          foreach (object item in cmbUser.Items)
 29          {
 30              if (item != cmbUser.SelectedItem)
 31              {
 32                  lbFriends.Items.Add(item);
 33              }
 34          }
 35          //新建通信套接字
 36          clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 37          //這裡的ip地址,埠號都是服務端綁定的相關數據。
 38          IPAddress ip = IPAddress.Parse(txtIP.Text);
 39          var endpoint = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text));
 40          try
 41          {
 42          clientSocket.Connect(endpoint); //鏈接有埠號與IP地址確定服務端.
 43          //登錄時給伺服器發送登錄消息例如張三| 
 44              string str = userName + "|" + " ";
 45              byte[] buffer = Encoding.UTF8.GetBytes(str);
 46              clientSocket.Send(buffer);
 47          }
 48          catch
 49          {
 50              MessageBox.Show("與伺服器連接失敗");
 51              lbFriends.Items.Clear();
 52          }
 53          //客戶端在接受服務端發送過來的數據是通過Socket 中的Receive方法,該方法會阻斷線程,所以我們自己為該方法創建了一個線程
 54          thread = new Thread(ReceMsg);
 55          thread.IsBackground = true; //設置後臺線程
 56          thread.Start();
 57      }
 58 
 59      public void ReceMsg()
 60      {
 61          while (true)
 62          {
 63 
 64              try
 65              {
 66                  var buffer = new byte[1024 * 1024 * 2];
 67                  int dateLength = clientSocket.Receive(buffer); //接收服務端發送過來的數據
 68                  //把接收到的位元組數組轉成字元串顯示在文本框中。
 69                  string ReceiveMsg = Encoding.UTF8.GetString(buffer, 0, dateLength);
 70                  string[] msgTxt = ReceiveMsg.Split('|');
 71                  string newstr ="      "+msgTxt[0] +":我"+ "\r\n"+"      " + msgTxt[2] + "           ____[" + DateTime.Now +"]" + "\r\n" + "\r\n";
 72                  ShowSmsg(newstr);
 73              }
 74              catch
 75              {
 76             
 77              }
 78          }
 79      }
 80 
 81      private void btnSend_Click(object sender, EventArgs e)
 82      {
 83          if (lbFriends.SelectedItems.Count != 1)
 84          {
 85              MessageBox.Show("請選擇好友");
 86              return;
 87          }
 88          string friend = lbFriends.SelectedItem.ToString();
 89          try
 90          {
 91              //界面顯示消息
 92              string newstr = "" + ":" + friend + "\r\n" + txtMsg.Text.Trim() + "           ____[" + DateTime.Now +
 93                              "]" + "\r\n" + "\r\n";
 94              ShowSmsg(newstr);
 95              //發往伺服器的消息     格式為 (發送者|接收者|信息)
 96              string str = userName + "|" + friend + "|" + txtMsg.Text.Trim();
 97              //將消息轉化為位元組數據傳輸
 98              byte[] buffer = Encoding.UTF8.GetBytes(str);
 99              clientSocket.Send(buffer);
100              txtMsg.Text = "";
101          }
102          catch
103          {
104              MessageBox.Show("與伺服器連接失敗");
105          }
106      }
107      //展示消息
108      private void ShowSmsg(string newStr)
109      {
110          txtChat.AppendText(newStr);
111      }
112      private void btnCloseSer_Click(object sender, EventArgs e)
113      {
114          clientSocket.Close();
115      } 
View Code

 

 

 

(2)伺服器端搭建

     我們上篇講到聊天伺服器與單個客戶端實現通信,伺服器通信的socket搭建後,開啟新的線程來監聽是否有客戶端連入,為了實現後期的客戶端對客戶端的通信我們首先要存儲客戶端的socket的IP與埠號,以及用戶名信息,伺服器接收到消息後將消息解析轉發。我實現的思路如下:

(0)伺服器頁面搭建,如下圖

圖片1

伺服器代碼:

  1 //客戶端通信套接字
  2      private Socket clientSocket;
  3      //新線程
  4      private Thread thread;
  5      //當前登錄的用戶
  6      private string userName = "";
  7      public Client()
  8      {
  9          InitializeComponent();
 10          //防止新線程調用主線程卡死
 11          CheckForIllegalCrossThreadCalls = false;
 12      }
 13 
 14      //通過IP地址與埠號與服務端建立鏈接      
 15      private void btnToServer_Click(object sender, EventArgs e)
 16      {
 17          //連接伺服器前先選擇用戶
 18          if (cmbUser.SelectedItem == null)
 19          {
 20              MessageBox.Show("請選擇登錄用戶");
 21              return;
 22          }
 23          userName = cmbUser.SelectedItem.ToString();
 24          this.Text = "當前用戶:" + userName;
 25          //登錄後禁止切換用戶
 26          cmbUser.Enabled = false;
 27          //載入好友列表
 28          foreach (object item in cmbUser.Items)
 29          {
 30              if (item != cmbUser.SelectedItem)
 31              {
 32                  lbFriends.Items.Add(item);
 33              }
 34          }
 35          //新建通信套接字
 36          clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 37          //這裡的ip地址,埠號都是服務端綁定的相關數據。
 38          IPAddress ip = IPAddress.Parse(txtIP.Text);
 39          var endpoint = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text));
 40          try
 41          {
 42          clientSocket.Connect(endpoint); //鏈接有埠號與IP地址確定服務端.
 43          //登錄時給伺服器發送登錄消息例如張三| 
 44              string str = userName + "|" + " ";
 45              byte[] buffer = Encoding.UTF8.GetBytes(str);
 46              clientSocket.Send(buffer);
 47          }
 48          catch
 49          {
 50              MessageBox.Show("與伺服器連接失敗");
 51              lbFriends.Items.Clear();
 52          }
 53          //客戶端在接受服務端發送過來的數據是通過Socket 中的Receive方法,該方法會阻斷線程,所以我們自己為該方法創建了一個線程
 54          thread = new Thread(ReceMsg);
 55          thread.IsBackground = true; //設置後臺線程
 56          thread.Start();
 57      }
 58 
 59      public void ReceMsg()
 60      {
 61          while (true)
 62          {
 63 
 64              try
 65              {
 66                  var buffer = new byte[1024 * 1024 * 2];
 67                  int dateLength = clientSocket.Receive(buffer); //接收服務端發送過來的數據
 68                  //把接收到的位元組數組轉成字元串顯示在文本框中。
 69                  string ReceiveMsg = Encoding.UTF8.GetString(buffer, 0, dateLength);
 70                  string[] msgTxt = ReceiveMsg.Split('|');
 71                  string newstr ="      "+msgTxt[0] +":我"+ "\r\n"+"      " + msgTxt[2] + "           ____[" + DateTime.Now +"]" + "\r\n" + "\r\n";
 72                  ShowSmsg(newstr);
 73              }
 74              catch
 75              {
 76             
 77              }
 78          }
 79      }
 80 
 81      private void btnSend_Click(object sender, EventArgs e)
 82      {
 83          if (lbFriends.SelectedItems.Count != 1)
 84          {
 85              MessageBox.Show("請選擇好友");
 86              return;
 87          }
 88          string friend = lbFriends.SelectedItem.ToString();
 89          try
 90          {
 91              //界面顯示消息
 92              string newstr = "" + ":" + friend + "\r\n" + txtMsg.Text.Trim() + "           ____[" + DateTime.Now +
 93                              "]" + "\r\n" + "\r\n";
 94              ShowSmsg(newstr);
 95              //發往伺服器的消息     格式為 (發送者|接收者|信息)
 96              string str = userName + "|" + friend + "|" + txtMsg.Text.Trim();
 97              //將消息轉化為位元組數據傳輸
 98              byte[] buffer = Encoding.UTF8.GetBytes(str);
 99              clientSocket.Send(buffer);
100              txtMsg.Text = "";
101          }
102          catch
103          {
104              MessageBox.Show("與伺服器連接失敗");
105          }
106      }
107      //展示消息
108      private void ShowSmsg(string newStr)
109      {
110          txtChat.AppendText(newStr);
111      }
112      private void btnCloseSer_Click(object sender, EventArgs e)
113      {
114          clientSocket.Close();
115      } 
View Code

 

 

(1)當兩個不同客戶端的連入,生成兩個通信套接字1,2。這時為了與客戶端實現通信我們有必要建立一個客戶端管理類,來存儲客戶端的信息。

(2)用戶名與客戶端通信的socket的IP與埠號對應,以Dictionary字典形式存入鍵:IP與埠號 ,值:用戶名(這裡為演示原理所以沒加入資料庫,只是模擬,下一章再加入資料庫);當用戶第一次連入,我們必須記錄他的IP並與用戶對應起來,如果區域網聊天IP在同一網段兩個客戶端還可以互相找到, 如果廣域網下兩個客戶端只有通過伺服器轉接才能找到。

(3)聲明一個全局消息委托 public delegate void DGSendMsg(string strMsg);

我的思路如下圖:

QQ截圖20160422200842

下麵是我寫的客戶端管理類:

image

  1 public class ClientManager
  2   {
  3       //好友列表控制項
  4       private System.Windows.Forms.ListBox listClient;
  5       //在伺服器上顯示消息的委托(全局)
  6       DGSendMsg dgSendMsg;
  7       //通信套接字key :客戶端ip value :對應的通信套接字
  8       private Dictionary<string, Socket> ClientSocket;
  9       // 通信套接字key :客戶端ip value :用戶名(由於沒有添加資料庫我們暫時這麼模擬,下篇我會講到)
 10       private Dictionary<string, string> UserSocket;
 11       public ClientManager()
 12       { }
 13       /// <summary>
 14       /// 客戶端管理類
 15       /// </summary>
 16       /// <param name="lb">列表控制項</param>
 17       /// <param name="dgSendMsg">顯示消息</param>
 18       public ClientManager(System.Windows.Forms.ListBox lb, DGSendMsg dgSendMsg)
 19       {
 20           //用戶列表
 21           this.listClient = lb;
 22           //消息委托
 23           this.dgSendMsg = dgSendMsg;
 24           //通信字典
 25           ClientSocket = new Dictionary<string, Socket>();
 26           //用戶字典
 27           UserSocket = new Dictionary<string, string>();
 28       }
 29       #region  添加客戶端通信套接字+ public void AddClient(Socket sokMsg)
 30       /// <summary>
 31       /// 添加通信套接字
 32       /// </summary>
 33       /// <param name="sokMag">負責通信的socket</param>
 34       public void AddClient(Socket sokMsg)
 35       {
 36           //獲取客戶端通信套接字
 37           string strEndPoint = sokMsg.RemoteEndPoint.ToString();
 38           //通信套接字加入字典
 39           ClientSocket.Add(strEndPoint, sokMsg);
 40           //sokServer.Accept()這個接收消息的方法會使線程卡死,所以要開啟新線程
 41           Thread thrMag = new Thread(ReciveMsg);
 42           //設置為後臺線程防止卡死
 43           thrMag.IsBackground = true;
 44           //開啟線程 為新線程傳入通信套接字參數
 45           thrMag.Start(sokMsg);
 46           dgSendMsg(strEndPoint + "成功連接上服務端~~~!" + DateTime.Now.ToString());
 47       }
 48       #endregion
 49       bool isReceing = true;
 50       #region void ReciveMsg(object sokMsgObj)    服務接收客戶端消息
 51       /// <summary>
 52       /// 服務接收客戶端消息
 53       /// </summary>
 54       /// <param name="sokMsgObj">客戶端Scoket</param>
 55       void ReciveMsg(object sokMsgObj)
 56       {
 57           Socket sokMsg = null;
 58           try
 59           {
 60               //sokMsg接收消息的socket
 61               sokMsg = sokMsgObj as Socket;
 62               //創建接收消息的緩衝區   預設5M
 63               byte[] arrMsg = new byte[5 * 1024 * 1024];
 64               //迴圈接收
 65               while (isReceing)
 66               {
 67                   //接收到的真實消息存入緩衝區     並保存消息的真實長度(因為5M緩衝區不會全部用掉)
 68                   int realLength = sokMsg.Receive(arrMsg);
 69                   //將緩衝區的真實數據轉成字元串
 70                   string strMsg = Encoding.UTF8.GetString(arrMsg, 0, realLength);
 71                   //dgSendMsg(strMsg);
 72     
 73                   string[] msgTxt = strMsg.Split('|');
 74                   //  msgTxt.Length == 2說明用戶第一次連入,我們必須記錄他的IP並與用戶對應起來,如果區域網聊天
 75                   //IP在同一網段兩個客戶端還可以互相找到, 如果廣域網下只有通過伺服器轉接才能找到
 76                   if (msgTxt.Length == 2)
 77                   {
 78                       //如果用戶名已登錄則強制下線
 79                       if (UserSocket.Values.Contains(msgTxt[0]))
 80                       {
 81                           sokMsg.Close();
 82                           return;
 83                       }
 84                       UserSocket.Add(sokMsg.RemoteEndPoint.ToString(), msgTxt[0]);
 85                       //顯示列表
 86                       listClient.Items.Add(sokMsg.RemoteEndPoint + "---" + msgTxt [0]+ @"---上線~\(≧▽≦)/~啦啦啦");
 87                       continue;
 88                   }
 89 
 90                     //發送信息給客戶端
 91                   SendMsgToClient(strMsg);
 92               }
 93           }
 94           catch
 95           {
 96               //連接出錯說明客戶端連接斷開
 97               RemoveClient(sokMsg.RemoteEndPoint.ToString());
 98           }
 99       }
100       #endregion
101       /// <summary>
102       /// 移除下線用戶信息
103       /// </summary>
104       /// <param name="strClientID">IP:埠</param>
105       public void RemoveClient(string strClientID)
106       {
107           //要移除的用戶名
108           string name = UserSocket[strClientID];
109           //  要移除的線上管理的項
110           string onlineStr = strClientID + "---" + UserSocket[strClientID] + @"---上線~\(≧▽≦)/~啦啦啦";
111      
112           //移除線上管理listClient 集合
113           if (listClient.Items.Contains(onlineStr))
114           {
115               listClient.Items.Remove(onlineStr);
116           }
117           //斷開socket連接
118           if (ClientSocket.Keys.Contains(strClientID))
119           {
120               ClientSocket[strClientID].Close();
121               ClientSocket.Remove(strClientID);
122               UserSocket.Remove(strClientID);
123           }
124           dgSendMsg(strClientID + "---" + name + @"---下線_"+DateTime.Now);
125       }
126       public void SendMsgToClient(string Msg)
127       {
128           //解析發來的數據
129 
130           string[] msgTxt = Msg.Split('|');
131 
132           //不可解析數據
133           if (msgTxt.Length < 
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 昨天是周五太放鬆了,晚上沒有加班只顧著放鬆,玩了一晚上,今天又是睡了一上午,沒有學習。這下放鬆過分了沒有總結,也沒有完成任務。今天來總結一下昨天的學習成果。 昨天設計的資料庫今天老大點評了一下發現問題確實很多。比如用戶表和許可權表的拆分問題,是否應該拆分取決於許可權的使用頻率。使用頻率高時因該拆分。 對 ...
  • 近些天,看了一些博客園大牛關於webApi項目的的文章,也有請教師兄一些問題,自己做了個Demo試了試,收穫甚多。感謝感謝,下麵是我一些學習的總結,如若有錯的地方請多多指教!! WebApi登陸與身份驗證 因為在調用介面的時候都必須傳sessionKey參數過去,所以必須先登錄驗證身份。 如果是已註 ...
  • 今天幫同事看一個問題,她用為了實現動畫效果用主線程執行Thread.Sleep,然後界面就卡死了。 這個問題好解決,new 一個Thread就行了,但是更新WPF的界面需要主線程的操作,然後習慣性的打出Invoke,但是居然沒有。百度了一下發現WPF要用Dispatcher.Invoke ,故寫篇日 ...
  • 兩種方法,一種是利用error.StackTrace,另外一種是try-catch找到錯誤行數,具體如下: 一、error.StackTrace代碼 int i = ex.StackTrace.IndexOf("行號"); 二、try-catch代碼 try { //////////////// / ...
  • 相信博客園的讀者大多都是千萬“碼農”中的一員,每個人都寫過很多代碼,但並不是每一個人都能寫出高質量的代碼。rome is not built in one day !——完成高質量的代碼也不是一蹴而就的。為了寫出高質量的代碼,我們需要藉助一些手段,“代碼重構”基本上是最常用的手段,甚至是唯一的手段。 ...
  • 新框架的容器部分終於調通了!容器實在太重要了,所以有用了一個名詞叫“核心容器”。 容器為什麼那麼重要呢?這個有必要好好說道說道。 1、首先我們從框架名稱面向介面編程說起,什麼是面向介面編程?(這個度娘回答一下) 解讀一下:類是個體的定義(建模), 個體的每一方面都可以是一個介面 說白點,其一介面可以 ...
  • 第7章 成員資格、授權和安全性 7.1 安全性 ASP.NET MVC 提供了許多內置的保護機制(預設利用 HTML 輔助方法和Razor 語法進行 HTML編碼以及請求驗證等功能特性,以及通過基架構建的控制器白名單表單元素來防止重覆提交攻擊) 永遠不要相信用戶提交的任何數據。 實際的例子 每次渲染 ...
  • 首先在此申明,此抓取內容及發佈的地址,只用於個人研究,如涉及到版權問題,還及時聯繫作者。 目的: 其實最開始是在研究vs2013環境下使用xmargin做的app,研究到webView控制項的時候需要一個簡單的頁面來嵌套,但是個人覺得光是點測試數據沒什麼意思,就決定網上找下音樂方面的資源地址,可是搜索 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...