Linux网络编程系列之TCP协议编程

news2024/11/21 0:33:26

一、什么是TCP协议

        TCP(Transmission Control Protocol)协议是一种面向连接的、可靠的、基于字节流的传输控制协议,属于传输层。TCP协议可以通过错误检测、重传丢失的数据包、流量控制、拥塞控制等方式来实现可靠传输,同时也具有较好的效率和速度。

二、特性

        1、面向连接:TCP协议是一种面向连接的协议,需要在数据传输前先建立连接,传输完成后再释放连接。

        2、 可靠传输:TCP协议通过序号和确认机制来实现可靠传输,确保数据在网络中被正确无误地传输。

        3、流量控制:TCP协议通过窗口机制来实现流量控制,避免发送方发送过多数据导致接收方不堪重负。

        4、拥塞控制:TCP协议通过拥塞窗口控制、快速重传和快速恢复等机制来实现拥塞控制,避免网络拥塞导致数据传输的延迟和丢失。

        5、面向字节流:TCP协议是一种面向字节流的协议,数据被视为一连串的字节流进行传输。

        6、可以提供全双工传输:TCP协议可以同时接受发送方和接收方的数据传输,实现全双工传输。

        7、支持多路复用:TCP协议可以通过端口号来实现多路复用,使得多个应用程序可以同时使用同一个网络连接。

三、使用场景

        1、 网页浏览:网页浏览器使用TCP协议与Web服务器建立连接,以可靠地传输HTTP请求和响应数据。

        2、电子邮件传输:电子邮件客户端和邮件服务器之间的传输也使用TCP协议,以确保邮件的正确传输和接收。

        3、文件传输:文件传输协议(FTP)和远程拷贝协议(SCP)等协议均使用TCP协议进行可靠传输。

        4、数据库访问:数据库客户端通过TCP协议与数据库服务器建立连接,以进行数据的可靠读写操作(例如注册或者登录账号)。

        5、远程登录:远程登录协议(例如SSH)使用TCP协议进行可靠传输和安全身份验证。

        6、实时通信:实时通信协议(例如视频会议、语音聊天等,但一般使用UDP协议的多)也可以使用TCP协议进行可靠的数据传输。

        总之,任何需要可靠传输和连接保持的应用场景都可以使用TCP协议进行数据传输。

四、C/S架构TCP通信流程

        1、客户端

        (1)、建立套接字。使用socket()

        (2)、设置端口复用(可选,推荐)。使用setsockopt()

        (3)、绑定自己的IP地址和端口号(可以省略)。使用bind()

        (4)、向服务器发起连接请求。使用connect(),相当于第一个握手

        (5)、成功连接后可以进入收发数据。使用send()或者write()发送数据,recv()或者read()接收数据

        (6)、关闭套接字。使用close()

        2、服务端

        (1)、建立套接字。使用socket()

        (2)、设置端口复用(可选,推荐)。使用setsockopt()

        (3)、绑定自己的IP地址和端口号(不可以省略)。使用bind()

        (4)、设置监听。使用listen()

        (5)、接受连接请求。使用accept()

        (6)、成功连接后可以进入收发数据。使用send()或者write()发送数据,recv()或者read()接收数据

        (7)、关闭套接字。使用close()

五、相关的函数API

        1、建立套接字

// 建立套接字 
int socket(int domain, int type, int protocol);

// 接口说明
        返回值:成功返回一个套接字文件描述符,失败返回-1

        参数domain:用来指定使用何种地址类型,有很多,具体看别的资源
            (1)PF_INET 或者 AF_INET 使用IPV4网络协议
            (2)其他很多的,看别的资源

        参数type:通信状态类型选择,有很多,具体看别的资源
            (1)SOCK_STREAM    提供双向连续且可信赖的数据流,即TCP
            (2)SOCK_DGRAM     使用不连续不可信赖的数据包连接,即UDP
    
        参数protocol:用来指定socket所使用的传输协议编号,通常不用管,一般设为0

         2、设置端口状态

// 设置端口的状态
int setsockopt(int sockfd, 
               int level, 
               int optname,
               const void *optval, 
               socklen_t optlen);


// 接口说明
        返回值:成功返回0,失败返回-1
        参数sockfd:待设置的套接字

        参数level: 待设置的网络层,一般设成为SOL_SOCKET以存取socket层

        参数optname:待设置的选项,有很多种,具体看别的资源,这里讲常用的
            (1)、SO_REUSEADDR    允许在bind()过程中本地地址可复用,即端口复用
            (2)、SO_BROADCAST    使用广播的方式发送,通常用于UDP广播
            (3)、SO_SNDBUF       设置发送的暂存区大小
            (4)、SO_RCVBUF       设置接收的暂存区大小

        参数optval:待设置的值

        参数optlen:参数optval的大小,即sizeof(optval)

        3、绑定自己的IP地址和端口号

// 绑定自己的IP地址和端口号
 int bind(int sockfd, 
          const struct sockaddr *addr,
          socklen_t addrlen);

// 接口说明
        返回值:
        参数sockfd:待绑定的套接字

        参数addrlen:参数addr的大小,即sizeof(addr)

        参数addr:IP地址和端口的结构体,通用的结构体,根据sockfd的类型有不同的定义
        当sockfd的domain参数指定为IPV4时,结构体定义为
            struct sockaddr_in
            {
                unsigned short int sin_family;    // 需与sockfd的domain参数一致
                uint16_t sin_port;            // 端口号
                struct in_addr sin_addr;      // IP地址 
                unsigned char sin_zero[8];    // 保留的,未使用
            };
            struct in_addr
            {
                uin32_t s_addr;
            }
// 注意:网络通信时,采用大端字节序,所以端口号和IP地址需要调用专门的函数转换成网络字节序
    

        4、字节序转换接口 

// 第一组接口
// 主机转网络IP地址,输入主机IP地址
uint32_t htonl(uint32_t hostlong);

// 主机转网络端口,输入主机端口号
uint16_t htons(uint16_t hostshort);    // 常用

// 网络转主机IP,输入网络IP地址
uint32_t ntohl(uint32_t netlong);

// 网络转主机端口,输入网络端口
uint16_t ntohs(uint16_t netshort);


// 第二组接口,只能用于IPV4转换,IP地址
// 主机转网络
int inet_aton(const char *cp, struct in_addr *inp);

// 主机转网络
in_addr_t inet_addr(const char *cp);    // 常用

// 网络转主机
int_addr_t inet_network(const char *cp);

// 网络转主机
char *inet_ntoa(struct in_addr in);    // 常用

        5、设置监听

// 设置监听
int listen(int sockfd, int backlog);

// 接口说明
        返回值:成功返回0,失败返回-1
        参数sockfd:待监听的套接字
        参数backlog:指定同时能出处理的最大连接请求数量
            当sockfd的domain参数指定为AF_INET(IPV4)时,该参数backlog最大值为128

        6、向服务器发起连接请求 

// 向服务器发起连接请求
int connect(int sockfd, 
            const struct sockaddr *addr,
            socklen_t addrlen);

// 接口说明
        返回值:
        参数sockfd:成功返回0,失败返回-1
        参数addr:服务器的网络地址
        参数addrlen:参数addr的大小,即sizeof(addr)

        7、接收连接请求

// 接受连接请求
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

// 接口说明
        返回值:成功返回一个新连接上来的客户端套接字文件描述符,失败返回-1
        参数sockfd:服务端套接字
        参数addr:连接上来的客户端的网络地址
        参数addrlen:参数addr的大小,即sizeof(addr)

        8、发送数据

// 发送数据
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

// 接口说明
        返回值:成功返回成功发送的字节数,失败返回-1
        参数sockfd:发送者的文件描述符
        参数buf:数据缓冲区
        参数len:发送的数据大小
        参数flags:有很多种,一般设置为0


// 发送数据,也可以使用这个
ssize_t write(int fd, const void *buf, size_t count);

// 接口说明
        返回值:成功返回成功发送的字节数,失败返回-1
        参数fd:发送者的文件描述符
        参数buf:数据缓冲区
        参数count:发送的数据大小

        9、接收数据 

// 接收数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

// 接口说明
        返回值:成功返回实际接收的字节数,失败返回-1
            如果返回0,有以下几种情况:
            (1)、对等端已经有序关闭,下线了(TCP协议中经常出现)
            (2)、对等端发送一个字节数为0的数据报
            (3)、如果请求从流套接字接收的字节数为0,则也可能返回值0。

        参数sockfd:接收者套接字
        参数buf:数据缓冲区
        参数len:最大可接受的字节数
        参数flags:有很多种,一般设置为0

// 接收数据,也可以使用这个
ssize_t read(int fd, void *buf, size_t count);

// 接口说明
        返回值:成功返回实际接收的字节数,失败返回-1
            如果返回0,对等端已经有序关闭,下线了(TCP协议中经常出现)
        参数buf:数据缓冲区
        参数len:最大可接受的字节数
            

        10、关闭套接字

// 关闭套接字
int close(int fd);

// 接口说明
        返回值:成功返回0,失败返回-1
        参数fd:套接字文件描述符

六、案例

        使用TCP协议完成C/S架构的客户端和服务端通信演示

        客户端TcpClient.c

// TCP客户端的案例

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

#define CLIENT_IP   "192.168.64.128"    // 记得改为自己IP
#define CLIENT_PORT 10000   // 不能超过65535,也不要低于1000,防止端口误用

// 自定义的退出信号响应函数
void exit_handler(int sig)
{
    printf("[%d] exit\n", getpid());
    exit(0);
}

int main(int argc, char *argv[])
{
    // 注册自定义退出信号响应函数
    signal(34, exit_handler);

    // 1、建立套接字,指定IPV4网络地址,TCP协议
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd == -1)
    {
        perror("socket fail");
        return -1;
    }

    // 2、设置端口复用(推荐)
    int optval = 1; // 这里设置为端口复用,所以随便写一个值
    int ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
    if(ret == -1)
    {
        perror("setsockopt fail");
        close(sockfd);
        return -1;
    }

    // 3、绑定自己的IP地址和端口号(可以省略)
    struct sockaddr_in client_addr = {0};
    socklen_t addr_len = sizeof(struct sockaddr);
    client_addr.sin_family = AF_INET;   // 指定协议为IPV4地址协议
    client_addr.sin_port = htons(CLIENT_PORT);  // 端口号
    client_addr.sin_addr.s_addr = inet_addr(CLIENT_IP); // IP地址
    
    ret = bind(sockfd, (struct sockaddr*)&client_addr, addr_len);
    if(ret == -1)
    {
        perror("bind fail");
        close(sockfd);
        return -1;
    }

    // 4、向服务器发起连接请求
    uint16_t port = 0;  // 端口号
    char ip[20] = {0};  // IP地址
    struct sockaddr_in server_addr = {0};
    printf("please input server IP and port:\n");
    scanf("%s %hd", ip, &port);
    printf("IP = %s, port = %hd\n", ip, port);

    server_addr.sin_family = AF_INET;   // 指定IPV4地址类型
    server_addr.sin_port = htons(port); // 服务器端口号
    server_addr.sin_addr.s_addr = inet_addr(ip);    // 服务器IP地址
    
    // 设置简单的超时重连
    for(int i = 0; i < 10; i++)
    {
        // 向服务器发起连接请求,第一次握手
        ret = connect(sockfd, (struct sockaddr*)&server_addr, addr_len);
        if(ret == -1)
        {
            perror("connect fail");
            printf("try to connect after 1 second\n");
            sleep(1);
            if(i == 10)
            {
                printf("server log out, please connect again later\n");
                close(sockfd);
                return -1;
            }
        }
        else
        {
            printf("connect server success\n");
            break;  // 成功连接就退出
        }
    }
    
    // 5、收发数据
    char msg[128] = {0};    // 数据缓冲区
    pid_t pid = fork();
    // 父进程负责发送数据
    if(pid > 0)
    {
        while(getchar() != '\n');   // 清空多余的换行符
        while(1)
        {
            printf("please input data:\n");
            fgets(msg, sizeof(msg)/sizeof(msg[0]), stdin);

            ret = send(sockfd, msg, strlen(msg), 0);
            if(ret > 0)
            {
                printf("success: send %d bytes\n", ret);
            }
            else
            {
                perror("send error");
            }
        }
    }
    // 子进程负责接收数据
    else if(pid == 0)
    {
        while(1)
        {
            ret = recv(sockfd, msg, sizeof(msg)/sizeof(msg[0]), 0);
            if(ret > 0)
            {
                printf("recv data: %s\n", msg);
                memset(msg, 0, sizeof(msg));
            }
            // 服务器已经掉线,这里直接退出
            else if(ret == 0)
            {
                printf("server log out\n");
                close(sockfd);
                kill(getppid(), 34);    // 给父进程发送退出信号
                kill(getpid(), 34);     // 给自己发送退出信号
            }
        }
    }
    else
    {
        perror("fork fail");
        close(sockfd);
        return -1;
    }

    return 0;
}

        服务端TcpServer.c

 

// TCP服务器的案例

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

#define MAX_LISTEN  50  // 最大能处理的连接数
#define SERVER_IP   "192.168.64.128"    // 记得改为自己IP
#define SERVER_PORT 20000   // 不能超过65535,也不要低于1000,防止端口误用


// 自定义的退出信号响应函数
void exit_handler(int sig)
{
    printf("[%d] exit\n", getpid());
    exit(0);
}


int main(int argc, char *argv[])
{
    // 注册自定义退出信号响应函数
    signal(34, exit_handler);

    // 1、建立套接字,指定IPV4网络地址,TCP协议
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd == -1)
    {
        perror("socket fail");
        return -1;
    }

    // 2、设置端口复用(推荐)
    int optval = 1; // 这里设置为端口复用,所以随便写一个值
    int ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
    if(ret == -1)
    {
        perror("setsockopt fail");
        close(sockfd);
        return -1;
    }

    // 3、绑定自己的IP地址和端口号(不可以省略)
    struct sockaddr_in client_addr = {0};
    socklen_t addr_len = sizeof(struct sockaddr);
    client_addr.sin_family = AF_INET;   // 指定协议为IPV4地址协议
    client_addr.sin_port = htons(SERVER_PORT);  // 端口号
    client_addr.sin_addr.s_addr = inet_addr(SERVER_IP); // IP地址
    
    ret = bind(sockfd, (struct sockaddr*)&client_addr, addr_len);
    if(ret == -1)
    {
        perror("bind fail");
        close(sockfd);
        return -1;
    }

    // 4、设置监听
    ret = listen(sockfd, MAX_LISTEN);
    if(ret == -1)
    {
        perror("listen fail");
        close(sockfd);
        return -1;
    }

    // 5、接受连接请求
    printf("wait client connect...\n");
    uint16_t port = 0;
    char ip[20] = {0};
    int client_fd = accept(sockfd, (struct sockaddr*)&client_addr, &addr_len);
    if(client_fd == -1)
    {
        perror("accept fail");
        close(sockfd);
        return -1;
    }
    else
    {
        memset(ip, 0, sizeof(ip));
        strcpy(ip, inet_ntoa(client_addr.sin_addr));
        port = ntohs(client_addr.sin_port);
        printf("[%s:%d] connect\n", ip, port);
    }

    // 6、收发数据, 这里只做处理当个客户端的请求
    char msg[128] = {0};
    pid_t pid = fork();
    // 父进程负责发送数据
    if(pid > 0)
    {
        while(1)
        {
            printf("please input data:\n");
            fgets(msg, sizeof(msg)/sizeof(msg[0]), stdin);

            // 注意套接字是客户端的套接字,不是服务器的
            ret = send(client_fd, msg, strlen(msg), 0);
            if(ret > 0)
            {
                printf("success: send %d bytes\n", ret);
            }
            else
            {
                perror("send error");
            }
        }
    }
    // 子进程负责接收数据
    else if(pid == 0)
    {
        while(1)
        {
            ret = recv(client_fd, msg, sizeof(msg)/sizeof(msg[0]), 0);
            if(ret > 0)
            {
                printf("[%s:%d] send data: %s\n", ip, port, msg);
                memset(msg, 0, sizeof(msg));    // 清空数据区
            }
            // 客户端断开连接
            else if(ret == 0)
            {
                printf("[%s:%d] log out\n", ip, port);
                close(client_fd);
                close(sockfd);
                kill(getppid(), 34);    // 给父进程发送退出信号
                kill(getpid(), 34);     // 给自己发送退出信号
            }
        }
    }
    else
    {
        perror("fork fail");
        close(sockfd);
        return -1;
    }

    // 7、关闭套接字
    close(sockfd);

    return 0;
}

        通信演示

 

        注:上述演示的服务器只能处理一个客户端连接,如果要处理多个可以看本系列中的服务器篇,在最开头有链接

七、总结

        TCP(Transmission Control Protocol)协议是一种面向连接的、可靠的、基于字节流的传输控制协议,属于传输层,任何需要可靠传输和连接保持的应用场景都可以使用TCP协议进行数据传输。TCP协议下的客户端通信流程和服务器通信流程不完全一致,可以结合案例加深对TCP协议的理解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1064243.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

string和const char*参数类型选择的合理性对比

在编程中&#xff0c;我们经常需要处理字符串类型的参数。在C中&#xff0c;有两种常见的表示字符串的参数类型&#xff0c;即string和const char*。本文将对比这两种参数类型的特点&#xff0c;分析其在不同情况下的合理性&#xff0c;以便程序员能够根据实际需求做出正确的选…

超赞极简奶油风装修攻略~速来抄作业

如果您想将极简奶油风应用于自家装修&#xff0c;以下是小编的一些优化建议&#x1f3e0;✨&#xff1a;色彩选择&#x1f3a8;&#xff1a;主色调应选择简洁、柔和的颜色&#xff0c;如白色☁、米色☕、淡灰色&#x1f32b;等。在这些基础颜色中适度添加1-2个饱和度较高的活力…

接收机灵敏度和动态范围定义

一、接收机灵敏度 灵敏度是来自天线的最小信号电平的特定值&#xff0c;在该特定值处接收器可以提供足够的输出信噪比&#xff08;SNR&#xff09;。最小可辨别信号&#xff08;MDS&#xff09;是0dB射频信噪比&#xff08;RFSNR&#xff09;的信号电平。MDS通常以dBm表示。 图…

【kubernetes】kubernetes中的应用配置(ConfigMap和Secret)

目录 1 为什么需要ConfigMap和Secret2 k8s中给容器传递配置的方式3 ConfigMap的基本使用4 ConfigMap的实践5 Secret的基本使用6 ConfigMap和Secret的对比 1 为什么需要ConfigMap和Secret 应用程序启动过程中通常需要传递参数&#xff0c;当参数较多时会将参数保存到配置文件中…

Parse [5/10/2020 7:05:04 PM] with format [yyyy-MM-dd] error!

项目场景&#xff1a; 对日期格式转化报错&#xff1a; Parse [5/10/2020 7:05:04 PM] with format [yyyy-MM-dd] error! 问题描述 例如&#xff1a;数据日期格式无法强行转化为常见格式 String releaseDate"5/10/2020 7:05:04 PM";String format DateUtil.format…

C++设计模式-适配器(Adapter)

目录 C设计模式-适配器&#xff08;Adapter&#xff09; 一、意图 二、适用性 三、结构 四、参与者 五、代码 C设计模式-适配器&#xff08;Adapter&#xff09; 一、意图 将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工…

Python: 库decimal()用于浮点数相加

from decimal import Decimal a1.1 b2.2 print(Decimal(2.2)Decimal(1.1))结果为&#xff1a;3.3 Pyhton中浮点数是不能直接相加的。 可以看到结果并不对。 因此需要用到decimal 可以看到ac时不计算的结果是正确的。 因此在python中&#xff0c;计算浮点数时&#xff0c;一部…

SpringCloud学习一

单体应用存在的问题 随着业务的发展&#xff0c;开发变得越来越复杂。 修改、新增某个功能&#xff0c;需要对整个系统进行测试、重新部署。 一个模块出现问题&#xff0c;很可能导致整个系统崩溃。 多个开发团队同时对数据进行管理&#xff0c;容易产生安全漏洞。 各个模块…

王道考研操作系统——文件管理

磁盘的基础知识 .txt用记事本这个应用程序打开&#xff0c;文件最重要的属性就是文件名了 保护信息&#xff1a;操作系统对系统当中的各个用户进行了分组&#xff0c;不同分组的用户对文件的操作权限是不一样的 文件的逻辑结构就是文件内部的数据/记录应该被怎么组织起来&…

【C++深入浅出】类和对象下篇

一. 前言 老样子&#xff0c;先来回顾一下上期的内容&#xff1a;上期我们着重学了C类中的六大默认成员函数&#xff0c;并自己动手实现了一个日期类&#xff0c;相信各位对C中的类已经有了一定程度的了解。本期就是类和对象的最后一篇啦&#xff0c;终于要结束咯&#xff0c;吧…

Java编程题(完数)

题目 一个正整数的因子是所有可以整除它的正整数。而一个数如果恰好等于除它本身外的因子之和&#xff0c;这个数就称为完数。例如61&#xff0b;2&#xff0b;3(6的因子是1,2,3)。 现在&#xff0c;你要写一个程序&#xff0c;读入两个正整数n和m&#xff08;1<n<m<…

《Spring安全配置》

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

【Python】简记操作:Centos安装、卸载、升级Python运行环境

目录 安装 1、选择合适自己的python版本 2、选择合适的目录进行指定版本源码下载 3、解压编译安装 解压 编译安装&#xff0c;完成即可执行python相关命令 测试是否已成功安装python 4、设置python的全局环境变量&#xff08;/etc/profile&#xff09; 设置环境变量 校…

RPC分布式网络通信框架项目

文章目录 对比单机聊天服务器、集群聊天服务器以及分布式聊天服务器RPC通信原理使用Protobuf做数据的序列化&#xff0c;相比较于json&#xff0c;有哪些优点&#xff1f;环境配置使用项目代码工程目录vscode远程开发Linux项目muduo网络库编程示例CMake构建项目集成编译环境Lin…

【RabbitMQ 实战】08 集群原理剖析

上一节&#xff0c;我们用docker-compose搭建了一个RabbitMQ集群&#xff0c;这一节我们来分析一下集群的原理 一、基础概念 1.1 元数据 前面我们有介绍到 RabbitMQ 内部有各种基础构件&#xff0c;包括队列、交换器、绑定、虚拟主机等&#xff0c;他们组成了 AMQP 协议消息…

次方计数的拆贡献法(考虑组合意义)+限定类问题善用值域与位置进行ds:1006T3

对于多次方的计数问题可以考虑拆贡献。 题目问 ∣ S ∣ 3 |S|^3 ∣S∣3&#xff0c; ∣ S ∣ |S| ∣S∣ 表示选的点数。相当于在 ∣ S ∣ |S| ∣S∣ 中选了3次&#xff0c;也就是选了3个可相同的点。 先考虑3个不相同点的贡献&#xff0c;对应任意3个点&#xff0c;必然会对…

Go Gin Gorm Casbin权限管理实现 - 2. 使用Gorm存储Casbin权限配置以及`增删改查`

文章目录 0. 背景1. 准备工作2. 权限配置以及增删改查2.1 策略和组使用规范2.2 用户以及组关系的增删改查2.2.1 获取所有用户以及关联的角色2.2.2 角色组中添加用户2.2.3 角色组中删除用户 2.3 角色组权限的增删改查2.3.1 获取所有角色组权限2.3.2 创建角色组权限2.3.3 修改角色…

uni-app 经验分享,从入门到离职(实战篇)——模拟从后台获取图片路径数据后授权相册以及保存图片

文章目录 &#x1f4cb;前言⏬关于专栏 &#x1f3af;需求描述&#x1f3af;前置知识点&#x1f9e9;uni.showLoading()&#x1f9e9;uni.authorize()&#x1f9e9;uni.downloadFile()&#x1f9e9;uni.saveImageToPhotosAlbum() &#x1f3af;演示代码&#x1f9e9;关于图片接…

不能一棍子敲死刚诞生不久的USB-C,虽然它有时确实很惹人厌

今年iPhone机型最大的预期之一是从苹果专有的Lightning端口过渡到USB-C标准。一些人担心过渡需要他们更换所有配件&#xff0c;而另一些人&#xff08;包括你&#xff09;则期待着未来能够为iPad、MacBook和iPhone使用一根电缆。 然而&#xff0c;现在新机型已经问世&#xff…

微信小程序使用路由传参和传对象的方法

近期在做微信小程序开发&#xff0c;在页面跳转时&#xff0c;需要携带参数到下一个页面&#xff0c;尤其是将对象传入页面。为了方便重温&#xff0c;特此记录。 路由传字符串参数 原始页面 传递字符串参数比较简单。路由跳转有两种方式&#xff0c;一种是通过navigator组件…