TCP套接字编程

news2024/12/27 14:37:52

文章目录

  • 前言
  • 一、TCP套接字
    • 1.简单认识TCP协议
    • 2.listen函数
    • 3.accept函数
    • 4.通用TCP服务器
  • 二、大小写转换服务
    • 1.服务端
    • 2.客户端
  • 三、多进程版本TCP服务器
  • 四、多线程版本TCP服务器

前言

这篇文章是紧接着上一篇《UDP套接字编程》文章的,里面详细介绍了套接字编程的一些基本准备知识,这里就不过多介绍了,直接介绍如何使用TCP套接字进行编程,使用的函数接口还是一样的,只不过TCP和UDP存在区别,具体的区别这篇文章会介绍,贴上一篇文章的链接:
UDP套接字编程,建议先读这一篇

一、TCP套接字

1.简单认识TCP协议

TCP协议的全称是Transmission Control Protocol,即传输控制协议,它和UDP协议一样也是传输层协议,它的特点是有连接的,可靠传输,以及面向字节流传输,在socket函数的形参type中,SOCK_STREAM代表的就是TCP套接字。

在这里插入图片描述

2.listen函数

TCP服务器需要通过listen函数将服务器设置成为监听状态,因为TCP协议是需要连接的,服务器设置成为监听状态是在等待客户端连接它。

  1. int sockfd:socket文件描述符。
  2. int backlog
// listen
int listen(int sockfd, int backlog);

3.accept函数

在TCP服务器被设置成监听状态等待其它人来连接的时候,TCP服务器需要使用accept函数来获取连接。

  1. int sockfd:这个形参是一个socket套接字,它的核心工作是用来获取新的连接,所以它叫作监听套接字。
  2. struct sockaddr * addr:用来获取连接上的客户端的信息。
  3. socklen_t * addrlen:addr的大小。
  4. 返回值:如果连接成功,accept函数也会返回一个sockfd,与形参的sockfd不同的是,这是另一类socket套接字,它的核心工作是为用户提供网络服务,主要是进行IO。
// accept
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

4.通用TCP服务器

我们利用TCP套接字的编程接口写一个通用版本的TCP服务器,即只提供监听和获取网络连接,不提供其它任何服务,让浏览器暂时充当客户端,访问我们的服务器,测试是否能够连接成功:

UDP协议不是面向连接的,所以UDP服务器只需要创建套接字以后bind网络信息即可。TCP服务器在创建套接字和bind网络信息以后,还需要将TCP服务器设置成listen监听状态,只有设置监听状态才能等待客户端来连接。

当TCP服务器初始化完毕以后,就可以运行服务器了,服务器运行起来需要用accept函数来获取连接,如果此时没有客户端来连接服务器,它会继续循环重新获取连接,直到有人来连接为止。

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

using namespace std;

class TcpServer
{
public:
    TcpServer(int port, const string &ip = "")
        : _port(port), _ip(ip), _listenSock(-1)
    {
    }

    ~TcpServer()
    {}

public:
    void init()
    {
        // 1.创建套接字
        _listenSock = socket(AF_INET, SOCK_STREAM, 0);
        if(_listenSock < 0)
        {
            cerr << "socket error" << endl;
            exit(1);
        }
        cout << "socket success" << endl;

        // 2.bind
        // 2.1填充网络信息
        sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());

        // 2.2bind网络信息
        if(bind(_listenSock, (const sockaddr*)&local, sizeof(local)) < 0)
        {
            cerr << "bind error" << endl;
            exit(2);
        }
        cout << "bind success" << endl;

        // 3.listen
        if(listen(_listenSock, 5) < 0)
        {
            cerr << "listen error" << endl;
            exit(3);
        }
        cout << "listen success" << endl;

    }

    void start()
    {
        while(true)
        {
            // accept
            sockaddr_in peer;
            memset(&peer, 0, sizeof(peer));
            socklen_t len = sizeof(peer);
            int serviceSock = accept(_listenSock, (sockaddr*)&peer, &len);
            // 如果没有获取连接成功,则继续重新获取
            if(serviceSock < 0)
            {
                continue;
            }
            cout << "accept success" << endl;
        }
    }

private:
    uint16_t _port;  // 端口号
    string _ip;      // IP地址
    int _listenSock; // 监听套接字
};

int main()
{
    TcpServer svr(8080);
    svr.init();
    svr.start();
    return 0;
}

运行程序查看结果,我们先让服务器跑起来,然后打开浏览器,输入我们云服务器的IP地址:端口号,就可以让浏览器连接我们的服务器,由于我们的服务器没有提供任何服务,所以浏览器界面看不到任何东西,只会一直在加载。但我们在命令行后台可以看到,浏览器作为客户端已经成功连接我们的TCP服务器了,并且浏览器一般是多线程执行的,所以我们会看到连接了两次。

在这里插入图片描述

在这里插入图片描述

二、大小写转换服务

我们利用TCP服务器实现一个大小写转换的服务,来演示一下服务端和客户端如何进行TCP套接字的网络通信。客户端负责发送信息给服务端,服务端接收到信息以后,对信息里的小写字母转换成为大写字母,然后再把转换好的信息重新发送给客户端,客户端接收到转换好的信息之后再显示出来。

在上一篇UDP套接字编程中我们也实现了这么一个大小写转换服务,但是客户端和服务端进行数据发送和接收使用的是recvfrom函数和sendto函数。recvfrom函数和sendto函数是UDP套接字专用的接收数据和发送数据函数,它们发送的是用户数据报,是一个固定大小的报文。TCP是面向字节流的,所以不能用recvfrom函数和sendto函数。

TCP套接字要使用的是read函数和write函数进行信息发送和信息读取。

1.服务端

我们上面已经实现了TCP服务器的通用版本,只是还没有添加服务,所以服务端我们只需要在通用版本的基础上新增大小写转换的服务即可。

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

using namespace std;

class TcpServer
{
public:
    TcpServer(int port, const string &ip = "")
        : _port(port), _ip(ip), _listenSock(-1)
    {
    }

    ~TcpServer()
    {}

public:
    void init()
    {
        // 1.创建套接字
        _listenSock = socket(AF_INET, SOCK_STREAM, 0);
        if(_listenSock < 0)
        {
            cerr << "socket error" << endl;
            exit(1);
        }
        cout << "socket success" << endl;

        // 2.bind
        // 2.1填充网络信息
        sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());

        // 2.2bind网络信息
        if(bind(_listenSock, (const sockaddr*)&local, sizeof(local)) < 0)
        {
            cerr << "bind error" << endl;
            exit(2);
        }
        cout << "bind success" << endl;

        // 3.listen
        if(listen(_listenSock, 5) < 0)
        {
            cerr << "listen error" << endl;
            exit(3);
        }
        cout << "listen success" << endl;

    }

    void start()
    {
        while(true)
        {
            // accept
            sockaddr_in peer;
            memset(&peer, 0, sizeof(peer));
            socklen_t len = sizeof(peer);
            int serviceSock = accept(_listenSock, (sockaddr*)&peer, &len);
            // 如果没有获取连接成功,则继续重新获取
            if(serviceSock < 0)
            {
                continue;
            }
            cout << "accept success" << endl;

            // 正式提供服务
            transformService(serviceSock);
        }
    }

private:
    void transformService(int sock)
    {
        while(true)
        {
            char inbuffer[1024];
            char outbuffer[1024];
            ssize_t readRes = read(sock, inbuffer, sizeof(inbuffer) - 1);
            // 读取成功
            if(readRes > 0)
            {
                inbuffer[readRes] = '\0';
                // 如果客户端发送过来的是'quit'则退出服务
                if(strcasecmp(inbuffer, "quit") == 0)
                {
                    cout << "client quit" << endl;
                    break;
                }
                
                cout << "client #" << inbuffer << endl;

                // 小写转换成大写
                for(int i = 0; i < strlen(inbuffer); i++)
                {
                    if(isalpha(inbuffer[i]) && islower(inbuffer[i]))
                    {
                        outbuffer[i] = toupper(inbuffer[i]);
                    }
                    else
                    {
                        outbuffer[i] = inbuffer[i];
                    }
                }

                // 将转换后的信息发送回给客户端
                ssize_t wrietRes = write(sock, outbuffer, strlen(outbuffer));
                if(wrietRes < 0)
                {
                    cerr << "write error" << endl;
                    break;
                }
            }
            // 客户端关闭了
            else if(readRes == 0)
            {
                cout << "client quit" << endl;
                break;
            }
            // 读取失败
            else
            {
                cerr << "read error" << endl;
                break;
            }
        }

        // 这里一定要关闭服务套接字
        // 否则多进程访问,创建大量套接字不释放的话
        // 可能会导致资源无法申请,服务器无法正常提供服务
        close(sock);
    }

private:
    uint16_t _port;  // 端口号
    string _ip;      // IP地址
    int _listenSock; // 监听套接字
};

int main()
{
    TcpServer svr(8080);
    svr.init();
    svr.start();
    return 0;
}

2.客户端

TCP的客户端与UDP的客户端也不一样,UDP的客户端只需要创建套接字之后就可以通信了,但是TCP是面向连接的,所以TCP客户端在创建套接字之后,还需要用connect函数来连接服务器,只有连接成功了才能实现网络通信。

connect:

  1. int sockfd:填入客户端创建好的套接字。
  2. const struct sockaddr * addr:输入型参数,用来标定需要连接的服务器的网络信息。
  3. socklen_t addrlen:addr的大小。
// connect
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

客户端代码

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

using namespace std;

volatile bool isQuit = false;// 标志客户端是否退出

static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " serverIp serverPort" << std::endl;
    std::cerr << "Example:\n\t" << proc << " 127.0.0.1 8081\n"
              << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string serverIp = argv[1];
    uint16_t serverPort = atoi(argv[2]);

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

    // 2.connect
    // 2.1填充网络信息
    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());

    // 2.2connect
    if(connect(sock, (const sockaddr*)&server, sizeof(server)) != 0)
    {
        cerr << "connet error" << endl;
        exit(2);
    }

    while(!isQuit)
    {
        cout << "client #";
        string message;
        getline(cin, message);

        if(strcasecmp(message.c_str(), "quit") == 0)
        {
            isQuit = true;
        }

        ssize_t writeRes = write(sock, message.c_str(), message.size());
        if(writeRes <= 0)
        {
            cerr << "write error" << endl;
            break;
        }
        // 写入成功
        else
        {
            char buffer[1024];
            ssize_t readRes = read(sock, buffer, sizeof(buffer));
            if(readRes > 0)
            {
                buffer[readRes] = '\0';
                cout << "Server #" << buffer << endl;
            }
        }
    }

    return 0;
}

三、多进程版本TCP服务器

上面我们实现的大小写转换服务有局限,它只能让单进程正常访问执行,如果有多个进程同时连接服务器请求服务,就会出现问题。

我们可以演示一下:首先先运行服务端,然后运行客户端1,再运行客户端2,客户端1发送信息是可以正常完成大小写转换服务的,但客户端2发送信息却没有消息发回来,看上去是阻塞住了。

在这里插入图片描述

一旦客户端1退出了以后,客户端2历史输入的消息才能接收回来,客户端2也才能够正常地获取服务端的大小写转换服务:

在这里插入图片描述

这个问题其实是服务端目前仅仅是单进程版本,当多进程连接访问它时,后面连接上来的进程都会阻塞住。我们可以分析一下服务端的代码,当服务端运行start函数让服务器跑起来之后,服务器首先是accept等待连接,当客户端1连接上来以后,主执行流会继续向下执行,执行到transformService函数之后,主执行流进入了该函数的循环,此时客户端2再连接上来时,由于主执行流正在transformService函数里为客户端1提供服务,所以客户端2只能阻塞着,等客户端1的服务结束之后,主执行流才从transformService函数的循环中跳出来,继续执行accept获取客户端2的连接,此时客户端2才能正常获取服务。

在这里插入图片描述

所以我们要把服务器改成多进程版本,让多个进程同时访问服务器时不会出现问题。

我们在服务器启动的start函数中创建多进程,主执行流负责accept获取连接,获取连接成功之后主执行流再fork子进程,让子进程来负责处理大小写转换的任务。

但是有个问题就是,子进程在处理完任务以后就要退出了,退出之后需要父进程来回收子进程,否则子进程会变成僵尸进程,会导致内存泄漏的问题。但是如果父进程用waitpid函数回收子进程的话,有两种选择:阻塞式等待和非阻塞式等待。

如果父进程是阻塞式等待子进程,那么我们多进程的意义就不存在了,父进程会阻塞在那里等待回收子进程,无法继续执行accept获取连接。所以父进程必须非阻塞式等待,非阻塞式等待的话由于我们会创建多个子进程,所以我们必须将多个子进程的id保存起来,然后每次循环检测哪些子进程可以被回收,这样实现会比较麻烦。

所以我们最后采用的方法是用signal函数将SIGCHLD信号的处理方式设置成忽略,这样的话父进程就可以不用回收子进程了,子进程也不会变成僵尸进程。

void start()
{
    // 将SIGCHLD信号设置成忽略处理,父进程可以不用回收子进程
    signal(SIGCHLD, SIG_IGN);
    while (true)
    {
        // accept
        sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        socklen_t len = sizeof(peer);
        int serviceSock = accept(_listenSock, (sockaddr *)&peer, &len);
        // 如果没有获取连接成功,则继续重新获取
        if (serviceSock < 0)
        {
            continue;
        }
        cout << "accept success" << endl;

        pid_t id = fork();
        if(id < 0)
        {
            cerr << "fork error" << endl;
            break;
        }
        // 子进程
        if (id == 0)
        {
            // 最好把子进程继承过来的监听套接字也关闭了
            // 因为子进程用不上
            close(_listenSock);
            // 正式提供服务
            transformService(serviceSock);
            exit(0);
        }
        // 父进程
        // 父进程必须关闭服务套接字,因为父进程用不到服务套接字
        // 子进程继承了父进程的服务套接字,子进程使用就可以了
        // 父进程如果不关闭的话有可能会导致打开套接字资源过多而无法继续申请资源
        close(serviceSock);

    }
}

除了上述采用signal函数的方法解决子进程的僵尸问题,我们还可以采用另一种比较巧妙的方式:

在主执行流创建子进程之后,在子进程里再创建一个子进程,这样主执行流就是爷爷进程,主执行流创建的子进程就是爸爸进程,爸爸进程创建的子进程就是孙子进程。我们让孙子进程处理大小写转换的任务,爸爸进程一创建好就退出,然后爷爷进程回收爸爸进程,由此孙子进程变成了孤儿进程,由操作系统托管,操作系统负责其回收工作,不需要我们来处理。

void start()
{
    // 将SIGCHLD信号设置成忽略处理,父进程可以不用回收子进程
    signal(SIGCHLD, SIG_IGN);
    while (true)
    {
        // accept
        sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        socklen_t len = sizeof(peer);
        int serviceSock = accept(_listenSock, (sockaddr *)&peer, &len);
        // 如果没有获取连接成功,则继续重新获取
        if (serviceSock < 0)
        {
            continue;
        }
        cout << "accept success" << endl;

        // 爷爷进程
		pid_t id = fork();
		if (id < 0)
		{
			cerr << "fork error" << endl;
			break;
		}
		// 爸爸进程
		if (id == 0)
		{
			// 孙子进程
			if (fork() == 0)
			{
				// 最好把子进程继承过来的监听套接字也关闭了
				// 因为子进程用不上
				close(_listenSock);
				// 正式提供服务
				transformService(serviceSock);
				exit(0);
			}
			close(_listenSock);
			close(serviceSock);
			exit(0);
		}
		// 父进程
		// 父进程必须关闭服务套接字,因为父进程用不到服务套接字
		// 子进程继承了父进程的服务套接字,子进程使用就可以了
		// 父进程如果不关闭的话有可能会导致打开套接字资源过多而无法继续申请资源
		close(serviceSock);
		// 爷爷进程回收爸爸进程
		// 孙子进程变成了孤儿进程,交给操作系统托管
		waitpid(id, nullptr, 0);

    }
}

四、多线程版本TCP服务器

虽然多进程可以解决我们上面的问题,但是操作系统频繁创建进程的开销是非常大的,所以我们可以将进程换成线程,因为线程是轻量级进程,创建线程的成本比较小。我们让主执行流继续执行accept获取连接,让创建的线程来执行大小写转换任务。

struct ThreadData
{
    int sock;
    TcpServer *tcpServer;

    ThreadData(int serviceSock, TcpServer *t)
        : sock(serviceSock), tcpServer(t)
    {
    }
};

static void *callBack(void *args)
{
    // 线程分离
    pthread_detach(pthread_self());
    ThreadData *threadData = (ThreadData *)args;
    threadData->tcpServer->transformService(threadData->sock);
}

void start()
{
    // 将SIGCHLD信号设置成忽略处理,父进程可以不用回收子进程
    signal(SIGCHLD, SIG_IGN);
    while (true)
    {
        // accept
        sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        socklen_t len = sizeof(peer);
        int serviceSock = accept(_listenSock, (sockaddr *)&peer, &len);
        // 如果没有获取连接成功,则继续重新获取
        if (serviceSock < 0)
        {
            continue;
        }
        cout << "accept success" << endl;

        pthread_t thread;
        ThreadData *threadData = new ThreadData(serviceSock, this);
        pthread_create(&thread, nullptr, callBack, (void *)threadData);
    }
}

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

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

相关文章

在Vue项目中使用tinymce富文本编辑器

TinyMC编辑器简介 TinyMCE是一款易用、且功能强大的所见即所得的富文本编辑器。跟其他富文本编辑器相比&#xff0c;有着丰富的插件&#xff0c;支持多种语言&#xff0c;能够满足日常的业务需求并且免费。 TinyMCE的优势&#xff1a; 开源可商用&#xff0c;基于LGPL2.1 插…

虚拟机安装 Ubuntu 桌面版

目录 1、下载系统镜像 2、新建虚拟机 3、配置虚拟机 1、下载系统镜像 Ubuntu桌面版最新版本下载地址&#xff1a;Download | Ubuntu 桌面版 Ubuntu桌面版历史版本下载地址&#xff1a;Download | Ubuntu 桌面版&#xff08;历史版本&#xff09; 以下载18.04 版本为例&am…

【三位重建】NeRF原理+代码讲解

文章目录一、技术原理1.概览2.基于神经辐射场&#xff08;Neural Radiance Field&#xff09;的体素渲染算法3.体素渲染算法4.位置信息编码&#xff08;Positional encoding&#xff09;5.多层级体素采样二、代码讲解1.数据读入2.创建nerf1.计算焦距focal与其他设置2.get_embed…

CSS学习|这一篇就够了|笔记|总结|(超详细讲解)

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;老茶icon &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开兴好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;计…

误删除文件怎么找回 数据恢复用这些方法

误删除文件是很多人都会遇到的问题&#xff0c;尤其是在Windows 10系统中&#xff0c;有时候我们不小心按了ShiftDelete或者清空了回收站&#xff0c;就会导致文件永久消失。那么&#xff0c;误删除文件怎么找回呢?本文将介绍四种数据恢复的方法&#xff0c;帮助你轻松将误删除…

【PCIE体系结构五】PCIE配置和地址空间

&#x1f449;个人主页&#xff1a;highman110 &#x1f449;作者简介&#xff1a;一名硬件工程师&#xff0c;持续学习&#xff0c;不断记录&#xff0c;保持思考&#xff0c;输出干货内容 参考书籍&#xff1a; PCI_Express体系结构导读、 深入浅出SSD&#xff1a;固态存储…

【Java版oj】day30最难的问题、因子个数

目录 一、最难的问题 &#xff08;1&#xff09;原题再现 &#xff08;2&#xff09;问题分析 &#xff08;3&#xff09;完整代码 二、因子个数 &#xff08;1&#xff09;原题再现 &#xff08;2&#xff09;问题分析 &#xff08;3&#xff09;完整代码 三、DFS深度优…

Python实现批量图片下载及去重处理

背景 在爬虫应用开发中&#xff0c;常常需要批量下载图片&#xff0c;并对图片进行去重处理。Python 是一种非常流行的编程语言&#xff0c;也是开发爬虫应用的首选&#xff0c;本文将介绍如何使用 Python 下载图片&#xff0c;并对下载的图片进行去重处理。 内容 首先&…

win10彻底永久关闭自动更新【亲测有效】

一、禁用Windows Update服务 1、同时按下键盘 Win R&#xff0c;打开运行对话框&#xff0c;然后输入命令 services.msc &#xff0c;点击下方的“确定”打开服务&#xff0c;如下图所示。 2、找到 Windows Update 这一项&#xff0c;并双击打开&#xff0c;如图所示。 3、右击…

【行为型模式】责任链模式

文章目录1、简介2、结构3、实现方式3.1、案例引入3.2、结构分析3.3、具体实现4、责任链优缺点5、应用场景1、简介 责任链模式(Chain of Responsibility)是一种行为型设计模式&#xff0c;它允许对象在链上依次处理请求&#xff0c;用户只需要将请求发送到责任链上即可&#xf…

CocosCreator实战篇 | 实现刮刮卡和橡皮擦 | 擦除效果

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/dxt19980308 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由肩匣与橘编写&#xff0c;首发于CSDN&#x1f649; &#x1f4e2;生活依旧是美好而…

【SSM框架】spring的创建与使用

spring的创建与使用Spring项目的创建创建一个maven项目添加Spring依赖添加启动类将bean存储到Spring 中创建bean对象将bean对象存储到Spring容器中从Spring中获取bean创建Spring(上下文)对象从Spring中获取到bean对象使用Bean&#xff08;非必须&#xff09;从spring中获取Bean…

奇瑞版Model 3与Model Y登场:正式进军高端纯电

作者 | Amy 编辑 | 德新4月7日&#xff0c;奇瑞在北京举办「新能源之夜」。整场发布会都在传递一个讯息&#xff1a;奇瑞搞新能源&#xff0c;要大搞特搞&#xff01; 奇瑞在这场发布会上&#xff0c;对新能源的战略、技术、品牌和产品作了全面梳理。其中最引人注目的是&#x…

环信web、uniapp、微信小程序sdk报错详解---注册篇(二、三)

项目场景&#xff1a; 记录对接环信sdk时遇到的一系列问题&#xff0c;总结一下避免大家再次踩坑。这里主要针对于web、uniapp、微信小程序在对接环信sdk时遇到的问题。 注册篇(二) 注册用户报错400 原因分析&#xff1a; 从console控制台输出及network请求返回入手分析 可以看…

【三十天精通Vue 3】第十一天 Vue 3 过渡和动画详解

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: 三十天精通 Vue 3 文章目录引言一、Vue 3 过度和动画概述1.1过度和动画的简介二、Vue 3 过度2…

Java 集合框架面试问题集锦

Java集合框架&#xff08;例如基本的数据结构&#xff09;里包含了最常见的Java常见面试问题。很好地理解集合框架&#xff0c;可以帮助你理解和利用Java的一些高级特性。下面是面试Java核心技术的一些很实用的问题。 Q&#xff1a;最常见的数据结构有哪些&#xff0c;在哪些场…

telnet远程管理linux主机及Zlib、openssl、openssh升级

目录 一、telnet远程管理主机 1、检查是否安装telnet 2、安装telnet服务 3、测试telnet登录 二、zlib、openssl、openssh升级 1、下载zlib包 2、下载openssl包 3、下载openssh包 4、 编译安装zlib 5、编译安装openssl 6、准备升级openssh环境 ①注意必须使用telne…

LLM 快人一步的秘籍 —— Zilliz Cloud,热门功能详解来啦!

最近&#xff0c;我们发布了可处理十亿级向量数据的 Zilliz Cloud GA 版本&#xff0c;为用户提供开箱即用的向量数据库服务&#xff0c;大大降低了数据库的运维成本。 看过上一篇文章《可处理十亿级向量数据&#xff01;Zilliz Cloud GA 版本正式发布》的朋友们知道&#xff0…

【SSM】Spring6(十.面向切面编程AOP)

文章目录1.AOP2. AOP的七大术语3. 切点表达式4.使用Spring的AOP4.1 环境准备4.2 基于AspectJ的AOP注解式开发步骤4.3 所有通知类型4.4 切面顺序4.5 通用切点4.6 获取目标方法的方法签名4.7 全注解式开发4.8 基于XML配置的AOP5. 案例&#xff1a;事务处理1.AOP 将与核心业务无关…

Visual Studio Code跳转到CSS定义

Visual Studio Code 快速跳转到 VUE文件 或 CSS文件的定义位置&#xff08;跳转到class定义&#xff0c;跳转到css定义&#xff09;&#xff0c;插件Css Peek、Vue Peek 对提升开发效率上&#xff0c;事半功倍。 目录 1、跳转到CSS定义 1.1、CSS Peek 1.2、Vue Peek 2、其他…