900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > C++ 网络编程下的socket编程(TCP\UDP) 连接下位机

C++ 网络编程下的socket编程(TCP\UDP) 连接下位机

时间:2024-02-15 06:29:56

相关推荐

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.0WSADATA 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.0WSADATA 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+Connectvoid 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.0WSADATA 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的套接口地址结构),然后立即返回到调用进程

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。