当前位置: 首页 > news >正文

【网络】套接字 -- TCP

🥁作者华丞臧.
📕​​​​专栏:【网络】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注)。如果有错误的地方,欢迎在评论区指出。
推荐一款刷题网站 👉 LeetCode刷题网站


文章目录

  • 一、TCP协议
    • 1.1 socket 常见API
      • listen
      • accept
      • connect
    • 1.2 TCP服务器
      • init()
      • start()
    • 1.3 TCP客户端
    • 1.4 测试


一、TCP协议

对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识:

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

1.1 socket 常见API

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
  • socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符;
  • 应用程序可以像读写文件一样用read/write在网络上收发数据;
  • 如果socket()调用出错则返回-1;
  • 对于IPv4, family参数指定为AF_INET;
  • 对于TCP协议,type参数指定为SOCK_STREAM, 表示面向流的传输协议
  • protocol参数的介绍从略,指定为0即可。
// 绑定端口号 (TCP/UDP, 服务器) 
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
  • 服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接; 服务器需要调用bind绑定一个固定的网络地址和端口号;
  • bind()成功返回0,失败返回-1。
  • bind()的作用是将参数sockfd和myaddr绑定在一起, 使sockfd这个用于网络通讯的文件描述符监听
  • myaddr所描述的地址和端口号;
  • 前面讲过,struct sockaddr *是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度;

程序中对 sockaddr 参数是这样初始化的:

#include <netinet/in.h>
#include <arpa/inet.h>// 2.填充域
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port_);
  1. 将整个结构体清零;
  2. 设置地址类型为AF_INET;
  3. 网络地址为INADDR_ANY, 这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP 地址, 这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP 地址;
  4. 端口号为SERV_PORT, 我们定义为9999。

listen

// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
  • listen()声明sockfd处于监听状态, 并且最多允许有backlog个客户端处于连接等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是5);
  • listen()成功返回0,失败返回-1。

accept

// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
  • 三次握手完成后, 服务器调用accept()接受连接;
  • 如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
  • addr是一个传出参数,accept()返回时传出客户端的地址和端口号;
  • 如果给addr 参数传NULL,表示不关心客户端的地址;
  • addrlen参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区addr的长度
  • 以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区);

connect

// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 客户端需要调用connect()连接服务器;
  • connect和bind的参数形式一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址;
  • connect()成功返回0,出错返回-1;

1.2 TCP服务器

在这里插入图片描述

#include <iostream>
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>#include "Log.hpp"volatile bool quitSer = false;void Usage(void *vgs)
{std::cout << "Usage:./tcpserver port ip" << std::endl;
}
class server
{
public:server(int port, std::string ip = ""): sockfd_(-1), ip_(ip), port_(port){}~server(){}public:void init(){}void start(){}private:int sockfd_;uint16_t port_;std::string ip_;
};// ./tcpserver port ip
int main(int argc, char *argv[])
{if (argc < 2 || argc > 3){Usage(argv[0]);exit(USAGE_ERR);}uint16_t port = atoi(argv[1]);std::string ip;if (argc == 3)ip = argv[2];server tcpSer(port, ip);tcpSer.init();tcpSer.start();return 0;
}

init()

TCP套接字服务器端的 init() ,与UDP类似,只不过多了最后一步监听套接字,TCP套接字设置步骤如下:

在这里插入图片描述

void init()
{// 1. 创建套接字sockfd_ = socket(AF_INET, SOCK_STREAM, 0);if (sockfd_ < 0){logMessage(FATAL, "socket:%s[%d]", strerror(errno), sockfd_);exit(SOCK_ERR);}logMessage(DEBUG, "socket success..");// 2.填充域struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port_);ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));// 3. 绑定网络信息if (bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) < 0){logMessage(FATAL, "bind:%s[%d]", strerror(errno), sockfd_);exit(BIND_ERR);}logMessage(DEBUG, "bind success...");// 4. 监听套接字// 为什么要监听套接字? 因为TCP是面向连接的,在任何时候都可能请求连接 if (listen(sockfd_, 5) < 0){logMessage(FATAL, "listen:%s[%d]", strerror(errno), sockfd_);exit(LISTEN_ERR);}logMessage(DEBUG, "listen success...");// 完成
}

start()

在TCP套接字提供服务之前,需要使用accept获取连接,accept返回值是一个新的套接字文件描述符,使用该套接字文件描述符来进行网络通信。start()是server提供服务的接口,因此该函数必须是一个死循环(服务器都是在一个死循环当中)以给用户提供持续的服务;在start函数中,主要完成接收用户发送的消息并且将消息提取出来,其主要步骤如下图:
在这里插入图片描述

void start()
{char inbuffer_[1024]; // 用来接收客户端发来的消息// 提供服务while (true){quitSer = false;struct sockaddr_in peer;socklen_t len = sizeof(peer);// 5. 获取连接, accept 的返回值是一个新的 socketfdint serviceSock = accept(sockfd_, (struct sockaddr *)&peer, &len);if (serviceSock < 0){// 获取链接失败logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);continue;}while (!quitSer){memset(inbuffer_, 0, sizeof(inbuffer_));ssize_t s = recvfrom(serviceSock, inbuffer_, sizeof(inbuffer_) - 1, 0,(struct sockaddr *)&peer, &len);if (s > 0){std::cout << s << std::endl;// 接收成功inbuffer_[s] = '\0';}else if (s == -1){//logMessage(WARINING, "recvfrom fialed:%s[%d]", strerror(errno), sockfd_);continue;}uint16_t peerPort = ntohs(peer.sin_port);std::string peerIp = inet_ntoa(peer.sin_addr);if(s > 0)logMessage(NOTICE, "[%s:%d]# %s", peerIp.c_str(), peerPort, inbuffer_);if (strcmp(inbuffer_, "quit") == 0){quitSer = true;}else{sendto(serviceSock, inbuffer_, strlen(inbuffer_), 0,\(const struct sockaddr *)&peer, sizeof(peer)); }}logMessage(DEBUG, "quit server...");close(serviceSock);}
}

1.3 TCP客户端

TCP客户端与UDP不同的是,在进行网络通信之前需要对服务器发起连接请求,连接成功后才能进行网络通信。
在这里插入图片描述

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cassert>
#include <ctype.h>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"volatile bool quit = 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;
}
// ./clientTcp serverIp serverPort
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(USAGE_ERR);}std::string serverIp = argv[1];uint16_t serverPort = atoi(argv[2]);// 1. 创建socket SOCK_STREAMint sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){std::cerr << "socket: " << strerror(errno) << std::endl;exit(SOCK_ERR);}// 2. connect,发起连接请求,你想谁发起请求呢??当然是向服务器发起请求喽// 2.1 先填充需要连接的远端主机的基本信息struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverPort);inet_aton(serverIp.c_str(), &server.sin_addr);// 2.2 发起请求,connect 会自动帮我们进行bind!if (connect(sock, (const struct sockaddr *)&server, sizeof(server)) != 0){std::cerr << "connect: " << strerror(errno) << std::endl;exit(CONN_ERR);}std::cout << "info : connect success: " << sock << std::endl;std::string message;while (!quit){message.clear();std::cout << "请输入你的消息>>> ";std::getline(std::cin, message);if (strcasecmp(message.c_str(), "quit") == 0)quit = true;ssize_t s = write(sock, message.c_str(), message.size());logMessage(DEBUG, "write success...");if (s > 0){message.resize(1024);ssize_t s = read(sock, (char *)(message.c_str()), 1024);if (s > 0)message[s] = 0;std::cout << "Server Echo>>> " << message << std::endl;}else if (s <= 0){break;}}close(sock);return 0;
}

由于客户端不需要固定的端口号,因此不必调用bind(),客户端的端口号由内核自动分配。

注意:

  • 客户端不是不允许调用bind(),只是没有必要调用bind()固定一个端口号。否则如果在同一台机器上启动多个客户端, 就会出现端口号被占用导致不能正确建立连接;
  • 服务器也不是必须调用bind(),但如果服务器不调用bind(),内核会自动给服务器分配监听端口,每次启动服务器时端口号都不一样,客户端要连接服务器就会遇到麻烦。

1.4 测试

在这里插入图片描述

相关文章:

【网络】套接字 -- TCP

&#x1f941;作者&#xff1a; 华丞臧. &#x1f4d5;​​​​专栏&#xff1a;【网络】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。 推荐一款刷题网站 &#x1f449; LeetCode刷题网站 文章…...

NDK C++ map容器

map容器// TODO map容器 #include <iostream> #include <map>using namespace std;int main() {// TODO map<int, string>按key值排序&#xff0c;同一个key不可以重复插入map<int, string> map1;map1.insert(pair<int, string>(1, "111&qu…...

linux(Centos)安装docker

官网地址&#xff1a;Install Docker Engine on CentOS 首先检查linux系统版本及内核&#xff1a; 安装docker要求系统版本至少为7.x版本&#xff0c;内核至少为3.8以上 cat /etc/redhat-release # 查看系统版本号uname -r #查看linux系统内核 检查系统是否能连上外网&#…...

Delphi 中 FireDAC 数据库连接(处理错误)

参见&#xff1a;Delphi 中 FireDAC 数据库连接&#xff08;总览&#xff09;本主题描述了如何用FireDAC处理数据库错误。一、概述EFDDBEngineException类是所有DBMS异常的基类。单个异常对象是一个数据库错误的集合&#xff0c;可以通过EFDDBEngineException.Errors[]属性访问…...

算法小抄3-理解使用Python容器之列表

引言 首先说一个概念哈,程序算法数据结构,算法是条件语句与循环语句组成的逻辑结构,而数据结构也就是容器. 算法决定数据该如何处理,而容器则决定如何数据如何存储. 不同的语言对容器有不同的实现方式, 但他们的功能都是相似的, 打好容器基础,你就可以在各式各样的语言中来回横…...

Vue3中watch的value问题

目录前言一&#xff0c;ref和reactive的简单复习1.ref函数1.2 reactive函数1.3 用ref定义对象类型数据不用reactive二&#xff0c;watch的value问题2.1 ref2.1.1 普通类型数据2.1.2 对象类型数据2.1.3 另一种方式2.2 reactive三&#xff0c;总结后记前言 在Vue3中&#xff0c;…...

【线性筛+DP】最大和

看错题了&#xff0c;呃呃&#xff0c;其实就是个简单DP最大和 - 蓝桥云课 (lanqiao.cn)题意&#xff1a;思路&#xff1a;设dp[i]为以1为终点的最大和&#xff0c;然后枚举状态和决策就行了主要是线性筛的应用&#xff0c;它可以预处理出一个数的最小质因子是多少Code&#xf…...

openpnp - configure - 丢弃(Discard)位置的设置

文章目录openpnp - configure - 丢弃(Discard)位置的设置概述笔记设置丢弃位置吸取元件失败后, 吸嘴一直吸气的处理ENDopenpnp - configure - 丢弃(Discard)位置的设置 概述 测试时, 吸取了一个元件, 吸取成功了, 现在想将这个料丢掉. 点击控制面板-Special页中的Discard不好…...

java Object 万字详解 (通俗易懂)

基本介绍构造方法成员方法hashCode()getClass()toString()equals()finalize()JavaBean重写Object类的方法重写toString重写equals一、基本介绍Object类是java类层次最顶层的基类&#xff08;父类&#xff09;&#xff0c;所有类都是直接或间接继承自Object类&#xff0c;因此&a…...

Java并发简介(什么是并发)

文章目录并发概念并发和并行同步和异步阻塞和非阻塞进程和线程竞态条件和临界区管程并发的特点提升资源利用率程序响应更快并发的问题安全性问题缓存导致的可见性问题线程切换带来的原子性问题编译优化带来的有序性问题保证并发安全的思路互斥同步&#xff08;阻塞同步&#xf…...

团队API管理工具-YAPI

团队API管理工具-YAPI 推荐一款接口管理平台&#xff0c;操作简单、界面友好、功能丰富、支持markdown语法、可使用Postman导入、Swagger同步数据展示、LDAP、权限管理等功能。 YApi是高效、易用、功能强大的api管理平台&#xff0c;旨在为开发、产品、测试人员提供更优雅的接…...

学习记录 --- Pytorch优化器

文章目录参考文献什么是优化器optimizer的定义optimizer的属性defaultsstateparam_groupsoptimizer的方法zero_grad()step()add_param_group()state_dict()、load_state_dict()优化一个网络同时优化多个网络当成一个网络优化当成多个网络优化只优化网络的某些指定的层调整学习率…...

Flink State 状态后端分析

flink状态实现分析 state * State* |* -------------------InternalKvState* | |* MergingState |* | |* …...

和年薪30W的阿里测开工程师聊过后,才知道我的工作就是打杂的...

前几天和一个朋友聊面试&#xff0c;他说上个月同时拿到了腾讯和阿里的offer&#xff0c;最后选择了阿里。 阿里内部将员工一共分为了14个等级&#xff0c;P6是资深工程师&#xff0c;P7是技术专家。 其中P6和P7就是一个分水岭了&#xff0c;P6是最接近P7的不持股员工&#x…...

C#开发的OpenRA的界面布局数据加载

C#开发的OpenRA的界面布局数据加载 当显示完成加载界面之后,就是进行其它内容处理。 因为后面内容的加载会比较长时间,所以首先显示加载界面是一种非常友好的方法。 因此在软件设计里,尽可能先显示界面,让用户先看到程序正在运行, 然后再处理时间长的加载。如果不这样做,…...

并查集结构

文章目录并查集特点构建过程查找两个元素是否是同一集合优化查找领头元素设置两个元素为同一集合构建结构应用场景并行计算集合问题并查集特点 对于使用并查集构建的结构&#xff0c;可以使得查询两个元素是否在同一集合&#xff0c;以及合并集合的操作无限接近O(1) 构建过程…...

全国CSM敏捷教练认证将于2023年3月25-26开班,报名从速!

CSM&#xff0c;即Certified Scrum Master&#xff0c;是Scrum联盟发起的Scrum认证。 CSM可以帮助团队正确使用Scrum&#xff0c;从而提高项目整体成功的可能性。 CSM深刻理解Scrum的价值观、实践以及Scrum框架。 CSM是“服务型领导”&#xff0c;帮助Scrum团队一起紧密合作。 …...

JavaEE进阶第六课:SpringBoot ⽇志⽂件

上篇文章介绍了SpringBoot配置文件&#xff0c;这篇文章我们将会介绍SpringBoot ⽇志⽂件 荔枝1.日志有什么用2.自定义日志输出2.1获取程序日志对象2.2使用相关方法输出日志2.3日志级别2.3.1日志级别的作用2.3.2日志级别如何设置2.4日志格式3.持久化日志4.更简单的日志输出4.1使…...

外置MOS管平均电流型LED降压恒流驱动器

产品描述 AP5125 是一款外围电路简单的 Buck 型平均电 流检测模式的 LED 恒流驱动器&#xff0c;适用于 8-100V 电压 范围的非隔离式大功率恒流 LED 驱动领域。芯片采用 固定频率 140kHz 的 PWM 工作模式&#xff0c; 利用平均电 流检测模式&#xff0c;因此具有优异的负载调整…...

python+pytest接口自动化(6)-请求参数格式的确定

我们在做接口测试之前&#xff0c;先需要根据接口文档或抓包接口数据&#xff0c;搞清楚被测接口的详细内容&#xff0c;其中就包含请求参数的编码格式&#xff0c;从而使用对应的参数格式发送请求。例如某个接口规定的请求主体的编码方式为 application/json&#xff0c;那么在…...

开发手册——一、编程规约_3.代码格式

这篇文章主要梳理了在java的实际开发过程中的编程规范问题。本篇文章主要借鉴于《阿里巴巴java开发手册终极版》 下面我们一起来看一下吧。 1. 【强制】大括号的使用约定。如果是大括号内为空&#xff0c;则简洁地写成{}即可&#xff0c;不需要换行&#xff1b;如果是非空代码…...

十七、Django-restframework之序列化器(二)

1. 序列化器 REST framework提供了一个serializer类&#xff0c;它可以非常方便的序列化模型实例和查询集为JSON或者其他内容形式。它还提供反序列化&#xff0c;允许在验证传入数据后将解析的数据转换回复杂类型。 2. 定义序列化器 在crm应用目录下创建serializers.py文件&a…...

python GUI图形化编程-----wxpython

一、python gui&#xff08;图形化&#xff09;模块介绍&#xff1a; Tkinter :是python最简单的图形化模块&#xff0c;总共只有14种组建 Pyqt :是python最复杂也是使用最广泛的图形化 Wx :是python当中居中的一个图形化&#xff0c;学习结构很清晰 Pywin :是pyth…...

【Python 】yyyy-MM-dd HH:mm:ss 时间格式 时间戳 全面解读超详细

时间格式 时间格式(协议)描述gg时期或纪元。y不包含纪元的年份。不具有前导零。yy不包含纪元的年份。具有前导零。yyyy包含纪元的四位数的年份。M月份数字。一位数的月份没有前导零。MM月份数字。一位数的月份有一个前导零。MMM月份的缩写名称&#xff0c;在AbbreviatedMonthN…...

【C++】C++11 异常

目录 1. C语言传统的处理错误的方式 2. C异常概念 3. 异常的使用 3.1. 异常的抛出和捕获 3.2. 在函数调用链中异常栈展开匹配原则 3.3. 异常的重新抛出 3.4. 异常安全 3.5. 异常规范 4.自定义异常体系 5. C标准库的异常体系 6. 异常的优缺点 6.1. C异常的优点&…...

关于Thread.start()后的困惑、imap

在for循环中&#xff0c;接着开thread&#xff0c;开完就start&#xff0c;当时有个困惑&#xff0c;就是比如开的一个thread的这个start执行完&#xff0c;但是这个for循环还没执行完&#xff0c;那程序会跑到for循环的后面逻辑吗&#xff1f;比如下面13行for循环开始开第一个…...

qml学习之qwidget与qml结合使用并调用信号槽交互

学习qml系列之一说明&#xff1a; 学习qml系列之qwiget和qml信号槽的交互使用&#xff0c;并在qwidget中显示qml界面 在qml中发送信号到qwidget里 在qwidget里发送信号给qml 在qwidget里面调用qml界面方式 方式一&#xff1a;使用QQuickView 这个是Qt5.0中提供的一个类&…...

【 华为OD机试 2023】 组装新的数组(C++ Java JavaScript Python)

文章目录 题目描述输入描述输出描述备注用例题目解析C++JavaScriptJavaPython题目描述 给你一个整数M和数组N,N中的元素为连续整数,要求根据N中的元素组装成新的数组R,组装规则: R中元素总和加起来等于MR中的元素可以从N中重复选取R中的元素最多只能有1个不在N中,且比N中…...

【洛谷 P2089】烤鸡(循环枚举)

烤鸡 题目背景 猪猪 Hanke 得到了一只鸡。 题目描述 猪猪 Hanke 特别喜欢吃烤鸡&#xff08;本是同畜牲&#xff0c;相煎何太急&#xff01;&#xff09;Hanke 吃鸡很特别&#xff0c;为什么特别呢&#xff1f;因为他有 101010 种配料&#xff08;芥末、孜然等&#xff09;…...

windows10安装ubantu双系统

windows10安装ubantu双系统 文章目录windows10安装ubantu双系统一、安装前准备1.前期说明2.制作U盘启动器3.设置硬盘分区相关4.设置给ubantu系统的硬盘大小&#xff0c;设置为未分配&#xff08;删除卷&#xff09;二、进行安装1.设置bios相关2.进入bios启动界面选择U盘安装3.进…...

提供网站建设工具的公司/seo网站优化方案书

自定义注解 Target 自定义注解的使用范围 ElementType.METHOD&#xff1a;方法声明 ElementType.TYPE&#xff1a;类、接口&#xff08;包括注解类型&#xff09;或enum声明 ElementType.CONSTRUCTOR&#xff1a;构造器的声明 ElementType.FIELD&#xff1a;域声明&#xff0…...

旅游景点网站建设设计说明/蚂蚁bt

暖气来了&#xff0c;嗓子眼儿冒火、口腔溃疡、大便干燥&#xff0c;该怎么办呢&#xff1f;解放军309医院营养科主任医师张晔开出四字饮食处方&#xff1a;降、清、润、补。 降火汤——冬瓜配紫菜 很多家庭最爱做西红柿黄瓜片汤&#xff0c;其实冬季最好的汤是冬瓜汤&#xff…...

邢台哪儿做wap网站/成都专门做网络推广的公司

给出集合 [1,2,3,…,n]&#xff0c;其所有元素共有 n! 种排列。 按大小顺序列出所有排列情况&#xff0c;并一一标记&#xff0c;当 n 3 时, 所有排列如下&#xff1a; “123” “132” “213” “231” “312” “321” 给定 n 和 k&#xff0c;返回第 k 个排列。 示例 1&…...

天津建设工程信息网咨询电话/桔子seo查询

技术栈&#xff1a;vue2.0ssm 流程图&#xff1a; 前端&#xff1a; 1.新建vue项目 vue init webpack 项目名 2.安装所需的依赖 //安装element组件库 npm i element-ui -S //在main.js中引入element组件库 import ElementUI from element-ui; import element-ui/lib/theme-…...

建设网站只/怎么看百度指数

视频采集卡webcam只是弄乱了一些HTML5网络摄像头演示。 这是最好的3个HTML5 WebCam视频演示 。 如果您知道了&#xff0c;请发送给他们&#xff01; :) HTML5视频要求您接受网络摄像头访问&#xff0c;并且在页面被点击时会提示您。 1. HTML5网络摄像头视频 试试吧&#xff0…...

做ic销售的各种网站/重庆网站制作公司哪家好

https://blog.tengshe789.tech/2018/08/04/springboot/?hmsrtoutiao.io&utm_mediumtoutiao.io&utm_sourcetoutiao.io 这是一篇优雅的使用...