聊天室:服务器端-------------客户端

最终演示展示图:

一. 服务器端

  对服务端为了让主窗体后台不处理具体业务逻辑,因此对服务端进行了封装,专门用来处理某个客户端通信的过程。

而由于通信管理类中需要处理具体与某个客户端的通信业务,所以在构造函数中传入了具体的套接字对象。

  针对消息提醒:由于需要再通信管理类中进行消息提示,而需要调用主窗体的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通信的更多相关文章

  1. swoole实验版聊天室

    “swoole实验版聊天室”是依据一堂swoole培训课内容改编的,结合了bootstrap前端框架.redis数据库.jquery框架等实现基本功能,只是体现了swoole的应用,并不是为了专门写个 ...

  2. 如何利用WebSocket实现网页版聊天室

    花了将近一周的时间终于完成了利用WebSocket完成网页版聊天室这个小demo,期间还走过了一段"看似弯曲"的道路,但是我想其实也不算是弯路吧,因为你走过的路必将留下你的足迹.这 ...

  3. angular版聊天室|仿微信界面IM聊天|NG2+Node聊天实例

    一.项目介绍 运用angular+angular-cli+angular-router+ngrx/store+rxjs+webpack+node+wcPop等技术实现开发的仿微信angular版聊天室 ...

  4. 从前有个聊天室(socket&threading)

    服务器端: # -*- coding: utf-8 -*- import socket, threading con = threading.Condition() HOST = raw_input( ...

  5. 基于WebSocket实现网页版聊天室

    WebSocket ,HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议,其使用简单,应用场景也广泛,不同开发语言都用种类繁多的实现,仅Java体系中,Tomcat,Jetty,Sp ...

  6. 命令行界面的C/S聊天室应用 (Socket多线程实现)

    命令行界面即在Eclipe控制台输入数据. 服务器端包含多个线程,每个Socket对应一条线程,该线程负责读取对应输入流的数据(从客户端发送过来的数据),并将读到的数据向每个Socket输出流发送一遍 ...

  7. Swoole实现h5版聊天室笔记

    声明:该聊天室目前只有一对多,一对一的聊天功能,另外,因为没有使用到mysql,所以还存在比较多的缺陷地方,但知道原理就差不多了,这里主要分享下swoole简易的聊天室制作思路. 开发环境:cento ...

  8. golang简易版聊天室

    功能需求: 创建一个聊天室,实现群聊和单聊的功能,直接输入为群聊,@某人后输入为单聊 效果图: 群聊:   单聊: 服务端: package main import ( "fmt" ...

  9. 使用node.js实现多人聊天室(socket.io、B/S)

    通过B/S架构实现多人聊天,客户端连接服务器,发送信息,服务器接收信息之后返回给客户端. 主要是通过socket.io实现浏览器和服务器之间进行实时,双向和基于事件的通信. socket.io官方文档 ...

随机推荐

  1. [网站公告]11月26日00:00-04:00阿里云RDS升级

    大家好,11月26号00:00-04:00(今天夜里),阿里云将对我们所用的SQL Server RDS实例所在的物理主机做升级操作(目前博客园整站运行于阿里云上),升级期间RDS实例会有2次闪断,每 ...

  2. Python+Selenium练习篇之1-摘取网页上全部邮箱

    前面已经介绍了Python+Selenium基础篇,通过前面几篇文章的介绍和练习,Selenium+Python的webUI自动化测试算是入门了.接下来,我计划写第二个系列:练习篇,通过一些练习,了解 ...

  3. editrules

    editrules    editrules是用来设置一些可用于可编辑列的colModel的额外属性的.大多数的时候是用来在提交到服务器之前验证用户的输入合法性的.比如editrules:{edith ...

  4. iOS-文本段落样式NSMutableParagraphStyle与NSParagraphStyle的使用和一些富文本处理属性

    开发过程中,经常会遇到动态计算行高的问题, - (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)op ...

  5. [WC2013][luogu4074] 糖果公园 [树上带修改莫队]

    题面: 传送门 思路: 一道实现起来细节比较恶心的题目 但是其实就是一个裸的树上带修改莫队 好像树上莫队也出不了什么结合题目,不像序列莫队天天结合AC自动机.后缀数组...... 莫队学习请戳这里:莫 ...

  6. imx6 PCIE使能加载ath9k无线网卡

    imx6q配置pcie无线网卡遇到如下问题: imx6q-pcie 1ffc000.pcie: PCI host bridge to bus 0000:00 pci_bus 0000:00: root ...

  7. 洛谷P3045 [USACO12FEB]牛券Cow Coupons

    P3045 [USACO12FEB]牛券Cow Coupons 71通过 248提交 题目提供者洛谷OnlineJudge 标签USACO2012云端 难度提高+/省选- 时空限制1s / 128MB ...

  8. apache 列目录修复

    描述   :Apache默认配置时允许目录浏览.如果目录下没有索引文件,则会出现目录浏览,导致文件信息泄漏. 检测方法:直接访问目录,如果能看到目录下的文件信息,则说明存在目录浏览漏洞 加固方法: 1 ...

  9. Maven 的基本用法

    一.Maven 的安装 二.Maven的常用构建命令 1.mvn -v 查看maven版本 2.mvn compile 编译 3.mvn test 测试 4.mvn package 打包 5.Mvn  ...

  10. pat 甲级 1056. Mice and Rice (25)

    1056. Mice and Rice (25) 时间限制 100 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue Mice an ...