揭开Socket编程的面纱

本文介绍了TCP/IP协议、UDP及Socket的基本概念,并通过一个简单的MFC实现案例详细讲解了Socket编程的工作原理。
      对TCP/IPUDPSocket编程这些词你不会很陌生吧?随着网络技术的发展,这些词充斥着我们的耳朵。那么我想问:

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

什么是TCP/IPUDP

         TCP/IPTransmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。
         UDPUser 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编写,运行的界面如下:



4



5

       在客户端输入服务器端的IP地址和发送的数据,然后按发送按钮,服务器端接收到数据,然后回应客户端。客户端读取回应的数据,显示在界面上。
       下面是接收数据和发送数据的函数:

int    Receive(SOCKET fd,char *szText,int len)

{
       int cnt;
       int rc;
       cnt=len;

       while(cnt>0)
       {
              rc=recv(fd,szText,cnt,0);
              if(rc==SOCKET_ERROR)
              {
                     return -1;
             }

             if(rc==0)

                     return len-cnt;

              szText+=rc;

              cnt-=rc;

       }

       return len;

}

int Send(SOCKET fd,char *szText,int len)
{

       int cnt;

       int rc;

       cnt=len;

       while(cnt>0)

       {

              rc=send(fd,szText,cnt,0);

              if(rc==SOCKET_ERROR)

              {

                     return -1;

              }

              if(rc==0)

                     return len-cnt;

              szText+=rc;

              cnt-=rc;

       }

       return len;

}

服务器端:

       在服务器端,主要是启动Socket和监听线程。

#define DEFAULT_PORT      2000

void CServerDlg::OnStart()

{

       sockaddr_in local;

       DWORD dwThreadID = 0;

      

       local.sin_family=AF_INET;

       //设置的端口为DEFAULT_PORT

       local.sin_port=htons(DEFAULT_PORT);

       //IP地址设置成INADDR_ANY,让系统自动获取本机的IP地址。

       local.sin_addr.S_un.S_addr=INADDR_ANY;

 

       //初始化Socket

       m_Listening = socket(AF_INET,SOCK_STREAM,0);

       if(m_Listening == INVALID_SOCKET)

       {

              return ;

       }

       //将本地地址绑定到所创建的套接字上

       if(bind(m_Listening,(LPSOCKADDR)&local,sizeof(local)) == SOCKET_ERROR )

       {

              closesocket(m_Listening);

              return ;

       }

       //创建监听线程,这样也能响应界面上操作。

       m_hListenThread = ::CreateThread(NULL,0,ListenThread,this,0,&dwThreadID);

       m_StartBtn.EnableWindow(FALSE);

       m_StopBtn.EnableWindow(TRUE);

}

监听线程函数:
DWORD WINAPI CServerDlg::ListenThread(LPVOID lpparam)
{

       CServerDlg* pDlg = (CServerDlg*)lpparam;

       if(pDlg == NULL)

              return 0;

 

       SOCKET  Listening = pDlg->m_Listening;

       //开始监听是否有客户端连接。

       if(listen(Listening,40) == SOCKET_ERROR)

       {

              return 0;

       }

       char szBuf[MAX_PATH];

       //初始化

       memset(szBuf,0,MAX_PATH);

       while(1)

       {

              SOCKET ConnectSocket;

              sockaddr_in    ClientAddr;

              int                  nLen = sizeof(sockaddr);

              //阻塞直到有客户端连接,不然多浪费CPU资源。

              ConnectSocket = accept(Listening,(sockaddr*)&ClientAddr,&nLen);

              //都到客户端的IP地址。

              char *pAddrname = inet_ntoa(ClientAddr.sin_addr);

              pDlg->Receive(ConnectSocket,szBuf,100);

              //界面上显示请求数据。

              pDlg->SetRequestText(szBuf);

              strcat(szBuf," :我是老猫,收到(");

              strcat(szBuf,pAddrname);

              strcat(szBuf,"");

              //向客户端发送回应数据

              pDlg->Send(ConnectSocket,szBuf,100);

       }

       return 0;

}

       服务器端一直在监听是否有客户端连接,如有连接,处理客户端的请求,给出回应,然后继续监听。

客户端:

       客户端的发送函数:

#define DEFAULT_PORT      2000

void CClientDlg::OnSend()

{

       DWORD dwIP = 0;      

       TCHAR szText[MAX_PATH];

       memset(szText,0,MAX_PATH);

       m_IP.GetWindowText(szText,MAX_PATH);

       //把字符串形式的IP地址转成IN_ADDR结构需要的形式。

       dwIP = inet_addr(szText);

       m_RequestEdit.GetWindowText(szText,MAX_PATH);

 

       sockaddr_in local;

       SOCKET socketTmp;

       //必须是AF_INET,表示该socketInternet域中进行通信

       local.sin_family=AF_INET;

       //端口号

       local.sin_port=htons(DEFAULT_PORT);

       //服务器的IP地址。

       local.sin_addr.S_un.S_addr=dwIP;

      

       ////初始化Socket

       socketTmp=socket(AF_INET,SOCK_STREAM,0);

       //连接服务器

       if(connect(socketTmp,(LPSOCKADDR)&local,sizeof(local)) < 0)

       {

              closesocket(socketTmp);

              MessageBox("连接服务器失败。");

              return ;

       }

       //发送请求,为简单只发100字节,在服务器端也规定100字节。

       Send(socketTmp,szText,100);

       //读取服务器端返回的数据。

       memset(szText,0,MAX_PATH);

       //接收服务器端的回应。

       Receive(socketTmp,szText,100);

 

       TCHAR szMessage[MAX_PATH];

       memset(szMessage,0,MAX_PATH);

       strcat(szMessage,szText);

       //界面上显示回应数据。

       m_ReplyBtn.SetWindowText(szMessage);

       closesocket(socketTmp);

}

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



本文出自:http://goodcandle.cnblogs.com/archive/2005/12/10/294652.aspx

【完美复现】面向配电网韧性提升的移动储能预布局与动态调度策略【IEEE33节点】(Matlab代码实现)内容概要:本文介绍了基于IEEE33节点的配电网韧性提升方法,重点研究了移动储能系统的预布局与动态调度策略。通过Matlab代码实现,提出了一种结合预配置和动态调度的两阶段优化模型,旨在应对电网故障或极端事件时快速恢复供电能力。文中采用了多种智能优化算法(如PSO、MPSO、TACPSO、SOA、GA等)进行对比分析,验证所提策略的有效性和优越性。研究不仅关注移动储能单元的初始部署位置,还深入探讨其在故障发生后的动态路径规划与电力支援过程,从而全面提升配电网的韧性水平。; 适合人群:具备电力系统基础知识和Matlab编程能力的研究生、科研人员及从事智能电网、能源系统优化等相关领域的工程技术人员。; 使用场景及目标:①用于科研复现,特别是IEEE顶刊或SCI一区论文中关于配电网韧性、应急电源调度的研究;②支撑电力系统在灾害或故障条件下的恢复力优化设计,提升实际电网应对突发事件的能力;③为移动储能系统在智能配电网中的应用提供理论依据和技术支持。; 阅读建议:建议读者结合提供的Matlab代码逐模块分析,重点关注目标函数建模、约束条件设置以及智能算法的实现细节。同时推荐参考文中提及的MPS预配置与动态调度上下两部分,系统掌握完整的技术路线,并可通过替换不同算法或测试系统进一步拓展研究。
先看效果: https://pan.quark.cn/s/3756295eddc9 在C#软件开发过程中,DateTimePicker组件被视为一种常见且关键的构成部分,它为用户提供了图形化的途径来选取日期与时间。 此类控件多应用于需要用户输入日期或时间数据的场景,例如日程管理、订单管理或时间记录等情境。 针对这一主题,我们将细致研究DateTimePicker的操作方法、具备的功能以及相关的C#编程理念。 DateTimePicker控件是由.NET Framework所支持的一种界面组件,适用于在Windows Forms应用程序中部署。 在构建阶段,程序员能够通过调整属性来设定其视觉形态及运作模式,诸如设定日期的显示格式、是否展现时间选项、预设的初始值等。 在执行阶段,用户能够通过点击日历图标的下拉列表来选定日期,或是在文本区域直接键入日期信息,随后按下Tab键或回车键以确认所选定的内容。 在C#语言中,DateTime结构是处理日期与时间数据的核心,而DateTimePicker控件的值则表现为DateTime类型的实例。 用户能够借助`Value`属性来读取或设定用户所选择的日期与时间。 例如,以下代码片段展示了如何为DateTimePicker设定初始的日期值:```csharpDateTimePicker dateTimePicker = new DateTimePicker();dateTimePicker.Value = DateTime.Now;```再者,DateTimePicker控件还内置了事件响应机制,比如`ValueChanged`事件,当用户修改日期或时间时会自动激活。 开发者可以注册该事件以执行特定的功能,例如进行输入验证或更新关联的数据:``...
下载前必看:https://pan.quark.cn/s/246cd895653f 标题所提及的“湖南省娄底市DEM数字高程数据30m(含本市级范围shp文件).zip”文件,属于地理信息系统(GIS)领域的压缩存储单元,其中收纳的是针对湖南省娄底市区域的数字高程模型(Digital Elevation Model, DEM)相关数据。 此数据集采用30米的空间分辨率,具体表现为在每30米乘以30米的网格单元内设定一个数据点来表征该区域的绝对高度,其作为地形特征研究、地表状况解析、城市布局规划、生态状况评价等工作的基础支撑。 “DEM数字高程”这一术语,指的是运用遥感科学与地理信息系统技术,将地球表面实际的地形地貌形态转化为数字化表示,用以体现地表的立体起伏形态。 30米的精确度级别表明此类数据具备较高的精细度,能够精确呈现地形的高度变化特征,尽管对于诸如微小山丘或陡峭悬崖等细微地形要素可能无法进行详细捕捉。 “shp文件”是一种被广泛采纳的GIS数据载体格式,由Esri公司研发,主要功能在于存储地理空间信息,涵盖点、线、面等多种几何形态。 在此具体情境下,shp文件用以界定娄底市的地理轮廓,从而明确数据所覆盖的地理范围。 标签中的“30米地形数据”着重突出了数据的分辨率属性,“娄底市”与“地理信息数据”则清晰界定了数据的应用范畴与领域。 压缩文件内部的文件名称清单揭示了数据的不同构成要素:1. **娄底市范围.dbf**: 此类文件为关联性的数据库文档,负责存储与.shp文件相配套的属性信息,例如地理位置的元数据详情。 2. **娄底市dem.tif.ovr**: 作为TIFF图像的辅助文件,其用途在于储存额外的元数据或重叠数据,有助于优化图像的视觉呈现效能。 3. **娄底...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值