zoukankan      html  css  js  c++  java
  • 揭开Socket编程的面纱 (一)

    声明一下。这个系列是博客园一个高手的我转载来学了。不过对他的代码加了点小注释。嘿嘿

    TCP/IPUDPSocket编程这些词你不会很陌生吧?随着网络技术的发展,这些词充斥着我们的耳朵。那么我想问:

    1.        
    什么是TCP/IPUDP
    2.         Socket
    在哪里呢?
    3.         Socket
    是什么呢?
    4.        
    你会使用它们吗?

    什么是TCP/IPUDP

             TCP/IPTransmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。
             UDP
    User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。
           
    这里有一张图,表明了这些协议的关系。
                  图                                                                  

                                                                           
    1

           TCP/IP协议族包括运输层、网络层、链路层。现在你知道TCP/IPUDP的关系了吧。
    Socket
    在哪里呢?
          
    在图1中,我们没有看到Socket的影子,那么它到底在哪里呢?还是用图来说话,一目了然。

     

    图

    2

           原来Socket在这里。
    Socket
    是什么呢?
           Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
    你会使用它们吗?
          
    前人已经给我们做了好多的事了,网络间的通信也就简单了许多,但毕竟还是有挺多工作要做的。以前听到Socket编程,觉得它是比较高深的编程知识,但是只要弄清Socket编程的工作原理,神秘的面纱也就揭开了。
          
    一个生活中的场景。你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。    生活中的场景就解释了这工作原理,也许TCP/IP协议族就是诞生于生活中,这也不一定。

           图

     

     

    3

           先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
          
    在这里我就举个简单的例子,我们走的是TCP协议这条路(见图2)。例子用MFC编写,运行的界面如下:

     (Microsoft Foundation Classes),是一个微软公司提供的类库(class libraries),以C++类的形式封装了WindowsAPI,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。其中包含的类包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类。

     

    图

    4

     

    图

    5

           在客户端输入服务器端的IP地址和发送的数据,然后按发送按钮,服务器端接收到数据,然后回应客户端。客户端读取回应的数据,显示在界面上。
          
    客户端就一个函数完成了一次通信。在这里IP地址为何用127.0.0.1呢?使用这个IP地址,服务器端和客户端就能运行在同一台机器上,这样调试方便多了。当然你可以在你朋友的机器上运行Server程序(本人在局域网中测试过),在自己的机器上运行Client程序,当然输入的IP地址就该是你朋友机器的IP地址了。
          
    简单的理论和实践都说了,现在Socket编程不神秘了吧?希望对你有些帮助。   

     

    C#简单测试源码:/Files/lmjob/socket通信.rar

    这个代码的解析:

    服务端

     

                    int portNum = Convert.ToInt32(this.textBox1.Text);

                    //得到端口号码

                    TcpListener listener = new TcpListener(portNum);

    //建立TCP监听端口

    // TcpListener 类提供一些简单方法,用于在阻止同步模式下侦听和接受传入连接请求。可使用 TcpClient Socket 来连接 TcpListener。可使用 IPEndPoint、本地 IP 地址及端口号或者仅使用端口号,来创建 TcpListener。可以将本地 IP 地址指定为 Any,将本地端口号指定为 0(如果希望基础服务提供程序为您分配这些值)。如果您选择这样做,可在连接套接字后使用 LocalEndpoint 属性来标识已指定的信息。

    Start 方法用来开始侦听传入的连接请求。Start 将对传入连接进行排队,直至您调用 Stop 方法或它已经完成 MaxConnections 排队为止。可使用 AcceptSocket AcceptTcpClient 从传入连接请求队列提取连接。这两种方法将阻止。如果要避免阻止,可首先使用 Pending 方法来确定队列中是否有可用的连接请求。

     

                    listener.Start();

                   

                   

                        this.richTextBox1.AppendText("Waiting for connection..." + "\r");

     

                        TcpClient client = listener.AcceptTcpClient();

    //得到客户端的请求连接

    //为使 TcpClient 连接并交换数据,使用 TCP ProtocolType 创建的 TcpListener 或 Socket 必须侦听是否有传入的连接请求。可以使用下面两种方法之一连接到该侦听器: 
    
    ·                          创建一个 TcpClient,并调用三个可用的 Connect 方法之一。 
    
    ·                          使用远程主机的主机名和端口号创建 TcpClient。此构造函数将自动尝试一个连接。 
    
    

                        this.richTextBox1.AppendText("Connection accepted." + "\r");

                        //System.Net.Sockets.tc

                        NetworkStream ns = client.GetStream();

    //返回用于发送和接收数据的 NetworkStream

     

    //提供用于网络访问的基础数据流。 
    
    NetworkStream 类提供在阻止模式下通过 Stream 套接字发送和接收数据的方法。
    
    若要创建 NetworkStream,必须提供连接的 Socket。
    
    将 Write 和 Read 方法用于简单的单线程同步阻止 I/O。若要使用不同的线程来处理 I/O,则请考虑使用 BeginWrite 和 EndWrite 方法,或 BeginRead 和 EndRead 方法进行通信。 
    
    NetworkStream 不支持对网络数据流的随机访问
    
    

                        byte[] byteTime = Encoding.UTF8.GetBytes(this.textBox2.Text);

                        //得到要发送给客户端的数据。转换成BYTE类型数据

                        try

                        {                       

                            ns.Write(byteTime, 0, byteTime.Length);

                      // buffer

    类型:array<System..::.Byte>[]()[]
    
    
    
    类型 Byte 的数组,该数组包含要写入 NetworkStream 的数据。 
    
    offset 
    
    类型:System..::.Int32
    
    
    
    buffer 中开始写入数据的位置。 
    
    size 
    
    类型:System..::.Int32
    
    
    
    要写入 NetworkStream 的字节数。 
    
    

                            ns.Close();

                   //传输完毕,就立即关闭NS

                //这时等待客户端关闭client,客户端关闭完了,服务端才关闭

                            client.Close();

     

     

             //只传输一次,就关闭了。对应的客户端的代码也是这样连接的。

             // Close 方法将该实例标记为已释放并关闭 TCP 连接。调用此方法将关闭 Socket,并且还将关闭用于发送和接收数据的关联 NetworkStream(如果创建有一个这样的流)。

     

                        }

                        catch (Exception ee)

                        {

                            MessageBox.Show(ee.ToString());

                        }

                   

                    listener.Stop();

    //关闭监听端口

    //

    Stop 方法不会关闭任何已接受的连接。需要用户负责分别关闭这些连接。

     

                }

     

     

    客户端

       private void button1_Click(object sender, EventArgs e)

            {

                try

                   {

                       int portNum = Convert.ToInt32(this.textBox2.Text);

    //服务器端口,可以随意修改

                       string hostName = this.textBox1.Text; //服务器地址,127.0.0.1指本机

                       this.richTextBox1.AppendText("请求连接" + hostName + ":" + portNum.ToString() + "\r"); 

                     

                      TcpClient client = new TcpClient(hostName, portNum);

    //创建 TCP对象 TcpClient 构造函数 (String, Int32)  初始化 TcpClient 类的新实例并连接到指定主机上的指定端口。

     

    hostname 
    
    类型:System..::.String
    
    
    
    要连接到的远程主机的 DNS 名。 
    
    port 
    
    类型:System..::.Int32
    
    
    
    要连接到的远程主机的端口号。 
    
     
    
    

                      NetworkStream ns = client.GetStream();

              // client.GetStream();

            // 返回用于发送和接收数据的 NetworkStream

     

     

                      byte[] bytes = new byte[1024]; //长度

     

                      int bytesRead = ns.Read(bytes, 0, bytes.Length);//返回字节长度

    // buffer 
    
    类型:array<System..::.Byte>[]()[]
    
    
    
    类型 Byte 的数组,它是内存中用于存储从 NetworkStream 读取的数据的位置。 
    
    offset 
    
    类型:System..::.Int32
    
    
    
    buffer 中开始将数据存储到的位置。 
    
    size 
    
    类型:System..::.Int32
    
    
    
    要从 NetworkStream 中读取的字节数。 
    
    返回值
    类型:System..::.Int32
    
    
    
    从 NetworkStream 中读取的字节数。 
    
    

     

     

                      this.richTextBox1.AppendText(Encoding.UTF8.GetString(bytes, 0, bytesRead) + "\r");

                     //定义在1024个字符范围内去读取数据,然后得到真实数据长度。读出传输字节长度范围内所有数据。

     

                      //Rdstrm = new StreamReader(邮件服务流对象, System.Text.Encoding.GetEncoding("gb2312"));

                   

                      //服务端当它收到TCP请求连接时就把数据NS直接写出,写完了就关闭NS

                      client.Close();

                     //读完数据后,直接关闭TCP连接。

       //服务端受到连接关闭后,也关闭它的连接

                    }

                 catch (Exception ee)

                    {

                      

                      MessageBox.Show(ee.ToString());

     

                      this.richTextBox1.AppendText(ee.ToString() + "\r");

                    }

     

            }

     

     

     附图

    图

    图

    图

    图

  • 相关阅读:
    修改Cosbench源码 支持s3的 http range request 测试场景
    CEPH s3 java sdk PUT对象并在同一个PUT请求中同时设置ACL为 Public
    庆祝团队合著的《自主实现SDN虚拟网络与企业私有云》终于得以出版 --- 本人负责分布式存储部分的编写
    Cosbench测试 RGW S3 path_style_access=true模式支持
    RGW 系统吞吐量(TPS)、用户并发量、性能测试概念和公式
    CEPH 使用SSD日志盘+SATA数据盘, 随OSD数目递增对性能影响的递增测试
    使用CEPH RGW admin ops API 进行用户user AK/SK管理的秘诀
    prometheus consul docker redis_exporter 自动注册配置
    Consul 使用手册(感觉比较全了)
    RDS for MySQL权限问题(错误代码:1227,1725)
  • 原文地址:https://www.cnblogs.com/mahaisong/p/2116020.html
Copyright © 2011-2022 走看看