当前位置: 首页 > news >正文

树莓派开发笔记09-树莓派的UDP通信实验

github主页:https://github.com/snqx-lqh
gitee主页:https://gitee.com/snqx-lqh
本项目github地址:https://github.com/snqx-lqh/RaspberryPiLearningNotes
本项目gitee地址:https://gitee.com/snqx-lqh/RaspberryPiLearningNotes
欢迎交流

UDP通信

这篇文章,将介绍如何在树莓派中使用Socket套接字进行UDP通信,分别会实现C语言版和Python版,按道理,这个应该只要是Linux系统,都支持实现。

如果想看socket相关函数的具体解析,可以去其他博客看,我这里只会按照自己的理解简要说明一下。

一般的编程流程就是

服务端:创建服务端套接字->绑定套接字->收发数据

客户端:创建客户端套接字->设置服务器IP相关数据->收发数据

C语言实现

服务端

1、创建套接字

int socket(int domain, int type, int protocol);	
  • domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPv4)、AF_INET6(IPv6)。
  • type:指定socket类型。常用的socket类型有,SOCK_STREAM(流式套接字)、SOCK_DGRAM(数据报式套接字)
  • protocol:就是指定协议。常用的协议有,IPPROTO_TCP、PPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。当protocol为0时,会自动选择type类型对应的默认协议。

2、绑定套接字

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
  • addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。

3、接收数据

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
  • sockfd:这是套接字的文件描述符,用于标识在哪个套接字上接收数据。
  • buf:这是一个指向缓冲区的指针,用于存储接收到的数据。
  • len:这是 buf 缓冲区的大小,以字节为单位。它指定了缓冲区可以接收的最大数据量。
  • flag:这个参数目前对 recvfrom 函数而言几乎总是设置为 0
  • src_addr:这是一个指向 sockaddr 结构的指针,用于存储发送数据报的源地址。
  • addrlen:该变量在调用 recvfrom 之前应该被设置为 src_addr 指向的 sockaddr 结构的大小
  • 返回值:recvfrom 成功时返回接收到的字节数。如果连接被对方正常关闭,则返回 0。如果发生错误,则返回 -1,并设置全局变量 errno 以指示错误原因。

4、发送数据

ssize_t sendto(int sockfd, const void *buf, size_t len, int  flags,const struct sockaddr *dest_addr, socklen_t addrlen);

发送数据的时候需要指定发送的对象

完整代码

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>#define  SERVER_IP   "192.168.3.18"
#define  SERVER_PORT 8888 #define BUF_SIZE 1024void Server_recvform_sendto(int fd)
{int byte = 0, cnt = 0;char buf[BUF_SIZE] = {0};socklen_t len = sizeof(struct sockaddr_in);struct sockaddr_in clientaddr;if(fd <= 0) {perror("socket fd value err");return ;}while(1){memset(buf, 0, BUF_SIZE );//读取client数据,有数据更新才读取,否则阻塞 clientaddr会把客户端连接时候的地址端口信息保存byte = recvfrom(fd, buf, BUF_SIZE, 0, (struct sockaddr *)&clientaddr, &len);	//客户端关闭时,读取数据个数为0if(byte == 0)							{printf("sockfd:%d read over\n", fd);break;}if(byte < 0){perror("read failed");	break;}printf("client IP:%s, port:%d, datalen:%d, info:%s\n", inet_ntoa(clientaddr.sin_addr), clientaddr.sin_port, byte, buf );memset(buf, 0, BUF_SIZE );sprintf(buf, "server send cnt:%d\n", ++cnt);//往刚刚连接到自己的client发送信息sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)&clientaddr, sizeof(clientaddr));}close(fd);
}int main(int argc, void *argv[] )
{int listenfd;struct sockaddr_in serveraddr; //服务端IP地址信息//1、创建套接字listenfd = socket(AF_INET, SOCK_DGRAM, 0);if(listenfd < 0){perror("Create socket fail.");return -1;}	//2、绑定套接字memset( (void*)&serveraddr,0,sizeof(struct sockaddr_in) );serveraddr.sin_family 		= AF_INET;serveraddr.sin_port			= htons(SERVER_PORT);serveraddr.sin_addr.s_addr 	= inet_addr(SERVER_IP);if(bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in))<0)	//绑定{perror("bind error.");return -1;}//3、服务器接收和发送数据Server_recvform_sendto(listenfd);return 0;
}

然后编译这段代码

gcc -Wall server.c -o server  

-Wall 表示编译时显示所有警告

编译完成后调用生成的server文件

sudo ./server

想要停止这个程序,Ctrl+c即可。

客户端

1、创建套接字

2、设置要连接的地址相关

struct sockaddr_in srvaddr;                //IPV4地址结构体
memset(&srvaddr, 0, sizeof(srvaddr));
srvaddr.sin_family = AF_INET;              
srvaddr.sin_port = htons(SERVER_PORT);           //端口号   5001~65535
srvaddr.sin_addr.s_addr = inet_addr(SERVER_IP);  //链接服务器的IP地址

主要是connect函数,他会连接到我们设定的服务器地址,UDP的Connect函数是可选的,如果使用了Connect,后面就可以像TCP一样使用write和read进行通信。

3、发送数据和读数据

这个和服务端处理一样。

完整代码

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>#define BUF_SIZE     1024
#define SERVER_IP   "192.168.3.4"	//IP号之间不能有空格
#define SERVER_PORT 8888void Client_recvfrom_sendto(int socketfd, struct sockaddr_in *addr)
{int byte = 0, cnt = 0;unsigned char data[BUF_SIZE];socklen_t len = sizeof(struct sockaddr_in);struct sockaddr_in serveraddr;while(1){memset(data, 0, BUF_SIZE );sprintf(data, "client send data cnt:%d\n", ++cnt);//往服务器地址发送信息sendto(socketfd, data, strlen(data), 0, (struct sockaddr *)addr, sizeof(*addr));memset(data, 0, BUF_SIZE );//读取server数据,有数据更新才读取,否则阻塞byte = recvfrom(socketfd, data, BUF_SIZE, 0, (struct sockaddr *)&serveraddr, &len);if(byte == 0){perror("read over");break;}if(byte < 0){perror("read failed");break;}printf("server-->client datelen:%d info:%s\r\n",byte, data);	}	return;
}int main(int argc, void *argv[] )
{int socketfd = -1;struct sockaddr_in servaddr;//1、创建套接字if( (socketfd = socket(AF_INET, SOCK_DGRAM, 0) ) ==  -1)	{perror("socket create failed!");return -1;}//2、设置要连接的服务器相关信息memset(&servaddr, 0, sizeof(servaddr) );servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = inet_addr(SERVER_IP);servaddr.sin_port = htons(SERVER_PORT);//3、服务端接收和发送信息Client_recvfrom_sendto(socketfd, &servaddr);return 0;	
}

然后编译这段代码

gcc -Wall client.c -o client  

-Wall 表示编译时显示所有警告

编译完成后调用生成的client文件

sudo ./client

想要停止这个程序,Ctrl+c即可。

Python语言实现

服务端

其实步骤和C语言的是一样的,都是

建立socket对象、绑定本地的IP地址、使用套接字收发

直接看代码注释即可

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import socketdef main():# udp 通信地址,服务器的IP+端口号udp_addr = ('192.168.17.129', 8888)# 创建socket实例 SOCK_DGRAM代表是UDPudp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# 绑定端口udp_socket.bind(udp_addr)i = 0# 等待接收对方发送的数据while True:i = i + 1# 1024表示本次接收的最大字节数 recv_data 是接收到的数据 ip_port是这串数据的发送方IP和端口recv_data,ip_port = udp_socket.recvfrom(1024)  # 打印接收到的数据print("[From %s:%d]:%s" % (ip_port[0], ip_port[1], recv_data.decode("utf-8")))# 往发送方 ip_port 发送udp_socket.sendto(("Hello,I am a UDP socket for: " + str(i)) .encode('utf-8'),ip_port)print("send %d message" % i)if __name__ == '__main__':print("udp server ")main()

客户端

客户端的流程就是建立socket对象、绑定服务器的IP地址、发送和接收数据

具体实现查看代码

import socketdef main():# udp 通信地址,服务器的IP+端口号udp_addr = ('192.168.3.4', 8888)# 创建socket实例 SOCK_DGRAM代表是UDPudp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# 发送数据到指定的ip和端口for i in range(10):udp_socket.sendto(("Hello,I am a UDP socket for: " + str(i)) .encode('utf-8'), udp_addr)print("send %d message" % i)# 1024表示本次接收的最大字节数 recv_data 是接收到的数据 ip_port是这串数据的发送方IP和端口recv_data, ip_port = udp_socket.recvfrom(1024)   print(recv_data,ip_port)# 5. 关闭套接字udp_socket.close()if __name__ == '__main__':print("udp client ")main()

http://www.mrgr.cn/news/2413.html

相关文章:

  • 【人工智能】Transformers之Pipeline(十一):零样本图片分类(zero-shot-image-classification)
  • 内网拓扑可视化及管控技术
  • TypeScript学习笔记1---认识ts与js的异同、ts的所有数据类型详解
  • .Net插件开发开源框架
  • WPF学习(8) --Windows API函数的使用
  • 探索 HarmonyOS 的层叠布局:灵活的 Stack 容器
  • 原神4.8版本抽到角色和重点培养数据表
  • 高性能企业WEB服务器
  • 水利机械5G智能制造工厂物联数字孪生平台,推进制造业数字化转型
  • OpenCV4特征匹配
  • 快速上手 iOS Protocol Buffer
  • Xshell 连接服务器
  • 人工智能将原本需要数月的镜头设计工作缩短为一天
  • JVM(Java虚拟机) - 深入了解Java中的GC命令:如何优化垃圾回收
  • 【安装】通过VirtualBox结合Vagrant来安装虚拟机
  • 【系统架构设计师-2018年】案例分析-答案及详解
  • Linux虚拟机磁盘管理-添加磁盘
  • 软件测试用例的编写(六)
  • 【LeetCode每日一题】——301.删除无效的括号
  • 并查集(路径压缩、按秩合并、按大小合并)