WinForm版聊天室复习Socket通信
聊天室:服务器端-------------客户端
最终演示展示图:
一. 服务器端
对服务端为了让主窗体后台不处理具体业务逻辑,因此对服务端进行了封装,专门用来处理某个客户端通信的过程。
而由于通信管理类中需要处理具体与某个客户端的通信业务,所以在构造函数中传入了具体的套接字对象。
针对消息提醒:由于需要再通信管理类中进行消息提示,而需要调用主窗体的ShowMsg方法。因此将打印消息的方法通过委托传给了通信管理类的构造函数
同理针对意外关闭的客户端连接也同样通过委托将移除客户端的方法传给了通信管理类。
因此,再通信管理类的全局变量里就有了如下的定义:
//与某个客户端通信套接字
Socket sokMsg = null;
//通信线程
Thread thrMsg = null;
//创建一个委托对象, 在窗体显示消息的方法
DGShowMsg dgShow = null;
//创建一个关闭连接的方法
DGCloseConn dgCloseConn = null;
//通信管理类的构造函数
public MsgConnection(Socket sokMsg, DGShowMsg dgShow, DGCloseConn dgCloseConn).........
1.0 开始监听
1.1.1 创建监听套接字
//创建监听套接字,使用ip4协议,流式传输,tcp链接
sokWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
1.1.2 绑定端口
//获取网络节点对象
IPAddress address = IPAddress.Parse(txbIp.Text);
IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txbPort.Text));
// 绑定端口(其实内部 就向系统的端口表中注册了一个端口,并指定了当前程序句柄)
sokWatch.Bind(endPoint);
1.1.3 设置监听队列,指限制同时处理的连接请求数,即同时处理的客户端连接请求。
sokWatch.Listen();
1.1.4 开始监听,调用监听线程 执行 监听套接字的监听方法。
thrWatch = new Thread(WatchConncting);
thrWatch.IsBackground = true;
thrWatch.Start();
ShowMsg("服务器启动!");
2.0 服务器监听方法 + void WatchConncting()
void WatchConncting()
{
try
{
//循环监听客户端的连接请求。
while (isWatch)
{
//2.4开始监听,返回了一个通信套接字
Socket sockMsg = sokWatch.Accept();
//2.5 创建通信管理类
MsgConnection conn = new MsgConnection(sockMsg, ShowMsg, RemoveClient); //将当前连接成功的【与客户端通信的套接字】的标识保存起来,并显示到列表中
//将远程客户端的 ip 和 端口 字符串 存入列表
listOnline.Items.Add(sockMsg.RemoteEndPoint.ToString());
//将服务器端的通信套接字存入字典集合。
dictConn.Add(sockMsg.RemoteEndPoint.ToString(), conn);
ShowMsg("有客户端连接了!");
}
}
catch (Exception ex)
{
ShowMsg("异常" + ex);
}
}
3.0 服务端向指定的客户端发送消息
string strClient = listOnline.Text;
if (dictConn.ContainsKey(strClient))
{
string strMsg = txtInput.Text.Trim();
ShowMsg("向客户端【" + strClient + "】说:" + strMsg);
//通过指定的套接字将字符串发送到指定的客户端
try
{
dictConn[strClient].Send(strMsg);
}
catch (Exception ex)
{
ShowMsg("异常" + ex.Message);
}
}
4.0 根据要中断的客户端ipport关闭连接 + void RemoveClient(string clientIpPort)
//1.0 移除列表中的项
listOnline.Items.Remove(clientIpPort);
//2.0 关闭通信管理类
dictConn[clientIpPort].Close();
//3.0 从字典中移除对应的通信管理类的项
dictConn.Remove(clientIpPort);
5.0 选择要发送的文件
OpenFileDialog ofd = new OpenFileDialog();
if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
//将选中的要发送的文件路径,显示到文本框中。
txtFilePath.Text = ofd.FileName;
}
6.0 发送文件
string strClient = listOnline.Text;
if (dictConn.ContainsKey(strClient))
{
string strMsg = txtInput.Text.Trim();
//通过指定的套接字将字符串发送到指定的客户端
try
{
dictConn[strClient].SendFile(txtFilePath.Text.Trim());
}
catch (Exception ex)
{
ShowMsg("异常" + ex.Message);
}
}
7.0 向指定的客户端发送抖屏
string strClient = listOnline.Text;
if (dictConn.ContainsKey(strClient))
{
string strMsg = txtInput.Text.Trim();
//通过指定的套接字将字符串发送到指定的客户端
try
{
dictConn[strClient].SendShake();
}
catch (Exception ex)
{
ShowMsg("异常" + ex.Message);
}
}
8.0 打印消息 + ShowMsg(string strmsg)
this.txtShow.AppendText(strmsg + "\r\n");
服务器端通信管理类,负责处理与某个客户端通信的过程
public class MsgConnection
{
//与某个客户端通信套接字
Socket sokMsg = null;
//通信线程
Thread thrMsg = null;
//创建一个委托对象, 在窗体显示消息的方法
DGShowMsg dgShow = null;
//创建一个关闭连接的方法
DGCloseConn dgCloseConn = null; #region 1.0 构造函数
public MsgConnection(Socket sokMsg, DGShowMsg dgShow, DGCloseConn dgCloseConn)
{
this.sokMsg = sokMsg;
this.dgShow = dgShow;
this.dgCloseConn = dgCloseConn;
//创建通信线程,负责调用通信套接字,来接收客户端消息。
thrMsg = new Thread(ReceiveMsg);
thrMsg.IsBackground = true;
thrMsg.Start(this.sokMsg);
}
#endregion bool isReceive = true;
#region 2.0 接收客户端发送的消息
void ReceiveMsg(object obj)
{
Socket sockMsg = obj as Socket;
//3 通信套接字 监听客户端的消息,传输的是byte格式。
//3.1 开辟了一个 1M 的空间,创建的消息缓存区,接收客户端的消息。
byte[] arrMsg = new byte[ * * ];
try
{
while (isReceive)
{
//注意:Receive也会阻断当前的线程。
//3.2 接收客户端的消息,并存入消息缓存区。
//并 返回 真实接收到的客户端数据的字节长度。
int realLength = sockMsg.Receive(arrMsg);
//3.3 将接收的消息转成字符串
string strMsg = System.Text.Encoding.UTF8.GetString(arrMsg, , realLength);
//3.4 将消息显示到文本框
dgShow(strMsg);
}
}
catch (Exception ex)
{
//调用窗体类的关闭移除方法
dgCloseConn(sokMsg.RemoteEndPoint.ToString());
//显示消息
dgShow("客户端断开连接!");
}
}
#endregion #region 3.0 向客户端发送文本消息 + void Send(string msg)
/// <summary>
/// 3.0 向客户端发送文本消息
/// </summary>
/// <param name="msg"></param>
public void Send(string msg)
{
byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(msg);
//通过指定的套接字将字符串发送到指定的客户端
try
{
sokMsg.Send(MakeNewByte("str",arrMsg));
}
catch (Exception ex)
{
dgShow("异常" + ex.Message);
}
}
#endregion #region 4.0 向客户端发送文件 + void SendFile(string strPath)
/// <summary>
/// 4.0 向客户端发送文件
/// </summary>
/// <param name="strFilePath"></param>
public void SendFile(string strFilePath)
{
//4.1 读取要发送的文件
byte[] arrFile = System.IO.File.ReadAllBytes(strFilePath);
//4.2 向客户端发送文件
sokMsg.Send(MakeNewByte("file", arrFile));
}
#endregion #region 4.1 向客户端发送抖屏命令 + void SendShake()
/// <summary>
/// 4.1 向客户端发送抖屏命令
/// </summary>
public void SendShake()
{
sokMsg.Send(new byte[] { });
}
#endregion #region 5.0 返回带标识的新数组 + byte[] MakeNew(string type, byte[] oldArr)
/// <summary>
/// 返回带标识的新数组
/// </summary>
/// <param name="type"></param>
/// <param name="oldArr"></param>
/// <returns></returns>
public byte[] MakeNewByte(string type, byte[] oldArr)
{
//5.1 创建一个新数组(是原数组长度 +1)
byte[] newArrFile = new byte[oldArr.Length + ];
//5.2 将原数组数据复制到新数组中(从新数组下标为1的位置开始)
oldArr.CopyTo(newArrFile, );
//5.3 根据内容类型为新数组第一个元素设置标识符号
switch (type.ToLower())
{
case "str":
newArrFile[] = ; //只能存0-255之间的数值
break;
case "file":
newArrFile[] = ;
break;
default:
newArrFile[] = ;
break;
}
return newArrFile;
}
#endregion #region 6.0 关闭通信
/// <summary>
/// 关闭通信
/// </summary>
public void Close()
{
isReceive = false;
sokMsg.Close();
sokMsg = null;
}
#endregion
}
二 . 客户端
1.0 发送连接服务端请求
try
{
//1.0创建连接套接字,使用ip4协议,流式传输,tcp链接
sokMsg = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2.0获取要链接的服务端节点
//2.1获取网络节点对象
IPAddress address = IPAddress.Parse(txtIp.Text);
IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txtPort.Text));
//3 向服务端发送链接请求。
sokMsg.Connect(endPoint);
ShowMsg("连接服务器成功!"); //4 开启通信线程
thrMsg = new Thread(ReceiveMsg);
thrMsg.IsBackground = true;
//win7, win8 需要设置客户端通信线程同步设置,才能在接收文件时打开文件选择框
thrMsg.SetApartmentState(ApartmentState.STA);
thrMsg.Start();
}
catch (Exception ex)
{
ShowMsg("连接服务器失败!" + ex.Message);
}
2.0 接收服务端消息
//准备一个消息缓冲区域
byte[] arrMsg = new byte[ * * ];
try
{
while (isReceive)
{
//接收 服务器发来的数据,因为包含了一个标示符,所以内容的真实长度应该-1
int realLength = sokMsg.Receive(arrMsg)-;
switch (arrMsg[])
{
//文本
case :
GetMsg(arrMsg,realLength);
break;
//文件
case :
GetFile(arrMsg,realLength);
break;
default:
ShakeWindow();
break;
}
}
}
catch (Exception ex)
{
sokMsg.Close();
sokMsg = null;
ShowMsg("服务器断开连接!");
}
2.1 接收服务端文本消息 + GetMsg(byte[] arrContent, int realLength)
//获取接收的内容,去掉第一个标示符。
string strMsg = System.Text.Encoding.UTF8.GetString(arrContent, , realLength);
ShowMsg("服务器说:" + strMsg);
2.2 保存文件 + GetFile(byte[] arrContent, int realLength)
SaveFileDialog sfd = new SaveFileDialog();
if (sfd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
string savaPath = sfd.FileName;
//使用文件流,保存文件
using (System.IO.FileStream fs = new System.IO.FileStream(savaPath, System.IO.FileMode.OpenOrCreate))
{
//将收到的文件数据数组,写入硬盘。
fs.Write(arrContent, , realLength);
}
ShowMsg("保存文件到 【" + savaPath + "】成功!");
}
2.3 抖屏 + void ShakeWindow()
// 保存当前窗体位置
Point oldPoint = this.Location;
for (int i = ; i < ; i++)
{
//随机生成新位置
Point newPoint = new Point(oldPoint.X + ran.Next(), oldPoint.Y + ran.Next());
//将新位置设置给窗体
this.Location = newPoint;
System.Threading.Thread.Sleep();
this.Location = oldPoint;
//休息15毫秒
System.Threading.Thread.Sleep();
}
3.0 客户端发送消息到服务端
string strMsg = txtInput.Text.Trim();
if (strMsg != "")
{
byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
try
{
sokMsg.Send(arrMsg);
}
catch (Exception ex)
{
ShowMsg("发送消息失败!" + ex.Message);
}
}
else
{
MessageBox.Show("未输入任何信息!");
}
4.0 展示消息方法 + ShowMsg(string strmsg)
this.txtShow.AppendText(strmsg + "\r\n");
注:小弟不断学习中,还希望园友指导交流。同时也作为个人学习的一个小记录。【好记性不如烂笔头】(*^__^*)
源码分享: http://download.csdn.net/detail/huayuqiang/8270749
WinForm版聊天室复习Socket通信的更多相关文章
- swoole实验版聊天室
“swoole实验版聊天室”是依据一堂swoole培训课内容改编的,结合了bootstrap前端框架.redis数据库.jquery框架等实现基本功能,只是体现了swoole的应用,并不是为了专门写个 ...
- 如何利用WebSocket实现网页版聊天室
花了将近一周的时间终于完成了利用WebSocket完成网页版聊天室这个小demo,期间还走过了一段"看似弯曲"的道路,但是我想其实也不算是弯路吧,因为你走过的路必将留下你的足迹.这 ...
- angular版聊天室|仿微信界面IM聊天|NG2+Node聊天实例
一.项目介绍 运用angular+angular-cli+angular-router+ngrx/store+rxjs+webpack+node+wcPop等技术实现开发的仿微信angular版聊天室 ...
- 从前有个聊天室(socket&threading)
服务器端: # -*- coding: utf-8 -*- import socket, threading con = threading.Condition() HOST = raw_input( ...
- 基于WebSocket实现网页版聊天室
WebSocket ,HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议,其使用简单,应用场景也广泛,不同开发语言都用种类繁多的实现,仅Java体系中,Tomcat,Jetty,Sp ...
- 命令行界面的C/S聊天室应用 (Socket多线程实现)
命令行界面即在Eclipe控制台输入数据. 服务器端包含多个线程,每个Socket对应一条线程,该线程负责读取对应输入流的数据(从客户端发送过来的数据),并将读到的数据向每个Socket输出流发送一遍 ...
- Swoole实现h5版聊天室笔记
声明:该聊天室目前只有一对多,一对一的聊天功能,另外,因为没有使用到mysql,所以还存在比较多的缺陷地方,但知道原理就差不多了,这里主要分享下swoole简易的聊天室制作思路. 开发环境:cento ...
- golang简易版聊天室
功能需求: 创建一个聊天室,实现群聊和单聊的功能,直接输入为群聊,@某人后输入为单聊 效果图: 群聊: 单聊: 服务端: package main import ( "fmt" ...
- 使用node.js实现多人聊天室(socket.io、B/S)
通过B/S架构实现多人聊天,客户端连接服务器,发送信息,服务器接收信息之后返回给客户端. 主要是通过socket.io实现浏览器和服务器之间进行实时,双向和基于事件的通信. socket.io官方文档 ...
随机推荐
- redis 之相关命令
为什么缓存数据库更要首选redis?如何使用redis? 一.使用缓存数据库为什么首选用redis? 我们都知道,把一些热数据存到缓存中可以极大的提高速度,那么问题来了,是用Redis好还是Memca ...
- net user
net user 编辑 Net User命令是一个DOS命令,必须在Windows nt以上系统的MS-DOS模式下运行,所以首先要进入MS-DOS模式:选择“开始”菜单的“附件”选项的子选项“命令提 ...
- leetcode 【 Subsets II 】python 实现
题目: Given a collection of integers that might contain duplicates, S, return all possible subsets. No ...
- leetcode 【 Triangle 】python 实现
题目: Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjace ...
- 【homework #1】第一次作业被虐感受
当大二暑假结束,我发现我还是没有熟练掌握一门编程语言时,我就知道苦日子要来了. 这不,才开学第三周,就已经被虐的体无完肤了.连编译课用C语言写的词法分析,都要写很久.Debug很久才能写出来,更别提大 ...
- VC++中PostMessage、SendMessage和PeekMessage之间的区别
1, PostMessage只把消息放入队列,不管其他程序是否处理都返回,然后继续执行,这是个异步消息投放函数.而SendMessage必须等待其他程序处理消息完了之后才返回,继续执行,这是个同步消息 ...
- Unity 查找
GameObject.Find().Transform.Find查找游戏对象 1.前置条件 Unity中常用到查找对象,非隐藏的.隐藏的,各种方法性能有高有低,使用又有各种条件限制. 在此对查找的性能 ...
- java 例子
1. 本章学习总结 今天主要学习了三个知识点 封装 继承 多态 2. 书面作业 Q1. java HelloWorld命令中,HelloWorld这个参数是什么含义? 今天学了一个重要的命令javac ...
- js深度复制
项目过程遇到需要对一个对象处理,然后独立出来用,结果怎么处理都会影响到原有变量,原来是引用导致,只有深度复制才行. 最终用下面的赋值方法才搞成功 var result=$.extend( true, ...
- BZOJ 1087:[SCOI2005]互不侵犯King(状压DP)
[SCOI2005]互不侵犯King [题目描述] 在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案.国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子 ...