用c#写的一个局域网聊天客户端 类似小飞鸽
摘自:
http://www.cnblogs.com/yyl8781697/archive/2012/12/07/csharp-socket-udp.html

1:软件开启的时候先新开一个线程,该线程充当服务器端,一直死循环监听

2:开了新线程了,调用广播的方法

3:此时如果局域网内已经有有其它主机打开了这个软件,将会监听到这个广播,收到这个广播后将返回自己的主机,并且将监听到的主机添加自己的在线列表中,当然,发起广播的软件也能收到其它软件的回信,收到他们的主机后也加入自己的在线列表,这样各自的列表就都能建立起来,并且将当前的列表加入一个静态的泛型列表中(用于以后和其它用户的通信,维护他们的状态)

4:通信 发送消息:双击一个主机列表后 得到该主机host 传到交谈窗体 并查询出他主机的endpoint,这样就可以进行本机和向该endpoint点发送消息了

5:下线 下线之前软件会发一个下线的广播,其它的软件接到该广播的时候将会将该主机从自己的在线列表中移除

整体思路就这样,下面可以结合代码具体看一下

复制代码
#region Field

///

/// 在非主线程是对控件操作
///

///
private delegate void MyInvoke(string host);

///

/// 充当服务器 进行监听接收
///

private SocketUdpServer udpServer;

///

/// 充当客户端
///

private SocketUdpClient udpClient;

#endregion

#region Contructor

///

/// 构造函数
///

public FrmUser()
{
InitializeComponent();
init();
}

#endregion

#region Method

///

/// 初始化数据
///

private void init()
{
LanList.CurrentLanList = new List();
this.Text = "当前局域网内在线用户";
this.udpServer = SocketUdpServer.Instance;
this.udpServer.OnLineComplete += new SocketUdpServer.OnCompleteHander(this.OnLine_Event);
this.udpServer.DownLineComplete += new SocketUdpServer.OnCompleteHander(this.DownLine_Event);
this.udpServer.ErrorAppear += new SocketUdpServer.OnCompleteHander(this.Error_Event);
this.udpServer.Listen();
this.udpClient = new SocketUdpClient();
this.udpClient.Broadcast(DatagramType.OnLine);

}

///

/// 上线增加用户
///

/// 用户主机
private void AddUser(string host)
{
this.ilbUserList.Items.Add(host, 0);
}

///

/// 下线减少用户
///

/// 用户主机在列表的序号 懒了以下 应该将回调的委托参数定义为int的,这里用了string 到程序需要转化为Int
private void RemoveUser(string hostIndex)
{
this.ilbUserList.Items.RemoveAt(Convert.ToInt32(hostIndex));
}

#endregion

#region Event

///

/// 上线事件
///

///
///
private void OnLine_Event(SocketUdpServer socket, EventArgs e)
{
string host = socket.Message;
//如果该上线的用户在局域网列表中不存在
if (!LanList.CurrentLanList.Exists(x => x.Host == host))
{
while (!this.IsHandleCreated) ;
this.ilbUserList.Invoke(new MyInvoke(this.AddUser), host);
//将上线用户添加进静态的局域网列表
LanList.CurrentLanList.Add(new LanInfo()
{
Host = host,
State = TalkState.Waiting,
RemoteEndPoint = socket.RemoteEndPoint
});
}
}

///

/// 下线事件
///

///
///
private void DownLine_Event(SocketUdpServer socket, EventArgs e)
{
string host = socket.Message;
if (LanList.CurrentLanList.Exists(x => x.Host == host))
{
///判断是否是自己的主机下线 如果是自己的 则不需要操作
if (string.Compare(Dns.GetHostName(), host) != 0)
{
this.ilbUserList.Invoke(new MyInvoke(this.RemoveUser), LanList.CurrentLanList.FindIndex(x => x.Host == host).ToString());
//将该用户从局域网列表中移除

LanList.CurrentLanList.RemoveAll(x => x.Host == host);
}
}
}

///

/// 出现错误的事件
///

///
///
private void Error_Event(SocketUdpServer socket, EventArgs e)
{
XtraMessageBox.Show(socket.Message);
}

private void ilbUserList_DoubleClick(object sender, EventArgs e)
{
//XtraMessageBox.Show(ilbUserList.SelectedItem.ToString());
string host = ilbUserList.SelectedItem.ToString();
///打开窗口 设置为正在交谈
LanList.SetTalkState(host, TalkState.Talking);
(new FrmTalk(host)).Show();
}

///

/// 窗体关闭事 进行下线广播
///

///
///
private void FrmUser_FormClosed(object sender, FormClosedEventArgs e)
{
this.udpClient.Broadcast(DatagramType.DownLine);
this.udpServer.Stop();
Application.Exit();
}

///

/// 刷新按钮
///

///
///
private void btnRefresh_Click(object sender, EventArgs e)
{
//刷新 情况列表中的数据 重新上线广播
this.ilbUserList.Items.Clear();
LanList.CurrentLanList.Clear();
this.udpClient.Broadcast(DatagramType.OnLine);
}

#endregion
复制代码

  该页面主要是在线用户列表页面,同时监听其它软件发来的上线,下线,获取主机信息等数据报,维护当前在线的用户 和聊天状态

复制代码
#region Field

///

/// 自己的socket 充当服务器 接收消息
///

private SocketUdpServer selfSocket = null;
///

/// 对话的socket
///

private SocketUdpClient tallSocket = null;
///

/// 谈话对方的局域网信息
///

private LanInfo talkLan = null;
///

/// 当前用户主机
///

private string currentUserHost = "";

///

/// 对控件操作 在非主线程下需要调用此代理
///

private delegate void MyInvoke(string user,string message);

#endregion

#region Constructor

///

/// 通过远端主机名打开窗体
///

///
public FrmTalk(string host)
{
InitializeComponent();
if (this.talkLan == null)
{
this.talkLan = LanList.CurrentLanList.Find(x => x.Host == host);
}

this.currentUserHost = Dns.GetHostName();
this.Text = "正在和" + host + "聊天中";
this.Initializion();
}

///

/// 通过远端 端点打开窗体
///

///
public FrmTalk(EndPoint remotePoint)
{
this.talkLan = LanList.CurrentLanList.Find(x => string.Compare(x.RemoteEndPoint.ToString(), remotePoint.ToString()) == 0);
(new FrmTalk(talkLan.Host)).Show();
}

#endregion

#region Method

///

/// 初始化方法
///

private void Initializion()
{
this.selfSocket = SocketUdpServer.Instance;
///绑定收到信息事件
this.selfSocket.OnChatComplete += new SocketUdpServer.OnCompleteHander(this.ReceiveEvent);
//给谈话的socket初始化endpoint
this.tallSocket = new SocketUdpClient(this.talkLan.RemoteEndPoint);

}

///

/// 加载未读的信息
///

private void LoadUnReadMessage()
{
Queue queque = QueueMessage.GetAndRemove(talkLan.Host);
MessageInfo messageInfo=null;
if (queque != null)
{
while (queque.Count > 0)
{
//出队列
messageInfo = queque.Dequeue();
this.lbxMessage.Items.Add(talkLan.Host + ":" + messageInfo.ReceiveTime.ToString("yyyy-MM-dd HH:mm:ss"));
this.lbxMessage.Items.Add(messageInfo.Message);
}
}
}

///

/// 添加一行 在listboxcontrol中
///

/// 显示的用户
/// 消息
private void AddLine(string name,string message)
{
this.lbxMessage.Items.Add(name+ ":" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
this.lbxMessage.Items.Add(message);
}

///

/// 发送信息 由键盘回车和发送按钮调用
///

private void SendMessage()
{
try
{
string message = this.memInput.Text;
if (string.IsNullOrEmpty(message))
{
XtraMessageBox.Show("发送信息不能为空");
}
else
{
this.tallSocket.Send(message);
this.AddLine("我", message);
this.memInput.Text = "";
}
}
catch (Exception ex)
{
XtraMessageBox.Show(ex.Message);
}
}

#endregion

#region Event

///

/// 表单加载
///

///
///
private void FrmTalk_Load(object sender, EventArgs e)
{
this.LoadUnReadMessage();
}

///

/// 接收信息回调事件
///

///
///
private void ReceiveEvent(SocketUdpServer socket, EventArgs e)
{
//判断 远端的网络端点是否是当前的 打开的窗体
if (string.Compare(this.talkLan.RemoteEndPoint.ToString(), socket.RemoteEndPoint.ToString()) == 0)
{
this.lbxMessage.Invoke(new MyInvoke(this.AddLine), this.talkLan.Host, socket.Message);
}
}

///

/// 信息发送按钮
///

///
///
private void btnSend_Click(object sender, EventArgs e)
{
this.SendMessage();
}

private void FrmTalk_FormClosed(object sender, FormClosedEventArgs e)
{
//将其设置为非交谈状态
LanList.SetTalkState(talkLan.Host, TalkState.Waiting);
}

private void memInput_KeyDown(object sender, KeyEventArgs e)
{
///按下回车事件
if (e.KeyCode == Keys.Enter)
{
this.SendMessage();
}
}

#endregion
复制代码

  该页面就是聊天页面,主要是对相应的host进行通信聊天,发送和接收聊天信息,根据聊天窗口设置状态啊之类的

复制代码
#region Method

#region 停止当前监听和断开线程
///

/// 停止当前服务器的监听和断开线程
///

public void Stop()
{
this.listenThread.Abort();
this.listenSocket.Close();
}
#endregion

#region 监听

///

/// 开始监听
///

public void Listen()
{
ThreadStart method = new ThreadStart(this.ListenMethod);
this.listenThread = new Thread(method);
this.listenThread.Start();
}

///

/// 监听的方法
///

private void ListenMethod()
{
try
{
this.listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPEndPoint ipep = new IPEndPoint(IPAddress.Any, this.port);
this.listenSocket.Bind(ipep);//定义一个网络端点

IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);//定义要发送的计算机的地址
EndPoint remote = (EndPoint)(sender);//远程

///持续监听
while (true)
{
byte[] data = new byte[1024];

//准备接收
int recv = this.listenSocket.ReceiveFrom(data, ref remote);
string stringData = Encoding.UTF8.GetString(data, 0, recv);
//将接收到的信息转化为自定义的数据报类
Datagram recvicedataGram = Datagram.Convert(stringData);
this.message = recvicedataGram.Message;
string remotePoint = remote.ToString();
string remoteip = remotePoint.Substring(0, remotePoint.IndexOf(":"));
remote = new IPEndPoint(IPAddress.Parse(remoteip), this.port);
this.remoteEndPoint = remote;
this.Action(recvicedataGram.Type);

}
}
catch (Exception ex)
{
this.message = ex.Message;
this.ErrorAppear(this, new EventArgs());
}
}

///

/// 收到数据报后的动作
///

/// 数据报的类型
private void Action(DatagramType type)
{
switch (type)
{
case DatagramType.OnLine:
Datagram sendDataGram = new Datagram
{
Type = DatagramType.GiveInfo,
FromAddress = "",
ToAddress = "",
Message = Dns.GetHostName()
};
//告诉对方自己的信息
this.listenSocket.SendTo(Encoding.UTF8.GetBytes(sendDataGram.ToString()), this.remoteEndPoint);
this.OnLineComplete(this, new EventArgs());
break;
case DatagramType.GiveInfo:
///执行添加上线用户事件
this.OnLineComplete(this, new EventArgs());
break;
case DatagramType.DownLine:
///执行用户下线事件
///如果是自己下线
if (string.Compare(Dns.GetHostName(), message) == 0)
{
System.Windows.Forms.Application.Exit();
}
else
{
this.DownLineComplete(this, new EventArgs());
}
break;
case DatagramType.Chat:
//得到当前要交谈的用户
LanInfo lanInfo = LanList.CurrentLanList.Find(x => string.Compare(this.remoteEndPoint.ToString(), x.RemoteEndPoint.ToString()) == 0);
//如果有查询到该用户在自己这边登记过
if (lanInfo != null)
{

if (lanInfo.State == TalkState.Talking)
{
//正在交谈 直接打开这次窗口
this.OnChatComplete(this, new EventArgs());
}
else {
//没有交谈 将窗口加入信息的队列
MessageInfo messageInfo = new MessageInfo()
{
Message = this.message,
ReceiveTime = DateTime.Now,
RemoteEndPoint = this.remoteEndPoint
};
QueueMessage.Add(lanInfo.Host, messageInfo);
}
}
break;
}
}

#endregion

#endregion
复制代码

  充当服务器的 socket的监听,定义一些监听事件,在form里面使用该事件就可以了

复制代码
#region Delegate Event

///

/// 完成一个socket的代理
///

///
///
public delegate void OnCompleteHander(SocketUdpServer sender, EventArgs e);
///

/// 完成收到一个主机信息 即上线事件
///

public event OnCompleteHander OnLineComplete;
///

/// 完成下线事件
///

public event OnCompleteHander DownLineComplete;
///

/// 完成一次谈话 就一条信息
///

public event OnCompleteHander OnChatComplete;
///

/// 有错误出现
///

public event OnCompleteHander ErrorAppear;

#endregion
复制代码

  用户上线事件,下线事件,或者主机事件,chat聊天事件,再服务器接收到信息后 感觉信息分类执行不同的事件

在CHAT类型的数据报重要注意的是,当有数据过来接收到 但是该主机窗口并未打开时,要将要收到信息加入一个未读的信息队列中,当再次开发对该用户的聊天窗口时先要加载相应的未读信息队列,这样可以简单的实现离线信息的发送

接下来看下信息数据报的格式

复制代码
/*****************************************************************
* 定义广播的数据格式
* Type=OnLine,FromAdress=xxx,ToAdress=zzz,Message=mmm
* 类型为上线广播 从xxx主机到zzz主机 信息是mmm
* CHAT这个就是我的信息我的信息 可能有各种=,的字符串
* 这种就直接将CHAT去掉后 后面的都为mmm
*****************************************************************/

///

/// 定义数据报里面的几个字段
///

public class Datagram
{
#region Property

///

/// 数据报的类型 ,
///

public DatagramType Type
{
get;
set;
}

///

/// 发送者的网络地址
///

public string FromAddress
{
get;
set;
}

///

/// 接收者网络地址
///

public string ToAddress
{
get;
set;
}

///

/// 数据报的信息
///

public string Message
{
get;
set;
}

///

/// 信息 Message的长度
///

public int Length
{
get {
return this.Message.Length;
}
}

#endregion

#region Method

///

/// 重写下ToString
///

///
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.AppendFormat("Type={0},", this.Type.ToString());
sb.AppendFormat("FromAddress={0},", this.FromAddress.ToString());
sb.AppendFormat("ToAddress={0},", this.ToAddress.ToString());
sb.AppendFormat("Message={0}", this.Message.ToString());
return sb.ToString();
}

///

/// 将有效字符串转化成数据报
///

///
///
public static Datagram Convert(string str)
{
Datagram data = new Datagram();
//前面不是CHAT主要是建立连接 取消连接等信号传送
if (!str.StartsWith("CHAT"))
{
IDictionary idict = new Dictionary();

string[] strlist = str.Split(',');
for (int i = 0; i
/// 数据报的类型
///

public enum DatagramType
{
///

/// 上线 一应一答
///

OnLine=1,
///

/// 下线 一应
///

DownLine,
///

/// 确认收到 一应
///

///

/// 正常聊天 一应一答
///

Chat,
///

/// 给予个人的信息
///

GiveInfo

}

#endregion
复制代码
简单的定义一下发送的数据报的格式 可能发送的几种类型:

上线:主要用于软件刚刚开启时向局域网内发送上线广播

下线:软件在关闭之前再向局域网内发送一次下线广播

给出主机信息:用于收到上线广播后 再返回一个自己主机信息给对方,让让对方知道局域网中这台主机是上线的

聊天:就是平常的通信 这里特别注意的是,为考虑到聊天中也会出来,= 这两个协定的字符串,所以 开头加CHAT 表示纯粹聊天的数据报

复制代码
///

/// udp的客户端 主要用户发送数据
///

public class SocketUdpClient
{
#region Feild

///

/// 广播的socket
///

private Socket broadcastSocket;

///

/// 服务器的端口
///

private int port;

///

/// 远端的端点
///

private EndPoint remoteEndPoint = null;
///

/// 当前客户端
///

private Socket client = null;

#endregion

#region Constructor

///

/// 构造函数
///

public SocketUdpClient(EndPoint point)
{
this.client = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
this.remoteEndPoint = point;
}

///

/// 无参构造函数
///

public SocketUdpClient()
{
this.port = 9050;
}

#endregion

#region 进行广播
///

/// 进行广播 上线或者下线
///

/// 广播中发送的信息
public void Broadcast(DatagramType type)
{
this.broadcastSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPEndPoint iep = new IPEndPoint(IPAddress.Broadcast, this.port);
this.broadcastSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
Datagram dataGram = new Datagram
{
Type = type,
FromAddress = "",
ToAddress = "",
Message = Dns.GetHostName()
};

//将要发送的信息改为字节流
byte[] data = Encoding.ASCII.GetBytes(dataGram.ToString());
this.broadcastSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
this.broadcastSocket.SendTo(data, iep);
//this.broadcastSocket.Close();
}

#endregion

#region Method

///

/// 发送数据
///

/// 当前的数据
public void Send(string message)
{
byte[] data = Encoding.UTF8.GetBytes("CHAT" + message);

int i = client.SendTo(data, this.remoteEndPoint);
}

#endregion

}
复制代码
socket的client代码 实现广播 发送信息

以上简单的逻辑设计+代码就基本完成了这个简单的客户端聊天软件

说了那么多,接下来看下效果图:

本机这边的效果

局域网中另一端的效果

可以实现简单的 通讯

下面是源码下载:猛击我去下载它

大家在下载包中可以发现 有两个项目 一个是ITalk,他是我最初在写的时候使用的,窗体时继承dev的,效果稍微好一点

为考虑到各大读者可能没有安装dev,所以又一模一样的改了一个ITalkTradition,传统的winform

用c#写的一个局域网聊天客户端 类似小飞鸽的更多相关文章

  1. 搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 (1)

    搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 原文地址(英文):http://www.networkcomms.net/creating ...

  2. nodeJS+express+Jade写一个局域网聊天应用(node基础)

    为了复习一下nodeJS, 而且socketIO这东西听起来就好高端有木有, 而且有人写过了open, 也可以作为自己的参考有木有, 点击下载源代码: express是4.x的版本, 跟以前的配置有些 ...

  3. 用Socket做一个局域网聊天工具(转)

    原文:http://www.cnblogs.com/technology/archive/2010/08/15/1799858.html 程序设计成为简单的服务端和客户端之间的通信, 但通过一些方法可 ...

  4. 写了一个常规性生成merge 的小脚本

    现在使用数据库来写存储过程,动不动参数就会用到xml ,当然罗,优势也很明显,参数相对固定,而且灵活,如果要修改或者什么的,中间接口层也不需要做变化,只需要修改封装的存储过程以及程序传参就ok了. 随 ...

  5. 初学socket,c语言写的简单局域网聊天

    在客户端所在的目录新建一个IP.bwj的文件,写上服务端的IP,不要带空格,保存.双方都打开一个客户端和一个服务端就可以聊天了,(可以写自己的IP,自己跟自己聊..)没有第三方服务器,服务端所在的电脑 ...

  6. 【局域网聊天客户端篇】基于socket与Qt

    前言 暑假把linux下的高级编程和网络编程学习了一遍,学习很重要,但是也得有个练手的地方,所以必须做做项目来认识下自己所学习的知识. 能够找到小伙伴一起做项目也是一件很快乐的事情的,很幸运的有两个小 ...

  7. 写了一个web使用向导的小插件

    运行效果: 引入插件: <link rel="stylesheet" href="ez-guide.css"> <script src=&qu ...

  8. 闲着无聊时写的一个调用天气 API 的小 Demo

    分为两个部分--调用以及实现,并且由于不想折腾,直接使用了 Console 来调用. 通过firefox直接调用 Main 入口,调用以及输出 调用部分没什么好说的,主要是针对 dynamic 类型的 ...

  9. 用Go语言写了一个电脑搜索文件的小东西

    package main import ( "bytes" "fmt" "os" "os/exec" "pat ...

随机推荐

  1. Navi.Soft30.开放平台.腾讯.开发手册

    1系统简介 1.1功能简述 现在是一个信息时代,并且正在高速发展.以前获取信息的途径非常少,可能只有电视台,收音机等有限的来源,而现在的途径数不胜数,如:QQ,微信,官方网站,个人网站等等 本开发手册 ...

  2. ubuntu 下截图与快捷键设置

    1. 安装 这里使用的 kubuntu sudo apt-get install ksnapshot 2. 设置快捷键 点击左下角的开始菜单,选择`应用程序` `设置` `系统设置` ksnapsho ...

  3. ooofc.com域名备案问题导致无法正常访问临时解决方案

    各位尊敬的easyradius用户: 由于临时收到ooofc.coom域名备案被删除的消息,之后ooofc.com就无法访问.导致用户无法访问控制台oa.ooofc.com,及用户中心user.ooo ...

  4. Android软件安全开发实践(下)

    Android开发是当前最火的话题之一,但很少有人讨论这个领域的安全问题.本系列将分两期,探讨Android开发中常见的安全隐患和解决方案.第一期将从数据存储.网络通信.密码和认证策略这三个角度,带你 ...

  5. Intent用法简介

    Intent作为联系各Activity之间的纽带,其作用并不仅仅只限于简单的数据传递.通过其自带的属性,其实可以方便的完成很多较为复杂的操作.例如直接调用拨号功能.直接自动调用合适的程序打开不同类型的 ...

  6. Spring3系列4-多个配置文件的整合

    Spring3系列4-多个配置文件的整合 在大型的Spring3项目中,所有的Bean配置在一个配置文件中不易管理,也不利于团队开发,通常在开发过程中,我们会按照功能模块的不同,或者开发人员的不同,将 ...

  7. Android复制assets目录下的图片到内存

    转自:http://www.chenwg.com/android/android%E5%A4%8D%E5%88%B6assets%E7%9B%AE%E5%BD%95%E4%B8%8B%E7%9A%84 ...

  8. IE11 Enterprise Mode

    对IE11引入的Enterprise Mode进行了一些总结,对查阅的一些参考资料直接引用了英文,需要注意的地方用中文进行了一些注解.供大家参考. 1. The purpose of introduc ...

  9. string-->wstring-->string

    std::string src("三毛三毛三毛三毛三三三三流浪记"); size_t size = mbstowcs(NULL,src.c_str(),0); std::wstri ...

  10. android自定义view仿照MIUI中音量控制效果

    先看效果图: 这就是miui中的音量效果图,实现思路是自定义视图,绘制圆环,然后设置进度显示. 核心代码在onDraw中实现如下: @Override protected void onDraw(Ca ...