计算机学院课程设计
    业:    08网络工程           
课程名称:    TCP/IP协议分析实验     
课题名称:   基于TCP协议的C#聊天程序 
    :         桂学勤           
    : 084221019 
    间:    2010     6    18
目录
一:需求分析    2
1.1编写目的    2
1.2开发环境    2
1.3功能介绍    2
1.4Socket通信机制    2
二:代码实现    3
2.1服务器端主要代码    3
2.2客户端主要代码    10
三:程序运行界面    14
3.1登录界面    14
3.2登录成功后的聊天界面    14
3.3服务器端显示界面    15
四:总结    15
基于TCP协议的C#聊天程序
前言
随着互联网技术的飞速发展,基于网络的即时通信技术也给人们带来了诸多便利,人们也慢慢体会到了网上聊天的乐趣与无拘束的感觉。聊天工具作为当今使用最为广泛的即时通信工具之一,可以方便的同网络上的好友在线交流。在中国,最流行的莫过于腾讯公司的QQ,伴随着技术的不断升级,腾讯公司也为我们带来了越来越多的精彩的服务。这里我将利用Socket编程技术模拟QQ聊天功能,实现一个简单的室。
一:需求分析
    1.1编写目的
    编写该软件能够对自己所学的东西进行一次系统的回顾,加深对TCP协议的理解以及提升自己实际开发的能力。
    1.2开发环境
    操作系统:windows xp sp3
    内存:2G
    CPU:AMD Athlon(tm) 64 X2 Dual Core Processor 5200+ 2.71GHz
    编程软件:Microsoft Visual Studio 2010
    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
using System;
using System.Collections.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 delegate 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<string,Socket> userMap = new Dictionary<string,Socket>();//所有登陆服务器的用户map
        private TcpListener tcpListener = null;//侦听器
        public int ListenPort
        {
            get { return this.listenPort;}
            set { this.listenPort = value; }
        }
        public TcpListener TcpListener
        {
            get { pListener; }
            set { pListener = value; }
        }
        public Dictionary<string, Socket> UserMap
        {
            get { return this.userMap; }
            set { this.userMap = 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 += "服务器已经启动,正在监听客户端的连接……\r\n";
            smsg(lisWin, connInfo);
            while (true)
            {
                byte[] packetBuff = new byte[maxPacket];
                Socket client = tcpListener.AcceptSocket();
                client.Receive(packetBuff);
                string userName = Encoding.Unicode.GetString(packetBuff).TrimEnd('\0');
                if (userMap.ContainsKey(userName) && userMap.Count != 0)
                {
                    client.Send(Encoding.Unicode.GetBytes("0"));//该用户存在
                }
                else
                {
                    client.Send(Encoding.Unicode.GetBytes("1"));//该用户不存在
                }
                userMap.Add(userName,client);//保存客户端到Map
                string svrlog = string.Format("[系统消息]新用户【{0}】在【{1}】已连接... 当前在线人数:{2}\r\n\r\n", userName, DateTime.Now, userMap.Count);
                smsg(lisWin, svrlog);//利用委托更新服务器显示日志
                Thread clientThread = new Thread(new ParameterizedThreadStart(ThreadFunc));//开启一个子线程执行程序
                clientThread.Start(userName);
                foreach(KeyValuePair<string,Socket> 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)
        {
            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, onlineUserList);//保持到内存流
            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)
                {
                    while (true)
                    {
                        try
                        {
                            byte[] _cmdBuff = new byte[128];
                            client.Receive(_cmdBuff);//第一次接收命令,第二次接收内容
                            string _cmd = string.Concat(_cmdBuff[0].ToString(), _cmdBuff[1].ToString());
                            //00对所有人,01请求用户列表,02断开连接
                            if (_cmd == "00")
                            {
                                byte[] _msgBuff = new byte[maxPacket];
                                client.Receive(_msgBuff);
                                foreach (KeyValuePair<string, Socket> user in userMap)
                                {
                                    string uName = user.Key as string;
                                    Socket clientSkt = user.Value as Socket;
                                    if (!uName.Equals(userName))
                                    {
                                        clientSkt.Send(_msgBuff);
                                    }
                                }
                            }
                            else if (_cmd == "01")//请求用户列表
                            {
                                byte[] onlineBuff = SerializeOnlineUserList(obj);
                                //先发送响应信号,用于客户机的判断,"11"表示服务发给客户机的更新在线列表的命令
                                client.Send(new byte[] { 1, 1 });
                                client.Send(onlineBuff);
                            }
                            else if (_cmd == "02")//与服务器断开连接
                            {
                                userMap.Remove(userName);
                                string svrlog = string.Format("[系统消息]用户【{0}】在【{1}】已断开... 当前在线人数:{2}\r\n\r\n", userName, DateTime.Now, userMap.Count);
                                foreach (KeyValuePair<string, Socket> user in userMap)
                                {
                                    string uName = user.Key as string;
                                    Socket clientSkt = user.Value as Socket;
                                    clientSkt.Send(Encoding.Unicode.GetBytes(svrlog));//给其他用户通知现在上线的用户
                                }
                                smsg(lisWin, svrlog);//利用委托更新服务器显示日志
                                Thread.CurrentThread.Abort();//终止为该客户端开启的线程
                            }
                            else//发送给指定用户
                            {
                                string _receiver = Encoding.Unicode.GetString(_cmdBuff).TrimEnd('\0');
                                byte[] _packetBuff = new byte[maxPacket];
                                client.Receive(_packetBuff);
                                if (userMap.ContainsKey(_receiver))//是否存在该接收者
                                {
                                    //通过转发表查接收方的套接字
                                    Socket receiverSkt = userMap[_receiver] as Socket;
                                    receiverSkt.Send(_packetBuff);
                                }
                                else
                                {
                                    string sysMessage = string.Format("[系统消息]您刚才的内容没有发送成功。\r\n可能原因:用户【{0}】已离线或者网络阻塞。\r\n\r\n", _receiver);
                                    client.Send(Encoding.Unicode.GetBytes(sysMessage));
                                }
                            }
                        }
                        catch (SocketException)
                        {
                            string svrlog = string.Format("[系统消息]用户【{0}】的客户端在【{1}】意外终止!当前在线人数【{2}\r\n\r\n", userName, DateTime.Now, userMap.Count);
                            smsg(lisWin, svrlog);//利用委托更新服务器显示日志
                            //向所有客户机发送系统消息
                            foreach (KeyValuePair<string, Socket> user in userMap)
                            {
                                string uName = user.Key as string;
                                Socket clientSkt = user.Value as Socket;
                                clientSkt.Send(Encoding.Unicode.GetBytes(svrlog));
                            }
                            Thread.CurrentThread.Abort();
                        }
                    }
                }
            }
        }
    }
}
负责显示客户端连接信息的服务器窗口的后台处理代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Net.Sockets;
namespace TalkerServer
{
    public partial class ListenWindow : Form
    {
        private Listener listener = 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.ListenPort ;
            if (int.TryParse(txtEndpoint.Text, out port))
            {
                if (port < 1024 || port > 65535)
                {
                    MessageBox.Show("端口号不合法!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    return;
                }
                this.listenThread = new Thread(new ThreadStart(this.ThreadProcSafe));
                this.listenThread.Start();
            }
            else
            {
                MessageBox.Show("端口号不合法!","提示",MessageBoxButtons.OK,MessageBoxIcon.Information);
                return;
            }
        }
        public void showSvgMsg(ListenWindow lisWin ,string svrInfo)
        {
            ConnInfo.Text = svrInfo;
        }
        #region
        /// <summary>
        /// 1:访问 Windows 窗体控件本质上不是线程安全的。
        /// 2:如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。
        /// 3:如果从创建控件的线程之外的其他线程试图更改控件的状态,一半情况下是不行的
        /// 4:以事件句柄创建一个以线程安全方式调用windows窗体控件的线程。
        /// </summary>
        public void ThreadProcSafe()//线程安全访问控件
        {
            this.listener.Listen(SetText,"",this);
        }
        private void SetText(ListenWindow lisWin,string text)
        {
            if (ConnInfo.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetText);
                lisWin.Invoke(d, new object[] { this,text });
            }
            else
            {
                ConnInfo.AppendText(text);
            }
        }
        #endregion
        private void btnStopListen_Click(object sender, EventArgs e)
        {
            if (listener != null)
            {
                if (listener.TcpListener != null)
                {
                    listener.TcpListener.Stop();
                }
                if (listener.UserMap.Count != 0)
                {
                    foreach (Socket socket in listener.UserMap.Values)
                    {
                        socket.Shutdown(SocketShutdown.Both);
                    }
                    listener.UserMap.Clear();
                    listener.UserMap = null;
                }
                listener = null;
                this.btnListen.Enabled = true;
            }
        }
    }
}
2.2客户端主要代码
客户端聊天窗口后台代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Threading;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Collections.Specialized;
namespace TalkClient
{
    public partial class TalkWindow : Form
    {
        private string _username = null;//登陆用户名
        private int maxPacket = 2048;//2K的缓冲区
        private Thread receiveThread = null;//用于接收消息
        private NetworkStream _nws = null;//网络数据流
        delegate void SetTextCallback(TalkWindow talkWin, string text);
        /// <summary>
        /// 处理接收到的消息
        /// </summary>
        private void MsgHandle()
        {
            while (true)//无限循环,不断接收消息
            {
                byte[] packet = new byte[maxPacket];
                _nws.Read(packet, 0, packet.Length);
                string msg = string.Concat(packet[0].ToString(), packet[1].ToString());
                if (msg == "11")//更新在线用户列表
                {
                    byte[] onlineBuff = new byte[maxPacket];
                    int byteCnt = _nws.Read(onlineBuff, 0, onlineBuff.Length);
                    IFormatter format = new BinaryFormatter();
                    MemoryStream stream = new MemoryStream();
unicode在线工具                    stream.Write(onlineBuff, 0, byteCnt);
                    stream.Position = 0;
                    StringCollection onlineList = (StringCollection)format.Deserialize(stream);
                    drpReceiver.Items.Clear();
                    foreach (string onliner in onlineList)
                    {
                        if (!onliner.Equals(_username))
                        {
                            drpReceiver.Items.Add(onliner);
                        }
                    }
                }
                else
                {
                    string displaytxt = Encoding.Unicode.GetString(packet);
                    SetText(this,displaytxt);//添加到聊天内容中
                }
            }
        }
        /// <summary>
        /// 从非创建该控件的线程访问该控件,并且修改其Text属性
        /// </summary>
        /// <param name="talkWin"></param>
        /// <param name="text"></param>
        public void SetText(TalkWindow talkWin, string text)
        {
            if (AllMsg.InvokeRequired)
            {
                SetTextCallback stc = new SetTextCallback(SetText);
                talkWin.Invoke(stc, new object[] { this,text });
            }
            else
            {
                AllMsg.AppendText(text);
            }
        }
        private void btnSend_Click(object sender, EventArgs e)
        {
            string localTxt = "";//本地显示消息
            string remoteTxt = "";//其它用户显示的消息
            string msg = txtSelfMsg.Text.Trim();//本人将要发送的消息
            if (msg == string.Empty)
            {
                MessageBox.Show("不能发送空消息","提示",MessageBoxButtons.OK,MessageBoxIcon.Information);
                return;
            }
            //判断选择的聊天模式
            if (drpTalkModem.SelectedIndex == 0)//悄悄话模式
            {
                string receiver = drpReceiver.Text;
                if (receiver == string.Empty)
                {
                    MessageBox.Show("请选择发送悄悄话的人,若列表中为空,表明当前只有你一人在线!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    return;
                }
                else
                {
                    localTxt = string.Format("[私聊]您在 {0} {1} 说:\r\n{2}\r\n\r\n", DateTime.Now, receiver, msg);
                    remoteTxt = string.Format("[私聊]{0} {1} 对您说:\r\n{2}\r\n\r\n", _username, DateTime.Now, msg);
                    _nws.Write(Encoding.Unicode.GetBytes(receiver), 0, Encoding.Unicode.GetBytes(receiver).Length);
                }
            }
            else
            {
                localTxt = string.Format("[]您在 {0} 对所有人说:\r\n{1}\r\n\r\n", DateTime.Now, msg);
                remoteTxt = string.Format("[]{0} {1} 对所有人说:\r\n{2}\r\n\r\n", _username, DateTime.Now, msg);
                _nws.Write(new byte[] { 0, 0 }, 0, 2);
            }
            _nws.Write(Encoding.Unicode.GetBytes(remoteTxt), 0, Encoding.Unicode.GetBytes(remoteTxt).Length);
            txtAllMsg.AppendText(localTxt);//添加发送的消息到聊天记录里
            txtSelfMsg.Clear();//清空刚编辑发送的消息
        }
        /// <summary>
        /// 聊天模式,选择悄悄话,左边的在线用户列表可用,可以选择里面的任意用户进行聊天
        /// 选择所有人,消息将会发给所有人
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void drpTalkModem_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (drpTalkModem.SelectedIndex == 0)
            {
                drpReceiver.Enabled = true;
            }
            else
            {
                drpReceiver.Enabled = false;
            }
        }
        /// <summary>
        /// 当用户点击下拉列表时触发事件,请求服务器更新在线用户列表
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void drpReceiver_SelectedIndexChanged(object sender, EventArgs e)
        {
            _nws.Write(new byte[] { 0, 1 }, 0, 2);
        }
        private void btnClose_Click(object sender, EventArgs e)
        {
            DialogResult ret;
            ret = MessageBox.Show("确定与服务器断开连接吗?","退出",MessageBoxButtons.OKCancel,
                                    MessageBoxIcon.Question,MessageBoxDefaultButton.Button2);
            if (ret == DialogResult.OK)
            {
                _nws.Write(new byte[] { 0, 2 }, 0, 2);
                if (receiveThread != null)
                {
                    receiveThread.Abort();
                }
                _nws.Close();
                this.Owner.Close();
                this.Close();
            }
        }
        private void TalkWindow_Load(object sender, EventArgs e)
        {
            receiveThread = new Thread(new ThreadStart(MsgHandle));
            receiveThread.Start();
        }
    }
}
三:程序运行界面

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。