zoukankan      html  css  js  c++  java
  • 网络套接字学习以及聊天程序开发实例

    1.Socket相关概念
        
         ~socket叫做“套接字”,用于描述ip地址和端口。是一个通信链的句柄。
         ~socket非常类似于电话插座
         ~在Internet上有很多这样的主机,这些主机一般运行了很多个服务软件,同时提供几种服务.每种服务都打开一个socket,并绑定到一个端口上,不同的端口对应于不同的服务(应用程序).
         ~例如:http使用80端口 ftp使用21端口 smtp使用23端口
         两种类型
              一.流式Socket(STREAM):是一种面向连接的socket,针对于面向连接的TCP服务应用,安全,但是效率低
              二.数据报式Socket(DATAGRAM):是一种无连接的Socket,对应于无连接的UDP服务应用,不安全(丢失,顺序错乱,在接收端要分析重排及要求重发),但效率高

    2.Socket一般应用模式(服务器和客户端)
       
       监听客户端请求,客户端, 连接套接字 
         服务端的Socket(至少两个)
              一个负责接收客户端连接请求
              每成功接收到一个客户端的连接便在服务端产生一个套接字
                   ~为接收客户端连接时创建
                   ~每一个客户端对应一个Socket
          客户端Socket
              客户端Socket
                   必须制定连接服务端地址和端口
                   通过创建一个Socket对象来初始化一个到服务器端的TCP连接
    3.Socket通信基本流程
         服务端:
         1申请一个Socket
         2绑定到一个ip地址和一个端口
         3开启监听,等待接收连接
         客户端
         1申请一个socket
         2连接服务器(指明ip地址和端口号)
         !服务端接到连接请求后,产生一个新的socket(端口大于1024)与客户端建立连接并进行通讯,原监听socket继续听
        
    4.服务器端
         创建 服务端 负责监听 套接字 参数(使用ip4寻址地址,使用流式连接,使用tcp协议传输数据)
         获得文本框中的ip地址对象
         创建包含ip和port的网络节点对象
         TextBox.CheckForIllegalCrossThreadCalls = false; //关闭对文本框的跨线程操作

    namespace 聊天程序
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
                TextBox.CheckForIllegalCrossThreadCalls = false; //关闭对文本框的跨线程操作
            }

            Thread threadWatch = null ;//负责监听的 线程
            Socket socketWatch = null ;//负责监听的 套接字

            private void btnListen_Click(object sender, EventArgs e)
            {
                // 创建 服务端 负责监听 套接字 参数(使用ip4寻址地址,使用流式连接,使用tcp协议传输数据)
                socketWatch = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType .Tcp);
                //获得文本框中的ip地址对象
                IPAddress address = IPAddress .Parse(txtbxIP.Text.Trim());
                //创建包含ip和port的网络节点对象
                IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txtbxPort.Text.Trim()));
                // 负责监听的套接字 绑定到唯一的IP和端口上
                socketWatch.Bind(endPoint);
                //设置监听队列的长度,同时能够处理的连接最大数量,连接总数是没有上限的,取决于服务器的能力
                socketWatch.Listen(10);
                threadWatch = new Thread (WatchConnecting);
                threadWatch.IsBackground = true; //设置为后台线程
                threadWatch.Start(); //开启线程
                ShowMsg( "服务器启动监听!" );         
            }
            /// <summary>
            /// 监听客户端请求的方法
            /// </summary>
            void WatchConnecting()
            {
                while (true )//持续不断的监听新的客户端的连接请求
                {
                    //开启监听 客户端 连接请求 , 注意: Accept方法 会阻断当前的线程!
                    Socket sokConnection = socketWatch.Accept();//一旦监听到客户端的请求,就返回一个负责和该客户端通信的套接字sokConnection
                    ShowMsg( "客户端连接成功!" );
                }
            }

            void ShowMsg(string msg)
            {
                txtbxMsg.AppendText(msg + "\r\n");
            }
        }
    }
    5.客户端
         private void btnLink_Click(object sender, EventArgs e)
            {
                //得到IPAddress
                IPAddress address = IPAddress .Parse(txtbxIP.Text.Trim());
                //得到IPEndPoint
                IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txtbxPort.Text.Trim()));
                //创建套接字
                Socket socketClint = new Socket( AddressFamily.InterNetwork, SocketType .Stream, ProtocolType.Tcp);
                //套接字连接
                socketClint.Connect(endPoint);
            }
    6.案例基础实现
         TCP下的stream操作,一个服务器端对应多个客户端,实现服务器端选择性发送信息,客户端发送信息到服务端:
    ----------------------------------------------------服务端--------------------------------------------
     
    namespace 聊天程序服务端
    {
        public partial class Form1 : Form
        {
           
            public Form1()
            {
                InitializeComponent();
                TextBox .CheckForIllegalCrossThreadCalls = false; //关闭对文本框的跨线程操作
            }
            Thread threadWatch = null ; //负责监听的 线程
            Socket socketWatch = null ; //负责监听的 套接字
            Socket sokConnection = null ; //
            private void btnListen_Click( object sender, EventArgs e)
            {
                // 创建 服务端 负责监听 套接字 参数(使用ip4寻址地址,使用流式连接,使用tcp协议传输数据)
                socketWatch = new Socket ( AddressFamily.InterNetwork, SocketType .Stream, ProtocolType .Tcp);
                //获得文本框中的ip地址对象
                IPAddress address = IPAddress .Parse(txtbxIP.Text.Trim());
                //创建包含ip和port的网络节点对象
                IPEndPoint endPoint = new IPEndPoint(address, int .Parse(txtbxPort.Text.Trim()));
                // 负责监听的套接字 绑定到唯一的IP和端口上
                socketWatch.Bind(endPoint);
                //设置监听队列的长度,同时能够处理的连接最大数量,连接总数是没有上限的,取决于服务器的能力
                socketWatch.Listen(10);
                threadWatch = new Thread (WatchConnecting);
                threadWatch.IsBackground = true ; //设置为后台线程
                threadWatch.Start(); //开启线程
                ShowMsg( "服务器启动监听!" );         
            }
            //保存了服务端所有负责和客户端通信的套接字
            Dictionary <string , Socket> dict = new Dictionary < string, Socket >();
            //保存服务器所有连接的通信套接字 receive方法的线程
            Dictionary <string , Thread> dictThread = new Dictionary < string, Thread >();
            /// <summary>
            /// 监听客户端请求的方法
            /// </summary>
            void WatchConnecting()
            {
                while (true ) //持续不断的监听新的客户端的连接请求
                {
                    //开启监听 客户端 连接请求 , 注意: Accept方法 会阻断当前的线程!
                    sokConnection = socketWatch.Accept(); //一旦监听到客户端的请求,就返回一个负责和该客户端通信的套接字sokConnection
                    //向列表控件中 添加一个客户端的ip端口字符串,作为客户端的唯一标示
                    listbx.Items.Add(sokConnection.RemoteEndPoint.ToString());
                    //将于客户端通信的套接字对象 sokConnection 添加到 键值对集合中 并以客户端ip端口作为键
                    dict.Add(sokConnection.RemoteEndPoint.ToString(), sokConnection);
                    ShowMsg( "客户端连接成功!" +sokConnection.RemoteEndPoint.ToString());
                   
                    //创建 通信线程
                    ParameterizedThreadStart pts = new ParameterizedThreadStart(RecMsg);
                    Thread tdRec = new Thread(pts);
                    tdRec.IsBackground = true ;//设置为后台线程
                    tdRec.SetApartmentState( ApartmentState .STA);
                    tdRec.Start(sokConnection);
                    dictThread.Add(sokConnection.RemoteEndPoint.ToString(), tdRec);
                }
            }
            /// <summary>
            /// 接收字符串
            /// </summary>
            void RecMsg(object sok)
            {
                Socket sokConn = sok as Socket;
                byte [] recByte = new byte[1024 * 1024 * 2];
                while (true )
                {
                    int length = -1;
                    try
                    {
                        length = sokConn.Receive(recByte); //接收二进制数据字节流
                    }
                    catch (SocketException se)
                    {
                        ShowMsg( "异常: " + se.Message + sokConn.RemoteEndPoint.ToString());
                        //从通信套接字字典中移除被中断连接的 通信套接字对象
                        dict.Remove(sokConn.RemoteEndPoint.ToString());
                        //从 通信线程 集合中 删除 被中断连接的 通信线程
                        dictThread.Remove(sokConn.RemoteEndPoint.ToString());
                        //从列表中删除 被中断连接的 ip:Port
                        listbx.Items.Remove(sokConn.RemoteEndPoint.ToString());
                        break ;
                    }
                    catch (Exception ex)
                    {
                        ShowMsg( "异常: " + ex.Message);
                        break ;
                    }
                    //将接收到的流转换成string类型
                    if (recByte[0] == 0)//判断 发送过来的数据 的第一个元素0 发送过来的是文本
                    {
                        //此时 是将 数组 所有元素 都转换成字符串 而真正接受到 只有几个服务端的几个字节
                        string strMagRec = System.Text.Encoding .UTF8.GetString(recByte, 1, length-1);
                        ShowMsg(strMagRec);
                    }
                    else if (recByte[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(recByte, 1, length - 1);
                                ShowMsg( "文件保存成功:" + fileSavePath);
                            }
                        }
                    }
                }
            }
            void ShowMsg(string msg)
            {
                txtbxMsg.AppendText(msg + "\r\n" );
            }
            /// <summary>
            /// 发送实现
            /// </summary>
            private void btnSend_Click( object sender, EventArgs e)
            {
                //必须选择发送对象
                if (string .IsNullOrEmpty(listbx.Text))
                {
                    MessageBox .Show("请选择要发送的对象" );
                }
                else
                {
                    string strMsg = txtbxMsgSnd.Text.Trim();
                    if (string .IsNullOrEmpty(strMsg))
                    {
                        ShowMsg( "发送文本不能为空!请输入!" );
                        return ;
                    }
                    //将要发送的字符串转成uft8对应的 数组
                    byte [] arrMag = System.Text.Encoding .UTF8.GetBytes(strMsg);
                    byte [] arrMagSend = new byte[arrMag.Length+1];
                    Buffer .BlockCopy(arrMag, 0, arrMagSend, 1, arrMag.Length);
                    string strClintKey = listbx.SelectedItem.ToString();
                    try
                    {
                        dict[strClintKey].Send(arrMagSend);
                    }
                    catch (SocketException se)
                    {
                        MessageBox .Show("异常: " + se.Message);
                        return ;
                    }
                    ShowMsg( "发送出去:" + strMsg);
                    //sokConnection.Send(arrMag);
                }
            }
            /// <summary>
            /// 群发
            /// </summary>
            private void btnSendAll_Click( object sender, EventArgs e)
            {
                string strMsg = txtbxMsgSnd.Text;
                byte [] arrMsg = System.Text.Encoding .UTF8.GetBytes(strMsg);
                byte [] arrMagSend = new byte[arrMsg.Length + 1];
                Buffer .BlockCopy(arrMsg, 0, arrMagSend, 1, arrMsg.Length);
                foreach (Socket sok in dict.Values)
                {
                    try
                    {
                        sok.Send(arrMagSend);
                    }
                    catch (SocketException se)
                    {
                        MessageBox .Show("异常: " + se.Message);
                        return ;
                    }
                }
                ShowMsg( "群发完毕:)" );
            }
        }
       
    }

     
      
    ----------------------------------客户端----------------------------------------------

     
    namespace 聊天程序客户端
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
                TextBox .CheckForIllegalCrossThreadCalls = false;
            }
            Thread threadRec = null ; //创建接收线程
            Socket socketClint = null ; //创建接收套接字
           
            /// <summary>
            /// 连接服务器
            /// </summary>
            private void btnLink_Click( object sender, EventArgs e)
            {
                //得到IPAddress
                IPAddress address = IPAddress .Parse(txtbxIP.Text.Trim());
                //得到IPEndPoint
                IPEndPoint endPoint = new IPEndPoint(address, int .Parse(txtbxPort.Text.Trim()));
                //创建客户端套接字
                socketClint = new Socket ( AddressFamily.InterNetwork, SocketType .Stream, ProtocolType .Tcp);
                //套接字连接
                socketClint.Connect(endPoint);
                ShowMsg( "连接上服务器了!!哦也!" );
                threadRec = new Thread (RecMsg);
                threadRec.IsBackground = true ;
                threadRec.Start();
            }
           
            /// <summary>
            /// 接收方法
            /// </summary>
            void RecMsg()
            {
                //定义一个 接收用的 缓存区(2M字节数组)
                byte [] recByte = new byte[1024 * 1024 * 2];
                while (true )
                {
                    int length = -1;
                    try
                    {
                        length = socketClint.Receive(recByte); //接收二进制数据字节流
                    }
                    catch (SocketException se)
                    { //Exception是最顶级的异常父类,我们最好用最接近的异常,这里用SocketException
                        ShowMsg( "异常: " + se.Message);
                        break ;
                    }
                    catch (Exception ex)
                    { //里面涉及装箱操作,会多一些操作
                        ShowMsg( "异常:" + ex.Message);
                    }
                    //将接收到的流转换成string类型
                    if (recByte[0] == 0)//判断 发送过来的数据 的第一个元素0 发送过来的是文本
                    {
                        //此时 是将 数组 所有元素 都转换成字符串 而真正接受到 只有几个服务端的几个字节
                        string strMagRec = System.Text.Encoding .UTF8.GetString(recByte, 1, length - 1);
                        ShowMsg(strMagRec);
                    }
                    else if (recByte[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(recByte, 1, length - 1);
                                ShowMsg( "文件保存成功:" + fileSavePath);
                            }
                        }
                    }
                }
            }
            #region 在窗体文本框中显示字符串 - ShowMsg(string msg)
            /// <summary>
            /// 在窗体文本框中显示字符串
            /// </summary>
            /// <param name="msg"></param>
            void ShowMsg(string msg)
            {
                txtbxMsg.AppendText(msg + "\r\n" );
            }
            #endregion
            #region 发送文本-btnSend_Click
            /// <summary>
            /// 发送文本
            /// </summary>
            private void btnSend_Click( object sender, EventArgs e)
            {
                string strMsg = txtbxSndMsg.Text.Trim();
                //将要发送的字符串转成uft8对应的 数组
                byte [] arrMag = System.Text.Encoding .UTF8.GetBytes(strMsg);
                byte [] arrMagSend = new byte[arrMag.Length + 1];
                Buffer .BlockCopy(arrMag, 0, arrMagSend, 1, arrMag.Length); //拷贝
                try
                {
                    socketClint.Send(arrMagSend); //发送
                }
                catch (SocketException se)
                {
                    MessageBox .Show("异常: " + se.Message);
                    return ;
                }
                ShowMsg( "发送出去:" + strMsg);
                //sokConnection.Send(arrMag);
            }
            #endregion
            #region 选择要发送的文件 - void btnOpenFile_Click
            /// <summary>
            /// 选择发送文件
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void btnOpenFile_Click( object sender, EventArgs e)
            {
                OpenFileDialog ofd = new OpenFileDialog();
                if (ofd.ShowDialog() == System.Windows.Forms. DialogResult.OK)
                {
                    txtbxFilePath.Text = ofd.FileName;
                }
            }
            #endregion
            /// <summary>
            /// 向服务器发送文件
            /// </summary>
            private void btnSendFile_Click( object sender, EventArgs e)
            {
                //用文件流打开用户选择的文件
                //使用using是因为FileStream是挺占内存的类,使用using是为了更好地释放内存
                using (FileStream fs = new FileStream (txtbxFilePath.Text,FileMode .Open))
                {
                    byte [] arrMsg = new byte[1024 * 1024 * 2]; //2M大小
                    //将文件数据读到 数组
                    int length = fs.Read(arrMsg, 0, arrMsg.Length);
                    byte [] arrFileSend = new byte[length + 1];
                    arrFileSend[0] = 1; //代表发送的是文件数据
                    //块拷贝 将arrMsg数组的元素从0开始拷贝,拷贝到arrFileSend从1开始,拷贝length长度
                    Buffer .BlockCopy(arrMsg, 0, arrFileSend, 1, length);
                    //流拷贝 缺点 从0开始拷贝
                    //arrMsg.CopyTo(arrFileSend, 0);
                    try
                    {
                        socketClint.Send(arrFileSend);
                    }
                    catch (SocketException se)
                    {
                        MessageBox .Show("异常: " + se.Message);
                        return ;
                    }
                    ShowMsg( "已发送:" + txtbxFilePath.Text);
                }
            }
        }
    }


  • 相关阅读:
    python计算机视觉项目实践
    Codeforces Round #256 (Div. 2) B (448B) Suffix Structures
    SonarLint插件的安装与使用
    后缀表达式求值
    有用代码段2
    提高Java代码质量的Eclipse插件之Checkstyle的使用具体解释
    Intellij Idea搭建Spark开发环境
    代码备忘, TODO宏实现
    浏览器自己主动填表安全漏洞:查看浏览器保存的password
    PDO 查询mysql返回字段整型变为String型解决方法
  • 原文地址:https://www.cnblogs.com/zhujinghui/p/3369228.html
Copyright © 2011-2022 走看看