代码改变世界

揭开Socket编程的面纱 (一)

  沐海  阅读(1570)  评论(0编辑  收藏  举报

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

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

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

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

                    //System.Net.Sockets.tc

                    NetworkStream ns = client.GetStream();

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

 

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

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

                    try

                    {                       

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

                  // buffer

                        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 类的新实例并连接到指定主机上的指定端口。

 

                  NetworkStream ns = client.GetStream();

          // client.GetStream();

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

 

 

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

 

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

 

 

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

                }

 

        }

 

 

 附图

图

图

图

图

(评论功能已被禁用)
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述
记录生活、工作、学习点滴!
E-Mail:mahaisong@hotmail.com 欢迎大家讨论。
沐海博客园,我有一颗,卓越的心!
点击右上角即可分享
微信分享提示