09 UDP通信
UDP通信¶
这篇文章,将介绍如何在树莓派中使用Socket套接字进行UDP通信,分别会实现C语言版和Python版,按道理,这个应该只要是Linux系统,都支持实现。
如果想看socket相关函数的具体解析,可以去其他博客看,我这里只会按照自己的理解简要说明一下。
一般的编程流程就是
服务端:创建服务端套接字->绑定套接字->收发数据
客户端:创建客户端套接字->设置服务器IP相关数据->收发数据
C语言实现¶
服务端¶
1、创建套接字
- 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、绑定套接字
- 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 1024
void 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);
//客户端关闭时,读取数据个数为0
if(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;
}
然后编译这段代码
-Wall 表示编译时显示所有警告
编译完成后调用生成的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 8888
void 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;
}
然后编译这段代码
-Wall 表示编译时显示所有警告
编译完成后调用生成的client文件
想要停止这个程序,Ctrl+c即可。
Python语言实现¶
服务端¶
其实步骤和C语言的是一样的,都是
建立socket对象、绑定本地的IP地址、使用套接字收发
直接看代码注释即可
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import socket
def main():
# udp 通信地址,服务器的IP+端口号
udp_addr = ('192.168.17.129', 8888)
# 创建socket实例 SOCK_DGRAM代表是UDP
udp_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 socket
def main():
# udp 通信地址,服务器的IP+端口号
udp_addr = ('192.168.3.4', 8888)
# 创建socket实例 SOCK_DGRAM代表是UDP
udp_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()