正常情况下我们需要对下位机进行通信需要使用Socket进行连接操作,而在网络编程中又分为面向连接(TCP)和面向无连接(UDP)这两种,针对这两种方式,我们不做具体的原理解释,只说各自的特点和各自的应用场景:

UDP的特性是:数据报,无连接,简单,不可靠,会丢包,会乱序(实际中遇到的主要是丢包)
TCP的特性是:流式,有连接,复杂,可靠,延迟较大、带宽占用较大(均是相对于UDP来说)

有这样的特性其实是非常明显的,在早期,通常来说,我们对于传输数据量比较大的数据交互时一般都会使用UDP连接,对于文字消息我们一般采用TCP连接,但是因为保密性和数据丢包的原因,现在的聊天软件的视频通信我们一般也不再使用UDP,转而使用HTTP协议,但是UDP的弊端也恰恰是他最大的优点,对于一些对于数据量较大,且对数据准确性没有特殊请求的连接时我们还是会使用UDP协议(比如笔者在做PC机连接下位机时,通常数据超过2M/s时TCP就会出现明显的卡顿)。这里我们以连接下位机为例,来详细讲解一下基于UDP和TCP的网络编程过程(PC连接下位机),还是老规矩,先放一张图片,来看看我们想要得到的效果:需要下载示例Demo的请点击此处下载(注:此Demo仅包含客户端|上位机的创建,下位机可以像笔者一样使用TCP工具进行模拟或者直接连接对应的硬件设备)

接下来,我们大致了解一下网络编程中标准的基于Socket的TCP/UDP编程步骤:

基于TCP(面向连接)的socket编程的服务器端程序如下:

1、创建套接字(socket)
2、将套接字绑定到一个本地地址和端口上(bind)
3、将套接字设为监听模式,准备接收客户端请求(listen)
4、等待客户请求到来,当请求到来后,接收连接请求,返回一个新的对应于此次连接的套接字(accept)
5、用返回的套接字和客户端进行通信(send/recv)
6、返回,等待另一客户请求
7、关闭套接字

基于TCP(面向连接)的socket编程的客户端程序如下:

1、创建套接字(socket)
2、向服务器发出连接请求(connect)
3、和服务器端进行通信(send/recv)
4、关闭套接字

基于UDP(面向对象)的socket编程的服务器端程序如下:

1、创建套接字(socket)
2、将套接字绑定到一个本地地址和端口上(bind)
3、等待接收数据(recvfrom)
4、关闭套接字

基于UDP(面向对象)的socket编程的客户端程序如下:

1、创建套接字(socket)
2、向服务器发送数据(sendto)
3、关闭套接字

好了,接下来,详细介绍我们在MFC下实现客户端|上位机的具体步骤

Step1:我们需要新建一个MFC工程,并添加一个按钮用来触发我们的连接操作

Step2:然后我们在Dlg文件中调用Socket,名字随意

SOCKET m_socket;

Step3:添加自定义消息,首先是在头文件中宏定义

#define WM_Sock WM_USER+1

然后在类向导中添加自定义消息(注名称必须一致),否则运行过程会出现错误

完成添加后我们编辑消息处理函数的内容(这里只放最简单的一个判断,不添加其他任何操作或者处理)

 afx_msg LRESULT Csocket_demoDlg::OnSock(WPARAM wParam, LPARAM lParam){char cs[512] = "";  //定义一个用来存放接收数据的字符串csif (lParam == FD_READ) //sock收到消息,触发FD_READ{if (SOCKET_ERROR == recv(m_socket, cs, 512, NULL))//使用recv函数来进行判别收到的内容{MessageBox("接收数据失败!"); //如果触发连接错误,则弹出接收数据失败提示框return FALSE;}else{}}return 0;}

Step4:最后就是在按钮的响应事件中添加处理函数

首先是标准的TCP模式:

//这是一个标准的TCP连接过程
void Csocket_demoDlg::OnBnClickedButton1()
{// 这是一个标准的TCP连接过程CString serv_addr = "192.168.0.104", serv_port = "5050";// TODO:  在此添加控件通知处理程序代码int port;SOCKADDR_IN addr;WORD   wVersionRequested;//定义socket1.1或者socket2.0     WSADATA   wsaData;   //定义装载socket版本的变量  int   err;   //错误变量  wVersionRequested = MAKEWORD(2, 2);   //定义连接为socket2.0  err = WSAStartup(wVersionRequested, &wsaData);   //装载socket2.0支持  if (0 != err)//判断是否装载成功  {return;}if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)//判断版本号,是否和定义的一样  {WSACleanup();   //若出问题,卸载支持,并结束程序返回-1  return;}//创建TCP套接字m_socket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 0);//判断套接字错误if (INVALID_SOCKET == m_socket){MessageBox("创建套接字失败!");return;}//判断注册网络错误if (SOCKET_ERROR == WSAAsyncSelect(m_socket, m_hWnd, WM_Sock, FD_READ)){MessageBox("注册网络读取事件失败!");return;}//判断服务器地址和端口号错误if (serv_port == "" || serv_addr == ""){MessageBox("服务器地址或端口不能为空!!!");}else{port = atoi(serv_port.GetBuffer(1));//将端口字符串转换为整形addr.sin_family = AF_INET;addr.sin_addr.S_un.S_addr = inet_addr(serv_addr.GetBuffer(1));//转换服务器ip地址addr.sin_port = ntohs(port);//设置非阻塞模式unsigned long ul = 1;int ret = ioctlsocket(m_socket, FIONBIO, (unsigned long*)&ul);if (ret == SOCKET_ERROR)exit(0);//TCP模式下调用Connect()connect(m_socket, (SOCKADDR*)&addr, sizeof(SOCKADDR));//对下位机发送指令CString connected;connected = "设备已经连接";send(m_socket, connected, 50, 0);}
}

然后是一个标准的UDP连接过程代码:

// 这是一个标准的UDP连接过程
void Csocket_demoDlg::OnBnClickedButton1()
{// 这是一个标准的UDP连接过程CString serv_addr = "192.168.0.104", serv_port = "5050";// TODO:  在此添加控件通知处理程序代码int port;SOCKADDR_IN addr;WORD   wVersionRequested;//定义socket1.1或者socket2.0     WSADATA   wsaData;   //定义装载socket版本的变量  int   err;   //错误变量  wVersionRequested = MAKEWORD(2, 2);   //定义连接为socket2.0  err = WSAStartup(wVersionRequested, &wsaData);   //装载socket2.0支持  if (0 != err)//判断是否装载成功  {return;}if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)//判断版本号,是否和定义的一样  {WSACleanup();   //若出问题,卸载支持,并结束程序返回-1  return;}//创建UDP套接字m_socket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 0);//判断套接字错误if (INVALID_SOCKET == m_socket){MessageBox("创建套接字失败!");return;}//判断注册网络错误if (SOCKET_ERROR == WSAAsyncSelect(m_socket, m_hWnd, WM_Sock, FD_READ)){MessageBox("注册网络读取事件失败!");return;}//判断服务器地址和端口号错误if (serv_port == "" || serv_addr == ""){MessageBox("服务器地址或端口不能为空!!!");}else{port = atoi(serv_port.GetBuffer(1));//将端口字符串转换为整形addr.sin_family = AF_INET;addr.sin_addr.S_un.S_addr = inet_addr(serv_addr.GetBuffer(1));//转换服务器ip地址addr.sin_port = ntohs(port);//设置非阻塞模式unsigned long ul = 1;int ret = ioctlsocket(m_socket, FIONBIO, (unsigned long*)&ul);if (ret == SOCKET_ERROR)exit(0);//对下位机发送指令CString connected;connected = "设备已经连接";sendto(m_socket, connected, 50, 0,NULL,NULL);}
}

最后,提供一种非标准的连接方式

//这是一种非常规的连接过程UDP+Connect
void Csocket_demoDlg::OnBnClickedButton1()
{//这是一种非常规的连接过程UDP+ConnectCString serv_addr = "192.168.0.104", serv_port = "5050";// TODO:  在此添加控件通知处理程序代码int port;SOCKADDR_IN addr;WORD   wVersionRequested;//定义socket1.1或者socket2.0     WSADATA   wsaData;   //定义装载socket版本的变量  int   err;   //错误变量  wVersionRequested = MAKEWORD(2, 2);   //定义连接为socket2.0  err = WSAStartup(wVersionRequested, &wsaData);   //装载socket2.0支持  if (0 != err)//判断是否装载成功  {return;}if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)//判断版本号,是否和定义的一样  {WSACleanup();   //若出问题,卸载支持,并结束程序返回-1  return;}//创建TCP套接字m_socket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 0);//创建UDP套接字/*m_socket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 0);*///判断套接字错误if (INVALID_SOCKET == m_socket){MessageBox("创建套接字失败!");return;}//判断注册网络错误if (SOCKET_ERROR == WSAAsyncSelect(m_socket, m_hWnd, WM_Sock, FD_READ)){MessageBox("注册网络读取事件失败!");return;}//判断服务器地址和端口号错误if (serv_port == "" || serv_addr == ""){MessageBox("服务器地址或端口不能为空!!!");}else{port = atoi(serv_port.GetBuffer(1));//将端口字符串转换为整形addr.sin_family = AF_INET;addr.sin_addr.S_un.S_addr = inet_addr(serv_addr.GetBuffer(1));//转换服务器ip地址addr.sin_port = ntohs(port);//设置非阻塞模式unsigned long ul = 1;int ret = ioctlsocket(m_socket, FIONBIO, (unsigned long*)&ul);if (ret == SOCKET_ERROR)exit(0);//TCP模式下调用Connect()//UDP下也可以使用connect,使用connect函数之后可不必使用sendto函数connect(m_socket, (SOCKADDR*)&addr, sizeof(SOCKADDR));//对下位机发送指令CString connected;connected = "设备已经连接";send(m_socket, connected, 50, 0);}
}

这里,在文件中我已经给了详细的注释,针对非标准的连接方式特别说明几点:

1、程序中UDP和TCP的连接仅在创建套接字时有不同

//创建TCP套接字
m_socket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 0);
//创建UDP套接字
m_socket = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0, 0);

2、连接时都使用了Connect函数:这里具体的解释可以参考原文博客,这里只做简单摘要

标准的udp客户端开了套接口后,一般使用sendto和recvfrom函数来发数据,实际上,udp发送数据有两种方法供大家选用的:
方法一: 
socket----->sendto()或recvfrom() 
方法二: 
socket----->connect()----->send()或recv().(此时sendto,recvfrom仍可用)

给UDP套接口调用connect,与TCP不同的是:没有三路握手过程。内核只是检查是否存在立即可知的错误(例如一个显然不可达的目的地),记录对端的IP地址和端口号(取自传递给connect的套接口地址结构),然后立即返回到调用进程

C++ 网络编程下的socket编程(TCP\UDP),连接下位机相关推荐

  1. 网络编程—网络基础概览、socket,TCP/UDP协议

    网络基础概览 socket概览 socket模块-TCP/UDP的实现 TCP/UDP总结 网络基础概览 osi七层协议各层主要的协议 # 物理层传输电信号1010101010 # 数据链路层,以太网 ...

  2. vc 网络编程(socket)TCP/UDP 介绍

    在网上找了很多的资料,现将这些资料整合起来,详细介绍一下VC下的socket编程,并提供一个服务器客户端具体的实例.希望对您有所帮助 一.原理部分 在网络编程中最常用的方案便是Client/Serve ...

  3. Linux Kernel TCP/IP Stack — Socket Layer — TCP/UDP Socket 网络编程

    目录 文章目录 目录 TCP/UDP Socket 逻辑架构 创建 Socket 绑定 Socket 请求建立 Socket 连接 监听 Socket 接受请求 关闭连接 数据的发送和接收 send ...

  4. socket recv 服务端阻塞 python_网络编程(基于socket编程)

    网络编程(基于socket编程) socket套接字:应用程序通常通过socket"套接字"向网络发送请求或应答网络请求,是主机间或同一计算机中的进程间相互通讯 socket是介于 ...

  5. 网络编程及三大协议(TCP + UDP + Http)

    网络编程及三大协议(TCP + UDP + Http) 一.网络编程 1.计算机网络 是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络 ...

  6. Linux下简单socket编程

    Linux下简单socket编程 socket的英文翻译是接口.插座的意思,很形象,就相当于将两个台电脑用一根线连起来,线的两头分别是插头,插在两台电脑上,借此实现通信. 两台电脑通信,实际上是这两台 ...

  7. c++多线程模式下的socket编程(线程池实现)

    socket 编程可以说是一个基本的技术掌握,而多个客户端向服务端发送请求又是一个非常常见的场景,因此多线程模式下的socket编程则显得尤为常见与重要. 本文主要利用线程池的技术,来实现多线程的模式 ...

  8. windows 下实现socket编程_传送文件

    windows 下实现socket编程_传送文件 其实,Windows下的socket编程与Linux下的类似,这里不再赘述! 参考:http://blog.csdn.net/chudongfang2 ...

  9. php udp发送和接收_php socket通信(tcp/udp)实例分析

    本文实例讲述了php socket通信(tcp/udp)方法.分享给大家供大家参考,具体如下: 注意 1.在socket_bind的时候ip地址不能真回环地址如127.0.0.1 2.server.p ...

最新文章

  1. 网络数据包分析软件Wireshark简介
  2. 动态引入js只能生效一次_干货丨动态插入的script脚本执行时间
  3. 一周内,在闲鱼上被疯狂转了 2 万次的 Linux 命令大全!!!
  4. C语言中的static 详细分析
  5. 第7章:MapReduce编程
  6. Python 字符串操作基础
  7. thinking-in-java(21)并发2
  8. 小白也能懂的 Nacos 服务模型介绍
  9. mysql 日期型中文报错_mysql日期类型默认值'0000-00-00' 报错,是什么问题?
  10. 5404. 用栈操作构建数组
  11. JSP自定义标签rtexprvalue属性 1
  12. Python 语言程序设计(1)
  13. C++实现均值滤波器和中值滤波器
  14. AI变身超级赌神,德扑六人局击败世界冠军!
  15. Android Locale
  16. web前端面试总结(自认为还算全面哈哈哈哈哈!!!)
  17. 禅意Python - The Zen of Python
  18. 【每天一个Linux命令】09. Linux中chown的用法
  19. Google组织架构不学传统大公司
  20. 截至2020年3月,所有与Android Auto兼容的汽车

热门文章

  1. 【蓝桥杯刷题冲刺辅导】掌握递归·DFS解题套路,这一文足以?
  2. Java导出数据到Excel文件
  3. 百度地图离线开发demo(热力图)
  4. Minecraft 1.19.2 Forge模组开发 07.拼图建筑(jigsaw)
  5. 计算机c盘无法扩展,C盘不够大怎么办 怎么扩展C盘容量合并硬盘分区
  6. sqoop导入数据到hive
  7. 数学形态学之腐蚀算法
  8. 服务器文件增量备份,服务器文件增量备份
  9. python向kafka发送json数据_python3实现从kafka获取数据,并解析为json格式,写入到mysql中...
  10. Java并发编程实战(学习笔记十 第十一章 性能与可伸缩性)