zoukankan      html  css  js  c++  java
  • 基于TCP异步的聊天室程序

    话说这个学期我们有一门课叫“中间件”,老师叫我们做一个基于TCP的聊天程序,主要结构如图

    1.所有Client端需要与Server端连接(感觉这句话好白痴,TCP肯定要连接了才能工作)

    2.Client端的功能是可以群发和私聊(用过QQ都应该知道什么是群发和私聊吧),但都必须经过Server端中转,也就是实现了类似通讯中间件的功能。

    PS:开始写之前我是对网络编程这块完全没有认识的,上网找了几个TCP的程序,都是只能实现群发功能,或者只能实现client与server之间相互发的功能,

    还没有哪个是可以实现上面所说的功能的程序的(如果有的请留言给我,我去下一个下来学习一下,O(∩_∩)O谢谢)。

    实现方法有好多,用Socket类可以实现,用 TcpClient类和TcpListener类也可以实现,我就选择了后者,因为比较简单。

    下面就列一下我用到的技术:

    多线程,异步回调,委托,设计模式的观察者模式…………

    先让大家看一下客户端和服务器端的界面先吧(本人不会做界面,而且界面上有很多Label是用来检查接收的情况,请大家选择性过滤掉)

    server端的

    client端的

    1.Server端先启动服务,新建一个线程,绑定一个套接字,之后监听

    2.Client端点击连接之后,就会与Server端建立连接。

    3.每当有一个Client加入Server时,Server都会通知所有的Client更新用户列表(观察者模式)

    4.点私聊和用户之后,就可以私聊;点群发,就发个所有用户。

    代码解说,先看看解决方案

     

    先说一下client端

    声明变量
    System.Collections.ArrayList clientlist = new System.Collections.ArrayList();
    private bool isExit = false;
    private delegate void SetListBoxCallBack(string str);
    private SetListBoxCallBack setlistboxcallback;
    private delegate void SetTextBoxReceiveCallBack(string str);
    private SetTextBoxReceiveCallBack settextboxreceivecallback;
    private delegate void SetComboBoxCallBack(string str);
    private SetComboBoxCallBack setcomboboxcallback;
    private delegate void RemoveComboBoxItemsCallBack(DataReadWrite datareadwrite);
    private RemoveComboBoxItemsCallBack removecomboboxcallback;
    private TcpClient client;
    private NetworkStream ns;
    private ManualResetEvent allDone = new ManualResetEvent(false);
    构造函数
    public ClientMain()
    {
    InitializeComponent();
    setlistboxcallback
    = new SetListBoxCallBack(SetListBox); //注册listbox回调函数
    settextboxreceivecallback = new SetTextBoxReceiveCallBack(SetTextBoxReceive); ////注册textbox回调函数
    setcomboboxcallback = new SetComboBoxCallBack(SetComboBox); ////注册combobox回调函数
    removecomboboxcallback = new RemoveComboBoxItemsCallBack(RemoveComboBoxItems);
    }
    //注册combobox删除的回调函数
    连接按钮
    //连接按钮事件
    private void btn_Connect_Click(object sender, EventArgs e)
    {
    client
    = new TcpClient(AddressFamily.InterNetwork);
    IPAddress serverip
    = IPAddress.Parse(txt_Server.Text);
    //采用的是异步方式
    AsyncCallback receiveCallBack = new AsyncCallback(ReceiveCallBack);
    allDone.Reset();

    try
    {
    //开始连接
    client.BeginConnect(serverip, Convert.ToInt32(txt_Port.Text),receiveCallBack,client);
    txt_ReceiveMsg.Invoke(settextboxreceivecallback,
    string.Format("本机终结点:{0}", client.Client.LocalEndPoint));
    txt_ReceiveMsg.Invoke(settextboxreceivecallback,
    "开始与服务器连接");
    allDone.WaitOne();
    btn_Connect.Enabled
    = false;
    }
    catch
    {
    MessageBox.Show(
    "登录服务器失败,请确认服务器是否正常工作!");
    }
    }
    接收和回调
    //接收回调函数
    private void ReceiveCallBack(IAsyncResult iar)
    {
    allDone.Set();
    try
    {
    client
    = (TcpClient)iar.AsyncState;
    client.EndConnect(iar);
    txt_ReceiveMsg.Invoke(settextboxreceivecallback,
    string.Format("与服务器{0}连接成功", client.Client.RemoteEndPoint));
    ns
    = client.GetStream();
    DataRead dataRead
    = new DataRead(ns, client.ReceiveBufferSize);
    ns.BeginRead(dataRead.msg,
    0, dataRead.msg.Length, ReadCallBack, dataRead);
    }
    catch
    {
    MessageBox.Show(
    "已经与服务器断开连接!");
    this.Close();
    }

    }

    //读取回调函数
    private void ReadCallBack(IAsyncResult iar)
    {
    try
    {
    DataRead dataread
    = (DataRead)iar.AsyncState;
    int recv = dataread.ns.EndRead(iar);
    lbluserlist.Text
    = Encoding.Unicode.GetString(dataread.msg, 0, recv);
    string userlist1 = lbluserlist.Text;
    if (userlist1.StartsWith("#"))
    {
    lb_Users.Items.Clear();
    string[] abc = userlist1.Split(new char[] { '#' });
    for (int i = 1; i < abc.Length ; i++)
    lb_Users.Items.Add(abc[i]);
    }
    else
    txt_ReceiveMsg.Invoke(settextboxreceivecallback, Encoding.Unicode.GetString(dataread.msg,
    0, recv));

    if (isExit == false)
    {

    dataread
    = new DataRead(ns, client.ReceiveBufferSize);
    ns.BeginRead(dataread.msg,
    0, dataread.msg.Length, ReadCallBack, dataread);
    }
    }
    catch (Exception e)
    {
    MessageBox.Show(e.Message);
    }
    finally
    { }
    }
    发送功能
    //发送按钮事件
    private void btn_Send_Click(object sender, EventArgs e)
    {
    //如果群发按钮被选中时
    if (rbteam.Checked == true)
    {
    //lb_Users.SelectedItems.Clear();
    SendString(txt_SendMsg.Text);
    }
    //如果私聊按钮被选中时
    else if (rbself.Checked == true)
    {
    if (lb_Users.SelectedItem != null)
    SendString(
    "#" + lb_Users.SelectedItem.ToString().Trim() + "#" + txt_SendMsg.Text);
    //label10.Text = "#" + lb_Users.SelectedItem.ToString().Trim() + "#" + txt_SendMsg.Text;
    else
    MessageBox.Show(
    "你还没有选取要私聊的对象");
    }

    //label9.Text = "对大家说:" + txt_SendMsg.Text;

    txt_SendMsg.Clear();
    }


    //发送函数
    private void SendString(string str)
    {
    try
    {
    byte[] bytesdata = Encoding.Unicode.GetBytes(str + "\r\n");
    ns.BeginWrite(bytesdata,
    0, bytesdata.Length, new AsyncCallback(SendCallBack), ns);
    ns.Flush();
    }
    catch (Exception e)
    {
    MessageBox.Show(e.Message);
    }
    finally
    { }
    }

    //发送回调函数
    private void SendCallBack(IAsyncResult iar)
    {
    try
    {
    ns.EndWrite(iar);
    }
    catch (Exception e)
    {
    MessageBox.Show(e.Message);
    }
    finally
    { }
    }
    回调函数的实现
    //listbox回调函数
    private void SetListBox(string str)
    {
    lb_Users.Items.Add(str);
    }

    //txtbox回调函数
    private void SetTextBoxReceive(string str)
    {
    txt_ReceiveMsg.AppendText(str
    +"\r\n");
    }

    在看看Server端

    开始阶段
    public ServerMain()
    {
    InitializeComponent();
    setlistboxcallback
    = new SetListBoxCallBack(SetLbListBox);
    setlistboxcallback2
    = new SetListBoxCallBack(SetListBox);
    removelistboxcallback
    = new RemoveListBoxCallBack(RemoveListBoxItems);
    setcomboboxcallback
    = new SetComboBoxCallBack(SetComboBox);
    removecomboboxcallback
    = new RemoveComboBoxItemsCallBack(RemoveComboBoxItems);
    }



    private bool isExit = false;
    System.Collections.ArrayList clientlist
    = new System.Collections.ArrayList();
    TcpListener listener;
    private delegate void SetListBoxCallBack(string str);
    private SetListBoxCallBack setlistboxcallback;
    private SetListBoxCallBack setlistboxcallback2;
    private delegate void RemoveListBoxCallBack(DataReadWrite datareadwrite);
    private RemoveListBoxCallBack removelistboxcallback;
    private ManualResetEvent allDone = new ManualResetEvent(false);
    private delegate void SetComboBoxCallBack(string str);
    private SetComboBoxCallBack setcomboboxcallback;
    private delegate void RemoveComboBoxItemsCallBack(DataReadWrite datareadwrite);
    private RemoveComboBoxItemsCallBack removecomboboxcallback;

    //开始服务按钮
    private void btn_Start_Click(object sender, EventArgs e)
    {
    //新建线程接受connect
    Thread myThread = new Thread(new ThreadStart(AcceptConnection));
    myThread.Start();
    btn_Start.Enabled
    = false;
    btn_End.Enabled
    =true;
    }

    //线程AcceptConnection
    private void AcceptConnection()
    {
    IPAddress[] ip
    = Dns.GetHostAddresses(Dns.GetHostName());
    //IPAddress ipp = IPAddress.Parse("192.168.76.103");
    listener = new TcpListener(ip[0], Convert.ToInt32(txt_ServerPort.Text));
    listener.Start();
    while (isExit == false)
    {
    try
    {
    allDone.Reset();
    AsyncCallback callback
    = new AsyncCallback(AcceptTcpClientCallBack);
    lst_ServerList.Invoke(setlistboxcallback,
    "开始等待连接");
    listener.BeginAcceptTcpClient(callback, listener);
    allDone.WaitOne();
    }
    catch (Exception e)
    {
    MessageBox.Show(e.Message);
    break;
    }
    finally
    { }
    }
    }

    private void AcceptTcpClientCallBack(IAsyncResult iar)
    {
    try
    {
    allDone.Set();
    TcpListener mylistener
    = (TcpListener)iar.AsyncState;
    TcpClient client
    = mylistener.EndAcceptTcpClient(iar);
    lst_ServerList.Invoke(setlistboxcallback,
    "已接收客户连接" + client.Client.RemoteEndPoint);
    listBox1.Invoke(setlistboxcallback2, client.Client.RemoteEndPoint.ToString());
    comboBox1.Invoke(setcomboboxcallback, client.Client.RemoteEndPoint.ToString());
    DataReadWrite datareadwrite
    = new DataReadWrite(client);
    clientlist.Add(datareadwrite);

    //发送成员列表,这里用了观察者模式
    SendList();

    //与服务器连接后的不同的客户端的datareadwrite开始异步接收数据
    datareadwrite.ns.BeginRead(datareadwrite.read, 0, datareadwrite.read.Length, ReadCallBack, datareadwrite);
    }
    catch (Exception e)
    {
    lst_ServerList.Invoke(setlistboxcallback, e.Message);
    //MessageBox.Show(e.Message);
    return;
    }
    finally
    { }
    }

    private void SendList()
    {
    string userlist = "";
    for (int i = 0; i < clientlist.Count; i++)
    {
    userlist
    = userlist + "#" + ((DataReadWrite)clientlist[i]).client.Client.RemoteEndPoint.ToString();
    label14.Text
    = userlist;
    }
    foreach (DataReadWrite drw in clientlist)
    SendString(drw, userlist);
    }
    运行阶段
    //读取消息回调函数
    private void ReadCallBack(IAsyncResult iar)
    {
    DataReadWrite datareadwrite
    = (DataReadWrite)iar.AsyncState;
    try
    {

    int recv = datareadwrite.ns.EndRead(iar);
    string aa = Encoding.Unicode.GetString(datareadwrite.read, 0, recv);
    //string bb = "";
    stringaa.Text = aa;
    if (isExit == false)
    {
    if (aa.Substring(0, 1) != "#") //群发为#,没有则为单发
    {
    aa
    = aa.Insert(0, datareadwrite.client.Client.RemoteEndPoint.ToString() + "对大家说:");
    //stringbb.Text = aa.Substring(1, aa.Length-1);
    foreach (DataReadWrite drw in clientlist)
    SendString(drw, aa);
    }
    else //私聊时截取ip地址,收到的信息为“#192.168.76.102:3315#消息”
    {
    int c=aa.LastIndexOf("#"); //截取ip地址的位置index
    string ipaddress = aa.Substring(1, c - 1); //截取ip地址
    for (int i = 0; i <= listBox1.Items.Count - 1; i++)
    {
    if (ipaddress == listBox1.Items[i].ToString())
    {
    //找到需要发送的对象
    DataReadWrite obj = (DataReadWrite)clientlist[i];
    //增加发送源的IP地址和端口号
    aa = aa.Insert(c + 1, datareadwrite.client.Client.RemoteEndPoint.ToString() + "跟你说:");
    SendString(obj, aa.Substring(c
    +1));
    break;
    //MessageBox.Show(aa.Substring(c, aa.Length - 2));
    }
    }
    }
    datareadwrite.ns.BeginRead(datareadwrite.read,
    0, datareadwrite.read.Length, ReadCallBack, datareadwrite);
    }
    }
    catch (Exception e)
    {
    lst_ServerList.Invoke(setlistboxcallback, e.Message);
    listBox1.Invoke(removelistboxcallback, datareadwrite);
    comboBox1.Invoke(removecomboboxcallback, datareadwrite);
    //SendList();
    }
    finally
    { }
    }

    //发送消息
    private void SendString(DataReadWrite datareadwrite, string str)
    {
    try
    {
    datareadwrite.write
    = Encoding.Unicode.GetBytes(str + "\r\n");
    datareadwrite.ns.BeginWrite(datareadwrite.write,
    0, datareadwrite.write.Length, new AsyncCallback(SendCallBack), datareadwrite);
    datareadwrite.ns.Flush();
    lst_ServerList.Invoke(setlistboxcallback,
    string.Format("向{0}发送:{1}", datareadwrite.client.Client.RemoteEndPoint, str));
    }
    catch (Exception e)
    {
    lst_ServerList.Items.Add(e.Message);
    //发送失败时,清除发送不成功的IP地址
    listBox1.Invoke(removelistboxcallback, datareadwrite);
    comboBox1.Invoke(removecomboboxcallback, datareadwrite);
    //SendList();
    }
    finally
    {
    }
    }

    //发送回调
    private void SendCallBack(IAsyncResult iar)
    {
    DataReadWrite datareadwrite
    = (DataReadWrite)iar.AsyncState;
    try
    {
    datareadwrite.ns.EndRead(iar);
    }
    catch (Exception e)
    {
    lst_ServerList.Invoke(setlistboxcallback, e.Message);
    listBox1.Invoke(removelistboxcallback, datareadwrite);
    comboBox1.Invoke(removecomboboxcallback, datareadwrite);
    }
    finally
    { }
    }


    //停止服务按钮
    private void btn_End_Click(object sender, EventArgs e)
    {
    isExit
    = true;
    allDone.Set();

    btn_Start.Enabled
    = true;
    btn_End.Enabled
    = false;

    lst_ServerList.Items.Add(
    "服务器于" + DateTime.Now.ToString() + "停止运行.");
    }
    回调函数实现
    private void RemoveListBoxItems(DataReadWrite datareadwrite)
    {
    int index = clientlist.IndexOf(datareadwrite);
    listBox1.Items.RemoveAt(index);
    }

    private void SetListBox(string str)
    {
    listBox1.Items.Add(str);
    }

    private void SetLbListBox(string str)
    {
    lst_ServerList.Items.Add(str);
    lst_ServerList.SelectedIndex
    = listBox1.Items.Count - 1;
    lst_ServerList.ClearSelected();

    }

    private void RemoveComboBoxItems(DataReadWrite datareadwrite)
    {
    int index = clientlist.IndexOf(datareadwrite);
    comboBox1.Items.RemoveAt(index);
    }

    private void SetComboBox(object obj)
    {
    comboBox1.Items.Add(obj);
    }

    再介绍一下DataReadWrite类,这个类是实现将一个client的属性和功能集成到一个类中,主要是负责处理数据的,包括一个tcpclient对象,

    一个网络流对象,2个负责读写的字节数组

    DataReadWrite
    public class DataReadWrite
    {
    public TcpClient client;
    public NetworkStream ns;
    public byte[] read;
    public byte[] write;
    public DataReadWrite(TcpClient client)
    {
    this.client = client;
    ns
    = client.GetStream();
    read
    = new byte[client.ReceiveBufferSize];
    write
    = new byte[client.SendBufferSize];
    }

    public void InitReadArray()
    {
    read
    = new byte[client.ReceiveBufferSize];
    }

    public void InitWriteArray()
    {
    write
    = new byte[client.SendBufferSize];
    }

    在构造函数里得到连接上服务器的句柄,包含服务器的IP地址信息,从client对象上得到流对象,流对象就是用来在网络上进行流的读写。

    以下是实现的截图

    尝试了群发和私聊

    由于是新手,所以里面用词方面可能有些不准确,而且程序这是初成品,还有很多方面的BUG。例如退出时会发生异常,当有人离开时,列表不会更新。

    希望大家看了之后给点意见和建议,谢谢大家撒。有空多留言,谢谢

    参考书籍:《visual c#网络编程技术与实践》和《c#网络应用高级编程》

    ps:漏了源码,现在上传qqsocket.rar

  • 相关阅读:
    观点 | 为什么说云主机比物理机故障率更低?
    7 天玩转 ASP.NET MVC — 第 7 天
    浅谈 OneAPM 在 express 项目中的实践
    Swift 2.0 到底「新」在哪?
    DevOps:怎么实现源代码注释和系统文档的自动化更新?
    JavaScript学习之路-为什么要学习JavaScript语法
    Android精通之OrmLite数据库框架,Picasso框架,Okio框架,OKHttp框架
    Android精通之OrmLite数据库框架,Picasso框架,Okio框架,OKHttp框架
    Android精通之OrmLite数据库框架,Picasso框架,Okio框架,OKHttp框架
    AndroidStudio制作登录和注册功能的实现,界面的布局介绍
  • 原文地址:https://www.cnblogs.com/cookies9/p/1750244.html
Copyright © 2011-2022 走看看