【计算机网络学习之路】TCP socket编程

文章目录

  • 前言
  • 一. 服务器
    • 1. 初始化服务器
    • 2. 启动服务器
  • 二. 客户端
  • 三. 多进程服务器
  • 结束语

前言

本系列文章是计算机网络学习的笔记,欢迎大佬们阅读,纠错,分享相关知识。希望可以与你共同进步。

本篇博客基于UDP socket基础,介绍TCP socket编程接口和细节

UDP socket编程可参看【计算机网络学习之路】UDP socket编程

本次编写的服务器和客户端依然是最简单的echo服务器

一. 服务器

服务器的基本框架:

tcp_server.hpp

#pragma once

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

namespace ns_server
{
    const uint16_t default_port = 8888;
    class TcpServer
    {
    public:
        TcpServer(uint16_t port = default_port) : _port(port){}
        void InitServer(){}//初始化服务器
        void Start(){}//启动服务器
        ~TcpServer(){}
    private:
        int _sock; // 监听套接字
        uint16_t _port;  // 端口号
    };
}

tcp_server.cc

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

using namespace std;
using namespace ns_server;

static void usage(char*argv)
{
    cout<<"Usage\n\t"<<argv<<" serverPort"<<endl;
}
int main(int argc,char*argv[])
{
    if(argc!=2)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port=atoi(argv[1]);

    unique_ptr<TcpServer> usvr(new TcpServer(echo,port));
    usvr->InitServer();
    usvr->Start();

    return 0;
}

1. 初始化服务器

服务器的初始化,还是一样的

  1. 创建套接字
  2. 绑定套接字
 void InitServer()
{
	// 1.创建套接字
    _sock = socket(AF_INET, SOCK_STREAM, 0);
    if (_listensock < 0)
    {
		std::cerr << "create sock error," << strerror(errno) << std::endl;
        exit(1);
    }

    std::cout << "create listensock success: " << _sock << std::endl;

    // 2.绑定套接字
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(_port);
    local.sin_addr.s_addr = INADDR_ANY;

    if (bind(_sock , (struct sockaddr *)&local, sizeof(local)) < 0)
    {
		std::cerr << "bind error," << strerror(errno) << std::endl;
        exit(2);
    }
}

需要注意的是,socket的第二个参数为SOCK_STREAM面向字节流

TCP与UDP不同的地方是,TCP是面向连接的,UDP是无连接的
所以TCP还需要listen

在这里插入图片描述

返回值:成功返回0,失败返回-1并设置错误码

backlog参数需要在后续TCP详解中学习,先定义大小为32

const int backlog = 32;
void InitServer()
{
	// 1.创建套接字
    _sock = socket(AF_INET, SOCK_STREAM, 0);
    if (_listensock < 0)
    {
		std::cerr << "create sock error," << strerror(errno) << std::endl;
        exit(1);
    }

    std::cout << "create listensock success: " << _sock << std::endl;

    // 2.绑定套接字
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(_port);
    local.sin_addr.s_addr = INADDR_ANY;

    if (bind(_sock , (struct sockaddr *)&local, sizeof(local)) < 0)
    {
		std::cerr << "bind error," << strerror(errno) << std::endl;
        exit(2);
    }
	
	// 3.监听
    if (listen(_listensock, backlog) < 0)
    {
        std::cerr << "listen error," << strerror(errno) << std::endl;
        exit(3);
    }
}

初始化到此就结束了
接下来是启动服务器

2. 启动服务器

TCP通过accept获取客户端连接

在这里插入图片描述

  • sockfd:socket返回的文件描述符
  • addr:输入输出型参数,客户端信息的结构体
  • addrlen:输入输出型参数,结构体大小。注意:需要传入addr的大小
  • 返回值是网络文件描述符

在TCP中,socket返回的网络文件可以理解为连接文件,内部保存了连接信息
而accept是从连接文件中获取连接,然后创建套接字,网络文件。
真正通信的是connect创建的网络文件

我们将私有成员的_sock改为_listensock

void Start()
{
	while (true)
	{
		struct sockaddr_in client;
        memset(&client, 0, sizeof(client));
        socklen_t len = sizeof(client);
        int sock = accept(_listensock, (struct sockaddr *)&client, &len);
        if (sock < 0)
        {
			std::cerr << "accept error" << std::endl;
			continue;
		}

        // 提取客户端信息
        std::string clientIp = inet_ntoa(client.sin_addr);
        uint16_t clientPort = ntohs(client.sin_port);

        std::string name = "[" + clientIp + ":" + std::to_string(clientPort) + "]";
        std::cout << "create sock " << sock << " from " << _listensock << std::endl;
    }
}

接下来就可以在connect返回的套接字中读写数据了。
本次使用readwrite

void Start()
{
	while (true)
	{
		struct sockaddr_in client;
        memset(&client, 0, sizeof(client));
        socklen_t len = sizeof(client);
        int sock = accept(_listensock, (struct sockaddr *)&client, &len);
        if (sock < 0)
        {
			std::cerr << "accept error" << std::endl;
			continue;
		}

        // 提取客户端信息
        std::string clientIp = inet_ntoa(client.sin_addr);
        uint16_t clientPort = ntohs(client.sin_port);

        std::string name = "[" + clientIp + ":" + std::to_string(clientPort) + "]";
        std::cout << "create sock " << sock << " from " << _listensock << std::endl;

        char buffer[1024];
        while (true)
        {
			int n = read(sock, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
				buffer[n] = '\0';
                std::cout << name << "# " << buffer << std::endl;
                std::string responce = buffer;//返回收到的数据
                int m = write(sock, responce.c_str(), responce.size());
            }
            else if (n == 0)
            {
                // 写端关闭
                std::cout << name << " quit,me to" << std::endl;
                close(sock);
                break;
            }
            else
            {
                // 读数据异常
                std::cerr << "read error" << std::endl;
                break;
            }
		}
	}
}

完整代码:
tcp_server.hpp

#pragma once

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

namespace ns_server
{
    const uint16_t default_port = 8888;
    const int backlog = 32;
    class TcpServer
    {
    public:
        TcpServer(func_t func, uint16_t port = default_port) : _port(port), _func(func)
        {
        }

        void InitServer()
        {
            // 1.创建套接字
            _listensock = socket(AF_INET, SOCK_STREAM, 0);
            if (_listensock < 0)
            {
                std::cerr << "create sock error," << strerror(errno) << std::endl;
                exit(1);
            }

            std::cout << "create listensock success: " << _listensock << std::endl;

            // 2.绑定套接字
            struct sockaddr_in local;
            local.sin_family = AF_INET;
            local.sin_port = htons(_port);
            local.sin_addr.s_addr = INADDR_ANY;

            if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                std::cerr << "bind error," << strerror(errno) << std::endl;
                exit(2);
            }

            // 3.监听
            if (listen(_listensock, backlog) < 0)
            {
                std::cerr << "listen error," << strerror(errno) << std::endl;
                exit(3);
            }
        }

        void Start()
        {
            while (true)
            {
                struct sockaddr_in client;
                memset(&client, 0, sizeof(client));
                socklen_t len = sizeof(client);
                int sock = accept(_listensock, (struct sockaddr *)&client, &len);
                if (sock < 0)
                {
                    std::cerr << "accept error" << std::endl;
                    continue;
                }

                // 提取客户端信息
                std::string clientIp = inet_ntoa(client.sin_addr);
                uint16_t clientPort = ntohs(client.sin_port);

                std::string name = "[" + clientIp + ":" + std::to_string(clientPort) + "]";

                std::cout << "create sock " << sock << " from " << _listensock << std::endl;

                char buffer[1024];
                while (true)
                {
                    int n = read(sock, buffer, sizeof(buffer) - 1);
                    if (n > 0)
                    {
                        buffer[n] = '\0';
                        std::cout << name << "# " << buffer << std::endl;
                        std::string responce = buffer;
                        int m = write(sock, responce.c_str(), responce.size());
                    }
                    else if (n == 0)
                    {
                        // 写端关闭
                        std::cout << name << " quit,me to" << std::endl;
                        close(sock);
                        break;
                    }
                    else
                    {
                        // 读数据异常
                        std::cerr << "read error" << std::endl;
                        break;
                    }
                }
            }
        }

        ~TcpServer()
        {
        }

    private:
        int _listensock; // 监听套接字
        uint16_t _port;  // 端口号
    };
}

PS:上述的服务器是单进程,所以只能同时处理一个客户端,读者可以尝试添加一下多进程,多线程或者线程池

本篇博客最后会贴出多进程的方案

二. 客户端

客户端就不作封装了

最开始也是要创建套接字
然后TCP的客户端需要connect服务器

在这里插入图片描述

  • sockfd:socket返回的文件描述符
  • addr:服务器信息的结构体
  • addrlen:结构体大小。
  • 返回值:成功返回0,失败返回-1并设置错误码

注意:connect时OS会bind客户端
UDP是在发送数据时才会bind

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

using namespace std;

static void usage(char *argv)
{
    cout << "Usage:\n\t" << argv << " serverIp serverPort" << endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    string serverIp = argv[1];
    uint16_t serverPort = atoi(argv[2]);

    // 1.创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        cerr << "create sock error," << strerror(errno) << endl;
        exit(SOCKET_ERR);
    }

    cout<<"create sock sucess:"<<sock<<endl;

    // 2. 连接
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(serverIp.c_str());
    server.sin_port = htons(serverPort);

    int cnt = 5; // 记录重连次数
    // connect时会bind
    while (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0)
    {
        cout << "正在重连...还有" << cnt-- << "次" << endl;
        if (cnt <= 0)
        {   
            cerr<<"连接失败"<<endl;
            exit(CONNECT_ERR);
        }
        sleep(1);
    }
    // 连接成功
    string name = "["+serverIp + ":" + to_string(serverPort)+"]";
    cout << "connect " << name << " sucess" << endl;


    return 0;
}

然后也可以开始读写数据了

完整代码:

tcp_client.cc

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

using namespace std;

static void usage(char *argv)
{
    cout << "Usage:\n\t" << argv << " serverIp serverPort" << endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    string serverIp = argv[1];
    uint16_t serverPort = atoi(argv[2]);

    // 1.创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        cerr << "create sock error," << strerror(errno) << endl;
        exit(SOCKET_ERR);
    }

    cout<<"create sock sucess:"<<sock<<endl;

    // 2. 连接
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(serverIp.c_str());
    server.sin_port = htons(serverPort);

    int cnt = 5; // 记录重连次数
    // connect时会bind
    while (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0)
    {
        cout << "正在重连...还有" << cnt-- << "次" << endl;
        if (cnt <= 0)
        {   
            cerr<<"连接失败"<<endl;
            exit(CONNECT_ERR);
        }
        sleep(1);
    }

    // 连接成功
    string name = "["+serverIp + ":" + to_string(serverPort)+"]";
    cout << "connect " << name << " sucess" << endl;

    // 发送消息
    while (true)
    {
        cout << "please enter your message# ";
        string message;
        getline(cin, message);

        int n = write(sock, message.c_str(), message.size());
        if (n < 0)
        {
            cerr << "write error," << strerror(errno) << endl;
            break;
        }
        else if (n == 0)
        {
            cout << "读端关闭,停止写" << endl;
            break;
        }

        char buffer[1024];
        int m = read(sock, buffer, sizeof(buffer) - 1);
        if (m > 0)
        {
            buffer[n] = '\0';
            cout<<name<<" echo "<<buffer<<endl;
        }
        else if (m == 0)
        {
            // 写端关闭
            std::cout << name << " quit,me to" << std::endl;
            close(sock);
            break;
        }
        else
        {
            // 读数据异常
            std::cerr << "read error" << std::endl;
            break;
        }
    }

    return 0;
}

三. 多进程服务器

tcp_server.hpp

#pragma once

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


namespace ns_server
{
    const uint16_t default_port = 8888;
    const int backlog = 32;
   
    class TcpServer
    {
    public:
        TcpServer(uint16_t port = default_port) : _port(port)
        {
        }

        void InitServer()
        {
            // 1.创建套接字
            _listensock = socket(AF_INET, SOCK_STREAM, 0);
            if (_listensock < 0)
            {
                std::cerr << "create sock error," << strerror(errno) << std::endl;
                exit(1);
            }

            std::cout << "create listensock success: " << _listensock << std::endl;

            // 2.绑定套接字
            struct sockaddr_in local;
            local.sin_family = AF_INET;
            local.sin_port = htons(_port);
            local.sin_addr.s_addr = INADDR_ANY;

            if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                std::cerr << "bind error," << strerror(errno) << std::endl;
                exit(2);
            }

            // 3.监听
            if (listen(_listensock, backlog) < 0)
            {
                std::cerr << "listen error," << strerror(errno) << std::endl;
                exit(3);
            }
        }

        

        void Start()
        {
        	//忽略子进程的信号,不需要等待子进程退出(推荐!!!)
            signal(SIGCHLD,SIG_IGN);
            while (true)
            {
                struct sockaddr_in client;
                memset(&client, 0, sizeof(client));
                socklen_t len = sizeof(client);
                int sock = accept(_listensock, (struct sockaddr *)&client, &len);
                if (sock < 0)
                {
                    std::cerr << "accept error" << std::endl;
                    continue;
                }
                std::cout << "create sock " << sock << " from " << _listensock << std::endl;

                // 多进程
                pid_t id = fork();
                if (id < 0)
                {
                    close(sock);
                    continue;
                }
                else if (id == 0)
                {
                    //子进程
                    close(_listensock);//建议关掉不需要的fd
                    if(fork()>0)
                        exit(0);//子进程退掉,后续为孙子进程
                    // 提取客户端信息
                    std::string clientIp = inet_ntoa(client.sin_addr);
                    uint16_t clientPort = ntohs(client.sin_port);
                    service(sock, clientIp, clientPort);
                    exit(0);
                }
                //父进程
                //一定关掉不需要的fd,防止fd泄露
                close(sock);

                //pid_t ret=waitpid(id,nullptr,0);//默认为阻塞等待
                //pid_t ret=waitpid(id,nullptr,WNOHANG);//非阻塞
                //if(ret==id) std::cout<<"wait "<<id<<" sucess"<<std::endl;
            }
        }

		void service(int sock, std::string &clientIp, uint16_t&clientPort)
        {
            std::string name = "[" + clientIp + ":" + std::to_string(clientPort) + "]";
            char buffer[1024];
            while (true)
            {
                int n = read(sock, buffer, sizeof(buffer) - 1);
                if (n > 0)
                {
                    buffer[n] = '\0';
                    std::cout << name << "# " << buffer << std::endl;
                    std::string responce = buffer;
                    int m = write(sock, responce.c_str(), responce.size());
                }
                else if (n == 0)
                {
                    // 写端关闭
                    std::cout << name << " quit,me to" << std::endl;
                    close(sock);
                    break;
                }
                else
                {
                    // 读数据异常
                    std::cerr << "read error" << std::endl;
                    break;
                }
            }
        }

        ~TcpServer()
        {
        }

    private:
        int _listensock; // 监听套接字
        uint16_t _port;  // 端口号
    };
}

结束语

本篇博客到此结束,感谢看到此处。
欢迎大家纠错和补充
如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【SpringCloud】认识微服务、服务拆分以及远程调用

SpringCloud 1.认识微服务 1.1单体架构 单体架构&#xff1a;将业务的所有功能集中在一个项目中开发&#xff0c;打成一个包部署 单体架构的优缺点&#xff1a; 优点&#xff1a; 架构简单&#xff0c;部署成本低 缺点&#xff1a; 耦合度高&#xff08;维护困难&#x…

设备状态监测与故障诊断系统的作用

随着工业生产的发展和技术的进步&#xff0c;设备状态监测与故障诊断系统在工业领域中扮演着越来越重要的角色。这一系统通过实时监测设备的状态和参数&#xff0c;及时发现潜在的故障&#xff0c;并提供预警信号&#xff0c;以降低生产中断、提高安全性和维护效率。以下将详细…

Django 模型和Admin站点管理(三)

一、定义模型 &#xff08;1&#xff09; 创建模型类&#xff0c;必须要继承自 models.Model from django.db import models# Create your models here. #设计数据库 #创建模型 class UserModel(models.Model):namemodels.CharField(max_length30) #对应于SQL name varchar(30…

慕尼黑电子展Samtec Demo | 回环测试带来Samtec产品组合优异表现

【摘要/前言】 大家好&#xff01;Electronica虎家展台Demo系列回来咯。 实践出真知&#xff0c;再好的纸面数据都不如来一场实际的测试和演示。Samtec团队始终在努力为客户带来卓越的产品和优质服务。而这其中&#xff0c;Demo演示的存在至关重要。演示过程可以为大家带来了…

ubuntu编译sqlite3并使用

SQLite3是一种轻量级的关系型数据库管理系统&#xff0c;它是在C语言基础上实现的。SQLite3具有许多优点&#xff0c;例如&#xff1a; 1.灵活&#xff1a;它可以在多种操作系统上运行&#xff0c;并且可以将多个数据库文件合并成一个文件。 2.易于使用&#xff1a;SQLite3使用…

“云浮云福保”暖心回归! 保障升级价格不变,医保个账可为全家缴费!

11月22日&#xff0c;2024年“云浮云福保”项目启动会在广东省云浮市迎宾馆成功举办。记者在会上获悉&#xff0c;“云浮云福保”是在云浮市医疗保障局、云浮市金融工作局、国家金融监督管理总局云浮监管分局指导下&#xff0c;的指导下&#xff0c;由中国人民财产保险股份有限…

网络安全之渗透测试入门准备

渗透测试入门所需知识 操作系统基础&#xff1a;Windows&#xff0c;Linux 网络基础&#xff1a;基础协议与简单原理 编程语言&#xff1a;PHP&#xff0c;python web安全基础 渗透测试入门 渗透测试学习&#xff1a; 1.工具环境准备&#xff1a;①VMware安装及使用&#xff1b…

Modbus转Profinet网关连接PLC与天信流量计通讯案例

本文将为您详细介绍如何成功连接PLC与天信流量计&#xff1a;从选择合适的Modbus转Profinet网关开始&#xff0c;到设置网关以实现通讯连接&#xff0c;还会涵盖部署和故障排除过程中可能遇到的一些问题。 首先&#xff0c;选择合适的Modbus转Profinet网关至关重要。我们选用基…

竞赛python区块链实现 - proof of work工作量证明共识算法

文章目录 0 前言1 区块链基础1.1 比特币内部结构1.2 实现的区块链数据结构1.3 注意点1.4 区块链的核心-工作量证明算法1.4.1 拜占庭将军问题1.4.2 解决办法1.4.3 代码实现 2 快速实现一个区块链2.1 什么是区块链2.2 一个完整的快包含什么2.3 什么是挖矿2.4 工作量证明算法&…

微信小游戏上线流程

微信小游戏上线是一个需要经过一系列步骤的过程。以下是一个一般性的微信小游戏上线流程&#xff0c;请注意&#xff0c;上述步骤可能会有微信平台的政策和规定的变化&#xff0c;因此建议在开发过程中及时查阅微信小游戏的官方文档和最新政策。北京木奇移动技术有限公司&#…

前后端分离SpringBoot+vue的买菜农副产品多功能商城

1&#xff0c;项目介绍 本系统主要针对买菜而设计&#xff0c;其功能有菜品基本信息管理、商品类别管理、系统订单管理、评论管理、系统用户管理等功能模块。并且本系统采用了现在流行的SpringBootVue进行的设计与实现&#xff0c;其中Tomcat为服务器&#xff0c;MySQL为数据库…

SpringBoot定时任务(一看就会)

一、引入依赖 定时任务是spring boot框架提供的基础能力之一&#xff0c;所以其依赖是在spring-boot-starter里面&#xff0c;但是一般开发的时候我们直接引入web依赖即可&#xff0c;web依赖中包含了spring-boot-starter。要注意的是Spring Boot 从版本1.3.0开始提供对定时任务…

写单元测试,没你想得那么简单!

前言 单元测试是什么我们就简单介绍一下&#xff1a; 单元测试是针对程序模块&#xff08;软件设计的最小单位&#xff09;来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。 接下来是本人对单元测试的理解和实践。里面没有废话&#xff0c;希望每句话能说到你心…

LangChain: 类似 Flask/FastAPI 之于 Django,LangServe 就是「LangChain 自己的 FastAPI」

原文&#xff1a;LangChain: 类似 Flask/FastAPI 之于 Django&#xff0c;LangServe 就是「LangChain 自己的 FastAPI」 - 知乎 说明&#xff1a;LangServe代替 langchainserver 成为新的langchain 部署工具 官网资料&#xff1a;&#x1f99c;️&#x1f3d3; LangServe | &…

批量插入SQL 错误 [933] [42000]: ORA-00933: SQL 命令未正确结束

使用DBeaver向【oracle数据库】插入大量数据 INSERT INTO Student(name,sex,age,address,birthday) VALUES(Nike,男,18,北京,2000-01-01) ,(Nike,男,18,北京,2000-01-01) ,(Nike,女,18,北京,2000-01-01) ,(Nike,女,18,北京,2000-01-01) ,(Nike,男,18,北京,2000-01-01) ,(Nike…

【JDK源码阅读】什么是 avoid getfield opcode ?

说明&#xff1a;JDK源码版本为 Oracle JDK 8 1. 背景 阅读 java.lang.String 的源码&#xff0c;会发现有些地方注释为/* avoid getfield opcode */&#xff0c;此处的代码是将当前类定义的成员变量引用为本地变量&#xff0c;从字面意思理解&#xff0c;是为了避免使用 get…

计算机基础知识——字,字节,进制,short,byte等

目录 进制位&#xff0c;字节&#xff0c;字Byte&#xff0c;ShortByteBuf有符号数和无符号数 进制 HEX&#xff0c;Hexadecimal &#xff0c;十六进制。 DEC&#xff0c;Decimal &#xff0c;十进制。 OCT&#xff0c;Octal &#xff0c;八进制。 BIN&#xff0c;Binary &a…

基于java和uniapp的即时聊天源码

聊天IM&#xff0c;支持单聊、群聊、朋友圈、摇一摇、附近的人、收藏、扫码、机器人、文字、图片、名片、实时音视频通话等功能。用uniapp开发&#xff0c;支持打包成多终端&#xff01; 推送&#xff1a;uniPush websocket资源&#xff1a;阿里OSS&#xff08;图片、声音、视…

JMeter —— 接口自动化测试(数据驱动)

前言 之前我们的用例数据都是配置在HTTP请求中&#xff0c;每次需要增加&#xff0c;修改用例都需要打开JMeter重新编辑&#xff0c;当用例越来越多的时候&#xff0c;用例维护起来就越来越麻烦&#xff0c;有没有好的方法来解决这种情况呢&#xff1f;我们可以将用例的数据存…

vxe编辑保存表格

业务需求&#xff1a; 1、需要点击编辑时&#xff0c;全部表格显示编辑框&#xff0c;点击保存&#xff0c;全部保存。 2、因为位置问题&#xff0c;产品经理把24小时分成了两行&#xff0c;开发就得分两个表格。列标题是写死的&#xff0c;文字偏移也是写死的&#xff0c;其他…
最新文章