基于socket、多线程的客户端服务器端聊天程序

简介: 服务器端: using System;using System.Windows.Forms;using System.Net.Sockets;using System.

服务器端:



using System;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Net;//IPAddress,IPEndPoint(ip和端口)类
using System.Threading;
using System.Collections.Generic;
using System.IO;
namespace MyChatRoomServer
{
    public partial class Server : Form
    {
        public Server()
        {
            InitializeComponent();
            //一个线程正在访问当前UI线程,要关闭微软设置的对文本框操作的检查
            TextBox.CheckForIllegalCrossThreadCalls = false;
        }

        Thread threadWatch = null;//负责监听客户端连接请求的线程
        Socket socketWatch = null;//负责监听的套接字

        private void btnBeginListen_Click(object sender, EventArgs e)
        {
            //创建服务端负责监听的套接字,参数(IPV4寻址协议,使用流格式,使用Tcp传输协议)
            socketWatch = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
            //获得文本框中IP地址对象
            IPAddress address = IPAddress.Parse(txtIP.Text.Trim());
            //获取端口号
            IPEndPoint endpoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim()));
            //将负责监听的套接字绑定到唯一的IP和端口上
            socketWatch.Bind(endpoint);
            //设置监听队列的长度,意思就是同时发送请求只能10个
            socketWatch.Listen(10);
            //创建一个新的套接字专门负责跟客户端通信,注意:Accept方法会阻塞当前程序
            //Socket sockConnection = socketWatch.Accept();

            //创建负责监听的线程,并传入监听的方法
            threadWatch = new Thread(WatchConnecting);
            threadWatch.IsBackground = true;//设置为后台线程,只要前台的一结束,那么程序就结束
            threadWatch.Start();//启动线程

            ShowMsg("服务器启动监听成功!");
        }
        //保存了服务器端所有负责和客户端通信的套接字
        Dictionary<string, Socket> dict = new Dictionary<string, Socket>();
        //保存了服务器端所有负责调用通信套接字Recive方法的线程
        Dictionary<string, Thread> dictThread = new Dictionary<string, Thread>();
        //Socket sokConnection = null;//负责通信的套接字
        /// <summary>
        /// 监听客户端请求的方法
        /// </summary>
        void WatchConnecting()
        {
            while (true) //加上循环持续不断的监听新的客户端的连接
            {
                //开始监听客户端连接请求,注意:Accept方法会阻断当时线程
                Socket sokConnection = socketWatch.Accept();
                //将列表控件中追加一个客户端的ip端口字符串,作为客户端的唯一标识
                lblOnline.Items.Add(sokConnection.RemoteEndPoint.ToString());
                //将与客户端通信的套接字对象sokConnection添加到键值对集合中,并以客户端IP端口作为键
                dict.Add(sokConnection.RemoteEndPoint.ToString(), sokConnection);
                //创建一个委托
                ParameterizedThreadStart pts = new ParameterizedThreadStart(RecMsg);
                //创建通信线程
                Thread thr = new Thread(pts);
                thr.IsBackground = true; //设置为后台
                thr.Start(sokConnection); //将线程的参数传入
                dictThread.Add(sokConnection.RemoteEndPoint.ToString(),thr);
                ShowMsg("客户端连接成功!"+sokConnection.RemoteEndPoint.ToString());
            }
        }
        /// <summary>
        /// 服务端负责监听客户端发来的数据方法
        /// </summary>
        void RecMsg(object socketClientPara)
        {
            Socket socketClient = socketClientPara as Socket;
            while (true)
            {
                //定义一个2M的接受数据的缓存区
                byte[] arrMsgRec = new byte[1024 * 1024 * 2];//手动准备2M空间
                //将接受到的数据存入arrMsgRec数组,并返回真正接收到的数据长度
                int length = socketClient.Receive(arrMsgRec);
                try
                {
                    length = socketClient.Receive(arrMsgRec);
                }
                catch (SocketException ex)
                {
                    ShowMsg("异常" + ex.Message+",RemoteEnd=" + socketClient.RemoteEndPoint.ToString());
                    //从通信套接字集合中删除被中断连接的通信套接字
                    dict.Remove(socketClient.RemoteEndPoint.ToString());//删除异常的端口和IP
                    //从通信线程中删除被中断的连接通信线程对象
                    dictThread.Remove(socketClient.RemoteEndPoint.ToString());//删除异常的线程
                    //有异常则跳出,不执行后面的
                    //从列表中删除被中断的连接IP:port
                    lblOnline.Items.Remove(socketClient.RemoteEndPoint.ToString());
                    break;
                    //return;
                }
                catch (Exception ex)
                {
                    ShowMsg("异常" + ex.Message);
                    //有异常则跳出,不执行后面的
                    return;
                }
                if (arrMsgRec[0] == 0)//判断传过来的第一个数据是0,则代表是文本数据
                {
                    //将数组转成字符串,此时是将数组所有的元素都转成字符串,而真正接收到的只是服务器端传来的一些字符
                    string strMsgRec = System.Text.Encoding.UTF8.GetString(arrMsgRec, 1, length-1);
                    ShowMsg(strMsgRec);
                }
                //如果是1,则代表发送过来的是文件数据(文件/图片...)
                else if (arrMsgRec[0] == 1)
                {
                    //保存文件选择框对象
                    SaveFileDialog sfd = new SaveFileDialog();
                    if(sfd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                    {
                        string fileSavePath = sfd.FileName;//获得文件保存路径
                        //创建文件流,让文件流来根据路径创建一个文件
                        using (FileStream fs = new FileStream(fileSavePath, FileMode.Create))
                        {
                            fs.Write(arrMsgRec,1,length-1);
                            ShowMsg("文件保存成功:"+fileSavePath);
                        }
                    }
                }
            }
        }

        void ShowMsg(string msg)
        {
            txtMsg.AppendText(msg + "\r\n");
        }

        private void btnCloseServer_Click(object sender, EventArgs e)
        {

            //threadWatch.Abort();//关闭线程

            //ShowMsg("服务器启动监听成功!");
        }
        //发送消息到客户端
        private void btnSend_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(lblOnline.Text))
            {
                MessageBox.Show("请在左侧选择要发送的好友");
            }
            else
            {
                string strMsg = txtMsgSend.Text.Trim();
                //将要发送的字符串转成utf8对应的字节数组
                byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
                string strClientKey = lblOnline.Text;
                //通过key找到字典集合中对应的与某个用户客户端通信的套接字的send方法,发数据给对方
                try
                {
                    dict[strClientKey].Send(arrMsg);
                    //一旦上面的出现异常,下面的就不执行
                    ShowMsg("发送了数据出去:" + strMsg);
                }
                //sokConnection.Send(arrMsg);
                catch (SocketException ex)
                {
                    ShowMsg("发送时异常:"+ex.Message);
                    return;
                }
                catch (Exception ex)
                {
                    ShowMsg("发送时异常:" + ex.Message);
                    return;
                }
                
            }
        }
        //服务器群发消息
        private void btnSendToAll_Click(object sender, EventArgs e)
        {
            string strMsg = txtMsgSend.Text.Trim();
            //将要发送的字符串转成utf8对应的字节数组
            byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
            //便利当前字典里面所有的通信套接字
            foreach (Socket s in dict.Values)
            {
                s.Send(arrMsg);
            }
            ShowMsg("群发完毕!:)");
        }
    }
}


客户端:



using System;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;


namespace MyChatRoomClient
{
    public partial class FChatClient : Form
    {
        public FChatClient()
        {
            InitializeComponent();
            //一个线程正在访问当前UI线程,要关闭微软设置的对文本框操作的检查
            //关闭跨线程检查
            TextBox.CheckForIllegalCrossThreadCalls = false;
        }


        IPAddress address = null;
        IPEndPoint endpoint = null;
        Socket socketClient = null; //客户端套接字




        Thread threadClient = null;//客户端负责接受服务端发来的消息的线程
        //客户端发送请求到服务器
        private void btnConnect_Click(object sender, EventArgs e)
        {
            address = IPAddress.Parse(txtIP.Text.Trim());
            endpoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim()));
            socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            try
            {
                socketClient.Connect(endpoint);
                //创建线程,监听服务器端发来的消息
                threadClient = new Thread(RecMsg);
                threadClient.IsBackground = true;
                threadClient.Start();
                
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.ToString());
            }
        }
        /// <summary>
        /// 监听服务器端发来的消息
        /// </summary>
        void RecMsg()
        {
            while (true)
            {
                //定义一个2M的接受数据的缓存区
                byte[] arrMsgRec = new byte[1024 * 1024 * 2];//手动准备2M空间
                //将接受到的数据存入arrMsgRec数组,并返回真正接收到的数据长度
                int length = socketClient.Receive(arrMsgRec);
                //将数组转成字符串,此时是将数组所有的元素都转成字符串,而真正接收到的只是服务器端传来的一些字符
                string strMsgRec = System.Text.Encoding.UTF8.GetString(arrMsgRec,0,length);
                ShowMsg(strMsgRec);
            }
        }
        #region 在窗体文本框中显示消息-void ShowMsg(string msg)
        /// <summary>
        /// 在窗体文本框中显示消息
        /// </summary>
        /// <param name="msg">消息</param>
        void ShowMsg(string msg)
        {
            txtMsg.AppendText(msg + "\r\n");
        } 
        #endregion


        #region 选择要发送的文件-btnChooseFile_Click
        //选择要发送的文件
        private void btnChooseFile_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                txtFilePath.Text = ofd.FileName;
            }
        }
        #endregion






        //向服务器发送文本消息
        private void btnSendMsg_Click(object sender, EventArgs e)
        {
            string strMsg = txtMsgSend.Text.Trim();
            byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
            byte[] arrMsgSend = new byte[arrMsg.Length + 1];
            arrMsgSend[0] = 0;//设置标识位,0代表是文字
            Buffer.BlockCopy(arrMsg, 0, arrMsgSend, 1, arrMsg.Length);
            socketClient.Send(arrMsgSend);
            ShowMsg("我发送了:" + strMsg);


        //向服务端发送文件
        private void btnSendFile_Click(object sender, EventArgs e)
        {
            //用文件流打开用户选择的文件
            using (FileStream fs = new FileStream(txtFilePath.Text, FileMode.Open))
            {
                byte[] arrFile = new byte[1024*1024*2];//定义一个2M缓存区
                //将文件数据读到数组arrFile中,并获得读取的真是数据长度
                int length = fs.Read(arrFile,0,arrFile.Length);
                byte[] arrFileSend = new byte[length + 1];
                arrFileSend[0] = 1;//代表发送的是文件数据
                //for (int i = 0; i < length; i++)
                //{
                //    arrFileSend[i + 1] = arrFile[i];
                //}
                //数据块的拷贝,将arrFile从第0个开始拷贝,拷贝到arrFileSend,从第一个开始存放
                Buffer.BlockCopy(arrFile,0,arrFileSend,1,length);
                //arrFile.CopyTo(arrFileSend,length);只能从0开始拷贝
                //发送了包含了标识位的新数据到服务端
                socketClient.Send(arrFileSend);
            }
        }
        
    }
}

源码文件: http://download.csdn.net/detail/s10141303/5892933

相关文章
|
5月前
|
缓存 网络协议 Linux
c++实战篇(三) ——对socket通讯服务端与客户端的封装
c++实战篇(三) ——对socket通讯服务端与客户端的封装
112 0
|
6月前
|
存储 网络协议 数据可视化
C++实现socket通信
了解如何实现socket通信以及TCP连接的过程中发生了什么
97 1
|
6月前
|
监控 Unix Linux
采用异步socket实现客户端和服务端的通信
采用异步socket实现客户端和服务端的通信
45 0
|
6月前
|
网络协议
Socket通信详细介绍2
Socket通信详细介绍
47 0
|
6月前
|
网络协议 Unix Linux
Socket通信详细介绍1
Socket通信详细介绍
82 0
|
存储 缓存 网络协议
【Java网络编程】基于UDP-Socket 实现客户端、服务器通信
【Java网络编程】基于UDP-Socket 实现客户端、服务器通信
493 0
|
网络协议 Linux
Linux网络编程服务端的创建
Linux网络编程服务端的创建
107 0
|
安全 测试技术 C#