1、总结 15基于TCP协议的C#聊天程序前言随着互联网技术的飞速发展,基于网络的即时通信技术也给人们带来了诸多便利,人们也慢慢体会到了网上聊天的乐趣与无拘束的感觉。聊天工具作为当今使用最为广泛的即时通信工具之一,可以方便的同网络上的好友在线交流。在中国,最流行的莫过于腾讯公司的QQ,伴随着技术的不断升级,腾讯公司也为我们带来了越来越多的精彩的服务。这里我将利用Socket编程技术模拟QQ聊天功能,实现一个简单的在线聊天室。需求分析 1.1编写目的 编写该软件能够对自己所学的东西进行一次系统的回顾,加深对TCP协议的理解以及提升自己实际开发的能力。 1.2开发环境 操作系统:windows xp
2、sp3 内存:2G CPU:AMD Athlon(tm) 64 X2 Dual Core Processor 5200+ 2.71GHz 编程软件:Microsoft Visual Studio 2010 1.3功能介绍 该程序是利用c#语言编写的一个基于Socket的简单聊天软件,最要实现了用户登录,但登录时只需要提供用户名,不需要输入密码。具有私聊和群聊两种聊天模式,即允许多人在线聊天,并且在线用户聊天时,可以将消息发送给一个用户,亦可以将消息发送给所有人。聊天的消息内容包括:用户名称、发送时间、发送正文、以及消息模式。断开连接的同时会关闭客户端,此时用户若希望聊天,需要再次登录服务器。
3、1.4Socket通信机制 Socket编程是建立在应用层TCP/IP协议之上的。目前最流行的是客户机/服务器模式,在面向连接的 Client/Server 模型中,Server 端的 socket 总是等待一个 Client 端的请求。客户机/服务器模型的工作流程图如下图所示:服务器程序特点:1 一般启动后就一直处于运行状态,以等待客户机进程的请求;2 使用的端口往往是熟知端口,便于客户机进程连接请求;3 一般拥有较多的系统资源,以便及时响应各个客户机进程的请求;4 可以并行处理多个客户机进程的请求,但数目是有一定的限制;5 在通信时一般处于被动的一方,不需要知道客户机的IP地址和端口信息。
4、客户机程序的特点: 在需要服务器进程的服务时将向服务器进程请求服务,并建立通信连接,得到满足并完成处理后就终止通信连接; 使用向系统申请的临时端口与服务器进程进行通信,通信完成后将释放该端口; 拥有相对较少的系统资源; 在通信时属于主动的一方,需要事先知道服务器的IP地址和端口信息客户机与服务器模式又分为两大类:面向连接的交互(TCP)和面向无连接的交互(UDP),本程序是面向连接的交互,交互机制如下图:代码实现2.1服务器端主要代码负责监听客户端请求,并根据客户端命令执行不同操作的Listener类,Listener.csusing System;using System.Collectio
5、ns.Generic;using System.Linq;using System.Text;using System.Net.Sockets;using System.Net;using System.Threading;using System.Collections.Specialized;using System.Runtime.Serialization;using System.IO;using System.Runtime.Serialization.Formatters.Binary;namespace TalkerServer class Listener public de
6、legate void ShowMsg(ListenWindow lisWin, string svrInfo);/svrInfo要显示的服务器信息,lisWin显示的窗口对象 private ListenWindow lisWin = null;/服务器监听窗口对象 private ShowMsg smsg = null;/消息显示委托对象 private int listenPort = 8888;/监听端口,默认8888 private const int maxPacket = 64 * 1024;/缓冲区大小 private Dictionary userMap = new Dict
7、ionary();/所有登陆服务器的用户map private TcpListener tcpListener = null;/侦听器 public int ListenPort get return this.listenPort; set this.listenPort = value; public TcpListener TcpListener get return this.tcpListener; set this.tcpListener = value; public Dictionary UserMap get return this.userMap; set this.use
8、rMap = value; public void Listen(ShowMsg smsg,string svrInfo,ListenWindow lisWin) this.lisWin = lisWin; this.smsg = smsg; string connInfo = string.Empty; IPAddress ipAddr = Dns.GetHostAddresses(Dns.GetHostName()0; tcpListener = new TcpListener(ipAddr,listenPort); tcpListener.Start(); connInfo += 服务器
9、已经启动,正在监听客户端的连接rn; smsg(lisWin, connInfo); while (true) byte packetBuff = new bytemaxPacket; Socket client = tcpListener.AcceptSocket(); client.Receive(packetBuff); string userName = Encoding.Unicode.GetString(packetBuff).TrimEnd(0); if (userMap.ContainsKey(userName) & userMap.Count != 0) client.Sen
10、d(Encoding.Unicode.GetBytes(0);/该用户存在 else1/该用户不存在 userMap.Add(userName,client);/保存客户端到Map中 string svrlog = string.Format(系统消息新用户【0】在【1】已连接. 当前在线人数:【2】rnrn, userName, DateTime.Now, userMap.Count); smsg(lisWin, svrlog);/利用委托更新服务器显示日志 Thread clientThread = new Thread(new ParameterizedThreadStart(Threa
11、dFunc);/开启一个子线程执行程序 clientThread.Start(userName); foreach(KeyValuePair user in userMap) string uName = user.Key as string; Socket clientSkt = user.Value as Socket; if (!uName.Equals(userName) clientSkt.Send(Encoding.Unicode.GetBytes(svrlog); /序列化在线用户列表 private byte SerializeOnlineUserList(Object obj
12、) StringCollection onlineUserList = new StringCollection(); foreach (object o in userMap.Keys) if (o != obj)/序列化的在线列表中不包含自身登陆用户 onlineUserList.Add(o as string);/转换语句 IFormatter format = new BinaryFormatter();/以二进制格式将对象或整个连接对 MemoryStream stream = new MemoryStream(); format.Serialize(stream, onlineUs
13、erList);/保持到内存流 byte ret = stream.ToArray(); stream.Close(); return ret; public void ThreadFunc(Object obj) Socket client = null; string userName = (string)obj; if (userMap.TryGetValue(userName, out client) if (client != null) try byte _cmdBuff = new byte128; client.Receive(_cmdBuff);/第一次接收命令,第二次接收内
14、容 string _cmd = string.Concat(_cmdBuff0.ToString(), _cmdBuff1.ToString(); /00对所有人,01请求用户列表,02断开连接 if (_cmd = 00) byte _msgBuff = new bytemaxPacket; client.Receive(_msgBuff); foreach (KeyValuePair clientSkt.Send(_msgBuff); else if (_cmd = 01)/请求用户列表 byte onlineBuff = SerializeOnlineUserList(obj); /先发
15、送响应信号,用于客户机的判断,11表示服务发给客户机的更新在线列表的命令 client.Send(new byte 1, 1 ); client.Send(onlineBuff);02)/与服务器断开连接 userMap.Remove(userName);系统消息用户【0】在【1】已断开. 当前在线人数:/给其他用户通知现在上线的用户 Thread.CurrentThread.Abort();/终止为该客户端开启的线程 else/发送给指定用户 string _receiver = Encoding.Unicode.GetString(_cmdBuff).TrimEnd( byte _pack
16、etBuff = new bytemaxPacket; client.Receive(_packetBuff); if (userMap.ContainsKey(_receiver)/是否存在该接收者 /通过转发表查找接收方的套接字 Socket receiverSkt = userMap_receiver as Socket; receiverSkt.Send(_packetBuff); string sysMessage = string.Format(系统消息您刚才的内容没有发送成功。rn可能原因:用户【0】已离线或者网络阻塞。rnrn, _receiver); client.Send(
17、Encoding.Unicode.GetBytes(sysMessage); catch (SocketException)系统消息用户【0】的客户端在【1】意外终止!当前在线人数【2】rnrn /向所有客户机发送系统消息负责显示客户端连接信息的服务器窗口的后台处理代码:using System.ComponentModel;using System.Data;using System.Drawing;using System.Windows.Forms; public partial class ListenWindow : Form private Listener listener =
18、null; private Thread listenThread = null; delegate void SetTextCallback(ListenWindow lisWin,string text); public ListenWindow() InitializeComponent(); listener = new Listener(); private void btnListen_Click(object sender, EventArgs e) this.btnListen.Enabled = false; int port = this.listener.ListenPo
19、rt ; if (int.TryParse(txtEndpoint.Text, out port) if (port 65535) MessageBox.Show(端口号不合法!, 提示, MessageBoxButtons.OK, MessageBoxIcon.Information); return; this.listenThread = new Thread(new ThreadStart(this.ThreadProcSafe); this.listenThread.Start();,MessageBoxButtons.OK,MessageBoxIcon.Information);
20、public void showSvgMsg(ListenWindow lisWin ,string svrInfo) lisWin.txtConnInfo.Text = svrInfo; #region / / 1:访问 Windows 窗体控件本质上不是线程安全的。 / 2:如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。 / 3:如果从创建控件的线程之外的其他线程试图更改控件的状态,一半情况下是不行的 / 4:以事件句柄创建一个以线程安全方式调用windows窗体控件的线程。/summary public void ThreadProcSafe()/线程安
21、全访问控件 this.listener.Listen(SetText,this); private void SetText(ListenWindow lisWin,string text) if (lisWin.txtConnInfo.InvokeRequired) SetTextCallback d = new SetTextCallback(SetText); lisWin.Invoke(d, new object this,text ); lisWin.txtConnInfo.AppendText(text); #endregion private void btnStopListen
22、_Click(object sender, EventArgs e) if (listener ! if (listener.TcpListener ! listener.TcpListener.Stop(); if (listener.UserMap.Count ! foreach (Socket socket in listener.UserMap.Values) socket.Shutdown(SocketShutdown.Both); listener.UserMap.Clear(); listener.UserMap = null; listener = null; this.btn
23、Listen.Enabled = true;2.2客户端主要代码客户端聊天窗口后台代码:namespace TalkClient public partial class TalkWindow : private string _username = null;/登陆用户名 private int maxPacket = 2048;/2K的缓冲区 private Thread receiveThread = null;/用于接收消息 private NetworkStream _nws = null;/网络数据流 delegate void SetTextCallback(TalkWindow talkWin, string text); / 处理接收到的消息 private void MsgHandle() while (true)/无限循环,不断接收消息 byte packet = new bytemaxPacket; _nws.Read(packet, 0, pa
copyright@ 2008-2023 冰点文库 网站版权所有
经营许可证编号:鄂ICP备19020893号-2