【Linux 网络编程1】使用UDP/TCP编写套接字,多进程/多线程版本的TCP编写的套接字,将套接字封装
目录
1.学习网络编程前的一些基础知识
2.UDP(user datagram protocol)协议的特点
3.使用有UPD编写套接字
4.使用TCP编写套接字
4.2.TCP客服端
4.3.TCP服务器端
4.4.单进程版本(没有人会使用)
4.5.多进程版本
4.6.多线程版本
5.把套接字封装
1.学习网络编程前的一些基础知识
1.1.IP地址
- IP地址是在IP协议中, 用来标识网络中不同主机的地址(一个IP地址标识一台主机);
- 通常使用 "点分十进制" 的字符串表示IP地址, 例如127.0.0.1 ; 用点分割的每一个数字表示一个字节, 范围是 0 - 255,就是一个字节的大小,IP地址刚好是4字节, 32位的整数;
1.2.端口号(port)
- 端口号是一个2字节16位的整数
- 端口号用来标识一个进程,那么IP地址+端口号就可以标识某一台主机的某一个进程
1.3.TCP/IP四层模型

1.4.网络字节序
- 网络字节序就是大端字节序(低位放在高地址上),使用相同的字节序便于网络间通信;
有系统提供的接口

2.UDP(user datagram protocol)协议的特点
UDP协议是一个传输层协议
- 无连接:没有连接,客户端发给服务器端,服务器端要先保存客户端的信息,服务器端再使用这个信息发给对应的客户端(简单地说就是需要指明发送给谁)
- 不可靠传输:只是传递数据,成功与否都不会反馈
- 面向数据报:不能向面向字节流的TCP一样使用read和write来读写
3.使用有UPD编写套接字
3.1.服务器端
3.1.1.创建套接字

int sock=socket(AF_INET,SOCK_DGRAM,0);//创建套接字if(sock<0){std::cerr<<"socket fail: "<<errno<<std::endl;return 1;}
3.1.2.bind服务器
网络字节序就是大端字节序(低位放在高地址上),使用相同的字节序便于网络间通信;
- 1.需要将人识别的点分十进制,字符串风格IP地址,转化为4字节整数IP.2.考虑大小端的问题
服务器不用bind一个固定的IP的原因
- 1.云服务器,不允许bind公网IP,另外,在一般编写也不会指明IP.2.一般主机有多个IP,如果只是bind一个IP,发给其他IP的数据就不会交给主机处理
- INADDR_ANY只要是发给这个主机的数据都会被处理

struct sockaddr_in local;local.sin_family=AF_INET;local.sin_port=htons(port);//1.需要将人识别的点分十进制,字符串风格IP地址,转化为4字节整数IP.2.考虑大小端的问题//1.云服务器,不允许bind公网IP,另外,在一般编写也不会指明IP.2.一般主机有多个IP,如果只是bind一个IP,发给其他IP的数据就不会交给主机处理;//INADDR_ANY只要是发给这个主机的数据都会被处理local.sin_addr.s_addr=INADDR_ANY;if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)//bind主机{std::cerr << "bind error : " << errno << std::endl;return 2;}
3.1.3.传递接受数据
- UDP是无连接的,需指明发给谁,也需要保存发送端的信息

//业务逻辑char message[1024];bool quit=false;while(!quit){//保存发送端信息struct sockaddr_in peer; socklen_t len=sizeof(peer);//接受数据ssize_t s=recvfrom(sock,message,sizeof(message)-1,0,(struct sockaddr*)&peer,&len);if(s>0){message[s]=0;std::cout<<"client# "<<message<<std::endl;}else{std::cerr<<"recvfrom"<<errno<<std::endl;return 2;}//给对端发送一个你好吗std::string tm="你好吗?";std::cout<<"server to client: "<<tm<<std::endl;sendto(sock,tm.c_str(),tm.size(),0,(struct sockaddr*)&peer,len);}
3.2.客户端
- 客户端不需要显示bind,当传输第一个数据是会自动随机bind一个port(没有被使用的port)
#include<iostream>
#include<cerrno>
#include<string>
#include<cstdlib>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>//client serverIP serverPort
int main(int argc,char* argv[])
{if(argc!=3){std::cout<<"请按格式输入: client serverIP serverPort"<<std::endl;return 2;}int sock=socket(AF_INET,SOCK_DGRAM,0);//创建套接字if(sock<0){std::cout<<"socket create errno: "<<errno<<std::endl;return 1;}//客户端不用显示bind,OS会自动bind;//服务器端会有规划,让port是没有被占用的,让别人来访问这个port;//client正常发送数据的时候,OS会自动给你bind,采用随机端口的方式struct sockaddr_in server;server.sin_family=AF_INET;server.sin_port=htons(atoi(argv[2]));server.sin_addr.s_addr=inet_addr(argv[1]);while(1){std::string message;std::cout<<"请输入#";std::cin>>message;sendto(sock,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));struct sockaddr_in tmp;socklen_t tlen=sizeof(tmp);char buffer[1024];ssize_t s=recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&tmp,&tlen);if(s>0){std::cout<<"server say#: "<<buffer<<std::endl;buffer[s]=0; }else{std::cerr<<"recvfrom"<<errno<<std::endl;return 2;}}return 0;
}
4.使用TCP编写套接字
4.1.TCP的特点
TCP是一个传输层协议,和UDP的特点相反
- 有连接
- 可靠传输
- 面向字节流
4.2.TCP客服端
先写的客户端,因为服务器端会写多个版本

#include<iostream>
#include<stdio.h>
#include<cerrno>
#include<unistd.h>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>void Usage()
{std::cout<<"usage:./client server_IP server_port"<<std::endl;
}
int main(int argc,char* argv[])
{if(argc!=3){Usage();return 1;}//建立套接字int sock=socket(AF_INET,SOCK_STREAM,0);if(sock<0){std::cerr<<"socket"<<errno<<std::endl;return 2;}//自动bind//连接服务器struct sockaddr_in local;local.sin_addr.s_addr=inet_addr(argv[1]);local.sin_port=htons(atoi(argv[2]));local.sin_family=AF_INET;connect(sock,(struct sockaddr*)&local,sizeof(local));//业务逻辑while(1){char buffer[1024]={0};std::cout<<"请输入";fgets(buffer,sizeof(buffer),stdin);write(sock,buffer,sizeof(buffer));char mes[1024]={0};read(sock,mes,sizeof(mes));std::cout<<mes<<std::endl;}return 0;
}
4.3.TCP服务器端
4.3.1.创建套接字和bind服务器
- socket的第二个参数是SOCK_STREAM,UPD是SOCK_DGRAM(datagram),TCP是SOCK_STREAM(stream,就是字节流)
//建立套接字int listen_sock=socket(AF_INET,SOCK_STREAM,0);if(listen_sock<0){std::cerr<<"socket"<<errno<<std::endl;return 1;}//bind服务器struct sockaddr_in local;local.sin_family=AF_INET;local.sin_port=htons(PORT);local.sin_addr.s_addr=INADDR_ANY;//INADDR_ANY会bind执行代码的主机if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0){std::cerr<<"bind"<<errno<<std::endl;return 2;}
4.3.2.listen设为聆听状态

//listen状态listen(listen_sock,5);
4.3.3.accept接受客户端的连接,并返回一个文件描述符
- 因为TCP套接字是有连接的,连接成功后也是使用返回的套接字和对端进行网络通信

struct sockaddr_in tmp;
socklen_t tlen=sizeof(tmp);
//建立连接
int fd=accept(listen_sock,(struct sockaddr*)&tmp,&tlen);
4.4.单进程版本(没有人会使用)
- 一次只能让一个客户端访问
- 写端关闭读端读到文件结尾,再读返回0
int main()
{//...创建套接字、bind、listen都省略了,每次都一样冗余while(1){struct sockaddr_in tmp;socklen_t tlen=sizeof(tmp);//建立连接int fd=accept(listen_sock,(struct sockaddr*)&tmp,&tlen);if(fd<0){std::cerr<<"accept "<<errno<<std::endl;return 3;}std::cout<<"get a new link "<<std::endl;//1.单进程versionwhile(1){char buffer[1024]={0};ssize_t s=read(fd,buffer,sizeof(buffer));if(s>0){buffer[s]=0;std::cout<<"client to server:"<<buffer<<std::endl;std::string message;message+="server to client:你好!";//给连接的客户端发一个你好write(fd,message.c_str(),message.length());}else if(s==0){//写端关闭读端读到文件结尾,再读返回0std::cout<<"client quit!"<<std::endl;break;}else{std::cerr<<"read "<<errno<<std::endl;break;}}}
}
4.5.多进程版本
4.5.1.父进程是一个循环,他要一直接收新的客服端不能等待子进程,解决方法
-
父进程等待子进程,子进程创建后再创建孙子进程执行后序代码,子进程秒退等待时间可以忽略不计;下面代码
pid_t pid = fork();if(pid<0){continue;}else if(pid==0){//子进程close(listen_sock);if(fork()>0)exit(0);//子进程创建就直接退出,创建的孙子进程执行后序代码,孙子进程变成孤儿进程被1号进程领养serviceIO(fd);close(fd);exit(0);}else{//父进程close(fd);//子进程的PCB以父进程的PCB做模板初始化,不是共享的//waitpid(&pid,NULL,0);//父进程等待子进程,子进程创建后再创建孙子进程执行后序代码,子进程秒退等待时间可以忽略不计}
2.signal(SIGCHLD,SIG_IGN);//忽略SIGCHLD信号,子进程将自动释放;
2.关闭不用的文件描述符,父进程关闭accept返回的文件描述符,子进程关闭socket返回的文件描述符
void serviceIO(const int& fd)
{//1.单进程version,做成一个函数while(1){char buffer[1024]={0};ssize_t s=read(fd,buffer,sizeof(buffer));if(s>0){buffer[s]=0;std::cout<<"client to server:"<<buffer<<std::endl;std::string message;message+="server to client:你好!";//给连接的客户端发一个你好write(fd,message.c_str(),message.length());}else if(s==0){//写端关闭读端读到文件结尾,再读返回0std::cout<<"client quit!"<<std::endl;break;}else{std::cerr<<"read "<<errno<<std::endl;break;}}
}int main()
{//...创建套接字、bind、listen都省略了,每次都一样冗余signal(SIGCHLD,SIG_IGN);//忽略SIGCHLD信号,子进程将自动释放while(1){struct sockaddr_in tmp;socklen_t tlen=sizeof(tmp);//建立连接int fd=accept(listen_sock,(struct sockaddr*)&tmp,&tlen);std::cout<<"get a new link "<<std::endl;if(fd<0){std::cerr<<"accept "<<errno<<std::endl;return 3;}//2.多进程versionpid_t pid = fork();if(pid<0){continue;}else if(pid==0){//子进程close(listen_sock);serviceIO(fd);close(fd);exit(0);}else{//父进程close(fd);//子进程的PCB以父进程的PCB做模板初始化,不是共享的}}
}
4.6.多线程版本
- 线程是共享PCB的,所以在线程内使用完毕关闭文件描述符即可;
- 不等待线程可以使用线程分离的方法
void serviceIO(const int& fd)
{//1.单进程versionwhile(1){char buffer[1024]={0};ssize_t s=read(fd,buffer,sizeof(buffer));if(s>0){buffer[s]=0;std::cout<<"client to server:"<<buffer<<std::endl;std::string message;message+="server to client:你好!";//给连接的客户端发一个你好write(fd,message.c_str(),message.length());}else if(s==0){//写端关闭读端读到文件结尾,再读返回0std::cout<<"client quit!"<<std::endl;break;}else{std::cerr<<"read "<<errno<<std::endl;break;}}
}
void* HandlerRequest(void* agrs)
{pthread_detach(pthread_self());int fd=*((int*)agrs);serviceIO(fd);close(fd);//记得关闭文件描述符
}
int main()
{while(1){struct sockaddr_in tmp;socklen_t tlen=sizeof(tmp);//建立连接int fd=accept(listen_sock,(struct sockaddr*)&tmp,&tlen);std::cout<<"get a new link "<<std::endl;if(fd<0){std::cerr<<"accept "<<errno<<std::endl;return 3;}pthread_t tid;pthread_create(&tid,nullptr,HandlerRequest,(void*)&fd);}return 0;
}
5.把套接字封装
- 使用静态是因为不用创建对象就可以使用sock::Socket()等函数;
#pragma once
#include<iostream>
#include<cstdlib>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>namespace ns_socket{class sock{public:static int Socket(){int sock=socket(AF_INET,SOCK_STREAM,0);if(sock<0){std::cerr<<"socket"<<std::endl;exit(1);}return sock;}static void Bind(int sock,char* port){struct sockaddr_in local;local.sin_family=AF_INET;local.sin_addr.s_addr=INADDR_ANY;local.sin_port=htons(atoi(port));if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){std::cerr<<"bind"<<std::endl;exit(2);}}static void Listen(int sock){if(listen(sock,5)<0){std::cerr<<"listen"<<std::endl;exit(3);}}static int Accept(int sock) {struct sockaddr_in tmp;socklen_t tlen=sizeof(tmp);int new_sock=accept(sock,(struct sockaddr*)&tmp,&tlen);if(new_sock<0){std::cerr<<"accept"<<std::endl;exit(4);}return new_sock; }static void Connect(int sock,char* server_ip,char* server_port){struct sockaddr_in local;local.sin_family=AF_INET;local.sin_addr.s_addr=inet_addr(server_ip);local.sin_port=htons(atoi(server_port));if(connect(sock,(struct sockaddr*)&local,sizeof(local))<0){std::cerr<<"connect"<<std::endl;exit(5);}else{std::cout<<"connet success"<<std::endl;}}};
}
相关文章:
【Linux 网络编程1】使用UDP/TCP编写套接字,多进程/多线程版本的TCP编写的套接字,将套接字封装
目录 1.学习网络编程前的一些基础知识 2.UDP(user datagram protocol)协议的特点 3.使用有UPD编写套接字 4.使用TCP编写套接字 4.2.TCP客服端 4.3.TCP服务器端 4.4.单进程版本(没有人会使用) 4.5.多进程版本 4.6.多线程版本 5.把套接字封装 1…...
滑动窗口求最大和最小
滑动窗口 要区分最小和最大滑窗,内层while循环的条件和更新结果的地方 核心: 关键的区别在于,最大滑窗是在迭代右移右边界的过程中更新结果,而最小滑窗是在迭代右移左边界的过程中更新结果。 最小滑窗 给定数组 nums࿰…...
跟ChatGPT学量化,菜鸟炒股也不怕
你是一个python资深量化开发工程师,请介绍量化交易的基本框架 量化交易的基本框架包括以下几个步骤: 数据获取:获取市场数据和公司财务数据等,可以使用API接口、爬虫等方式获取。数据清洗和预处理:对获取到的数据进行清…...
扬帆优配|2600亿新能源巨头狂飙!外资唱多中国:再涨15%
全国停摆的危机,正在迫临法国。 大停工正在将法国推向风险境地。法国政府估计,当地时间3月7日,将迸发全国大型停工游行。法国总工会宣告,到时将让全法国停摆。法国担任交通业务的部长级代表克莱蒙博讷正告称,7日将成为…...
ChatGPT技术与商业模式及产业发展布局方案
文章目录模块一:概念模块二:架构模块三:技术模块四:算力模块五:体验模块六:应用模块七:商业模块八:产业模块九:建议结语主要内容: 采用模块化教学方法&#x…...
CIMCAI port ai shipping ai artificial intelligence smart port
上海人工智能独角兽中集集团高科技中集飞瞳,是全球应用落地最广,规模最大,最先进的的港航人工智能高科技企业,工业级成熟港航人工智能产品全球规模化落地应用,全球前三大船公司及港口码头应用落地。上海人工智能独角兽…...
《数据解构》HashMap源码解读
👑作者主页:Java冰激凌 📖专栏链接:数据结构 目录 了解HashMap HashMap的构造 两个参数的构造方法 一个参数的构造方法 不带参数的构造方法 哈希表初始化的长度 HashMap源码中的成员 Pt Get 了解HashMap 首先我们要明…...
Databend 开源周报 第 83 期
Databend 是一款现代云数仓。专为弹性和高效设计,为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务:https://app.databend.com 。Whats New探索 Databend 本周新进展,遇到更贴近你心意的 Databend 。Support for WebHDFSHDFS 是大数…...
Spring | 基础
1. IOC和DI IOC:控制反转,其思想是反转资源获取的方向,传统的资源查找方式要求组件向容器发起请求查找资源,作为回应,容器适时的返回资源。而应用了 IOC 之后,则是**容器主动地将资源推送给它所管理的组件…...
windows7安装sql server 2000安装步骤 及安装过程中遇到的问题和解决方式
提示:文章写完后windows7安装sql server 2000安装步骤 及安装过程中遇到的问题和解决方式, 文章目录一、ms sql server 2000是什么?版本简介:**特点:****优点:**二、步骤1.下载安装包及Sq4补丁包2.安装 ms …...
Python 开发-批量 FofaSRC 提取POC 验证
数据来源 学习内容和目的: ---Request 爬虫技术,lxml 数据提取,异常护理,Fofa 等使用说明---掌握利用公开或 0day 漏洞进行批量化的收集及验证脚本开发Python 开发-某漏洞 POC 验证批量脚本---glassfish存在任意文件读取在默认4…...
Linux系统中部署软件
目录 1.Mysql 2.Redis 3.ZooKeeper 声明 致谢 1.Mysql 参考:CentOS7安装MySQL 补充: ① 执行:rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022 再执行:yum -y install mysql-community-server ② mysql…...
PHP常用框架介绍与比较
HP是一种广泛应用于Web开发的编程语言。随着互联网的快速发展,PHP的应用场景变得越来越广泛,从简单的网站到复杂的Web应用程序都可以使用PHP来开发。为了更好地组织和管理PHP代码,开发人员经常会使用框架来提高开发效率和代码质量。 本文将介绍一些常用的PHP框架,并进行简…...
Umi + React + Ant Design Pro 项目实践(一)—— 项目搭建
学习一下 Umi、 Ant Design 和 Ant Design Pro 从 0 开始创建一个简单应用。 首先,新建项目目录: 在项目目录 D:\react\demo 中,安装 Umi 脚手架: yarn create umi # npm create umi安装成功: 接下来,…...
MySQL知识点总结(1)
目录 1、sql、DB、DBMS分别是什么,他们之间的关系? 2、什么是表? 3、SQL语句怎么分类呢? 4、导入数据 5、什么是sql脚本呢? 6、删除数据库 7、查看表结构 8、表中的数据 10、查看创建表的语句 11、简单的查询…...
day45第九章动态规划(二刷)
今日任务 70.爬楼梯(进阶)322.零钱兑换279.完全平方数 70.爬楼梯(进阶) 题目链接: https://leetcode.cn/problems/climbing-stairs/description/ 题目描述: 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不…...
第十四届蓝桥杯第三期模拟赛原题与详解
文章目录 一、填空题 1、1 找最小全字母十六进制数 1、1、1 题目描述 1、1、2 题解关键思路与解答 1、2 给列命名 1、2、1 题目描述 1、2、2 题解关键思路与解答 1、3 日期相等 1、3、1 题目描述 1、3、2 题解关键思路与解答 1、4 乘积方案数 1、4、1 题目描…...
client打包升级
目录 前言 一、client如何打包升级? 二、使用步骤 1.先进行改版本 2.执行打包升级命令 总结 前言 本文章主要记录一下,日常开发中,常需要进行打包升级的步骤。 一、client如何打包升级? # 升级发布版本 ## 修改版本 * 父p…...
Blazor_WASM之3:项目结构
Blazor_WASM之3:项目结构 Blazor WebAssembly项目模板可选两种,Blazor WebAssemblyAPP及Blazor WebAssemblyAPP-Empty 如果使用Blazor WebAssemblyAPP模板,则应用将填充以下内容: 一个 FetchData 组件的演示代码,该…...
OperWrt 包管理系统02
文章目录 OperWrt 包管理系统OPKG简介OPKG的工作原理OPKG命令介绍软件包的更新、安装、卸载和升级等功能软件包的信息查询OPKG配置文件说明OPKG包结构(.ipk)OPKG演示案例OperWrt 包管理系统 OPKG简介 OPKG(Open/OpenWrt Package)是一个轻量快速的软件包管理系统,是 IPKG…...
全方位降本增效,Captain AI重构OZON运营成本结构
当前OZON市场竞争日趋激烈,人力、物流、广告、库存等各项运营成本持续攀升,利润空间不断压缩,“降本”与“增效”成为商家生存发展的核心命题。不同于单一工具仅能优化某一项成本,Captain AI立足OZON商家全运营场景,以…...
告别驱动开发:手把手教你用himm工具在用户空间玩转Hi3516的GPIO
用户空间高效操控Hi3516 GPIO:himm工具实战指南 在嵌入式开发领域,传统的内核驱动开发往往需要经历漫长的编译、加载和调试周期。对于快速硬件验证和原型开发而言,这种开发模式显得过于笨重。海思Hi3516平台提供的himm工具,为开发…...
YouTube 转 MP3 工具里,为什么预览要放在下载前
很多转换工具看起来解决的是“我要一个 MP3 文件”,但真正影响体验的,往往不是页面上有没有下载按钮。 用户真正想确认的是:这个链接是不是被正确识别了,转换任务是不是还在进行,最后得到的音频是不是值得保存。对 Yo…...
Python并发模型全景解析
Python并发模型全景解析:线程、协程、多进程与GIL深度实战 🐍 Python 的并发编程一直是个让人困惑的话题:GIL 是什么?什么时候用线程?什么时候用协程?什么时候用多进程?本文从底层原理到生产实战,彻底讲清楚 Python 的四种并发模型,附带性能对比测试和真实踩坑经验。…...
NVIDIA显卡终极调校指南:用Profile Inspector释放游戏潜能的简单方法
NVIDIA显卡终极调校指南:用Profile Inspector释放游戏潜能的简单方法 【免费下载链接】nvidiaProfileInspector 项目地址: https://gitcode.com/gh_mirrors/nv/nvidiaProfileInspector 还在为游戏卡顿、画面撕裂而烦恼吗?NVIDIA Profile Inspect…...
实战解析:用高斯过程回归搞定不确定性预测
1. 高斯过程回归能解决什么问题 我第一次接触高斯过程回归是在一个金融风控项目里。当时我们需要预测未来三个月的用户违约概率,但传统机器学习模型只能给出一个冰冷的数字预测,完全无法体现预测的可信程度。这就像天气预报只告诉你"明天会下雨&quo…...
Go语言匿名函数如何写_Go语言匿名函数和闭包教程【对比】
Go匿名函数写作func(参数)返回类型{函数体},需完整声明;闭包是匿名函数引用外层局部变量并逃逸出作用域时形成的行为结果,捕获变量引用而非值。Go 里匿名函数怎么写,直接上手就用Go 的匿名函数就是没名字的函数字面量,…...
用MATLAB和Vivado搞个带通FIR滤波器:从FDATool到IP核的完整配置流程
从MATLAB到FPGA:带通FIR滤波器的工程化实现全指南 在数字信号处理领域,FIR滤波器因其线性相位特性和稳定性成为工程师的首选工具。当我们需要从高速采样信号中提取特定频段时,带通FIR滤波器的设计就变得尤为关键。本文将带您完整走通从MATLAB…...
从F103到F407:老STM32玩家升级指南,详解性能差异与项目移植实战
从F103到F407:老STM32玩家升级指南,详解性能差异与项目移植实战 对于熟悉STM32F1系列开发的工程师来说,升级到F407系列既是一次性能跃迁的机会,也伴随着学习曲线和移植挑战。本文将深入剖析两款芯片的差异,并提供可落地…...
从玩具车到巡检机器人:聊聊麦克纳姆轮底盘选型与ROS导航的那些‘坑’
从玩具车到巡检机器人:麦克纳姆轮底盘选型与ROS导航实战避坑指南 当你第一次看到麦克纳姆轮机器人在仓库里流畅地横向漂移时,很难不被这种"违反物理常识"的运动方式吸引。但真正把麦轮应用到巡检机器人或AGV项目时,才会发现那些炫酷…...
