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

来源:互联网 发布:软件开发前端和后端 编辑:程序博客网 时间:2024/06/06 05:07


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


原创粉丝点击