【Linux篇】Socket编程UDP

【Linux篇】Socket编程UDP
📌 个人主页:孙同学_
🔧 文章专栏:Liunx
💡 关注我,分享经验,助你少走弯路!

文章目录

Socket编程UDP

UDP是一种无连接、面向数据报、不可靠的传输层协议。具有不建立连接,不保证到达,不保证顺序,不重传,不拥塞控制,速度快,开销小 的特点。
我们想要网络通信,想要UDP的编写,我们想要以网络收发的话首先得把网络文件打开.

创建一个套接字

在这里插入图片描述
#include<sys/types.h>/* See NOTES */#include<sys/socket.h>intsocket(int domain,int type,int protocol);
  • domain:第一个参数是一个域的概念,表明所创建出来的套接字是本地通信还是网络通信,AF_UNIX表示本地通信,AF_INET表示网络通信。
  • type:表示所要创建的套接字的类型,最常见的有两种UDPTCP
  • protocol:表示我们要设定的协议类型。

返回值:创建成功返回一个文件描述符,创建失败-1被返回

为套接字绑定一个端口号

创建套接字相当于打开了文件,将来别人给我们发消息时,我们的ip是多少,端口号是多少别人并不清楚,而且这个网络文件并没有套接字相关的信息,所以我们需要把我们刚刚打开的网络文件和套接字信息进行绑定。

在这里插入图片描述
#include<sys/types.h>/* See NOTES */#include<sys/socket.h>intbind(int sockfd,conststructsockaddr*addr,socklen_t addrlen);

sockfd:就是我们刚才创建socket时对应的返回值。
sockaddr *addr:sockaddr结构
addrlen:第二个结构体参数的大小

返回值:绑定成功0被返回,否则-1被返回

清零一个缓冲区

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


我是一个服务器,将来客户端要给我发消息,我也要给客户端发消息,我会不会把我的端口号和ip地址也发给对方?
IP地址的信息和端口号信息一定要发送给网络!
而当前我们定义的IP地址和端口号在我们本地存储,属于本地存储格式。
所以我们就要把我们的数据从本地格式转化成网络序列

在这里插入图片描述


IP地址属于4字节风格的IP地址。而我们服务器里面保存的ip地址用的是字符串风格的ip地址,点分十进制,所以在IP地址这里我们不仅仅要做本地风格转网络序列,我们还要将IP地址转成4字节。
所以:a.将IP地址转成4字节。b.将4字节转成网络序列。

inet_addr

在这里插入图片描述


这一个函数就将上面的两步工作全做了.

UDP套接字的初始化部分

voidInit(){//1. 创建套接字 _sockfd =socket(AF_INET,SOCK_DGRAM,0);//创建套接字if(_sockfd <0){LOG(LogLevel::FATAL)<<"socket error!";exit(1);}LOG(LogLevel::INFO)<<"socket error,sockfd:"<< _sockfd;//2.绑定套接字信息 IP+port//2.1 填充sockaddr_in结构体structsockaddr_in local;bzero(&local,sizeof(local)); local.sin_family = AF_INET;//协议家族,表示网络通信 local.sin_port =htons(_port);//ip地址也是如此 local.sin_addr.s_addr =inet_addr(_ip.c_str());//结构体不能直接赋值int n =bind(_sockfd,(structsockaddr*)&local,sizeof(local));//绑定if(n <0){LOG(LogLevel::FATAL)<<"bind error";exit(2);}LOG(LogLevel::INFO)<<"bind success,sockfd:"<< _sockfd;}
为什么要用htons?
因为网络字节序是大端

启动(Start)
我们使用的大部分软件,一旦打开都是不会自动退出的。死循环

我们对应的服务端一要收消息,二要发消息

收消息

在这里插入图片描述
 ssize_t recvfrom(int sockfd,void*buf, size_t len,int flags,structsockaddr*src_addr, socklen_t *addrlen);//成功返回实际收到的字节数。-1表示出错

sockfd:我们刚刚创建的套接字
*buf,len:用户自定义的缓冲区和该缓冲区的长度。把收来的数据放入缓冲区中
flags:阻塞式的读或者非阻塞式的读。默认设置为0表示阻塞式的IO
*src_addr:输出型参数,要求我们用户传递一个sockaddr_in结构
*addrlen:既是输入又是输出,作为输入时表明用户传进来的结构体的大小。返回时,即输出时,是实际读到的信息的大小

返回值;成功返回实际收到的数据的字节数,失败返回-1

char buffer[1024];structsockaddr_in peer; socklen_t len =sizeof(peer);//1. 收消息 size_t s =recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(structsockaddr*)&peer,&len);if(s >0){ buffer[s]=0;//最后一位置0LOG(LogLevel::DEBUG)<<"buffer:"<< buffer;//2. 发消息}

发消息

在这里插入图片描述
ssize_t sendto(int sockfd,constvoid*buf, size_t len,int flags,conststructsockaddr*dest_addr, socklen_t addrlen);

UDP的sockfd既可以读又可以写。UDP通信其实是全双工的。
sockfd:
*buf,len:待发送数据的起始地址和长度。
flags:为0表示阻塞式发送
*dest_addr,addrlen:表示消息要发送给谁

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

//2. 发消息 std::string echo_string ="server echo@: "; echo_string += buffer;sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(structsockaddr*)&peer,len);

所以未来客户端给服务器发消息,服务器就能直接收到,所以UDP不面向连接。

Client访问目标服务器要知道服务器的什么??
必须知道服务器的IP地址和端口号
但是作为服务端,我怎么指代服务器端的ip地址和端口号呢?
客户端和服务器是一家公司写的,所以客户端就内置了服务端的ip和端口号

// 问题:client要不要绑定,client要不要显示的绑定?
//答案是client是需要绑定的,但是client端不需要我们显式的调用bind 函数

在我们首次使用UDP发送消息的时候,调用sendto这样的接口的时候,操作系统会自动给我们的客户端进行绑定。
1.客户端的ip地址操作系统是知道的
2.客户端的端口号采用随机端口号的方式

为什么?
一个端口号只能被一个进程绑定,
为什么要让客户端进行随机的绑定端口号呢?
答案是如果让我们今天显式的去bind,其实显式的去bind也是可以的,但是我们不推荐这样干,我们客户端采用随机端口的方式是为了保证客户端的端口号不发生冲突。
结论:客户端采用随机端口号的方式,解决端口号冲突的问题

客户端的端口号是几不重要,知道保证是唯一的就行。

为什么服务器端需要显式的绑定呢?
服务器端将来会有很多的客户端来访问服务端,所以服务端的端口号和ip地址必须是众所周知的并且不能随意改变的。

所以客户端我们就只需创建套接字就可以了

小知识:

AF_INET表示的是协议家族
AF_INET也叫做PF_INET
在这里插入图片描述


绑定本地环回

在这里插入图片描述


可以通信

在这里插入图片描述


绑定内网ip

在这里插入图片描述


在这里插入图片描述


也可以通信

在这里插入图片描述

现象1:绑定公网ip无法绑定
现象2:绑定127.0.0.1 或者内网 ip可以绑定
现象3:服务端绑定内网ip,客户端用127.0.0.1访问不了。反过来也是不行。

为什么用公网ip不行?
主要原因是公网ip并没有配置到我们的机器上,公网ip是和我们的内网ip是做了映射的。所以公网ip无法被直接Bind

为什么服务端绑定内网ip,客户端用127.0.0.1访问不了?
我们可以用netstat -anup
其中的a表示的是所有的意思,u表示的是udp,p表示显示进程相关的信息,n表示把能显示成数字的数字化。


我i们的服务器绑定的ip地址是172的,如果我们拿客户端127去访问,但是服务器中并没有127的ip地址,所以我们访问不到。

结论: 如果我们显示的进行地址bind,client未来访问的时候,就必须使用server端绑定的地址信息。

我们一般在做服务器开发的时候,我们所对应的服务器端不建议手动绑定特定的ip,不仅仅是因为公网ip不让我们绑定,并且客户端只能用绑定的ip。

在这里插入图片描述


我们的服务端在绑定的时候必须绑成INADDR_ANY,直接填成这宏即可,而这个宏对应的值其实是0.
为什么呢?
如果我们的服务端绑定固定的ip,那么服务端就只能和固定ip的客户端进行通信,只能收到特定ip的报文,但是如果我们今天有多个ip呢?
服务端的ip设置为0表示的是来自任何ip的特定端口号的报文我们都能进行接收。

那么就意味着我们创建服务端的时候不用把ip传进来,在构造的时候,只需要一个端口号。

服务端起来了

在这里插入图片描述


我们可以看到服务端的地址是0.0.0.0,端口号是8080

在这里插入图片描述


服务端的ip地址为0的话就意味着拿这台机器上的任何一个ip地址就都能访问服务端了。

在这里插入图片描述


客户端绑定本地环回

在这里插入图片描述


客户端绑定内网IP

在这里插入图片描述


客户端绑定公网IP


我们所对应的文件描述符在线程之间是共享的吗?
答案是是的
我们的udp是支持全双工的,有没有一种可能一个线程正在读,另一个线程正在写呢?
答案是可以的

我们的线程对应的任务是什么?
消息转发,即消息路由

补充内容:
地址转化函数

sockaddr_in 中的成员 struct in_addr
sin_addr
表示 32 位 的 IP 地址

但是我们通常用点分十进制的字符串表示 IP 地址,以下函数可以在字符串表示 和in_addr 表示之间转换;
字符串转 in_addr 的函数:

在这里插入图片描述


in_addr 转字符串的函数:

在这里插入图片描述


带const的参数只能是输入。


UDP通信流程拆分

服务端(Server):

创建一个Socket.绑定(Bind)一个固定的地址和端口(不然别人不知道往哪里寄信)。等着收信(recvfrom),收到后可以回信(sendto)。

客户端(Client)

创建一个Socket.直接写好地址发信(sendto)。等着收回信(recvfrom)。

简单演示:

第一步:准备“信封”结构体
我们用struct sockaddr_in来表示地址。

structsockaddr_in addr; addr.sin_family = AF_INET;// 使用 IPv4 addr.sin_port =htons(8888);// 端口号,htons 是为了处理字节序 addr.sin_addr.s_addr = INADDR_ANY;// 监听所有网卡地址

第二步:服务端代码示例(server.c)

#include<stdio.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<string.h>intmain(){// 1. 创建 UDP Socketint sockfd =socket(AF_INET, SOCK_DGRAM,0);// 2. 准备地址并绑定structsockaddr_in servaddr, cliaddr;memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = INADDR_ANY;// 监听任何 IP servaddr.sin_port =htons(8888);// 监听 8888 端口bind(sockfd,(structsockaddr*)&servaddr,sizeof(servaddr));printf("服务器已启动,等待数据...\n");// 3. 接收数据char buffer[1024];socklen_t len =sizeof(cliaddr);int n =recvfrom(sockfd, buffer,1024,0,(structsockaddr*)&cliaddr,&len); buffer[n]='\0';printf("收到来自客户端的消息: %s\n", buffer);// 4. 原样发回去sendto(sockfd, buffer, n,0,(structsockaddr*)&cliaddr, len);close(sockfd);return0;}

第三步:客户端代码示例(client.c)

#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<arpa/inet.h>#include<sys/socket.h>intmain(){// 1. 创建 UDP Socket// AF_INET: 使用 IPv4 地址// SOCK_DGRAM: 使用数据报协议(即 UDP)int sockfd =socket(AF_INET, SOCK_DGRAM,0);if(sockfd <0){perror("Socket creation failed");return-1;}// 2. 准备服务端的地址信息structsockaddr_in servaddr;memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port =htons(8888);// 必须和服务器监听的端口一致// 将字符串形式的 IP(如 127.0.0.1)转换为网络二进制格式// 127.0.0.1 是本地回环地址,代表“这台电脑自己”if(inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr)<=0){printf("Invalid address\n");return-1;}// 3. 发送数据给服务端char*msg ="你好,服务器!我是小白。";sendto(sockfd, msg,strlen(msg),0,(structsockaddr*)&servaddr,sizeof(servaddr));printf("消息已发送...\n");// 4. 接收服务端的“回声”char buffer[1024];socklen_t len =sizeof(servaddr);// 此时用 servaddr 接收返回信息即可int n =recvfrom(sockfd, buffer,1024,0,(structsockaddr*)&servaddr,&len); buffer[n]='\0';// 添加字符串结束符printf("收到服务器的回应: %s\n", buffer);// 5. 关门大吉close(sockfd);return0;}

扩展:
虽然TCP更稳定,但是UDP在以下场景是王者:

  • 视频通话/直播:卡一下没关系,只要实时就行。
  • 在线游戏:你不能接受点一下技能要等1秒的握手延迟。
  • 简单查询:比如DNS查询。

👍 如果对你有帮助,欢迎:

  • 点赞 ⭐️
  • 收藏 📌
  • 关注 🔔

Read more

救命!这 AI 写代码比我还快?速戳飞算 JavaAI 2.0测评

救命!这 AI 写代码比我还快?速戳飞算 JavaAI 2.0测评

救命!这 AI 写代码比我还快?飞算 JavaAI 2.0.0 测评速戳 * 前言 * 一、飞算 AI,功能有多惊艳?看这👇 * 二、亲身试炼:飞算 JavaAI 如何变身为我的 “开发战友”?😚 * 2.1飞算AI插件的安装 * 2.2 体验飞算AI生成代码 * 2.3创建一个完整项目的流程 * 三、飞算AI与其他主流开发平台进行对比 * 3.1飞算JavaAI 的核心优势 * 3.2飞算AI支持的Java版本 * 总结: 前言 当 “传统代码开发” 遇上 “飞算 AI”,会迸发出怎样的惊喜?回想以往,面对复杂的功能需求,程序员只能手动编写大量代码。而现在,有了飞算 AI,

By Ne0inhk
JAVA IO流:从基础原理到实战应用

JAVA IO流:从基础原理到实战应用

JAVA IO流:从基础原理到实战应用 1.1 本章学习目标与重点 💡 掌握IO流的核心概念与分类,理解字节流与字符流的区别和适用场景。 💡 熟练使用字节流完成文件的读取与写入操作,解决文件拷贝等实际问题。 💡 掌握字符流的使用方法,处理文本文件的编码与解码问题。 💡 了解缓冲流、转换流、对象流等高级IO流的原理,提升IO操作效率。 ⚠️ 本章重点是 字节流与字符流的核心用法 和 高级IO流的实战应用,这是JAVA文件操作的必备技能。 1.2 IO流核心概念与分类 1.2.1 什么是IO流 💡 IO流(Input/Output Stream)是JAVA中用于处理设备之间数据传输的技术,主要负责数据的读取(Input)和写入(Output)。 常见的IO操作包括文件读写、网络通信数据传输等。IO流的核心思想是以流的方式处理数据,数据像水流一样从一个设备流向另一个设备,实现数据的传输与处理。 1.2.2 IO流的分类标准 JAVA中的IO流体系庞大,可按照不同标准进行分类,核心分类方式有以下三种: 1.

By Ne0inhk
幻想简历!博主本人期望的 AI Agent 全栈简历:Java + Python + Vue3 跨语言实战,代码已开源!

幻想简历!博主本人期望的 AI Agent 全栈简历:Java + Python + Vue3 跨语言实战,代码已开源!

幻想简历!博主本人期望的 AI Agent 全栈简历:Java + Python + Vue3 跨语言实战,代码已开源! AI Agent 面试八股文100问,点击我跳转!!! 大家好,我是 ZEEKLOG 上累计浏览量百万的技术博主 👋 过去一年,我从持续输出 Java/前端/AI 技术文章,逐步转向 工程化落地 AI Agent 系统 的实战开发。 如今,我将自己打磨数月的 Agent 实习生简历 完整公开—— 不仅包含 跨语言全栈技术栈(Java + Python + Vue3),还附带 两个已开源的生产级项目(金融信贷平台 + AI 刷题系统),代码、文档、部署方案一应俱全。 这篇简历不是“纸上谈兵”

By Ne0inhk
Java+Leaflet:湖南省道路长度WebGIS的构建与实践

Java+Leaflet:湖南省道路长度WebGIS的构建与实践

目录 前言 一、基础空间数据简介 1、涉及相关表 2、省域道路长度检索 二、Java后台实现 1、道路视图对象 2、Mapper空间检索查询 3、控制API实现 三、WebGIS界面实现 1、里程图例及初始化 2、各地市信息展示 四、成果展示 1、总体展示 2、分区域说明 五、总结 前言         在当今数字化时代,地理信息系统(GIS)技术在各个领域都发挥着至关重要的作用。它不仅为城市规划、交通管理、环境保护等提供了强大的技术支持,也为公众获取地理信息提供了便捷的途径。湖南省作为中国中部地区的重要省份,拥有复杂的地理环境和庞大的交通网络。如何高效地管理和展示湖南省的道路长度信息,对于交通规划、物流运输以及公众出行都具有极其重要的意义。因此,我们开展了基于Java和Leaflet的湖南省道路长度WebGIS系统的构建与实践研究。         湖南省地处中国中部,交通网络密集且复杂。随着经济的快速发展和城市化进程的加快,湖南省的道路建设不断推进,

By Ne0inhk