📝前言:

这篇文章我们来讲讲Linux——udpsocket

🎬个人简介:努力学习ing
📋个人专栏:Linux
🎀CSDN主页 愚润求学
🌄其他专栏:C++学习笔记C语言入门基础python入门基础C++刷题专栏


一、预备理论知识

  • 计算机之间通过网络进行数据的传输(Socket通信),本质上是:两个计算机的两个进程间进行通信(进程是人的代表)
    • 两个网络进程间通信,看到的同一份资源是网路
    • 每个数据的发送和接收都必须经过套接字(socket)
      • 套接字负责维护网络通信的各种信息(如本地/远程地址、端口、协议类型等)。
      • 操作系统通过套接字来区分不同的网络连接和数据流。
      • 只有通过套接字,操作系统才能知道数据要发到哪里、从哪里收。
    • IP 用来标识是哪一台主机,Port端口号用来标识是主机上的哪一个进程
    • 一个端口号只能被一个进程占用,但是一个进程可以绑定多个端口号
  • 如何理解绑定?
    • 我们可以认为传输层有一张元素为:<端口号, 进程链表> 的hash
    • 当一个进程要和一个端口号绑定的时候,就会被加入到对应端口号的链表当中
    • 当收到网络传输过来的报文以后,就会提取(传输层有多种协议)其中的目的端口,然后去hash里面找到对应的进程(把数据交给对应的进程)

二、重要接口

1. socket

创建网络套接字

int socket(int domain, int type, int protocol);
  • domain:协议族,确定网络层使用的协议
    • 可以选:AF_INET(IPv4 协议),AF_INET6(IPv6 协议)…
  • type:套接字类型(常用的如下)
    • SOCK_DGRAM:无连接的 UDP 协议
    • SOCK_STREAM:面向连接的 TCP 协议
  • protocol:通常用 0 就行

TCP 是面向连接、可靠的字节流传输协议(需三次握手建立连接,数据无丢失、有序到达),而 UDP 是无连接、不可靠的数据包传输协议(无需建立连接,数据可能丢失、无序,但是 更简单,更快)

2. 发 / 收信息

向指定的IP和端口发消息

// ssize_t 是一个有符号整数类型
ssize_t sendto(
		int sockfd,          // 已绑定的套接字描述符
		const void *buf,     // 要发送数据的缓冲区
		size_t len,          // 数据长度 
		int flags,           // 发送标志
        const struct sockaddr *dest_addr,  // 目标地址结构指针
        socklen_t addrlen);  // 目标地址结构的长度

从指定 IP 和端口接收信息,将数据从内核层的Socket接收到用户层的缓冲区

ssize_t recvfrom(
		int sockfd,  // 从哪个套接字接收(远端传来的数据是先存到套接字缓冲区的)
		void *buf,   
		size_t len, 
		int flags, 
        struct sockaddr *src_addr,  // 接收到的数据来自哪个地址(对端地址)
        socklen_t *addrlen);

两个sockfd是一个东西,通过它向服务端发送数据,也通过它接收服务端返回的数据。

3. sockaddr结构体

struct sockaddr是一个C语言版本的“基类”(我们在传递参数的时候,要强转成:struct sockaddr*
对于
绑定IP和端口号就是填充对应不同协议的结构体,填充IPV4的struct sockaddr_in

  • sin_family:地址家族
  • sin_port :端口号
  • sin_addr.s_addr:IP(在sockaddr_in里面专门用一个sin_addr来管理IP,里面有本地主机以 uint32_t 存储的IPs_addr

4. bind

bind() 是一个核心系统调用,用于将套接字(socket)与特定的 IP 地址和端口号绑定,从而让程序能够在指定的地址和端口上接收网络请求。

int bind(int sockfd,  // 套接字描述符
		const struct sockaddr *addr,  // sockaddr 结构体
		socklen_t addrlen);  // addr 结构体的长度

5. 本地和网络的序列转换

关于大小端存储:
不同的机器,可能会用不同的方案(大小端)来存储IP,为了统一,网络规定,发到网络上的必须是大端的。所以需要转换。

网络转主机

  • ntohs:转端口
  • inet_ntop:转 IP
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

参数说明

  • af:地址族(Address Family),指定 IP 地址类型:
    • AF_INET:表示处理 IPv4 地址
    • AF_INET6:表示处理 IPv6 地址
  • src:指向二进制格式 IP 地址的指针:
    • 对于 IPv4,通常是 struct in_addr* 类型(存储在 struct sockaddr_in 的 sin_addr 成员中)
    • 对于 IPv6,通常是 struct in6_addr* 类型(存储在 struct sockaddr_in6 的 sin6_addr 成员中)
  • dst:指向用于存储转换结果的字符串缓冲区的指针,转换后的 IP 地址字符串会被写入这里。
  • size:dst 缓冲区的大小(字节数),用于防止缓冲区溢出。建议使用标准宏:
    • INET_ADDRSTRLEN:IPv4 地址字符串最大长度(16 字节,如 “255.255.255.255” 加终止符)
    • INET6_ADDRSTRLEN:IPv6 地址字符串最大长度(46 字节,如 “ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff” 加终止符)

主机转网络(是上面的逆操作)

  • htons接口:将 16 位端口号 从主机字节序转换为网络字节序(大端序,内部会自行判断是否需要转换)
  • inet_pton:转IP,将字符串形式的IP地址转换成网络序列(二进制,存储在结构体中)

示例:

// 处理 IPv4
struct in_addr ipv4_addr;
if (inet_pton(AF_INET, "192.168.1.1", &ipv4_addr) == 1) {
    // 转换成功
}

// 处理 IPv6
struct in6_addr ipv6_addr;
if (inet_pton(AF_INET6, "2001:db8::1", &ipv6_addr) == 1) {
    // 转换成功
}

转换好的结果会被存在ipv6_addr这个结构体里面

三、理解通信流程

服务端创建

  • 服务端创建 socket 套接字,然后 bind 一个 ip + port,则后续发送到这个 ip + port 的信息都会到这个套接字

客户端创建

  • 客户端创建socket,但是不需要显式bind

客户端发送信息给服务端

  • 客户端调用sendto发送信息给服务端(这里当然需要知道服务端的ip+port,参见sendto接口),如果这时候,客户端的socket没有绑定,则系统会自动给客户端分配一个ip+port来绑定这个socket

服务端接收客户端的信息

  • 服务端调用recvfrom从自己的socket接受信息(客户端发送信息到的ip+port是和服务端的这个socket绑定的),于是便拿到了客户端发送的信息
  • 如果在recvfrom中保存来自客户端的信息,则此时拿到的发送方(客户端)的ip+port,就是之前系统自动分配给客户端的,用于标识(发送方)主机唯一性的

套接字通信的工作原理

  • 套接字绑定ip和port,即相当于作为这个主机的这个进程的网络通信窗口
  • 当要某个主机要把数据发送出去的时候,其实是通过自己绑定的socket的发送缓冲区发送的。要发送到某个ip+port,其实是发送到了它绑定的socket的接收缓冲区。

sendto函数工作原理:

  • 数据复制:把传入的buffer的数据复制到源套接字的发送缓冲区
  • 补全源地址信息:如果这个套接字没有绑定源ip和port,系统会自动分配临时的并绑定
  • 打包成 UDP 数据包:内核会从发送缓冲区取出数据,加上UDP头部,IP头部
  • 发送:把这个数据包交给网卡,网卡通过物理网络(网线)发送到目标地址
  • 数据一旦复制到内核缓冲区,sendto 就会返回(不会等数据实际到达目标),返回成功复制的字节数【也就是说,于我们而言,数据能正常到socket发送缓冲区我们就认为发送了,后续的事(打包,路由,发送)是内核干的,对我们透明】

四,实操案例

1. 单词翻译

代码有点太长了,如果有需要,可以访问我的Github
注意:

  • 公网IP其实没有配置到我们机器的IP上,无法被bind
  • 本地环回:要求c、s必须在一台机器上,表明我们是本地通信,client发送的数据,不会被推送到网络而是在OS内部,转一圈直接交给对应的服务器端(常用于网络代码的测试)

运行结果:

客户端
在这里插入图片描述
服务端:在这里插入图片描述

2. 简单群聊

获取代码 → 点击


🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!

Logo

纵情码海钱塘涌,杭州开发者创新动! 属于杭州的开发者社区!致力于为杭州地区的开发者提供学习、合作和成长的机会;同时也为企业交流招聘提供舞台!

更多推荐