简单的TCP网络程序·单进程(后端服务器)

news2024/10/5 23:29:05

目录

文件1:tcpServer.cc

文件2:tcpServer.hpp

1.提出日志概念 -- 在后续完善

日志格式 -- 暂定简单的打印功能

2.创建套接字

SOCK_STREAM -- socket参数

3.bind自己的套接字

4.设置socket 为监听状态 *

新接口1:listen

函数1:initServer()

新接口2:accept *

接口1:read

接口2:write

文件描述符本质是数组下标 -- 有限

ulimit -- 查看本机可以开多少个文件描述符

函数2:serviceIO()

至此基本的功能完成 -- 测试1

准备工作

文件3:tcpClient.cc

文件4:tcpClient.hpp

函数3:initClient()

新接口3:connect *

函数4:start()

至此TCP通信的功能完成 -- 测试2

全部代码

log.hpp

makefile

tcpClient.cc

tcpClient.hpp

tcpServer.cc

tcpServer.hpp


直接代码开整

文件1:tcpServer.cc

准备阶段 -- 目前和UDP是一样的

#include "tcpServer.hpp"
#include <memory>

using namespace std;
using namespace server;

static void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " local_port\n\n"; // 命令提示符
}

// tcp服务器,启动和 udp server 一模一样
// ./tcpserver lock_port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);

    unique_ptr<TcpServer> tsvr(new TcpServer());
    tsvr->initServer();
    tsvr->start();
     
    return 0;
}

文件2:tcpServer.hpp

1.提出日志概念 -- 在后续完善

日志格式 -- 暂定简单的打印功能

这个日志在后序完善TCP之后再进行修改,现在只实现简单的打印功能

2.创建套接字

SOCK_STREAM -- socket参数

3.bind自己的套接字

        这里有个细节,我们会发现当我们接受数据的时候是不需要主机转网路序列的,因为关于IO类的接口,内部都帮我们实现了这一功能,这里不帮我们做是因为我们传入的是一个结构体,系统做不到

4.设置socket 为监听状态 *

新接口1:listen

底层链接的长度+1,先不管他,在后序讲原理再讲述

函数1:initServer()

        void initServer()
        {
            // 1. 创建socket文件套接字对象 -- 流式套接字
            _sock = socket(AF_INET, SOCK_STREAM, 0); // 第三个参数默认 0
            if (_sock < 0)
            {
                logMessage(FATAL, "create socket error");
                exit(SOCKET_ERR);
            }
            logMessage(NORMAL, "create socket success");

            // 2.bind绑定自己的网路信息 -- 注意包含头文件
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_port = htons(_port);      // 这里有个细节,我们会发现当我们接受数据的时候是不需要主机转网路序列的,因为关于IO类的接口,内部都帮我们实现了这一功能,这里不帮我们做是因为我们传入的是一个结构体,系统做不到
            local.sin_addr.s_addr = INADDR_ANY; // 接受任意ip地址
            if (bind(_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                logMessage(FATAL, "bind socket error");
                exit(BIND_ERR);
            }
            logMessage(NORMAL, "bind socket success");

            // 3. 设置socket 为监听状态 -- TCP与UDP不同,它先要建立链接之后,TCP是面向链接的,后面还会有“握手”过程
            if (listen(_sock, gbacklog) < 0) // 第二个参数backlog后面再填这个坑
            {
                logMessage(FATAL, "listen socket error");
                exit(LISTEN_ERR);
            }
            logMessage(FATAL, "listen socket success");
        }

注意这里是起始版本,在认识下面的一个接口的时候,需要整改

新接口2:accept *

        一个比喻:就像一家饭店的门口招呼人的张三,当张三从外边招呼人进来的时候,就向饭店里面喊人,让李四去服务客人,但是张三不会进来,又返回去在门口拉客

        因为随着客户端的不断增多,TCP服务器上可能存在多个套接字,就像饭店里面会有多个客人有多个服务员

至此我们需要把之前的_sock 修改为 _listensock

至此我们获取的sock就是一个文件操作符,可以使用文件操作类的函数进行处理,例如read之类的

接口1:read

从一个文件描述符中读取

接口2:write

文件描述符本质是数组下标 -- 有限

ulimit -- 查看本机可以开多少个文件描述符

函数2:serviceIO()

        void serviceIO(int sock)
        {
            // 先用最简单的,读取再写回去
            char buffer[1024];
            while (true)
            {
                ssize_t n = read(sock, buffer, sizeof(buffer) - 1);
                if (n > 0)
                {
                    // 截至目前,我们把读到的数据当作字符串
                    buffer[n] = 0;
                    std::cout << "recv message: " << buffer << std::endl;

                    std::string outbuffer = buffer;
                    outbuffer += "server[echo]";

                    write(sock, outbuffer.c_str(), outbuffer.size()); // 在多路转接的时候再详谈write的返回值
                }
                else if(n == 0)
                {
                    // 代表client退出 -- 把它想象成一个建立好的管道,客户端不写了,并且把它的文件描述符关了,读端就会像管道一样读到 0 TCP同理
                    logMessage(NORMAL, "client quit, me too!");
                }
            }
        }

至此基本的功能完成 -- 测试1

准备工作

文件3:tcpClient.cc

调用逻辑 

#include "tcpClient.hpp"
#include <memory>

using namespace std;

static void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " serverip serverport\n\n"; // 命令提示符
}

// ./tcpclient serverip serverport  调用逻辑
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);

    unique_ptr<TcpClient> tcli(new TcpClient(serverip, serverport));
    tcli->initClient();
    tcli->start();

    return 0;
}

文件4:tcpClient.hpp

函数3:initClient()

    void initClient()
    {
        // 1. 创建socket
        _sock = socket(AF_INET, SOCK_STREAM, 0);
        if (_sock < 0)
        {
            // 客户端也可以有日志,不过这里就不再实现了,直接打印错误
            std::cout << "socket create error" << std::endl;
            exit(2);
        }

        // 2. tcp的客户端要不要bind? 要的! 但是不需要显示bind,这里的client port要让OS自定!
        // 3. 要不要listen? -- 不需要!客户端不需要建立链接
        // 4. 要不要accept? -- 不要!
        // 5. 要什么? 要发起链接!
    }

新接口3:connect *

函数4:start()

 void start()
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(_serverport);
        server.sin_addr.s_addr = inet_addr(_serverip.c_str());

        if (connect(_sock, (struct sockaddr *)&server, sizeof(server)) != 0)
        {
            std::cerr << "socket connect error" << std::endl;
        }
        else
        {
            std::string msg;
            while (true)
            {
                std::cout << "Enter# ";
                std::getline(std::cin, msg);
                write(_sock, msg.c_str(), msg.size());

                char buffer[NUM];
                int n = read(_sock, buffer, sizeof(buffer) - 1);
                if (n > 0)
                {
                    // 目前我们把读到的数据当成字符串, 截至目前
                    buffer[n] = 0;
                    std::cout << "Server回显# " << buffer << std::endl;
                }
                else
                {
                    break;
                }
            }
        }
    }

至此TCP通信的功能完成 -- 测试2

        但是至此,我们所写的不过是一个单进程版的,所以会出现下面的情况,后续需要进一步修改成为多进程形式的

全部代码

log.hpp

#pragma once

#include <iostream>
#include <string>

// 定义五种不同的信息
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3     //一种不影响服务器的错误
#define FATAL 4     //致命错误

void logMessage(int level, const std::string message)
{
    // 格式如下
    // [日志等级] [时间戳/时间] [pid] [message]
    // [FATAL0] [2023-06-11 16:46:07] [123] [创建套接字失败]

    // 暂定
    std::cout << message << std::endl;
}

makefile

cc=g++
.PHONY:all
all:tcpserver tcpclient

tcpclient:tcpClient.cc
	$(cc) -o $@ $^ -std=c++11

tcpserver:tcpServer.cc
	$(cc) -o $@ $^ -std=c++11
 
.PHONY:clean
clean:
	rm -f tcpserver tcpclient

tcpClient.cc

#include "tcpClient.hpp"
#include <memory>

using namespace std;

static void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " serverip serverport\n\n"; // 命令提示符
}

// ./tcpclient serverip serverport  调用逻辑
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);

    unique_ptr<TcpClient> tcli(new TcpClient(serverip, serverport));
    tcli->initClient();
    tcli->start();

    return 0;
}

tcpClient.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#define NUM 1024

class TcpClient
{
public:
    TcpClient(const std::string &serverip, const uint16_t &port)
        : _sock(1), _serverip(serverip), _serverport(port)
    {
    }
    void initClient()
    {
        // 1. 创建socket
        _sock = socket(AF_INET, SOCK_STREAM, 0);
        if (_sock < 0)
        {
            // 客户端也可以有日志,不过这里就不再实现了,直接打印错误
            std::cout << "socket create error" << std::endl;
            exit(2);
        }

        // 2. tcp的客户端要不要bind? 要的! 但是不需要显示bind,这里的client port要让OS自定!
        // 3. 要不要listen? -- 不需要!客户端不需要建立链接
        // 4. 要不要accept? -- 不要!
        // 5. 要什么? 要发起链接!
    }

    void start()
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(_serverport);
        server.sin_addr.s_addr = inet_addr(_serverip.c_str());

        if (connect(_sock, (struct sockaddr *)&server, sizeof(server)) != 0)
        {
            std::cerr << "socket connect error" << std::endl;
        }
        else
        {
            std::string msg;
            while (true)
            {
                std::cout << "Enter# ";
                std::getline(std::cin, msg);
                write(_sock, msg.c_str(), msg.size());

                char buffer[NUM];
                int n = read(_sock, buffer, sizeof(buffer) - 1);
                if (n > 0)
                {
                    // 目前我们把读到的数据当成字符串, 截至目前
                    buffer[n] = 0;
                    std::cout << "Server回显# " << buffer << std::endl;
                }
                else
                {
                    break;
                }
            }
        }
    }
    ~TcpClient()
    {
        if(_sock >= 0) close(_sock);    //不写也行,因为文件描述符的生命周期随进程,所以进程退了,自然也就会自动回收了
    }

private:
    int _sock;
    std::string _serverip;
    uint16_t _serverport;
};

tcpServer.cc

#include "tcpServer.hpp"
#include <memory>

using namespace server;
using namespace std;

static void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " local_port\n\n"; // 命令提示符
}

// tcp服务器,启动上和udp server一模一样
// ./tcpserver local_port
int main(int argc, char *argv[])
{ 
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);

    unique_ptr<TcpServer> tsvr(new TcpServer(port));
    tsvr->initServer();
    tsvr->start();

    return 0;
}

tcpServer.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "log.hpp"

namespace server
{
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR

    };

    static const uint16_t gport = 8080;
    static const int gbacklog = 5; // 10、20、50都可以,但是不要太大比如5千,5万

    class TcpServer
    {
    public:
        TcpServer(const uint16_t &port = gport) : _listensock(-1), _port(port)
        {
        }
        void initServer()
        {
            // 1. 创建socket文件套接字对象 -- 流式套接字
            _listensock = socket(AF_INET, SOCK_STREAM, 0); // 第三个参数默认 0
            if (_listensock < 0)
            {
                logMessage(FATAL, "create socket error");
                exit(SOCKET_ERR);
            }
            logMessage(NORMAL, "create socket success");

            // 2.bind绑定自己的网路信息 -- 注意包含头文件
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_port = htons(_port);      // 这里有个细节,我们会发现当我们接受数据的时候是不需要主机转网路序列的,因为关于IO类的接口,内部都帮我们实现了这一功能,这里不帮我们做是因为我们传入的是一个结构体,系统做不到
            local.sin_addr.s_addr = INADDR_ANY; // 接受任意ip地址
            if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                logMessage(FATAL, "bind socket error");
                exit(BIND_ERR);
            }
            logMessage(NORMAL, "bind socket success");

            // 3. 设置socket 为监听状态 -- TCP与UDP不同,它先要建立链接之后,TCP是面向链接的,后面还会有“握手”过程
            if (listen(_listensock, gbacklog) < 0) // 第二个参数backlog后面再填这个坑
            {
                logMessage(FATAL, "listen socket error");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL, "listen socket success");
        }

        void start()
        {
            for (;;) // 一个死循环
            {
                // 4. server 获取新链接
                // sock 和client 进行通信的fd
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accept error, next"); // 这个不影响服务器的运行,用ERROR,就像张三不会因为没有把人招呼进来就不干了
                    continue;
                }
                logMessage(NORMAL, "accept a new link success");
                std::cout << "sock: " << sock << std::endl;

                // 5. 这里就是一个sock, 未来通信我们就用这个sock, 面向字节流的,后续全部都是文件操作!
                // 我们就可以直接使用read之类的面向字节流的操作都行
                // version 1
                serviceIO(sock);
                close(sock); // 走到这里就说明客户端已经关闭
                             // 对一个已经使用完毕的sock,我们要关闭这个sock,要不然会导致,文件描述符会越来越少,因为文件描述符本质就是一个数组下标
                             // 只要是数组下标就会有尽头,提供服务的上限 就等于文件描述符的上限
                             // 对一个已经使用完毕的sock,我们要关闭这个sock,要不然会导致,文件描述符泄漏
            }
        }

        void serviceIO(int sock)
        {
            // 先用最简单的,读取再写回去
            char buffer[1024];
            while (true)
            {
                ssize_t n = read(sock, buffer, sizeof(buffer) - 1);
                if (n > 0)
                {
                    // 截至目前,我们把读到的数据当作字符串
                    buffer[n] = 0;
                    std::cout << "recv message: " << buffer << std::endl;

                    std::string outbuffer = buffer;
                    outbuffer += "server[echo]";

                    write(sock, outbuffer.c_str(), outbuffer.size()); // 在多路转接的时候再详谈write的返回值
                }
                else if (n == 0)
                {
                    // 代表client退出 -- 把它想象成一个建立好的管道,客户端不写了,并且把它的文件描述符关了,读端就会像管道一样读到 0 TCP同理
                    logMessage(NORMAL, "client quit, me too!");
                    break;
                }
            }
        }

        ~TcpServer() {}

    private:
        int _listensock; // 修改二:改为listensock 不是用来进行数据通信的,它是用来监听链接到来,获取新链接的!
        uint16_t _port;
    };

} // namespace server

转下文:简单的TCP网络程序·多进程、多线程(后端服务器)_

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

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

相关文章

Spring Boot进阶(46):集成Jackson之快速入门 | 超级详细,建议收藏

1. 前言&#x1f525; 在上一期《SpringBoot之Jackson配置全局时间日期格式》文中提到Jackson&#xff0c;了解到有很多小伙伴对它很感兴趣&#xff1b;顾这一期&#xff0c;我就重点带着大家以最基础的教学方式领大家入门&#xff0c;废话不多说&#xff0c;咱们这就开始。 这…

(字符串) 925. 长按键入 ——【Leetcode每日一题】

❓925. 长按键入 难度&#xff1a;简单 你的朋友正在使用键盘输入他的名字 name。偶尔&#xff0c;在键入字符 c 时&#xff0c;按键可能会被长按&#xff0c;而字符可能被输入 1 次或多次。 你将会检查键盘输入的字符 typed。如果它对应的可能是你的朋友的名字&#xff08;…

【大学物理实验】表面张力

文章目录 选择题选择题 液体表面张力只存在与液体的: A. 内部 B. 底部 C. 表面 D. 表面和内部 正确答案: C 本实验中,下面哪一件测量仪器(工具)是不需要的: A. 力敏传感器 B. 数字电压表 C. 游标卡尺 D. 物理天平 正确答案: D 关于吊环从液体中拉脱力(即最大表面张力)…

距离和相似性度量

文章目录 1. 距离度量1.1 欧几里得距离(Euclidean Distance)1.2 明可夫斯基距离(Minkowski Distance)1.3 曼哈顿距离(Manhattan Distance)1.4 切比雪夫距离(Chebyshev Distance)1.5 马哈拉诺比斯距离(Mahalanobis Distance) 2. 相似性度量2.1 向量空间余弦相似度(Cosine Simila…

盘点五种最常用加密算法!

大家好&#xff0c;我是老三&#xff0c;大家都知道我是一个臭做支付的&#xff0c;支付常常要和一些加签、验签&#xff0c;加密、解密打交道&#xff0c;今天&#xff0c;就给大家来盘点一下最常见的5种加密算法。 前言 大家平时的工作中&#xff0c;可能也在很多地方用到了…

封神榜科技成果 - 国产训练大模型

封神榜科技成果 Fengshenbang 1.0: 封神榜开源计划1.0中英双语总论文&#xff0c;旨在成为中文认知智能的基础设施。 BioBART: 由清华大学和IDEA研究院一起提供的生物医疗领域的生成语言模型。(BioNLP 2022) UniMC: 针对zero-shot场景下基于标签数据集的统一模型。(EMNLP 2022)…

STM32单片机(六)TIM定时器 -> 第三节:TIM输出比较

❤️ 专栏简介&#xff1a;本专栏记录了从零学习单片机的过程&#xff0c;其中包括51单片机和STM32单片机两部分&#xff1b;建议先学习51单片机&#xff0c;其是STM32等高级单片机的基础&#xff1b;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 &#xff1a;适用于想要…

『2023北京智源大会』视觉与多模态大模型

『2023北京智源大会』视觉与多模态大模型 文章目录 一. Drag Your GAN: Interactive Point-based Manipulation on the Generative Image Manifold | 潘新钢 | 南洋理工大学1. Image Manipulation(图像编辑)背景2. Drag Your GAN 二. Machine Learning for 3D Content Creatio…

实验篇(7.2) 14. 站对站安全隧道 - 多条隧道负载均衡(上)(FortiGate-IPsec) ❀ 远程访问

【简介】IPsec VPN虽然价廉物美&#xff0c;但是由运营商原因&#xff0c;经常会出访问慢、不稳定甚至断开的情况&#xff0c;好在现在大多数企业都有二条甚至更多条宽带&#xff0c;我们可以创建多条IPsec VPN&#xff0c;来保证正常访问。 实验要求与环境 OldMei集团深圳总部…

友盟分享之新浪微博站(签名apk下载)

适用环境&#xff1a; 1 单独集成新浪微博分享 2 友盟分享新浪微博 集成步骤&#xff1a; 1 注册新浪微博开发者账号 新浪微博开放平台-首页 2 选择要接入的应用类型 根据官网提示输入对应资料&#xff0c;进行申请 4 创建应用的时候&#xff0c;Android需要输入签名&#x…

NFC无源电子墨水屏

NFC电子纸造就无源可视 电子墨水标签 NFCE-paper For NFC Batteryless E-ink Tag 产品参数 产品型号 PN29_S 尺寸(mm) 95*46.4*5.4mm 显示技术 电子墨水屏 显示区域(mm) 29(H) * 66.9(V) 分辨率(像素) 296*128 像素尺寸(mm) 0.227*0.226 显示颜色 黑/白 视…

摩尔定律放缓后,AMD应如何引领自适应的风潮?

编者按&#xff1a;自适应计算如何为核心市场带来动力&#xff1f;近日&#xff0c;在AMD“自适应和嵌入式产品技术日”活动日上&#xff0c;AMD 全球副总裁唐晓蕾表示&#xff0c;创新是驱动发展的引擎&#xff0c;百行百业的数字化与智能化转型离不开创新输送的源源不断的强劲…

【监控】Zabbix:企业级开源监控解决方案

文章目录 一、zabbix的基本概述二、zabbix的构成三、zabbix的监控对象四、zabbix的常用术语五、zabbix的工作流程六、zabbix进程详解七、zabbix的监控框架7.1 三种架构模式的架构图如下&#xff1a;7.2 每个模块的工作职责&#xff1a; 八、zabbix源码安装及部署一、服务端安装…

电子工程师,一起来聊聊PCB板上的Mark点吧

在PCB设计中&#xff0c;电子工程师需要注意很多方面&#xff0c;新手工程师经常会忽略Mark点&#xff0c;但资深工程师们却对Mark点又爱又恨&#xff0c;甚至不得不花时间耗费在Mark点上&#xff0c;为什么Mark点如此重要&#xff1f;该如何设计Mark点&#xff1f; 一、Mark点…

Linux基础IO - 文件系统 | 软硬链接

之前的文章中我们与文件有关的内容谈论的都是被打开的文件&#xff0c;那么如果文件没有被打开呢&#xff1f;这样文件就一定不再内存中&#xff0c;只能在磁盘外设中存储&#xff0c;本文中我们就来讲述磁盘中文件的相关知识。 磁盘的物理存储结构 一个磁盘由多个盘片叠加而…

如何撰写高效且实用的Prompt

很多人说GPT并没有什么让人惊艳的地方&#xff0c;但实际上&#xff0c;他们并没有发挥好它的潜能。在很多情况下&#xff0c;他们往往没有使用恰当的prompt。一个恰到好处的prompt就如同魔法师在施展魔法时所需要吟唱的咒语&#xff0c;只有那些正确无误的咒语&#xff0c;才能…

Zstack实习-基础知识总结归纳-持续更新

什么是虚拟化&#xff1f; 虚拟化技术是一种将物理计算资源&#xff0c;如服务器、存储和网络等&#xff0c;转化成虚拟的逻辑资源的技术。通过虚拟化技术&#xff0c;可以将多个独立的操作系统运行在同一台物理计算机上&#xff0c;实现资源的共享&#xff0c;提高硬件的利用率…

商业智能之“道、法、器”——企业级BI能力构建指南

一个企业级BI项目看似简单&#xff0c;但实际建设难度却远超想象。如何从0到1搭建BI项目&#xff0c;有没有详细的实施步骤&#xff0c;有没有合适的BI工具推荐&#xff0c;这是很多企业在上BI系统前心中的疑问。因此&#xff0c;本文会详细探讨企业BI能力构建&#xff0c;到底…

【业务功能篇26】 ROW_NUMBER() 排名函数 给表单数据增加序列号

业务场景&#xff1a; 当业务在进行月报报表制作时&#xff0c;会有些模块是需要填充当前月的top问题单&#xff0c;那么这些问题单&#xff0c;在第一列就要给标记序列号从1开始的序号&#xff0c;所以这个序号是根据业务选择的问题单后&#xff0c;在根据当前问题单记录进行的…

BSN全球技术创新发展峰会在武汉举办,“延安链”正式发布

原标题&#xff1a;《第二届BSN全球技术创新发展峰会在武汉成功举行》 6月9日&#xff0c;由湖北省人民政府指导&#xff0c;湖北省发展改革委、国家信息中心联合主办&#xff0c;中国移动、中国电信、中国联通、武汉市江汉区人民政府、区块链服务网络&#xff08;BSN&#xf…