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)-服务器搭建的更多相关文章

  1. Socket实现仿QQ聊天(可部署于广域网)附源码(1)-简介

    1.前言 本次实现的这个聊天工具是我去年c#程序设计课程所写的Socket仿QQ聊天,由于当时候没有自己的服务器,只能在机房局域网内进行测试,最近在腾讯云上买了一台云主机(本人学生党,腾讯云有个学生专 ...

  2. Socket实现仿QQ聊天(可部署于广域网)附源码(4)-加入数据库系统搭建完成

    1.前言 这是本系列的第四篇文章,上一篇我们讲到实现了客户端对客户端的抖屏与收发各种类型文件,本篇文章我们加入SQLServer数据库实现登录与好友的添加等功能,并对界面做了美化处理.向往常一样我会把 ...

  3. iOS 未读消息角标 仿QQ拖拽 简单灵活 支持xib(源码)

    一.效果 二.简单用法 超级简单,2行代码集成:xib可0代码集成,只需拖一个view关联LFBadge类即可 //一般view上加角标 _badge1 = [[LFBadge alloc] init ...

  4. android文件选择器、仿淘宝编辑页面、新手引导层等源码

    Android精选源码 单片机和安卓应用,传感器 文件选择器 android滑动选择的尺子view源码 android视频录制 视频压缩的源码 仿今日头条顶部导航指示器源码 Android框架+常用控 ...

  5. JS简单仿QQ聊天工具的制作

    刚接触JS,对其充满了好奇,利用刚学到的一点知识,写了一个简单的仿QQ聊天的东西,其中还有很多的不足之处,有待慢慢提高. 功能:1.在输入框中输入内容,点击发送,即可在上方显示所输入内容. 2.点击‘ ...

  6. 高仿qq聊天界面

    高仿qq聊天界面,给有需要的人,界面效果如下: 真心觉得做界面非常痛苦,给有需要的朋友. chat.xml <?xml version="1.0" encoding=&quo ...

  7. Android特效专辑(六)——仿QQ聊天撒花特效,无形装逼,最为致命

    Android特效专辑(六)--仿QQ聊天撒花特效,无形装逼,最为致命 我的关于特效的专辑已经在CSDN上申请了一个专栏--http://blog.csdn.net/column/details/li ...

  8. WPF仿QQ聊天框表情文字混排实现

    原文:WPF仿QQ聊天框表情文字混排实现 二话不说.先上图 图中分别有文件.文本+表情.纯文本的展示,对于同一个list不同的展示形式,很明显,应该用多个DataTemplate,那么也就需要Data ...

  9. 仿QQ聊天程序(java)

    仿QQ聊天程序 转载:牟尼的专栏 http://blog.csdn.net/u012027907 一.设计内容及要求 1.1综述 A.系统概述 我们要做的就是类似QQ这样的面向企业内部的聊天软件,基本 ...

随机推荐

  1. 【开发环境】OFFICE 完全卸载工具(微软)

    OFFICE没有正确安装,每次打开OFFICE都会提示: “The setup controller has encountered a problem during instll.Please re ...

  2. devise 小项目(一)

    Devise源于Warden,而warden是一个基于Rack的验证权限gem,不过,使用devise实际并不需要任何关于warden的知识. 如果你之前有一些其他类似的维护验证权限功能的gem的使用 ...

  3. winAPI 中 的 GlobalLock GlobalUnlock 的作用

    在项目中遇到GlobalLock  GlobalUnlock 这两个操作内存的函数. 百度百科解释为:锁定内存中指定的内存块,并返回一个地址值,令其指向内存块的起始处.除非用 GlobalUnlock ...

  4. [置顶]PADS PCB功能使用技巧系列之NO.004- 如何做到20H规则?

    电源层与地层之间变化的电场在板边缘会向外辐射电磁干扰(EMI),称为边沿效应.20H规则可将70%的电场限制在接地层边沿内,100H可达到98%. (1)在Layout中,选择菜单栏Setup -&g ...

  5. RHEL6.5及Win7的和谐共处(投机版)

    背景: 在Windows XP存在时,装了个RHEL6.5,用的是安装程序自带的Grub,后来将XP删除后重装了Windows7,RHEL的Grub被覆盖,启动不了RHEL了,于是补上RHEL的引导… ...

  6. ajax 跨域请求时url参数添加callback=?会实现跨域问题

    例如: 1.在 jQuery 中,可以通过使用JSONP 形式的回调函数来加载其他网域的JSON数据,如 "myurl?callback=?".jQuery 将自动替换 ? 为正确 ...

  7. angularjs 表单验证(不完整版)

    针对项目实践表单验证总结: angular 的 form表单验证:form内需要novalidate取消默认验证,用ng自己的验证,form的名字是非常必要的 栗子:以注册为栗子,下面是注册的部分: ...

  8. python发邮件实现Redis通知功能

    # -*- coding:utf-8 -*- import smtplib #import os from email.mime.text import MIMEText from email.mim ...

  9. spring 装配核心笔记

    (1)自动装配 开启ComponentScan(自动扫描), 通过在类使用注解@Component(默认bean id为类名第一个字符小写), 使用@Autowired实现属性,构造函数,成员函数的自 ...

  10. Apache HTTP Server 2.2.26 发布

    Apache遗留产品线2.2.26发布.2013-11-13 之前的版本是2013-07-02的2.2.25 同样先在开发目录下放出下载,然后放到正式目录下.修正了大量的Bug.目前的稳定版2.4系列 ...