Socket实现仿QQ聊天(可部署于广域网)附源码(2)-服务器搭建
1.前言
这是本系列的第二篇文章,第一篇文章得到了很多朋友们的支持,在这里表示非常的感谢。对于这一系列文章需要补充的是这只是一篇入门级别的Socket通信文章,对于专业人员来说完全可以跳过。本文只介绍一些基本TCP通信技术并使用该技术实现聊天功能。本篇文章实现聊天服务器搭建,我会把聊天服务器部署到广域网服务器上,到时候大家就可以可以在源码里面打开客户端与我聊天啦!(这只是一个初级版功能简单不支持离线消息,所以聊天的前提是我在线(用户名为张三的就是我,Q我吧)……),也可以自己打开两个客户端测试一下(除张三以外账户)。
2.本篇实现功能
1. 聊天室服务器端的创建。
2. 聊天室客户端的创建。
3. 实现客户与服务器的连接通讯。
4. 实现客户之间的私聊。
3.具体实现
(1)客户端搭建
1)运行过程 与服务端建立连接—>首次连接向服务器发送登录用户信息(格式例如 张三| )—>聊天:先将聊天消息发送到服务器,然后由服务器解析发给好友(发往服务器的消息如下 张三|李四|你好呀李四?),如图

客户端代码实现:
//客户端通信套接字
private Socket clientSocket;
//新线程
private Thread thread;
//当前登录的用户
private string userName = "";
public Client()
{
InitializeComponent();
//防止新线程调用主线程卡死
CheckForIllegalCrossThreadCalls = false;
} //通过IP地址与端口号与服务端建立链接
private void btnToServer_Click(object sender, EventArgs e)
{
//连接服务器前先选择用户
if (cmbUser.SelectedItem == null)
{
MessageBox.Show("请选择登录用户");
return;
}
userName = cmbUser.SelectedItem.ToString();
this.Text = "当前用户:" + userName;
//登录后禁止切换用户
cmbUser.Enabled = false;
//加载好友列表
foreach (object item in cmbUser.Items)
{
if (item != cmbUser.SelectedItem)
{
lbFriends.Items.Add(item);
}
}
//新建通信套接字
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//这里的ip地址,端口号都是服务端绑定的相关数据。
IPAddress ip = IPAddress.Parse(txtIP.Text);
var endpoint = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text));
try
{
clientSocket.Connect(endpoint); //链接有端口号与IP地址确定服务端.
//登录时给服务器发送登录消息例如张三|
string str = userName + "|" + " ";
byte[] buffer = Encoding.UTF8.GetBytes(str);
clientSocket.Send(buffer);
}
catch
{
MessageBox.Show("与服务器连接失败");
lbFriends.Items.Clear();
}
//客户端在接受服务端发送过来的数据是通过Socket 中的Receive方法,该方法会阻断线程,所以我们自己为该方法创建了一个线程
thread = new Thread(ReceMsg);
thread.IsBackground = true; //设置后台线程
thread.Start();
} public void ReceMsg()
{
while (true)
{ try
{
var buffer = new byte[ * * ];
int dateLength = clientSocket.Receive(buffer); //接收服务端发送过来的数据
//把接收到的字节数组转成字符串显示在文本框中。
string ReceiveMsg = Encoding.UTF8.GetString(buffer, , dateLength);
string[] msgTxt = ReceiveMsg.Split('|');
string newstr =" "+msgTxt[] +":我"+ "\r\n"+" " + msgTxt[] + " ____[" + DateTime.Now +"]" + "\r\n" + "\r\n";
ShowSmsg(newstr);
}
catch
{ }
}
} private void btnSend_Click(object sender, EventArgs e)
{
if (lbFriends.SelectedItems.Count != )
{
MessageBox.Show("请选择好友");
return;
}
string friend = lbFriends.SelectedItem.ToString();
try
{
//界面显示消息
string newstr = "我" + ":" + friend + "\r\n" + txtMsg.Text.Trim() + " ____[" + DateTime.Now +
"]" + "\r\n" + "\r\n";
ShowSmsg(newstr);
//发往服务器的消息 格式为 (发送者|接收者|信息)
string str = userName + "|" + friend + "|" + txtMsg.Text.Trim();
//将消息转化为字节数据传输
byte[] buffer = Encoding.UTF8.GetBytes(str);
clientSocket.Send(buffer);
txtMsg.Text = "";
}
catch
{
MessageBox.Show("与服务器连接失败");
}
}
//展示消息
private void ShowSmsg(string newStr)
{
txtChat.AppendText(newStr);
}
private void btnCloseSer_Click(object sender, EventArgs e)
{
clientSocket.Close();
}
(2)服务器端搭建
我们上篇讲到聊天服务器与单个客户端实现通信,服务器通信的socket搭建后,开启新的线程来监听是否有客户端连入,为了实现后期的客户端对客户端的通信我们首先要存储客户端的socket的IP与端口号,以及用户名信息,服务器接收到消息后将消息解析转发。我实现的思路如下:
(0)服务器页面搭建,如下图

服务器代码:
//客户端通信套接字
private Socket clientSocket;
//新线程
private Thread thread;
//当前登录的用户
private string userName = "";
public Client()
{
InitializeComponent();
//防止新线程调用主线程卡死
CheckForIllegalCrossThreadCalls = false;
} //通过IP地址与端口号与服务端建立链接
private void btnToServer_Click(object sender, EventArgs e)
{
//连接服务器前先选择用户
if (cmbUser.SelectedItem == null)
{
MessageBox.Show("请选择登录用户");
return;
}
userName = cmbUser.SelectedItem.ToString();
this.Text = "当前用户:" + userName;
//登录后禁止切换用户
cmbUser.Enabled = false;
//加载好友列表
foreach (object item in cmbUser.Items)
{
if (item != cmbUser.SelectedItem)
{
lbFriends.Items.Add(item);
}
}
//新建通信套接字
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//这里的ip地址,端口号都是服务端绑定的相关数据。
IPAddress ip = IPAddress.Parse(txtIP.Text);
var endpoint = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text));
try
{
clientSocket.Connect(endpoint); //链接有端口号与IP地址确定服务端.
//登录时给服务器发送登录消息例如张三|
string str = userName + "|" + " ";
byte[] buffer = Encoding.UTF8.GetBytes(str);
clientSocket.Send(buffer);
}
catch
{
MessageBox.Show("与服务器连接失败");
lbFriends.Items.Clear();
}
//客户端在接受服务端发送过来的数据是通过Socket 中的Receive方法,该方法会阻断线程,所以我们自己为该方法创建了一个线程
thread = new Thread(ReceMsg);
thread.IsBackground = true; //设置后台线程
thread.Start();
} public void ReceMsg()
{
while (true)
{ try
{
var buffer = new byte[ * * ];
int dateLength = clientSocket.Receive(buffer); //接收服务端发送过来的数据
//把接收到的字节数组转成字符串显示在文本框中。
string ReceiveMsg = Encoding.UTF8.GetString(buffer, , dateLength);
string[] msgTxt = ReceiveMsg.Split('|');
string newstr =" "+msgTxt[] +":我"+ "\r\n"+" " + msgTxt[] + " ____[" + DateTime.Now +"]" + "\r\n" + "\r\n";
ShowSmsg(newstr);
}
catch
{ }
}
} private void btnSend_Click(object sender, EventArgs e)
{
if (lbFriends.SelectedItems.Count != )
{
MessageBox.Show("请选择好友");
return;
}
string friend = lbFriends.SelectedItem.ToString();
try
{
//界面显示消息
string newstr = "我" + ":" + friend + "\r\n" + txtMsg.Text.Trim() + " ____[" + DateTime.Now +
"]" + "\r\n" + "\r\n";
ShowSmsg(newstr);
//发往服务器的消息 格式为 (发送者|接收者|信息)
string str = userName + "|" + friend + "|" + txtMsg.Text.Trim();
//将消息转化为字节数据传输
byte[] buffer = Encoding.UTF8.GetBytes(str);
clientSocket.Send(buffer);
txtMsg.Text = "";
}
catch
{
MessageBox.Show("与服务器连接失败");
}
}
//展示消息
private void ShowSmsg(string newStr)
{
txtChat.AppendText(newStr);
}
private void btnCloseSer_Click(object sender, EventArgs e)
{
clientSocket.Close();
}
(1)当两个不同客户端的连入,生成两个通信套接字1,2。这时为了与客户端实现通信我们有必要建立一个客户端管理类,来存储客户端的信息。
(2)用户名与客户端通信的socket的IP与端口号对应,以Dictionary字典形式存入键:IP与端口号 ,值:用户名(这里为演示原理所以没加入数据库,只是模拟,下一章再加入数据库);当用户第一次连入,我们必须记录他的IP并与用户对应起来,如果局域网聊天IP在同一网段两个客户端还可以互相找到, 如果广域网下两个客户端只有通过服务器转接才能找到。
(3)声明一个全局消息委托 public delegate void DGSendMsg(string strMsg);
我的思路如下图:

下面是我写的客户端管理类:

public class ClientManager
{
//好友列表控件
private System.Windows.Forms.ListBox listClient;
//在服务器上显示消息的委托(全局)
DGSendMsg dgSendMsg;
//通信套接字key :客户端ip value :对应的通信套接字
private Dictionary<string, Socket> ClientSocket;
// 通信套接字key :客户端ip value :用户名(由于没有添加数据库我们暂时这么模拟,下篇我会讲到)
private Dictionary<string, string> UserSocket;
public ClientManager()
{ }
/// <summary>
/// 客户端管理类
/// </summary>
/// <param name="lb">列表控件</param>
/// <param name="dgSendMsg">显示消息</param>
public ClientManager(System.Windows.Forms.ListBox lb, DGSendMsg dgSendMsg)
{
//用户列表
this.listClient = lb;
//消息委托
this.dgSendMsg = dgSendMsg;
//通信字典
ClientSocket = new Dictionary<string, Socket>();
//用户字典
UserSocket = new Dictionary<string, string>();
}
#region 添加客户端通信套接字+ public void AddClient(Socket sokMsg)
/// <summary>
/// 添加通信套接字
/// </summary>
/// <param name="sokMag">负责通信的socket</param>
public void AddClient(Socket sokMsg)
{
//获取客户端通信套接字
string strEndPoint = sokMsg.RemoteEndPoint.ToString();
//通信套接字加入字典
ClientSocket.Add(strEndPoint, sokMsg);
//sokServer.Accept()这个接收消息的方法会使线程卡死,所以要开启新线程
Thread thrMag = new Thread(ReciveMsg);
//设置为后台线程防止卡死
thrMag.IsBackground = true;
//开启线程 为新线程传入通信套接字参数
thrMag.Start(sokMsg);
dgSendMsg(strEndPoint + "成功连接上服务端~~~!" + DateTime.Now.ToString());
}
#endregion
bool isReceing = true;
#region void ReciveMsg(object sokMsgObj) 服务接收客户端消息
/// <summary>
/// 服务接收客户端消息
/// </summary>
/// <param name="sokMsgObj">客户端Scoket</param>
void ReciveMsg(object sokMsgObj)
{
Socket sokMsg = null;
try
{
//sokMsg接收消息的socket
sokMsg = sokMsgObj as Socket;
//创建接收消息的缓冲区 默认5M
byte[] arrMsg = new byte[ * * ];
//循环接收
while (isReceing)
{
//接收到的真实消息存入缓冲区 并保存消息的真实长度(因为5M缓冲区不会全部用掉)
int realLength = sokMsg.Receive(arrMsg);
//将缓冲区的真实数据转成字符串
string strMsg = Encoding.UTF8.GetString(arrMsg, , realLength);
//dgSendMsg(strMsg); string[] msgTxt = strMsg.Split('|');
// msgTxt.Length == 2说明用户第一次连入,我们必须记录他的IP并与用户对应起来,如果局域网聊天
//IP在同一网段两个客户端还可以互相找到, 如果广域网下只有通过服务器转接才能找到
if (msgTxt.Length == )
{
//如果用户名已登录则强制下线
if (UserSocket.Values.Contains(msgTxt[]))
{
sokMsg.Close();
return;
}
UserSocket.Add(sokMsg.RemoteEndPoint.ToString(), msgTxt[]);
//显示列表
listClient.Items.Add(sokMsg.RemoteEndPoint + "---" + msgTxt []+ @"---上线~\(≧▽≦)/~啦啦啦");
continue;
} //发送信息给客户端
SendMsgToClient(strMsg);
}
}
catch
{
//连接出错说明客户端连接断开
RemoveClient(sokMsg.RemoteEndPoint.ToString());
}
}
#endregion
/// <summary>
/// 移除下线用户信息
/// </summary>
/// <param name="strClientID">IP:端口</param>
public void RemoveClient(string strClientID)
{
//要移除的用户名
string name = UserSocket[strClientID];
// 要移除的在线管理的项
string onlineStr = strClientID + "---" + UserSocket[strClientID] + @"---上线~\(≧▽≦)/~啦啦啦"; //移除在线管理listClient 集合
if (listClient.Items.Contains(onlineStr))
{
listClient.Items.Remove(onlineStr);
}
//断开socket连接
if (ClientSocket.Keys.Contains(strClientID))
{
ClientSocket[strClientID].Close();
ClientSocket.Remove(strClientID);
UserSocket.Remove(strClientID);
}
dgSendMsg(strClientID + "---" + name + @"---下线_"+DateTime.Now);
}
public void SendMsgToClient(string Msg)
{
//解析发来的数据 string[] msgTxt = Msg.Split('|'); //不可解析数据
if (msgTxt.Length < )
{
return;
}
// 解析收消息的用户名
string strTo = msgTxt[];
//获得当前发送的用户
var nowtouser = UserSocket.Where(w => w.Value == strTo).FirstOrDefault();
if (nowtouser.Key != null)
{
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(Msg);
Socket conn = ClientSocket[nowtouser.Key];
conn.Send(buffer);
} }
}
效果:聊天 
(4)总结
本次实现了客户端对客户端的一对一聊天(本篇不涉及数据库),实现思路大体为:客户端1将消息发给服务器,服务器解析消息把消息发给客户端2。下一篇我们讲自定义协议发送文件,窗口抖动,以及各种文件格式的接收的解决思路。最后你可以打开源码的客户端,登录张三以外的客户端给我发消息,我这边登录的是张三的账户,或者打开两个客户端自己聊天(不需要运行服务端,默认是我的服务器IP,理论上有网就可以聊天),赶快试一下吧!!!
这个系列未完,待续。。。。。。。。。。。。。。。。。。。。。,期待您的关注
本次源码地址:http://pan.baidu.com/s/1eRPAZvk
Socket实现仿QQ聊天(可部署于广域网)附源码(2)-服务器搭建的更多相关文章
- Socket实现仿QQ聊天(可部署于广域网)附源码(1)-简介
1.前言 本次实现的这个聊天工具是我去年c#程序设计课程所写的Socket仿QQ聊天,由于当时候没有自己的服务器,只能在机房局域网内进行测试,最近在腾讯云上买了一台云主机(本人学生党,腾讯云有个学生专 ...
- Socket实现仿QQ聊天(可部署于广域网)附源码(4)-加入数据库系统搭建完成
1.前言 这是本系列的第四篇文章,上一篇我们讲到实现了客户端对客户端的抖屏与收发各种类型文件,本篇文章我们加入SQLServer数据库实现登录与好友的添加等功能,并对界面做了美化处理.向往常一样我会把 ...
- iOS 未读消息角标 仿QQ拖拽 简单灵活 支持xib(源码)
一.效果 二.简单用法 超级简单,2行代码集成:xib可0代码集成,只需拖一个view关联LFBadge类即可 //一般view上加角标 _badge1 = [[LFBadge alloc] init ...
- android文件选择器、仿淘宝编辑页面、新手引导层等源码
Android精选源码 单片机和安卓应用,传感器 文件选择器 android滑动选择的尺子view源码 android视频录制 视频压缩的源码 仿今日头条顶部导航指示器源码 Android框架+常用控 ...
- JS简单仿QQ聊天工具的制作
刚接触JS,对其充满了好奇,利用刚学到的一点知识,写了一个简单的仿QQ聊天的东西,其中还有很多的不足之处,有待慢慢提高. 功能:1.在输入框中输入内容,点击发送,即可在上方显示所输入内容. 2.点击‘ ...
- 高仿qq聊天界面
高仿qq聊天界面,给有需要的人,界面效果如下: 真心觉得做界面非常痛苦,给有需要的朋友. chat.xml <?xml version="1.0" encoding=&quo ...
- Android特效专辑(六)——仿QQ聊天撒花特效,无形装逼,最为致命
Android特效专辑(六)--仿QQ聊天撒花特效,无形装逼,最为致命 我的关于特效的专辑已经在CSDN上申请了一个专栏--http://blog.csdn.net/column/details/li ...
- WPF仿QQ聊天框表情文字混排实现
原文:WPF仿QQ聊天框表情文字混排实现 二话不说.先上图 图中分别有文件.文本+表情.纯文本的展示,对于同一个list不同的展示形式,很明显,应该用多个DataTemplate,那么也就需要Data ...
- 仿QQ聊天程序(java)
仿QQ聊天程序 转载:牟尼的专栏 http://blog.csdn.net/u012027907 一.设计内容及要求 1.1综述 A.系统概述 我们要做的就是类似QQ这样的面向企业内部的聊天软件,基本 ...
随机推荐
- Bootstrap的安装
在了解了HTML和CSS.JS之后,我咨询了之前做过这一块的学长,得知了Bootstrap的存在. Bootstrap (Web前端CSS框架) Bootstrap,来自 Twitter,是目前很受欢 ...
- Django的virtualenv环境搭建
安装virtualenv好处多多,可以让当前的项目使用单独的类库,实现系统类库的隔离,所以能够自由地控制当前项目类库的版本,不受系统类库的影响:还有其他N多好处. 下面是安装说明和使用示例: 首先安装 ...
- window下安装wamp环境
Wamp就是Windos Apache Mysql PHP集成安装环境,即在window下的apache.php和mysql的服务器软件.其中php环境配置是至关重要的一部分,本文就针对php在本地的 ...
- iOS开发-NSOperation与GCD区别
Mac OS X 10.6及iOS4.0之后导入了可以使全体线程更高效运行,并且使并行处理应用更易开发的架构,GCD(Grand Central Dispatch),同时引入的还有Run Loop, ...
- ASP.NET 4.5.256 has not been registered on the Web server. You need to manually configure your Web server for ASP.NET 4.5.256 in order for your site to run correctly
Microsoft .NET Framework 4.6安装后,用户可能会在使用Microsoft Visual Studio 创建(或打开现有项目时)网站.或Windows Azure项目时遇到下面 ...
- ASP.NET 5 入门(1) - 建立和开发ASP.NET 5 项目
ASP.NET入门(1) - 建立和开发ASP.NET 5 项目 ASP.NET 5 理解和入门 使用自定义配置文件 建立项目 首先,目前只有VS 2015支持开发最新的ASP.NET 5 程序,所以 ...
- MQTT V3.1----flow
该文章转自:聂永的博客(http://www.blogjava.net/yongboy/archive/2014/02/15/409893.html) 网络故障 在任何网络环境下,都会出现一方连接失败 ...
- C#实现 word、pdf、ppt 转为图片
office word文档.pdf文档.powerpoint幻灯片是非常常用的文档类型,在现实中经常有需求需要将它们转换成图片 -- 即将word.pdf.ppt文档的每一页转换成一张对应的图片,就像 ...
- 【Bugly干货】关于 Android N 那些你不知道的事儿
今年3月,Google 破天荒提前半年发布了 Android N 开发者预览版.当然,作为一个不合格的谷粉并没有第一时间体验安装,因为至今仍然能够回忆起来去年今日此门中(雾)兴冲冲刷了 Android ...
- 用WPF实现查找结果高亮显示
概述 我们经常会遇到这样的需求:到数据库里查找一些关键字,把带这些关键字的记录返回显示在客户端上.但如果仅仅是单纯地把文本显示出来,那很不直观,用户不能很轻易地看到他们想找的内容,所以通常我们还要做到 ...