UDP通信原理

1. 原理

UDP数据通讯分服务端(软件)和客户端
服务端(软件)(服务器)先运行,服务端,不需要事先知道客户端IP和port
客户端(软件)(客户端机器)后运行,一定是客户端先给服务端发
包,客户端一定先知道服务端的IP和port

2. 创建流程

服务端操作步骤
1)创建套接字,socket函数实现(可理解为创建一个接口,实现两个进程间通信)。
2)把ip地址和端口绑定到一起(和socket接口绑定),以方便接收数据。
3)接收客户端发送的数据包。
4)发送数据包给客户端。
5)关闭套接字。

客户端操作步骤(向服务端发送请求,故一定知道服务器的ip地址和端口)。
1)创建套接字,socket函数实现(可理解为创建一个接口,实现两个进程间通信)。
2)向服务端指定的ip和端口发送数据包。
3)接收服务端发来的数据包。
4)关闭socket。

图示:
在这里插入图片描述

3. 函数

3.1 创建socket

a. 头文件
#include <sys/types.h>
#include <sys/socket.h>

b. 函数原型
int socket(int domain, int type, int protocol);
int domain:地址族,具体参数可看下图,此处我们用AF_INET。
在这里插入图片描述
int type:使用协议类型,具体有如下几种类型
SOCK_STREAM 流式套接字(TCP)
SOCK_DGRAM 报文套接字(UDP)
SOCK_RAW 原始套接字: (IP,ICMP)

int protocol:协议编号,可配置为0(系统自己识别)。

c. 返回值
成功返回得到的文件描述符。
失败返回 -1。

3.2 绑定IP和port

a. 头文件
#include <sys/types.h>
#include <sys/socket.h>

b. 函数原型
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int sockfd:socket创建的文件描述符。
*const struct sockaddr addr:把IP和地址设置到对应的结构体中去。
对于该结构体我们通常使用以下结构体:
在这里插入图片描述
其中需要注意的是struct in_addr也是一个结构体
struct in_addr {
uint32_t s_addr; // 存储 IP 地址(网络字节序)
};
结构体仅包含一个名为 s_addr 的无符号 32 位整数型成员,用于存储 IP 地址。
socklen_t addrlen:表示 addr 参数对应类型的地址信息结构体的大小

c. 返回值
成功返回0。
失败返回 -1 ,并设置errno。
errno类型
EACCES:被绑定的地址是受保护的地址,仅超级用户能够
访问,例:普通用户将socket绑定到知名服务端口(端口号
为0~1023)上时,bind将返回EACCES错误。
EADDRINUSE:被绑定的地址正在使用中,例:将socket绑定到一个处于TIME_WAIT状态的socket地址。

3.3 接收数据

a.函数原型
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
int sockfd:套接字。
void *buf:数据存放的首地址,通常定义一个数组。
size_t len:期待接收的数据大小。
int flags:操作方式 0 表示默认操作。
struct sockaddr *src_addr: 获得发送方地址,谁发送的获得谁
的地址。
socklen_t *addrlen:值结果参数,必须进行初始化, 表示
表示对方实际地址的大小。

b. 返回值
成功返回实际接收的字节数,失败返回-1。

注意点
因为UDP通信没有连接的概念,所以我们每次读取数据都需要获取发送端的socket地址。即参数src_addr所指的内容,addrlen参数则指定该地址的长度。

3.4 发送数据

a.函数原型
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
int sockfd:套接字。
void *buf:数据存放的首地址,通常定义一个数组。
size_t len:期待发送的数据大小。
int flags:操作方式 0 表示默认操作。
const struct sockaddr *dest_addr: 向指定的地址发送数据。
socklen_t addrlen:发送的地址的大小。

b. 返回值
成功返回实际发送的字节数,失败返回-1。

客户端代码

#include <stdio.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
	int sockfd;
	struct sockaddr_in server_addr;
	struct sockaddr_in client_addr;
	socklen_t addrlen = sizeof(server_addr);
	char buf[100]={0};
	int n = 0;

	if(argc != 3)
	{
		fprintf(stderr,"Usage : %s ip port\n",argv[0]);
		return -1;

	}
	sockfd = socket(AF_INET,SOCK_DGRAM,0);
	if(sockfd < 0)
	{
		perror("socket");
		return -1;
	}

	bzero(&server_addr,addrlen);
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(atoi(argv[2]));
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);

	while(1)
	{
		memset(buf,0,sizeof(buf));
		fgets(buf,sizeof(buf),stdin);
		buf[strlen(buf)-1] = '\0';

		n = sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&server_addr,addrlen);
		//向服务器发送数据,sendto();
		if(n < 0)
		{
			perror("sendto");
			return -1;
		}

		bzero(buf,sizeof(buf));
		n = recvfrom(sockfd,buf,sizeof(buf),0,NULL,NULL);
		//接收服务器的数据,recvfrom()
		if(n < 0)
		{
			perror("recvfrom");
			return -1;
		}

		printf("Recv from IP: %s\n",inet_ntoa(server_addr.sin_addr));
		printf("Recv from port: %d\n",ntohs(server_addr.sin_port));
		printf("recv_buf: %s\n",buf);

	}
	close(sockfd);
	return 0;
}

服务器代码

#include <stdio.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>

int main(int argc, const char *argv[])
{
	int sockfd;
	struct sockaddr_in server_addr;
	struct sockaddr_in client_addr;
	socklen_t addrlen = sizeof(server_addr);
	char buf[100]={0};
	int n = 0;

	if(argc != 3)
	{
		fprintf(stderr,"Usage : %s ip port\n",argv[0]);
		return -1;

	}
	sockfd = socket(AF_INET,SOCK_DGRAM,0);//生成套接字
	if(sockfd < 0)//判断套接字是否生产成功
	{
		perror("socket");
		return -1;
	}

	bzero(&server_addr,addrlen);
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(atoi(argv[2]));//命令行中的端口填充进结构体
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);//命令行中的IP地址填充进结构体
	if(bind(sockfd,(struct sockaddr*)&server_addr,addrlen) < 0)
	//使用bind()函数,将套接字文件描述符和一个服务器地址类型变量进行绑定
	
	{
		perror("bind");
		return -1;
	}

	while(1)
	{
		bzero(buf,sizeof(buf));
		n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&client_addr,&addrlen);
		//接受客户端发送的数据,使用recvfrom()函数
		//接收客户端的网络数据
		if(n < 0)
		{
			perror("recvfrom");
			return -1;
		}

		printf("client IP: %s\n",inet_ntoa(client_addr.sin_addr));
		printf("client port: %d\n",ntohs(client_addr.sin_port));
		printf("recv_buf: %s\n",buf);
		
		n = sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&client_addr,addrlen);
		//向客户端发送数据,使用sendto()函数向服务器主机发送数据
		if(n < 0)
		{
			perror("sendto");
			return -1;
		}
	}
	close(sockfd);
	return 0;
}

在这里插入图片描述
该代码实现了在简单的UDP通信,通过命令行传参绑定IP地址和端口号,服务器做简单的回显。值得注意的是,客户端和服务端都需在最后关闭套接字。

  • 0
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值