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官方文档 ...
随机推荐
- Fragment 和 Activity 之间通信
在 Activity 中获取 Fragment 实例: FragmentManager 提供了一个类似于 findViewById 的方法,专门用于从布局文件中获取 Fragment 实例: //通过 ...
- requireJS入门学习
前言 最近网上.群里各种随便看,随便学.暑期实习还没找到,昨天开题过了,好好学习吧.最近一直看到前端的ADM,CMD规范,然后网上各种找资料看,看了好几个牛人的博客,写的很好,然后自我感觉了解了点,介 ...
- Python2 HTMLTestRunner自动化测试报告美化
python2 的测试报告美化,需要的同学直接用 #coding=utf-8 """ A TestRunner for use with the Python unit ...
- Python+Selenium练习篇之6-利用class name定位元素
有时候,我们在用firepath(不会的请点这里)查看元素的XPath信息,发现没有可以用来定位的id信息,这个时候我们就需要考虑用其他的可用的来定位元素.本文介绍如何通过元素节点中class nam ...
- 【转】UGUI(小地图的实现)与游戏关卡选择的简单实现
http://www.jianshu.com/p/68637029e9df 游戏中小地图的实现(场景用简单Cube组成先搭建如下图场景,真实场景实现方法也是一样) 图1-1小地图效果图 1.创建好场景 ...
- Hadoop入门第三篇-MapReduce试手以及MR工作机制
MapReduce几个小应用 上篇文章已经介绍了怎么去写一个简单的MR并且将其跑起来,学习一个东西动手还是很有必要的,接下来我们就举几个小demo来体验一下跑起来的快感. demo链接请参照附件:ht ...
- css对html中表格单元格td文本过长的处理
参考 http://www.cnblogs.com/lekko/archive/2013/04/30/3051638.html http://www.zhangxinxu.com/wordpress/ ...
- BZOJ2123 [Sdoi2013]森林 【主席树 + 启发式合并】
题目 输入格式 第一行包含一个正整数testcase,表示当前测试数据的测试点编号.保证1≤testcase≤20. 第二行包含三个整数N,M,T,分别表示节点数.初始边数.操作数.第三行包含N个非负 ...
- 重绘(repaints)与重排(reflows)
当页面布局和几何属性改变时就需要"重排" 避免在修改样式的过程中使用 offsetTop, scrollTop, clientTop, getComputedStyle() 这些属性, 它们都会刷新渲 ...
- bzoj 合集 1079 1791 1876 2208 2306
1079 记忆化瞎搞吧,[a][b][c][d][e][l]表示当前有能涂1次的油漆a个,能涂2次的b个….前一个颜色为l,再搞下转移就行了. 1791 基环树上找直径 1876 高精度 2208 看 ...