您的位置:首页 > 理论基础 > 计算机网络

Socket网络编程及其通信原理

2016-06-26 20:20 651 查看
   Socket套接字的解释是孔、插座的意思。它其实是网络通信提供的一个接口,作用就好像插座,封装了许多细节和操作。我们在编程时直接调用,不用细究它是怎么怎么做的,而侧重于做什么。Socket套接字分为有连接的流式套接字和无连接的数据报套接字以及原始套接字(这里不介绍)。流式套接字在数据交换前先建立链路,因此稳定,即保证了正确性、有序到达,是双向的、无重复(重复的会被丢弃)以及无记录边界(以字节流传输,不保护消息的独立性)的数据流服务。数据报套接字则是无连接的,尽力传输的。它没有链路,因此传输过程有可能会走丢,不能保证正确性。但开销也相比前者少的多,两者分别对应TCP与UDP,大家有兴趣可以借阅计算机网络的书认真了解。以下主要针对socket编程步骤来(针对有连接的流式套接字)讲解,以C#为例:

服务器端:

   1.创建套接字

          //定义一个套接字用于监听客户端发来的消息,三个参数(IP4寻址协议,流式连接,Tcp协议)

          Socket  socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

 解释:第一个指明通信协议族,第二个用于指定套接字的类型,第三个指定套接字使用的协议,若取0则可自动选择合适的协议。

返回的是一个套接字描述符,其实是个整型数。每个进程的进程空间都有一个套接字描述符表,存放着套接字与套接字的数据结构的对应关系

  2.绑定

 首先我们要理解端口的的作用,ip地址可以标识某个主机,但某个主机上可能有多个进程在通信,那我怎么找到另一边呢?那就是端口的作用。我们使用端口来标识进程,这样就能通过端口找到进程,好像一栋楼通过房间号来找人。因此我们要先声明好哪台主机哪个进程,即声明ip和端口

          //将IP地址和端口号绑定到网络节点point上

            IPEndPoint point = new IPEndPoint(address, port);

  可以理解成将ip和端口提取出来生成一个插孔。接下来解释把套接字插在这个插孔上,我们就能顺着插孔找到这个套接字了。

          //监听绑定的网络节点

            socketServer.Bind(point);

 3.侦听

        //将套接字的监听队列长度限制为length

            socketServer.Listen(length);

   开启监听模式,看是否有客户端连接,并限制最多连接长度

客户端:

 4.connect()连接请求

   socketClient.Connect(point);//socketClient是在客户端生成的套接字与服务端1生成方法相同,发出连接请求请求连接服务端的网络节点,point是相同的,不过也要创建一次

服务器端:

5.accept()

  当接收到客户端的连接请求,服务器端对客户端进行响应,当然如果连接数目超过上文限制的length就会失败。一旦连接成功就会生成一个新的套接字,由这个套接字负责这个连接的通信,就是这个连接的负责人。这个套接字与客户端请求的套接字就好像线段的两个端点,连成了一个通信通道。发出连接请求的客户端就是通过该通道与客户端收发数据。

  Socket connection = socketServer.Accept();

服务器端/客户端

6.send()

任何一端想发送信息都是通过该端的socket,如客户端的socketClient

    socketClient.Send(byte[] buffer,int offset,int size,SocketFlags socketflags)//使用指定的socketFlags把指定字节数的数据发送到已连接的Socket(参数可重载)

 buffer指数据存放的缓冲区,常用一个参数的,即socketClient.Send(buffer);//将buffer里数据放在socketClient发送

7.receive()接受数据

      //从socketSever获取接收的数据,并存入内存缓冲区buffer  返回一个字节数组的长度

       int  received = socketServer.Receive(buffer);

      值得注意的是其实发送和接受数据功能不是由send()和receive()这两个函数实现的,正如上文所说socket它封装了很多东西,告诉你做什么,而忽略怎么做,那两个函数也和插孔一样,其实真正发送接收的工作是下层协议栈实现的。send()作用其实是把用户进程缓冲区的数据发送到套接字的数据发送缓冲区中。receive()作用是把套接字数据缓冲区中的数据接收到用户进程的缓冲区。说白了,就好像运货。send()就是把货物装上货车的过程,等下层协议栈这辆车到达目的地,由receive()把货物卸下来。

      send()细节:套接字发送缓冲区即货车,待发送的数据是货物,调用时先比较货物长度是否大于货车容量,若大于则失败。若不大于再看货车出发没,就是协议是否开始发送缓冲区里的数据。开始就等待,否则看看车是不是空的,有东西就看剩余空间足不足够存放货物,足够就装上去,空车也装。否则就等下一趟车。

      receive()细节: 要等待所有数据都运到才开始卸货,若运的货大于接收端程序缓冲区,则分几次卸货卸完。

      成功调用send()不能代表发送成功,只能说明装货成功,具体是看下层协议栈。

      如果是无连接的数据报套接字则使用SendTo()和ReceiveFrom(),因为无连接不知道起点目的地,故要分别多指明目的地和起点。无连接也不用connect()函数,若调用了,则相当于有了默认的目的地址,可以调用send()和receive()函数。

8.close()关闭

 socketServer.Close();//关闭与释放相关资源

tcp模型:



udp模型:



接下来举一个最简单的示例,作用是测试往返时延
服务器端:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using System.Net.Sockets;

using System.Net;

namespace server

{

    public partial class Form1 : Form

    {

        public Form1()

        {

           

            InitializeComponent();

        }

        private void button1_Click(object sender, EventArgs e)

        {

            Socket socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            IPEndPoint point = new IPEndPoint(IPAddress.Parse(textBox1.Text),int.Parse(textBox2.Text));

            socketServer.Bind(point);

            socketServer.Listen(1);

           Socket conn= socketServer.Accept();

           while (true) {

           byte[] buffer=new byte [1024];

           int length=conn.Receive(buffe
4000
r);

           conn.Send(buffer);

           }

        }

    }

}

客户端:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using System.Net.Sockets;

using System.Net;

namespace client

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

        }

        Socket socketClient = null;

        private void button1_Click(object sender, EventArgs e)

        {

            socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            IPEndPoint point = new IPEndPoint(IPAddress.Parse(textBox1.Text),int.Parse(textBox2.Text));

            socketClient.Connect(point);

            MessageBox.Show("连接");

        }

        private void button2_Click(object sender, EventArgs e)

        {

            if (socketClient.Connected)

            {

                byte [] mes=Encoding.UTF8.GetBytes(textBox5.Text);

                socketClient.Send(mes);

                DateTime t1 = DateTime.Now;

                byte [] buffer=new  byte[1024];

                int length=socketClient.Receive(buffer);

                DateTime t2 = DateTime.Now;

                MessageBox.Show((t2-t1).ToString());

                string recv = Encoding.UTF8.GetString(buffer,0,length);

                textBox3.AppendText(recv);

            }

        }

    }

}

当然,这个实验存在许多问题,比如异常处理还有socket关闭问题,进一步修改

服务器端:

//开启服务

    private void button1_Click(object sender, EventArgs e)

        {//新建套接字

            socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //把ip地址和端口号绑定在网络节点上

            IPEndPoint point = new IPEndPoint(IPAddress.Parse(textBox1.Text), int.Parse(textBox2.Text));

            //绑定

            socketServer.Bind(point);

            //监听

            socketServer.Listen(10);

            label3.Visible = true;

            //创建多线程

            Thread threadListening = new Thread(Listening);

            threadListening.IsBackground = true;

            threadListening.Start();

 }

        private void Listening() {

            try

            {

                //接受一个连接

                conn = socketServer.Accept();

            

            }

            catch (Exception ex)

            {

                MessageBox.Show(ex.Message); //提示套接字监听异常  

                return;

            }

           

           ParameterizedThreadStart pts = new ParameterizedThreadStart(recv);

           Thread thread = new Thread(pts);

         thread.IsBackground = true;//设置为后台线程,随着主线程退出而退出  

            //启动线程  

         thread.Start(conn);

        }

        private void recv(object socketclientpara)

        {

            Socket conn = socketclientpara as Socket;

                while (true)

            {

                try

                {

                byte[] buffer = new byte[1024];

                //将套接字缓冲区里的数据放在buffer

                conn.Receive(buffer);

              

                    //直接将数据返回

                conn.Send(buffer);

                   

                }

                catch (Exception ex)//异常处理

                {

                   MessageBox.Show( ex.Message);

                   

                   socketServer.Close();

                   button1.Enabled = true ;

                   break;

                }

              

            }

            }

客户端:

    private void button1_Click(object sender, EventArgs e)

        {

            socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            IPEndPoint point = new IPEndPoint(IPAddress.Parse(textBox1.Text), int.Parse(textBox2.Text));

            try

            {

                socketClient.Connect(point);

                txtRespo.AppendText("连接成功");

                button1.Enabled = false;

            }

            catch (Exception)

            {

                txtRespo.AppendText("连接失败");

            }

        }

        static int count = 0;

        static double sum = 0;

        private void btnSend_Click(object sender, EventArgs e)

        {

            if (socketClient.Connected)

            {

                while(count<50)//往返50次

                {

                    try

                    {

                

                    byte[] mes = Encoding.UTF8.GetBytes(txtMsg.Text);

                    socketClient.Send(mes);

                    //获取发送时间

                    DateTime t1 = DateTime.Now;

                    byte[] buffer = new byte[1024];

                  //接受信息

                     socketClient.Receive(buffer);

                    DateTime t2 = DateTime.Now;

                 

                    TimeSpan span = (TimeSpan)(t2 - t1);

                   // txtMsg.Enabled = false;

                    //累加50次

                    sum += span.TotalMilliseconds;

                    count++;

                    txtRespo.AppendText("第"+count+"次时延"+span.TotalMilliseconds.ToString() + "\r\n");

                   // MessageBox.Show(count.ToString());

                    }

                    catch(Exception ex)//异常处理

                {

                   MessageBox.Show( ex.Message);

                   

                   socketClient.Close();

                   button1.Enabled = true ;

                   break;

                }

                    

                }

             

            } if (count==50)

                {

                   // txtMsg.Enabled = true;

                    btnSend.Enabled = false;

                  

                    txtAver.Text =( sum / 50).ToString();

                   

                }

        }

 这个结果依然存在一些问题,第一,多线程多客户端不可以 ,第二,同步问题,按钮异步操作不允许。第三,对于服务器端定时发送到客户端判断是否关闭了套接字还没有好方法。这些问题只是我的总结,方便以后进一步修改,你们明白就明白,不明白也没事。



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  网络编程 socket 通信