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

1.前言html

     這是本系列的第二篇文章,第一篇文章獲得了不少朋友們的支持,在這裏表示很是的感謝。對於這一系列文章須要補充的是這只是一篇入門級別的Socket通訊文章,對於專業人員來講徹底能夠跳過。本文只介紹一些基本TCP通訊技術並使用該技術實現聊天功能。本篇文章實現聊天服務器搭建,我會把聊天服務器部署到廣域網服務器上,到時候你們就能夠能夠在源碼裏面打開客戶端與我聊天啦!(這只是一個初級版功能簡單不支持離線消息,因此聊天的前提是我在線(用戶名爲張三的就是我,Q我吧)……),也能夠本身打開兩個客戶端測試一下(除張三之外帳戶)。數據庫

2.本篇實現功能數組

1. 聊天室服務器端的建立。服務器

2. 聊天室客戶端的建立。socket

3. 實現客戶與服務器的鏈接通信。ide

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

3.具體實現ui

(1)客戶端搭建this

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

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 < 2)
134           {
135               return;
136           }
137           //  解析收消息的用戶名
138           string strTo = msgTxt[1];
139           //得到當前發送的用戶
140           var nowtouser = UserSocket.Where(w => w.Value == strTo).FirstOrDefault();
141           if (nowtouser.Key != null)
142           {
143               byte[] buffer = System.Text.Encoding.UTF8.GetBytes(Msg);
144               Socket conn = ClientSocket[nowtouser.Key];
145               conn.Send(buffer);
146           }
147 
148       }
149   }
View Code

 

 

效果:聊天
1234

(4)總結

          本次實現了客戶端對客戶端的一對一聊天(本篇不涉及數據庫),實現思路大致爲:客戶端1將消息發給服務器,服務器解析消息把消息發給客戶端2。下一篇咱們講自定義協議發送文件,窗口抖動,以及各類文件格式的接收的解決思路。最後你能夠打開源碼的客戶端,登陸張三之外的客戶端給我發消息,我這邊登陸的是張三的帳戶,或者打開兩個客戶端本身聊天(不須要運行服務端,默認是個人服務器IP,理論上有網就能夠聊天),趕快試一下吧!!!

這個系列未完,待續。。。。。。。。。。。。。。。。。。。。。,期待您的關注

本次源碼地址:http://pan.baidu.com/s/1eRPAZvk

相關文章
相關標籤/搜索