基于Socket简单的TCP网络程序

news2024/11/19 10:25:53

小白苦学IT的博客主页

初学者必看:Linux操作系统入门

代码仓库:Linux代码仓库

❤关注我一起讨论和学习Linux系统

TCP单例模式的多线程版本的英汉互译服务器

我们先来认识一下与udp服务器实现的不同的接口:

TCP服务器端

socket():创建一个新的套接字,指定使用的协议族(如IPv4)、套接字类型(如SOCK_STREAM表示TCP)和协议(通常为0,表示使用默认协议)。

bind():将套接字绑定到一个特定的地址和端口号上,这样客户端就可以通过这个地址和端口号连接到服务器。

listen():使套接字进入监听状态,等待客户端的连接请求。可以指定最大连接队列长度。

accept():接受一个客户端的连接请求,并返回一个新的套接字,用于与这个客户端进行通信。原始的套接字继续用于监听其他客户端的连接请求。

read() :从已连接的客户端套接字读取数据。TCP是字节流协议,因此你需要按照某种协议或方式来分割和解析接收到的数据。

write():向已连接的客户端套接字发送数据。

TCP客户端

socket():同样创建一个新的套接字。

connect():发起一个到服务器地址和端口号的连接请求。

read() :从服务器套接字读取数据。

write():向服务器套接字发送数据。

封装TcpSocket

TcpServer.hpp

#pragma once

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
#include <cstring>
#include<pthread.h>
#include"ThreadPool.hpp"
#include "task.hpp"

const int defaultfd = -1;
const std::string defaultip = "0.0.0.0";
const int backlog = 5;

enum
{
    UsageError = 1,
    SocketError,
    BindError,
    ListenError,
};

class TcpServer;

class ThreadData1
{
public:
    ThreadData1(int fd,const std::string & ip,const uint16_t & port,TcpServer* t)
    :sockfd(fd),clientip(ip),clientport(port),tsvr(t)
    {

    }

public:
    int sockfd;
    std::string clientip;
    uint16_t clientport;
    TcpServer* tsvr;
};

class TcpServer
{

public:
    TcpServer(const uint16_t port, const std::string &ip = defaultip)
        : _listensock(-1), _port(port), _ip(ip)
    {
    }

    void InitServer()
    {
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            log.LogMessage(FATAL, "create socket error , errno:%d, strerror: %s", errno, strerror(errno));
            exit(SocketError);
        }
        log.LogMessage(INFO, "create socket success ,_listensock:%d", _listensock);

        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        inet_aton(_ip.c_str(), &(local.sin_addr));

        if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            log.LogMessage(FATAL, "bind error , errno:%d, strerror: %s", errno, strerror(errno));
            exit(BindError);
        }
        log.LogMessage(INFO, "bind socket success ,_listensock:%d", _listensock);

        // Tcp是面向连接的,所以服务器一般是比较“被动”的,服务器一种处于一种
        if (listen(_listensock, backlog) < 0)
        {
            log.LogMessage(FATAL, "listen error , errno:%d, strerror: %s", errno, strerror(errno));
            exit(ListenError);
        }
        log.LogMessage(INFO, "listen success ,_listensock:%d", _listensock);
    }


    void Start()
    {
        ThreadPool<Task>::GetInstance()->Start();
        log.LogMessage(INFO, "tcpServer is running ...");
        for (;;)
        {
            // 1.获取新链接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(_listensock, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                log.LogMessage(WARNING, "accept error , errno:%d, strerror: %s", errno, strerror(errno));
                continue;
            }
            uint16_t clientport = ntohs(client.sin_port);
            char clientip[32];
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));

            // 2.根据新连接来进行通信
            log.LogMessage(INFO, "get a new link ... client ip : %s, client port : %d , sockfd:%d", clientip, clientport, sockfd);

            //version4 线程池版本
            Task t(sockfd,clientport,clientip);
            ThreadPool<Task>::GetInstance()->Push(t);
        }
    }

    ~TcpServer() {}

private:
    int _listensock;
    uint16_t _port;
    std::string _ip;
};

ThreadPool.hpp

#pragma once

#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>

struct ThreadInfo
{
    pthread_t tid;
    std::string name;
};

static const int defalutnum = 10;

template <class T>
class ThreadPool
{
public:
    void Lock()
    {
        pthread_mutex_lock(&mutex_);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&mutex_);
    }
    void Wakeup()
    {
        pthread_cond_signal(&cond_);
    }
    void ThreadSleep()
    {
        pthread_cond_wait(&cond_, &mutex_);
    }
    bool IsQueueEmpty()
    {
        return tasks_.empty();
    }
    std::string GetThreadName(pthread_t tid)
    {
        for (const auto &ti : threads_)
        {
            if (ti.tid == tid)
                return ti.name;
        }
        return "None";
    }

public:
    static void *HandlerTask(void *args)
    {
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        std::string name = tp->GetThreadName(pthread_self());
        while (true)
        {
            tp->Lock();

            while (tp->IsQueueEmpty())
            {
                tp->ThreadSleep();
            }
            T t = tp->Pop();
            tp->Unlock();

            t();
        }
    }
    void Start()
    {
        int num = threads_.size();
        for (int i = 0; i < num; i++)
        {
            threads_[i].name = "thread-" + std::to_string(i + 1);
            pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);
        }
    }
    T Pop()
    {
        T t = tasks_.front();
        tasks_.pop();
        return t;
    }
    void Push(const T &t)
    {
        Lock();
        tasks_.push(t);
        Wakeup();
        Unlock();
    }
    static ThreadPool<T> *GetInstance()
    {
        if (nullptr == tp_) // ???
        {
            pthread_mutex_lock(&lock_);
            if (nullptr == tp_)
            {
                std::cout << "log: singleton create done first!" << std::endl;
                tp_ = new ThreadPool<T>();
            }
            pthread_mutex_unlock(&lock_);
        }

        return tp_;
    }

private:
    ThreadPool(int num = defalutnum) : threads_(num)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // a=b=c
private:
    std::vector<ThreadInfo> threads_;
    std::queue<T> tasks_;

    pthread_mutex_t mutex_;
    pthread_cond_t cond_;

    static ThreadPool<T> *tp_;
    static pthread_mutex_t lock_;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;

template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;

Main.cc

#include"TcpServer.hpp"
#include<memory>
#include<iostream>

void Usage(std::string proc)
{
    std::cout<<"\n\rUsage: "<<proc<<" port[1024+]"<<std::endl;
}

int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        exit(UsageError);
    }

    uint16_t port = std::stoi(argv[1]);
    std::unique_ptr<TcpServer> tcp_svr(new TcpServer(port));
    tcp_svr->InitServer();
    tcp_svr->Start();

    return 0;
}

TcpClient.cc

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

void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << " serverip  serverport[1024+]" << std::endl;
}

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

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        return 1;
    }

    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));
    server.sin_port = htons(serverport);

    // tcp要不要bind? 要bind 要不要显示的bind? 不用显示的bind 系统进行bind,随机端口
    // 客户端发起connect的时候,进行自动随机bind.
    int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
    if (n < 0)
    {
        std::cerr << "connect error" << std::endl;
        return 2;
    }

    std::string message;
    std::cout<<"please Enter# ";
    std::getline(std::cin,message);
    write(sockfd,message.c_str(),message.size());

    char inbuffer[4096];
    int r = read(sockfd,inbuffer,sizeof(inbuffer));
    if(r>0)
    {
        inbuffer[r] = 0;
        std::cout<<inbuffer<<std::endl;
    }
    close(sockfd);
    std::cout<<"Connection closed by foreign host"<<std::endl;
    return 0;
}

Init.hpp 

#pragma once

#include<iostream>
#include<string>
#include<fstream>
#include<unordered_map>
#include "Log.hpp"

const std::string dictname = "./translation.txt";
const std::string sep = ":";

static bool Split(std::string & s,std::string *part1,std::string *part2)
{
    auto pos = s.find(sep);
    if(pos==std::string::npos)
    {
        return false;
    }
    *part1 = s.substr(0,pos);
    *part2 = s.substr(pos+1);
    return true;
}

class Init
{
public:
    Init()
    {
        std::ifstream in(dictname);
        if(!in.is_open())
        {
            log.LogMessage(FATAL,"ifstream open %s error",dictname.c_str());
            exit(1);
        }
        std::string line;
        while(std::getline(in,line))
        {
            std::string part1,part2;
            Split(line,&part1,&part2);
            dict.insert({part1,part2});
        }
        in.close();
    }

    std::string translation(const std::string &key)
    {
        auto iter = dict.find(key);
        if(iter == dict.end())
        {
            return "UnKnow";
        }
        else return iter->second;

        
    }
private:
    std::unordered_map<std::string,std::string> dict;

};

Log.hpp 

#pragma once

#include <iostream>
#include <cstdarg>
#include <ctime>
#include <string>
#include <unistd.h>
#include <fstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

enum
{
    DEBUG = 0,
    INFO,
    WARNING,
    ERROR,
    FATAL
};

enum
{
    Screen = 10,
    Onefile,
    Classfile
};

std::string LevelToString(int level)
{
    switch (level)
    {
    case DEBUG:
        return "Debug";
    case INFO:
        return "Info";

    case WARNING:
        return "Warning";
    case ERROR:
        return "Error";
    case FATAL:
        return "Fatal";
    default:
        return "Unknown";
    }
}

const int defaultstyle = Screen;
const std::string default_filename = "log.";
const std::string logdir="log";

class Log
{
public:
    Log():style(defaultstyle),filename(default_filename)
    {
        mkdir(logdir.c_str(),0775);
    }

    void Enable(int sty)
    {
        style = sty;
    }

    std::string TimestampToLocalTime()
    {
        time_t curr = time(nullptr);
        struct tm *currtime = localtime(&curr);
        char time_buffer[128];
        snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",
                 currtime->tm_year + 1900, currtime->tm_mon, currtime->tm_mday, currtime->tm_hour,
                 currtime->tm_min, currtime->tm_sec);

        return time_buffer;
    }

    void WriteLog(const std::string &levelstr, const std::string &message)
    {
        switch (style)
        {
        case Screen:
            std::cout << message<<std::endl;
            break;
        case Onefile:
            WriteLogToOnefile("all", message);
            break;
        case Classfile:
            WriteLogToClassfile(levelstr, message);
            break;
        default:
            break;
        }
    }

    void WriteLogToOnefile(const std::string &logname, const std::string &message)
    {
        umask(0);
        int fd = open(logname.c_str(),O_CREAT | O_WRONLY | O_APPEND,0666);
        if(fd<0)return;
        write(fd,message.c_str(),message.size());
        close(fd);
        // std::ofstream out(logname);
        // if (!out.is_open())
        //     return;
        // out.write(message.c_str(), message.size());
        // out.close();
    }

    void WriteLogToClassfile(const std::string &levelstr, const std::string &message)
    {
        std::string logname = logdir;
        logname+="/";
        logname+=filename;
        logname += levelstr;
        WriteLogToOnefile(logname, message);
    }

    void LogMessage(int level, const char *format, ...) // 类c的日志接口
    {
        char rightbuffer[1024];
        va_list args;
        va_start(args, format);
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, args);
        va_end(args);

        char leftbuffer[1024];
        std::string curtime = TimestampToLocalTime();
        std::string levelstr = LevelToString(level);
        std::string idstr = std::to_string(getpid());
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%s][%s]",
                 levelstr.c_str(), curtime.c_str(), idstr.c_str());

        std::string logInfo = leftbuffer;
        logInfo += rightbuffer;

        WriteLog(levelstr, logInfo);
    }
    ~Log() {}

private:
    int style;
    std::string filename;
};


Log log;

class Conf
{
public:
    Conf()
    {
        log.Enable(Screen);
    }
    ~Conf(){}
};

Conf conf;

task.hpp

#pragma once
#include<string>
#include<iostream>
#include"Log.hpp"
#include"Init.hpp"

Init init;

class Task
{
public:
    Task(int sockfd, const uint16_t &clientport, const std::string &clientip)
    :clientip_(clientip),clientport_(clientport),sockfd_(sockfd)
    {}

    void Run()
    {
        char buffer[4096];
        // 测试代码
        ssize_t n = read(sockfd_, buffer, sizeof(buffer));
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "client key# " << buffer << std::endl;
            std::string echo_string = init.translation(buffer);

            write(sockfd_, echo_string.c_str(), echo_string.size());
        }
        else if (n == 0)
        {
            log.LogMessage(INFO, "%s:%d quit,server close sockfd:%d", clientip_.c_str(), clientport_, sockfd_);
        
        }
        else
        {
            log.LogMessage(WARNING, "read error,sockfd:%d,clientip:%s ,clientport:%d ", sockfd_, clientip_.c_str(), clientport_);
            
        }
        close(sockfd_);
    }

    void operator()()//运算符重载实现仿函数
    {
        Run();
    }

    ~Task() {}
private:
    int sockfd_;
    std::string clientip_;
    uint16_t clientport_;
};

translation.txt

# 英汉互译键值对  
# English-Chinese Key-Value Pairs  
  
hello: 你好  
world: 世界  
goodbye: 再见  
thank you: 谢谢  
please: 请  
welcome: 欢迎  
happy: 快乐的  
sad: 悲伤的  
angry: 生气的  
excited: 兴奋的  
  
apple: 苹果  
banana: 香蕉  
orange: 橙子  
grape: 葡萄  
peach: 桃子  
watermelon: 西瓜  
strawberry: 草莓  
cherry: 樱桃  
  
morning: 早上  
afternoon: 下午  
evening: 晚上  
night: 夜晚  
weekday: 工作日  
weekend: 周末  
January: 一月  
February: 二月  
March: 三月  
April: 四月

只是写入了部分单词,比较简单,想要实现的更完整可以自行加入一些单词与中文意思及其解释等内容可以让该词典内容更丰富。

 

运行结果:

服务器一直在运行,客户端访问一次就退出了。

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

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

相关文章

【C++初阶】String在OJ中的使用(一):仅仅反转字母、字符串中的第一个唯一字母、字符串最后一个单词的长度、验证回文串、字符串相加

前言&#xff1a; &#x1f3af;个人博客&#xff1a;Dream_Chaser &#x1f388;博客专栏&#xff1a;C &#x1f4da;本篇内容&#xff1a;仅仅反转字母、字符串中的第一个唯一字母、字符串最后一个单词的长度、验证回文串、字符串相加 目录 917.仅仅反转字母 题目描述&am…

【stm32】软件I2C读写MPU6050

软件I2C读写MPU6050(文章最后附上源码) 编码 概况 首先建立通信层的.c和.h模块 在通信层里写好I2C底层的GPIO初始化 以及6个时序基本单元 起始、终值、发送一个字节、接收一个字节、发送应答、接收应答 写好I2C通信层之后&#xff0c;再建立MPU6050的.c和.h模块 基于I2C通…

软考116-上午题-【计算机网络】-LINUX命令

一、真题 真题1&#xff1a; 真题2&#xff1a; 权限通常分为三类&#xff1a; 读&#xff08;r&#xff09;&#xff1a;允许读取文件内容或列出目录内容。写&#xff08;w&#xff09;&#xff1a;允许修改文件内容或在目录中创建/删除文件。执行&#xff08;x&#xff09;&…

stm32开发之threadx使用记录(主逻辑分析)

前言 threadx的相关参考资料 论坛资料、微软官网本次使用的开发板为普中科技–麒麟&#xff0c;核心芯片为 stm32f497zgt6开发工具选择的是stm32cubemx(代码生成工具)clion(代码编写工具)编译构建环境选择的是arm-none-gcc编译 本次项目结构 CMakeList对应的配置 set(CMAKE_…

SD-WAN国际网络专线:高效、合规且可靠的跨境连接解决方案

在数字化时代&#xff0c;企业对跨境网络连接的需求日益增长。SD-WAN技术作为一种新兴的解决方案&#xff0c;正逐渐成为构建跨境网络连接的首选。本文将探讨SD-WAN国际网络专线的发展现状、合规性要求以及选择时需要考虑的关键因素。 SD-WAN技术&#xff1a;跨境网络连接的新…

如何在没有备份的情况下从 iPad 恢复照片?

有很多操作都可能导致iPad照片丢失&#xff0c;包括误删除、出厂设置、iPad的iOS更新等。如果没有备份&#xff0c;似乎没有办法找回它们。然而&#xff0c;即使您将备份保留在 iCloud 或iTunes上&#xff0c;这些方式也需要您的 iPad 首先重置&#xff0c;从而用备份内容覆盖当…

堆排序解读

在算法世界中&#xff0c;排序算法一直是一个热门话题。推排序&#xff08;Heap Sort&#xff09;作为一种基于堆这种数据结构的有效排序方法&#xff0c;因其时间复杂度稳定且空间复杂度低而备受青睐。本文将深入探讨推排序的原理、实现方式&#xff0c;以及它在实际应用中的价…

lua学习笔记5(分支结构和循环的学习)

print("*****************分支结构和循环的学习******************") print("*****************if else语句******************") --if 条件 then end a660 b670 --单分支 if a<b thenprint(a) end --双分支 if a>b thenprint("满足条件")…

机器学习模型——逻辑回归

https://blog.csdn.net/qq_41682922/article/details/85013008 https://blog.csdn.net/guoziqing506/article/details/81328402 https://www.cnblogs.com/cymx66688/p/11363163.html 参数详解 逻辑回归的引出&#xff1a; 数据线性可分可以使用线性分类器&#xff0c;如果…

c# wpf LiveCharts 简单试验

1.概要 1.1 说明 1.2 环境准备 NuGet 添加插件安装 2.代码 <Window x:Class"WpfApp3.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"…

WindowsPowerShell安装配置Vim的折腾记录

说明 vim一直以来都被称为编辑器之神一样的存在。但用不用vim完全取决于你自己&#xff0c;但是作为一个学计算机的同学来说&#xff0c;免不了会和Linux打交道&#xff0c;而大部分的Linux操作系统都预装了vim作为编辑器&#xff0c;如果是简单的任务&#xff0c;其实vim只要会…

电商技术揭秘八:搜索引擎中的SEO内部链接建设与外部推广策略

文章目录 引言一、 内部链接结构优化1.1 清晰的导航链接1. 简洁明了的菜单项2. 逻辑性的布局3. 避免深层次的目录结构4. 使用文本链接5. 突出当前位置6. 移动设备兼容性 1.2 面包屑导航1. 显示当前页面位置2. 可点击的链接3. 简洁性4. 适当的分隔符5. 响应式设计6. 避免重复主页…

图像分割-RSPrompter

文章目录 前言1. 自动化提示器1.1 多尺度特征增强器1.2 RSPrompterAnchor-based PrompterQuery-based Prompter 2. SAM的扩展3. 结果WHU数据集NWPU数据集SSDD数据集 前言 《RSPrompter: Learning to prompt for remote sensing instance segmentation based on visual foundati…

Linux--03---虚拟机网络配置、拍摄快照和克隆

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.虚拟机网络配置1.虚拟机的联网模式模式1 仅主机模式特点模式2 桥接模式特点模式3 NAT模式特点关于模式的选择 2. 修改网络配置信息3.修改虚拟机ens33网卡的网络配…

「 典型安全漏洞系列 」12.OAuth 2.0身份验证漏洞

在浏览网页时&#xff0c;你肯定会遇到允许你使用社交媒体帐户登录的网站。此功能一般是使用流行的OAuth 2.0框架构建的。本文主要介绍如何识别和利用OAuth 2.0身份验证机制中发现的一些关键漏洞。 1. OAuth产生背景 为了更好的理解OAuth&#xff0c;我们假设有如下场景&#…

分享一个基于Multi-SLAM+3DGS的新一代三维内容生产技术

基于智能空间计算&#xff0c;新一代超逼真三维内容生成技术。 可自动化生成超逼真的大场景三维模型&#xff0c;并在各类终端和空间计算设备中&#xff0c;实现前所未有的沉浸式体验。 更可接入专业三维软件和应用平台&#xff0c;进行深度的模型开发与场景落地。 支持超大复杂…

【前端Vue】Vue0基础完整教程第5篇:vue指令(下),成绩案例【附代码文档】

Vue从0基础到大神学习完整教程完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;vue基本概念&#xff0c;vue-cli的使用&#xff0c;vue的插值表达式&#xff0c;{{ gaga }}&#xff0c;{{ if (obj.age > 18 ) { } }}&#xff0c;vue指令&#xff0c;综合…

C++【适配器模式】

简单介绍 适配器模式是一种结构型设计模式 | 它能使接口不兼容的对象能够相互合作。&#xff08;是适配各种不同接口的一个中间件&#xff09; 基础理解 举个例子&#xff1a;当你引用了一个第三方数据分析库&#xff0c;但这个库的接口只能兼容JSON 格式的数据。但你需要它…

Jenkins (五) - Docker SonarQube

Jenkins (五) - Docker SonarQube Jenkins 集成 SonarQube&#xff0c;编译项目并通过SonarQube分析项目 前提 基于已有的环境 Jenkins (四) - Docker SonarQube 基于 Jenkins (三) - 拉取编译 上的mockito-demo工程 配置工程 Administration -> Projects -> Manage…

axios快速入门

一、环境配置 1.1概述 上古浏览器页面在向服务器请求数据时&#xff0c;因为返回的是整个页面的数据&#xff0c;页面都会强制刷新一下&#xff0c;这对于用户来讲并不是很友好。并且我们只是需要修改页面的部分数据&#xff0c;但是从服务器端发送的却是整个页面的数据&#…