网络套接字学习以及聊天程序开发实例

 

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);

            }

        }

    }

}


 

你可能感兴趣的:(套接字)