• Socket实现仿QQ聊天(可部署于广域网)附源码(2)-服务器搭建


    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] +":我"+ "
    "+"      " + msgTxt[2] + "           ____[" + DateTime.Now +"]" + "
    " + "
    ";
     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 + "
    " + txtMsg.Text.Trim() + "           ____[" + DateTime.Now +
     93                              "]" + "
    " + "
    ";
     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] +":我"+ "
    "+"      " + msgTxt[2] + "           ____[" + DateTime.Now +"]" + "
    " + "
    ";
     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 + "
    " + txtMsg.Text.Trim() + "           ____[" + DateTime.Now +
     93                              "]" + "
    " + "
    ";
     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

  • 相关阅读:
    关于javascript with性能的一段阐述
    关于多线程传参问题
    C++ 入门简要笔记
    html中多个title
    关于大学生对抖音的使用情况调查分析报告
    eclipse如何导入lib文件夹下的包
    每周总结
    中文分词——jieba之分词后存入数据库
    软件需求与分析课堂测试十 — 软件设计师案例分析(历年软考题选取)
    2021年秋季学期课程总结及奖励加分
  • 原文地址:https://www.cnblogs.com/ATtuing/p/5422628.html
Copyright © 2020-2023  润新知