Socket实现仿QQ聊天(可部署于广域网)附源码(2)-服务器搭建
1.前言
这是本系列的第二篇文章,第一篇文章得到了很多朋友们的支持,在这里表示非常的感谢。对于这一系列文章需要补充的是这只是一篇入门级别的Socket通信文章,对于专业人员来说完全可以跳过。本文只介绍一些基本TCP通信技术并使用该技术实现聊天功能。本篇文章实现聊天服务器搭建,我会把聊天服务器部署到广域网服务器上,到时候大家就可以可以在源码里面打开客户端与我聊天啦!(这只是一个初级版功能简单不支持离线消息,所以聊天的前提是我在线(用户名为张三的就是我,Q我吧)……),也可以自己打开两个客户端测试一下(除张三以外账户)。
2.本篇实现功能
1. 聊天室服务器端的创建。
2. 聊天室客户端的创建。
3. 实现客户与服务器的连接通讯。
4. 实现客户之间的私聊。
3.具体实现
(1)客户端搭建
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 }
(2)服务器端搭建
我们上篇讲到聊天服务器与单个客户端实现通信,服务器通信的socket搭建后,开启新的线程来监听是否有客户端连入,为了实现后期的客户端对客户端的通信我们首先要存储客户端的socket的IP与端口号,以及用户名信息,服务器接收到消息后将消息解析转发。我实现的思路如下:
(0)服务器页面搭建,如下图
服务器代码:
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 }