C++ 网络编程下的socket编程(TCP\UDP),连接下位机
正常情况下我们需要对下位机进行通信需要使用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),连接下位机相关推荐
- 网络编程—网络基础概览、socket,TCP/UDP协议
网络基础概览 socket概览 socket模块-TCP/UDP的实现 TCP/UDP总结 网络基础概览 osi七层协议各层主要的协议 # 物理层传输电信号1010101010 # 数据链路层,以太网 ...
- vc 网络编程(socket)TCP/UDP 介绍
在网上找了很多的资料,现将这些资料整合起来,详细介绍一下VC下的socket编程,并提供一个服务器客户端具体的实例.希望对您有所帮助 一.原理部分 在网络编程中最常用的方案便是Client/Serve ...
- Linux Kernel TCP/IP Stack — Socket Layer — TCP/UDP Socket 网络编程
目录 文章目录 目录 TCP/UDP Socket 逻辑架构 创建 Socket 绑定 Socket 请求建立 Socket 连接 监听 Socket 接受请求 关闭连接 数据的发送和接收 send ...
- socket recv 服务端阻塞 python_网络编程(基于socket编程)
网络编程(基于socket编程) socket套接字:应用程序通常通过socket"套接字"向网络发送请求或应答网络请求,是主机间或同一计算机中的进程间相互通讯 socket是介于 ...
- 网络编程及三大协议(TCP + UDP + Http)
网络编程及三大协议(TCP + UDP + Http) 一.网络编程 1.计算机网络 是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络 ...
- Linux下简单socket编程
Linux下简单socket编程 socket的英文翻译是接口.插座的意思,很形象,就相当于将两个台电脑用一根线连起来,线的两头分别是插头,插在两台电脑上,借此实现通信. 两台电脑通信,实际上是这两台 ...
- c++多线程模式下的socket编程(线程池实现)
socket 编程可以说是一个基本的技术掌握,而多个客户端向服务端发送请求又是一个非常常见的场景,因此多线程模式下的socket编程则显得尤为常见与重要. 本文主要利用线程池的技术,来实现多线程的模式 ...
- windows 下实现socket编程_传送文件
windows 下实现socket编程_传送文件 其实,Windows下的socket编程与Linux下的类似,这里不再赘述! 参考:http://blog.csdn.net/chudongfang2 ...
- php udp发送和接收_php socket通信(tcp/udp)实例分析
本文实例讲述了php socket通信(tcp/udp)方法.分享给大家供大家参考,具体如下: 注意 1.在socket_bind的时候ip地址不能真回环地址如127.0.0.1 2.server.p ...
最新文章
- 网络数据包分析软件Wireshark简介
- 动态引入js只能生效一次_干货丨动态插入的script脚本执行时间
- 一周内,在闲鱼上被疯狂转了 2 万次的 Linux 命令大全!!!
- C语言中的static 详细分析
- 第7章:MapReduce编程
- Python 字符串操作基础
- thinking-in-java(21)并发2
- 小白也能懂的 Nacos 服务模型介绍
- mysql 日期型中文报错_mysql日期类型默认值'0000-00-00' 报错,是什么问题?
- 5404. 用栈操作构建数组
- JSP自定义标签rtexprvalue属性 1
- Python 语言程序设计(1)
- C++实现均值滤波器和中值滤波器
- AI变身超级赌神,德扑六人局击败世界冠军!
- Android Locale
- web前端面试总结(自认为还算全面哈哈哈哈哈!!!)
- 禅意Python - The Zen of Python
- 【每天一个Linux命令】09. Linux中chown的用法
- Google组织架构不学传统大公司
- 截至2020年3月,所有与Android Auto兼容的汽车
热门文章
- 【蓝桥杯刷题冲刺辅导】掌握递归·DFS解题套路,这一文足以?
- Java导出数据到Excel文件
- 百度地图离线开发demo(热力图)
- Minecraft 1.19.2 Forge模组开发 07.拼图建筑(jigsaw)
- 计算机c盘无法扩展,C盘不够大怎么办 怎么扩展C盘容量合并硬盘分区
- sqoop导入数据到hive
- 数学形态学之腐蚀算法
- 服务器文件增量备份,服务器文件增量备份
- python向kafka发送json数据_python3实现从kafka获取数据,并解析为json格式,写入到mysql中...
- Java并发编程实战(学习笔记十 第十一章 性能与可伸缩性)