c#聊天室程序Word下载.docx
《c#聊天室程序Word下载.docx》由会员分享,可在线阅读,更多相关《c#聊天室程序Word下载.docx(22页珍藏版)》请在冰点文库上搜索。
总结15
基于TCP协议的C#聊天程序
前言
随着互联网技术的飞速发展,基于网络的即时通信技术也给人们带来了诸多便利,人们也慢慢体会到了网上聊天的乐趣与无拘束的感觉。
聊天工具作为当今使用最为广泛的即时通信工具之一,可以方便的同网络上的好友在线交流。
在中国,最流行的莫过于腾讯公司的QQ,伴随着技术的不断升级,腾讯公司也为我们带来了越来越多的精彩的服务。
这里我将利用Socket编程技术模拟QQ聊天功能,实现一个简单的在线聊天室。
需求分析
1.1编写目的
编写该软件能够对自己所学的东西进行一次系统的回顾,加深对TCP协议的理解以及提升自己实际开发的能力。
1.2开发环境
操作系统:
windowsxpsp3
内存:
2G
CPU:
AMDAthlon(tm)64X2DualCoreProcessor5200+2.71GHz
编程软件:
MicrosoftVisualStudio2010
1.3功能介绍
该程序是利用c#语言编写的一个基于Socket的简单聊天软件,最要实现了用户登录,但登录时只需要提供用户名,不需要输入密码。
具有私聊和群聊两种聊天模式,即允许多人在线聊天,并且在线用户聊天时,可以将消息发送给一个用户,亦可以将消息发送给所有人。
聊天的消息内容包括:
用户名称、发送时间、发送正文、以及消息模式。
断开连接的同时会关闭客户端,此时用户若希望聊天,需要再次登录服务器。
1.4Socket通信机制
Socket编程是建立在应用层TCP/IP协议之上的。
目前最流行的是客户机/服务器模式,在面向连接的Client/Server模型中,Server端的socket总是等待一个Client端的请求。
客户机/服务器模型的工作流程图如下图所示:
服务器程序特点:
1一般启动后就一直处于运行状态,以等待客户机进程的请求;
2使用的端口往往是熟知端口,便于客户机进程连接请求;
3一般拥有较多的系统资源,以便及时响应各个客户机进程的请求;
4可以并行处理多个客户机进程的请求,但数目是有一定的限制;
5在通信时一般处于被动的一方,不需要知道客户机的IP地址和端口信息。
客户机程序的特点:
①在需要服务器进程的服务时将向服务器进程请求服务,并建立通信连接,得到满足并完成处理后就终止通信连接;
②使用向系统申请的临时端口与服务器进程进行通信,通信完成后将释放该端口;
③拥有相对较少的系统资源;
④在通信时属于主动的一方,需要事先知道服务器的IP地址和端口信息
客户机与服务器模式又分为两大类:
面向连接的交互(TCP)和面向无连接的交互(UDP),本程序是面向连接的交互,交互机制如下图:
代码实现
2.1服务器端主要代码
负责监听客户端请求,并根据客户端命令执行不同操作的Listener类,Listener.cs
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;
usingSystem.Net.Sockets;
usingSystem.Net;
usingSystem.Threading;
usingSystem.Collections.Specialized;
usingSystem.Runtime.Serialization;
usingSystem.IO;
usingSystem.Runtime.Serialization.Formatters.Binary;
namespaceTalkerServer
{
classListener
{
publicdelegatevoidShowMsg(ListenWindowlisWin,stringsvrInfo);
//svrInfo要显示的服务器信息,lisWin显示的窗口对象
privateListenWindowlisWin=null;
//服务器监听窗口对象
privateShowMsgsmsg=null;
//消息显示委托对象
privateintlistenPort=8888;
//监听端口,默认8888
privateconstintmaxPacket=64*1024;
//缓冲区大小
privateDictionary<
string,Socket>
userMap=newDictionary<
();
//所有登陆服务器的用户map
privateTcpListenertcpListener=null;
//侦听器
publicintListenPort
get{returnthis.listenPort;
}
set{this.listenPort=value;
}
publicTcpListenerTcpListener
get{returnthis.tcpListener;
set{this.tcpListener=value;
publicDictionary<
string,Socket>
UserMap
get{returnthis.userMap;
set{this.userMap=value;
publicvoidListen(ShowMsgsmsg,stringsvrInfo,ListenWindowlisWin)
this.lisWin=lisWin;
this.smsg=smsg;
stringconnInfo=string.Empty;
IPAddressipAddr=Dns.GetHostAddresses(Dns.GetHostName())[0];
tcpListener=newTcpListener(ipAddr,listenPort);
tcpListener.Start();
connInfo+="
服务器已经启动,正在监听客户端的连接……\r\n"
;
smsg(lisWin,connInfo);
while(true)
byte[]packetBuff=newbyte[maxPacket];
Socketclient=tcpListener.AcceptSocket();
client.Receive(packetBuff);
stringuserName=Encoding.Unicode.GetString(packetBuff).TrimEnd('
\0'
);
if(userMap.ContainsKey(userName)&
&
userMap.Count!
=0)
client.Send(Encoding.Unicode.GetBytes("
0"
));
//该用户存在
else
1"
//该用户不存在
userMap.Add(userName,client);
//保存客户端到Map中
stringsvrlog=string.Format("
[系统消息]新用户【{0}】在【{1}】已连接...当前在线人数:
【{2}】\r\n\r\n"
userName,DateTime.Now,userMap.Count);
smsg(lisWin,svrlog);
//利用委托更新服务器显示日志
ThreadclientThread=newThread(newParameterizedThreadStart(ThreadFunc));
//开启一个子线程执行程序
clientThread.Start(userName);
foreach(KeyValuePair<
userinuserMap)
stringuName=user.Keyasstring;
SocketclientSkt=user.ValueasSocket;
if(!
uName.Equals(userName))
clientSkt.Send(Encoding.Unicode.GetBytes(svrlog));
//序列化在线用户列表
privatebyte[]SerializeOnlineUserList(Objectobj)
StringCollectiononlineUserList=newStringCollection();
foreach(objectoinuserMap.Keys)
if(o!
=obj)//序列化的在线列表中不包含自身登陆用户
onlineUserList.Add(oasstring);
//转换语句
IFormatterformat=newBinaryFormatter();
//以二进制格式将对象或整个连接对
MemoryStreamstream=newMemoryStream();
format.Serialize(stream,onlineUserList);
//保持到内存流
byte[]ret=stream.ToArray();
stream.Close();
returnret;
publicvoidThreadFunc(Objectobj)
Socketclient=null;
stringuserName=(string)obj;
if(userMap.TryGetValue(userName,outclient))
if(client!
=null)
try
byte[]_cmdBuff=newbyte[128];
client.Receive(_cmdBuff);
//第一次接收命令,第二次接收内容
string_cmd=string.Concat(_cmdBuff[0].ToString(),_cmdBuff[1].ToString());
//00对所有人,01请求用户列表,02断开连接
if(_cmd=="
00"
)
byte[]_msgBuff=newbyte[maxPacket];
client.Receive(_msgBuff);
foreach(KeyValuePair<
clientSkt.Send(_msgBuff);
elseif(_cmd=="
01"
)//请求用户列表
byte[]onlineBuff=SerializeOnlineUserList(obj);
//先发送响应信号,用于客户机的判断,"
11"
表示服务发给客户机的更新在线列表的命令
client.Send(newbyte[]{1,1});
client.Send(onlineBuff);
02"
)//与服务器断开连接
userMap.Remove(userName);
[系统消息]用户【{0}】在【{1}】已断开...当前在线人数:
//给其他用户通知现在上线的用户
Thread.CurrentThread.Abort();
//终止为该客户端开启的线程
else//发送给指定用户
string_receiver=Encoding.Unicode.GetString(_cmdBuff).TrimEnd('
byte[]_packetBuff=newbyte[maxPacket];
client.Receive(_packetBuff);
if(userMap.ContainsKey(_receiver))//是否存在该接收者
//通过转发表查找接收方的套接字
SocketreceiverSkt=userMap[_receiver]asSocket;
receiverSkt.Send(_packetBuff);
stringsysMessage=string.Format("
[系统消息]您刚才的内容没有发送成功。
\r\n可能原因:
用户【{0}】已离线或者网络阻塞。
\r\n\r\n"
_receiver);
client.Send(Encoding.Unicode.GetBytes(sysMessage));
catch(SocketException)
[系统消息]用户【{0}】的客户端在【{1}】意外终止!
当前在线人数【{2}】\r\n\r\n"
//向所有客户机发送系统消息
负责显示客户端连接信息的服务器窗口的后台处理代码:
usingSystem.ComponentModel;
usingSystem.Data;
usingSystem.Drawing;
usingSystem.Windows.Forms;
{
publicpartialclassListenWindow:
Form
privateListenerlistener=null;
privateThreadlistenThread=null;
delegatevoidSetTextCallback(ListenWindowlisWin,stringtext);
publicListenWindow()
InitializeComponent();
listener=newListener();
privatevoidbtnListen_Click(objectsender,EventArgse)
this.btnListen.Enabled=false;
intport=this.listener.ListenPort;
if(int.TryParse(txtEndpoint.Text,outport))
if(port<
1024||port>
65535)
MessageBox.Show("
端口号不合法!
"
"
提示"
MessageBoxButtons.OK,MessageBoxIcon.Information);
return;
this.listenThread=newThread(newThreadStart(this.ThreadProcSafe));
this.listenThread.Start();
"
MessageBoxButtons.OK,MessageBoxIcon.Information);
publicvoidshowSvgMsg(ListenWindowlisWin,stringsvrInfo)
lisWin.txtConnInfo.Text=svrInfo;
#region
///<
summary>
///1:
访问Windows窗体控件本质上不是线程安全的。
///2:
如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。
///3:
如果从创建控件的线程之外的其他线程试图更改控件的状态,一半情况下是不行的
///4:
以事件句柄创建一个以线程安全方式调用windows窗体控件的线程。
/summary>
publicvoidThreadProcSafe()//线程安全访问控件
this.listener.Listen(SetText,"
this);
privatevoidSetText(ListenWindowlisWin,stringtext)
if(lisWin.txtConnInfo.InvokeRequired)
SetTextCallbackd=newSetTextCallback(SetText);
lisWin.Invoke(d,newobject[]{this,text});
lisWin.txtConnInfo.AppendText(text);
#endregion
privatevoidbtnStopListen_Click(objectsender,EventArgse)
if(listener!
if(listener.TcpListener!
listener.TcpListener.Stop();
if(listener.UserMap.Count!
foreach(Socketsocketinlistener.UserMap.Values)
socket.Shutdown(SocketShutdown.Both);
listener.UserMap.Clear();
listener.UserMap=null;
listener=null;
this.btnListen.Enabled=true;
2.2客户端主要代码
客户端聊天窗口后台代码:
namespaceTalkClient
publicpartialclassTalkWindow:
privatestring_username=null;
//登陆用户名
privateintmaxPacket=2048;
//2K的缓冲区
privateThreadreceiveThread=null;
//用于接收消息
privateNetworkStream_nws=null;
//网络数据流
delegatevoidSetTextCallback(TalkWindowtalkWin,stringtext);
///处理接收到的消息
privatevoidMsgHandle()
while(true)//无限循环,不断接收消息
byte[]packet=newbyte[maxPacket];
_nws.Read(packet,0,pa