【网络编程】简易的 p2p 模型,实现两台虚拟机之间的简单点对点通信,并以小见大观察 TCP 协议的具体运行

推荐一个零声教育学习教程,个人觉得老师讲得不错,分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,点击立即学习: https://github.com/0voice 链接

基本概念

p2p, 一个让所有投资者脊背发凉的金融概念, 一个去中心化的金融概念,其实它的本质只是一个技术。P2P(Peer-to-Peer)是一种去中心化的网络架构模式,中文通常翻译为"点对点"或"对等网络"。它代表了与传统的客户端-服务器(Client-Server)模型完全不同的网络通信理念。

P2P 的核心概念

  • 去中心化:
    • 没有中央服务器控制整个网络
    • 所有参与者(节点)地位平等
    • 每个节点既是客户端又是服务器(称为"对等体")
  • 直接通信:
    • 节点之间直接连接和交换数据
    • 不需要通过中间服务器中转
    • 通信路径更短,延迟更低
  • 资源共享:
    • 每个节点贡献自己的资源(带宽、存储、计算能力)
    • 资源分布在整个网络中
    • 节点越多,网络整体能力越强

与传统客户端-服务器模型的对比

特性 P2P 网络 客户端-服务器模型
架构 去中心化 中心化
节点角色 既是客户端又是服务器 严格区分客户端和服务器
扩展性 节点越多性能越好 服务器可能成为瓶颈
可靠性 单点故障不影响整个网络 服务器故障导致服务中断
资源分布 资源分散在各个节点 资源集中在服务器

P2P 的典型应用场景

  • 文件共享:
    • BitTorrent:用户直接从其他用户下载文件片段
    • 早期Napster:音乐文件共享(混合式P2P)
  • 加密货币:
    • 比特币/以太坊:交易验证通过P2P网络完成
    • 区块链技术的基础架构
  • 即时通讯:
    • 早期Skype:语音通话直接在对等体间建立
    • 某些隐私通讯应用
  • 内容分发:
    • P2P CDN:利用用户设备分发内容
    • 直播平台的P2P加速

我所能设想到的一个应用就是 “智能家具” 的设计,我们用手机与智能家居进行点对点的 P2P 连接,直接下命令,而非绕一大圈地经过中央服务器。这样的设计才是系统开销小,用户体验好。

业务拆解

我们在前面的基本概念介绍里面已经说到过 P2P 网络的各个节点既是客户端又是服务器,本篇文章之中,我们要抓住这一个点设计一个点对点通信的简易代码。至于像加密验证等 “高级玩意”,本篇文章是绝对不会涉及的。

问题来了,我们该怎么设计呢?我们可以尝试一下问自己,到底想要什么功能效果。我问过自己,可以分成两大类——主动类和被动类。

主动类的功能:

  1. 用户之间随时发起信息。
  2. 用户选择想要连接的对象 IP(可以重置 IP)。
  3. 自己是一个客户端,可主动发起并实现与对应 IP 的远程连接。
  4. 结束程序。

被动类的功能:

  1. 自己本身是服务器,被动监听到来访 IP,并随即分配套接字资源负责对应的 I/O 任务。
  2. 自动地接收信息。(这里回想起《角头》中白毛对 “憨春” 说:“憨春大,我 BOSS 找你那么多次,你为什么都已读不回呀?啊?”)

为了简化问题,本篇文章所展示的代码,只实现 “一个设备仅有一个连接,如果想要新的连接就必须删掉旧的连接” 的设计。

对于主动类的功能,我将采用 “用户界面” 式的循环交互设计,类似的代码可见我之前写过的一篇关于 “通讯录小项目” 的文章(原文链接 在此)。

对于被动类的功能,我将采用多线程编程的设计。有两个被动类的功能,那就有两个子线程分别负责。这两个子线程因 “一个设备仅有一个连接,如果想要新的连接就必须删掉旧的连接” 的简化,而使用了 “SELECT” 定时关注连接所对应的套接字是否对接收、读写等事件就绪。select 是一种多路复用(multiplexing)I/O 机制,用于同时监视多个文件描述符(file descriptors),以确定哪些文件描述符已经准备好进行 I/O 操作(如读取、写入或异常条件)。类似的代码可见我之前写过的一篇关于多路 I/O 复用的文章(原文链接 在此)。

为了确保程序能够被正常关闭,所建立套接字都是非阻塞的,即定时执行,重复循环计时。

代码实现

准备工作

准备头文件

#include <stdio.h>
#include <stdlib.h>     //  EXIT_FAILURE 是一个标准宏,exit 函数
#include <string.h>
#include <unistd.h>     //  close 函数
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h> // 添加select用于超时处理
#include <fcntl.h>				// 用于更改套接字的模式,比如非阻塞模式
#include <errno.h>				// 这是全局变量 errno,用于健壮的读取功能

准备宏定义

#define MAX_MSG_LEN 1024
#define PORT 5000

声明与定义全局变量,该全局变量能综合、集成所有的运行参数。我们将之命名为 NodeState

// 全局状态结构体
typedef struct {
   
   
    int server_fd;
    int connection_fd;
    int running;
    pthread_mutex_t lock;
    char peer_ip[16];  // 存储点分十进制IP地址
    int peer_port;
} NodeState;

// 声明并定义全局变量
// 点号(.)在这里是C99标准引入的指定初始化器语法的一部分。它的作用是明确指定结构体成员的初始化值,而不是依赖于成员在结构体中的顺序。
NodeState node_state = {
   
   
    .server_fd = -1,
    .connection_fd = -1,
    .running = 1,
    .lock = PTHREAD_MUTEX_INITIALIZER,
    .peer_ip = "",
    .peer_port = PORT
};

需要注意到的是,点号(.)在这里是C99标准引入的指定初始化器语法的一部分。它的作用是明确指定结构体成员的初始化值,而不是依赖于成员在结构体中的顺序。

紧接着是错误处理函数,

void error(const char *msg) {
   
   
    perror(msg);
    exit(EXIT_FAILURE);     //  EXIT_FAILURE 是一个标准宏,定义在 <stdlib.h> 中,用于表示程序执行失败。
    // 当 exit 函数被调用时,程序会执行以下操作:
    // 关闭所有打开的文件:关闭所有通过标准 I/O 函数(如 fopen)打开的文件流。
    // 刷新缓冲区:刷新所有标准 I/O 缓冲区,确保所有未写入的数据都被写入目标文件或设备。
    // 调用清理函数:执行所有通过 atexit 注册的清理函数(如果有)。
    // 终止程序:终止程序的执行,并将 status 参数作为退出状态码返回给操作系统。
}

当我们结束程序的时候,需要定义清理资源函数

// 清理资源
void cleanup() {
   
   
    pthread_mutex_lock(&node_state.lock);
    if (node_state.connection_fd != -1) {
   
   
        close(node_state.connection_fd);
        node_state.connection_fd = -1;  //  重置
    }
    if (node_state.server_fd != -1) {
   
   
        close(node_state.server_fd);
        node_state.server_fd = -1;  //  重置
    }
    pthread_mutex_unlock(&node_state.lock);
    printf("[*] Resources cleaned up\n");

    return;
}

为了能让程序正常结束,而非让套接字对应的 acceptrecv 函数在用户选择退出的时候,一直处于阻塞各自的线程之中,故而我们定义了套接字设置函数

// 设置套接字为非阻塞
void set_nonblocking(int sockfd) {
   
   
    int flags = fcntl(sockfd, F_GETFL, 0);
    if (flags == -1) {
   
   
        perror("fcntl F_GETFL");
        return;
    }
    if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值