聊聊在家里折腾单线复用的经历

这篇就当作是当时计网VLAN实验的一个真实实践吧。

现在大部分人家里的光猫应该都是光纤/网线/高频入户,然后在弱电箱里有个猫,提供一个宽带口和一个IPTV口(不讨论光猫拨号的话)。这个时候一般得要两根网线从弱电箱里走暗线出去,一根去接路由器(当然要是你的弱电箱很大能塞得下路由器而且信号和散热也还行就不用往下看了),一根去接机顶盒。但是很多家庭里一个地方只穿了一根,不够用的话就得折腾一下,我随便说几个方案:

  • 自己再穿一根线,如果暗管的空间还足够的话。这个比较稳,而且也不难,像这个網路不通怎麼辦|最完整拉網路線教學|自己的網路自己救|禾我一起DIY EP.11【弱電通】,实在不行也可以请人来穿一根。
  • 单线复用,利用VLAN,在这根网线上面承载多项不同子网的业务。好处很明显,物理上不需要动线路了。而且目前内网单口千兆跑几个业务问题也不太大,除非是1000M的宽带这种会跑不满,不然应该都没有什么问题。
  • 如果机顶盒支持走无线IPTV(这个比较蛋疼)、光猫支持无线(当然你也用不上这个无线的话),并且能进光猫超级管理页,那么把IPTV的VLAN划到无线上就OK。这个方案可行但我个人不太感冒,毕竟很多时候都只能走2.4GHz频段,拥挤的时候稳定性堪忧。
  • 目前还很小众但是我挺看好的方案:光纤。网线走明线很明显,不太好看,但是光纤不会。TB上面能买到隐形光纤,搭配光纤收发器就可以了,成本不会很高。这里有篇文章提到了vleop mesh用隐形光纤组网达到有线回程-路由器交流。应该也可以买那种有线身补强的然后穿暗管。

这里主要讨论的是第二种方案,并不是我懒得去穿线,而是客厅的暗管很细还穿了2根网线(电视占了一根并且很难改造)和1根同轴电缆,卡得很紧动都动不了(淦,当时他是怎么穿过去的,服了),只好买两个简易的网管交换机来做单线复用了。

其实如果你的路由器可以划VLAN的话(比如自己刷了固件的K2P之类的)或者支持IPTV流量隔离,那买一个其实就够了;另外如果你能完全控制光猫的VLAN,那么应该不需要买了。现在这个时候,家用的简单网管交换机有很多,比较经典的就是网件的GS105E/GS108E(接口数量不一样),价格有点高(不过也有很多洋垃圾)。最近也有人在淘亚马逊(中亚直邮/美亚转运)上面的网件GS908E,价格不错。TP-LINK的TL-SG2005/TL-SG2008(8口这个内置了电源大了点),还有水星的SG105 Pro/SG108 Pro也都可以。

开工之前来复习一下:

比如这几篇:Fundamentals of 802.1Q VLAN Tagging – Cisco MerakiTagged, Untagged, and Native VLANs – Network DirectionWhat is PVID – Jason’s Web SiteHow to configure 802.1Q VLAN on TP-Link Easy Smart/Unmanaged Pro Switches? | TP-Linkdifference between untagged VLAN and PVID。还有问题的话…那就去看802.1Q吧,看完顺便带带弟弟。

首先,我们希望把互联网、IPTV和内网这三个子网隔离开来。我们知道可以通过VLAN来隔离广播域,使得同一个物理交换机上面有多个不互通的子网,除非有三层设备(路由器之类的)做转发。其次,我们希望可以单线复用,就是在一根线上承载不同子网的业务。我们知道Trunk用来连接两个交换机,两个交换机上同一VLAN的设备就可以互相通信,就很符合现在的需求。

VLAN enabled ports are generally categorized in one of two ways, tagged or untagged. These may also be referred to as “trunk” or “access” respectively. The purpose of a tagged or “trunked” port is to pass traffic for multiple VLAN’s, whereas an untagged or “access” port accepts traffic for only a single VLAN. Generally speaking, trunk ports will link switches, and access ports will link to end devices.

Fundamentals of 802.1Q VLAN Tagging – Best Practices

不管是叫access还是untag,trunk还是tag。简言之,我们需要划分3个VLAN,分别承载互联网(光猫Internet到路由器WAN)、IPTV(光猫IPTV到IPTV机顶盒)和内网(和路由器LAN互通)。Trunk(tag)口用来连接这两个交换机,Access(untag)口用来连接设备,它们都用来决定离开这个端口时的动作;PVID来决定一个没有tag的包进入这个端口时应该如何做(不是所有厂商的交换机都有这个,有些是自动的)。

get到上面的点之后,就可以开工了。完工后的拓扑大概会是这个样子的,菜鸡本菜不会用Visio只能随便画一个了:

具体两个交换机上接线应该是这样的:

很显然了,两台交换机上的端口1都是Trunk口,然后端口2、端口3、端口45分别设不同的VLAN。如果假定VLAN 10给互联网,VLAN 20给IPTV,VLAN 11给内网,那么交换机A和B的设置应该一样,VLAN应该是这样的:

  • VLAN 10(Internet):端口1设为Tag;端口2设为Untag;
  • VLAN 20(IPTV):端口1设为Tag;端口3设为Untag;
  • VLAN 11(LAN):端口1设为Tag;端口4和5设为Untag;
  • PVID:端口1设为1,端口2设为10,端口3设为20,端口4和5设为11。

这适用于不在弱点箱内的主路由拨号然后通过弱电箱内的暗线共享给其它房间的设备(例如子路由)的场景。缺点也明显,某些地方的链路容易饱和。例如房间内的网线去公网最高就只能500Mbps,但对于我来说不是什么问题。要解决这个问题可以在光猫上拨号然后其它路由做AP,或者类似的方案,需要根据实际情况做取舍。

此时可以接线进行测试了,如果在两台交换机上不同VLAN间不能互相通讯而相同VLAN间可以通讯,那么应该可以开始正常使用了。

如果有任何错误或问题,烦请在评论区指出。不胜感激。

第5章 插叙:进程API 读书笔记

UNIX系统通过一对系统调用fork()exec()来创建新进程,通过wait()等待其创建的子进程执行完成。

5.1 fork()系统调用

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    printf("hello world (pid:%d)\n", (int) getpid());
    int rc = fork();
    if (rc < 0) { // fork failed
        fprintf(stderr, "fork failed\n");
        exit(1);
    } else if (rc == 0) { // child (new process)
        printf("hello, I am child (pid:%d)\n", (int) getpid());
    } else { // parent goes down this path (main)
        printf("hello, I am parent of %d (pid:%d)\n", rc, (int) getpid());
    }
    return 0;
}

对于操作系统来说,看起来有两个一样的程序在运行,并且都从fork()调用中返回。子进程不会从main()函数开始,而是直接从fork()调用中返回,就好像自己调用了fork()

子进程(child)并不是完全拷贝了父进程(parent)。子进程拥有自己的地址空间、寄存器、程序计数器等,但从fork()的返回值不同。父进程获得的返回值是子进程的PID,子进程获得的是0。

输出不是确定的(deterministic)。单CPU的系统上,两者都有先运行的可能。

CPU调度程序(scheduler)决定了某个时刻哪个进程被执行。

5.2 wait()系统调用

父进程等待子进程执行完毕。也可使用waitpid()

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[]) {
    printf("hello world (pid:%d)\n", (int) getpid());
    int rc = fork();
    if (rc < 0) { // fork failed; exit
        fprintf(stderr, "fork failed\n");
        exit(1);
    } else if (rc == 0) { // child (new process)
        printf("hello, I am child (pid:%d)\n", (int) getpid());
    } else { // parent goes down this path (main)
        int rc_wait = wait(NULL);
        printf("hello, I am parent of %d (rc_wait:%d) (pid:%d)\n", rc, rc_wait, (int) getpid());
    }
    return 0;
}

增加了wait()调用后,输出结果变得确定了。即使父进程先运行,会马上调用wait(),停下来等待子进程执行结束后才返回。附注中提到,有些情况下会在子进程退出前返回,细节见man

5.3 最后是exec()系统调用

有多个变体,个人看起来好像只是传参方式不一样而已。让子进程执行和父进程不一样的程序。而fork()只能运行相同程序。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

int main(int argc, char *argv[]) {
    printf("hello world (pid:%d)\n", (int) getpid());
    int rc = fork();
    if (rc < 0) { // fork failed; exit
        fprintf(stderr, "fork failed\n");
        exit(1);
    } else if (rc == 0) { // child (new process)
        printf("hello, I am child (pid:%d)\n", (int) getpid());
        char *myargs[3];
        myargs[0] = strdup("wc"); // program: "wc" (word count)
        myargs[1] = strdup("p3.c"); // argument: file to count
        myargs[2] = NULL; // marks end of array
        execvp(myargs[0], myargs); // runs word count
        printf("this shouldn’t print out");
    } else { // parent goes down this path (main)
        int rc_wait = wait(NULL);
        printf("hello, I am parent of %d (rc_wait:%d) (pid:%d)\n", rc, rc_wait, (int) getpid());
    }
    return 0;
}

exec()会从可执行程序中加载代码和静态数据,覆盖原本的代码段和静态数据,重新初始化堆和栈等内存空间。没有创建新进程,而是直接替换为不同的程序。成功调用不会返回。

5.4 为什么这么设计API

事实证明,分离fork()exec()的做法在构建UNIX shell的时候非常有用,这给了shell在fork之后exec之前运行代码的机会。

做对事(Get it right)。抽象和简化都不能替代做对事。

Lampson – 《Hints for Computer Systems Design》

有许多方式来设计创建进程的API,但fork()exec()的组合既简单又极其强大。

shell找到可执行程序,调用fork()创建新进程,调用exec()的某个变体来执行这个可执行程序,调用wait()等待该命令完成。子进程执行结束后,shell从wait()中返回并再次输出一个提示符,等待用户输入下一条命令。

重定向则在调用exec()前先关闭标准输出(standard output),然后打开文件。其实也可以用dup2()之类的,似乎比较方便。

close(STDOUT_FILENO);
open("./p4.output", O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);

UNIX管道也是用类似方法实现的,但用的是pipe()系统调用。一个进程的输出被链接到一个内核管道(pipe)上(队列),另一个进程的输入也被链接到了同一个管道上。前一个进程的输出无缝地作为后一个进程的输入。

5.5 其他API

UNIX中还有其他许多和进程交互的方式,比如通过kill()系统调用向进程发送信号(signal),包括要求睡眠、终止等。

第1章 入门 读书笔记

发现这本书真的挺不错的。图灵社区似乎只有纸质书的版权,各大电商均有在售。电子书似乎GitBook上有中文版,如果字体阅读起来难受的话,里面似乎提到,有提供一份正常的。

1.3 Git基础

1.3.5 三种状态

已提交(committed,已存入本地数据库)、已修改(modified,未提交到数据库)和已暂存(staged,对已修改文件做出标识并加入下一次要提交的快照)。

三个主要的区域:Git目录、工作目录、暂存区。

基本工作流:修改工作目录中的文件;暂存,将文件快照加入暂存区;提交,永久保存在Git目录中。

1.5 安装Git

1.5.3 Windows上的安装方法

在Git网站上下载,即Git for Windows项目,独立于Git。

或是安装Windows版的Git,既包含命令行版本的Git,也包括GUI。

1.6 Git的首次配置

使用git config来获取和设置配置变量。

/etc/gitconfig:所有用户及其仓库的值,通过--system参数。
~/.gitconfig~/.config/git/config:针对自己,通过--global参数。
当前仓库的Git目录(.git/config):针对单个仓库。

每一级都会覆盖上一级中的设置。

1.6.1 用户身份

每次提交都需要,并且会被保存,不可更改。

git config --global user.name "John Doe"
git config --global user.email "johndoe@example.com"

也可不带--global参数,不同项目设置不同的用户。

1.6.2 个人编辑器
git config --global core.editor emacs
1.6.3 检查个人设置
git config --list

查看某个键,例如:

git config user.name

1.7 获取帮助

git help <verb>
git <verb> --help
man git-<verb>

关于CLion的基本的安装和配置建议

可能有人要问我了:为什么要用这个IDE?这么复杂的安装过程我为什么不用Dev-C++或者CodeBlocks?VC6.0多好用啊!

这个…我的回答是:CLion确实是一个优秀的、强大的跨平台C/C++ IDE;安装过程本身不复杂,官网提供了详尽的文档;此外,理解(划重点)并按照向导一步步做下来也是相当简单的,写这篇主要是因为…很多同学真的看到英文就害怕,对此我只能引用一下这段话:

“英文在科学技术领域是世界语”这个事实在未来几十年都不会改变。我们在授课过程中应用的非技术词汇都很简单。因此停止抱怨,以开放的心态来迎接挑战吧。你会发现,其实挑战也并不大。

学堂在线 《电路原理》 于歆杰教授 朱桂萍教授 陆文娟教授 清华大学电机系

至于它的特性和优点,你可以看这里:Intelligent Coding Assistance & Code Analysis – Features | CLion。如果新手的你看不懂的话,我大概可以告诉你这几点:

  • 静态代码审查:在构建前即可知道代码中存在的问题,一般情况下一些比较傻的问题都能很快发现,可以节约大量的时间。这些提示对于提高你的代码质量也有帮助,我觉得这点非常重要。
  • 智能代码补全:你可以利用多种技术来进行补全,包括但不限于补全关键字、变量名或者使用模板,可以大幅提高编写代码的速度。
  • 你可以比较方便地在考试机房的电脑上安装并使用,而不需要花费很长时间,然而你可能需要等待较长时间才能把Visual Studio 2019(或者未来的更高版本)部署好。

Anyway,现在已经是2020年了,我再次修订这篇文章,还是那句话:建议你在学习和未来的开发过程中使用先进的工具。包括但不限于:

如果你期望使用Visual Studio,那么你只要到官网上下载对应的Installer,选中你需要的开发用途并安装就可以了。由于会自动配置工具链,整个安装过程非常友好且不劝退,安装好之后马上就可以开始使用。我建议你再装上ReSharper Ultimate,这可能对你学习或是开发有很大的帮助。

下文主要探讨CLion的一些配置建议。但不管怎样,这些都只是工具而已,水平怎么样还是要看个人,没有必要非要争个谁更厉害,甚至是用出优越感来。更重要的是你平时有没有努力学习和探索,给你个宇宙第一IDE,用了几年还是只会按几个常用的功能,其它的都不碰的话,那跟你一开始就用文本编辑器似乎没什么区别,甚至编辑器还要更快一点。

废话了这么多,开始说正事,希望我写的这些能让你看到,为什么它值得我们去用。此外,这不是教程向,也不会手把手地教你并给你截图,我始终反对无脑跟随一个教程的做法,希望你会理解后再去动手。

安装CLion

作为计算机专业学生,如果需要安装某某应用软件,正确的操作应当是前往官网,阅读说明后下载并安装。而非点击某某下载站的“快速下载”按钮。

你会发现,你可以选择直接下载CLion的安装程序进行安装;也可以选择下载JetBrains Toolbox,然后用Toolbox来安装并管理包括CLion在内的JetBrains IDE。我更推荐后者,虽然多了个应用,但是方便你管理IDE和最近的项目,并且可以静默为你安装更新。

那我就假设你已经安装好了Toolbox并且在当中安装了CLion。我的Toolbox大概长这样:

JetBrains Toolbox

申请教育授权

JetBrains全家桶对于教育用途来说都是免费的,具体可以自行前往这里查看。

不用我说,你应该要能找到这里,点击进入申请即可。通常情况下,这里会使用教育邮箱来验证你的身份,也就是学校为你提供的那个。

(深大特供)我想想估计又有好多同学会问:我忘记了密码怎么办?教育邮箱是什么?首先,忘记了密码,目前请持校园卡前往沧海校区(原南校区)致理楼C(原理工楼L3)1007进行重置,重置后记得在设置中绑定你的微信,这样的话,以后忘记密码也可以自己重置了。(最好还是弄个密码管理器)其次,教育邮箱通常是你所在的教育机构提供的、网域归属于该教育机构的邮箱。像我这样的人脑收藏夹应该比较少,那我就推荐你到内部网里点击这个:

内部网->学生邮箱

其它方式我就不扯了,自己研究吧。不管怎样,你申请到的教育授权有效期应该是1年的。

不过不用慌,每年到期前,你都会收到邮件提醒你续期,只要你没毕业(教育授权政策似乎在慢慢收紧来应对白嫖党)并且持续持有教育邮箱,你就能一直免费使用。

更多关于授权的信息就自己看吧。

启动CLion

不同于往常你可能用过的IDE,这里需要你自行选择并配置Toolchain才能开始构建项目。如果你一路Next来到主界面后就会发现你写的程序没法构建并运行。以前你使用的IDE可能已经为你包办好了一切,希望这不会让你感到特别不习惯。

在不同平台上,所需的工具链通常是不同的,简单说来大概是这样的:

  • Linux。我本来打算写的,但是想想就算是某厂预装了Linux系统笔记本的用户,应该第一时间就安装了Windows。除此之外的Linux用户应该都是能人,不用我来教,只要用包管理把cmakemakegccgdb这些东西装上就好了。
  • MacOS。由于本菜买不起,所以只好给你放个文档了:Quick Tutorial: Configure CLion on macOS – Help | CLion。据文档所述,开发所需的工具应该系统已经事先预装好了,你只需要检测它们是否能正常工作就可以了。
  • Windows,有很多选择。很多选择倒不是说它很好,我个人觉得都是因为不太完美所以才会搞出这么多方案来。包括但不限于WSL(子系统)、Cygwin、MinGW这些。

下面主要简单来说说Windows下的那些方案,具体的还是自己看文档比较好。

Windows Subsystem for Linux (WSL) on Windows 10 适用于Linux的Windows子系统

非常推荐你日常学习和开发用这个方案(和GUI无关的话)。(虽然现在已经有了WSL 2,但是由于它是基于Hyper-V的,开了之后会和VMware、VirtualBox这些应用冲突,有点蛋疼,所以我还没打算迁移过去。如果你想折腾的话,那么请:Install WSL 2 | Microsoft Docs。)

不管怎样,我都觉得看官方文档比较好:WSL – Help | CLion。其实就是在WSL中安装所需的工具链,然后CLion会通过SSH来调用它们。

首先还是要强调一下你需要使用Windows 10(1607之后)或是Windows Server 2019。更多信息请参阅这里:适用于 Linux 的 Windows 子系统 – 维基百科,自由的百科全书

安装WSL其实很简单,你首先需要打开这个特性。你可以在控制面板中的启用或关闭Windows功能中打开它:

控制面板->程序->启用或关闭Windows功能->适用于Linux的Windows子系统

当然,这和直接在PowerShell中执行如下语句是等效的:

Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

打开该特性后请务必重启你的电脑,否则并不会生效,后续打开WSL会直接提示The Windows Subsystem for Linux optional component is not enabled. Please enable it and try again.

随后在Windows Store中搜索WSL并安装你需要的Linux发行版:

不得不接受的现实是,目前还是Ubuntu的支持最好。

下面不打算假设你对Linux有多少了解,我只希望你会自己去了解…

首次打开Ubuntu时会让你设定用户名和密码,随后就可以进入系统。我们需要来安装软件包,在这之前,你需要先刷新软件包列表:

sudo apt update

如果成功,那么随后你就可以开始安装软件包了:

sudo apt install cmake make clang gcc g++ build-essential gdb

如果你发现下载软件包的速度慢到难以接受,那么更换软件源可能是最简单粗暴的解决方法。例如你可以到这里查看阿里提供的镜像的文档,照抄不是明智的做法,直接改域名可能更好一点。如果更换软件源,你需要重新执行apt update才会生效,然后再进行安装。

至此工具链应该已经安装好了。前面提到,CLion会通过SSH去连接WSL来调用工具链,默认情况下刚装好的Ubuntu WSL里的SSH Server没有密钥对,直接启动后应该会连接不上。当然,按照报错信息去找到对应地方生成密钥对再重启SSH Server就可以了。不过有个更简单的方法:

sudo apt remove openssh-server
sudo apt install openssh-server

直接删除并重新安装的时候,在安装过程中会自动生成,也就不用自己去折腾了。然后再启动就可以了。

题外话:注意安全

考虑到我们是学安全的社团,所以特意拿出来强调一下这个点:不要让别人连接你的WSL的SSH Server!除非你知道你在做什么。不过我敢说大部分时候大部分人都是不知道自己在做什么的……大部分教程是没有强调这个问题的。简单说来,SSH是用来远程通过命令行控制你的主机的,具体请出门左转wiki,不正确的配置可能会导致被恶意连接,从而造成系统被破坏的后果。SSH Server的配置文件默认应该会在/etc/ssh/sshd_config,默认情况下应该会监听来自所有地址的请求,也就是那个ListenAddress 0.0.0.0。如果你只有在本机连接的需求,那么改为只监听本机可能会是个好主意。别忘了还有IPv6,虽然地址量的庞大使得扫描变得困难,但是只监听本机仍然有助于保护你自己。

只监听本机有助于保护你,除非你有远程连接的需求

修改配置文件后,请记得重启你的SSH Server以使得更改生效。

此外,如果你只是希望依赖防火墙来阻断端口22的入站连接,那么你可能要花费更多的精力来保证防火墙策略和区域设置正确。既然如此,那为啥不一开始就直接设置成仅本机访问呢。

回归正题,如果配置好了之后就可以来启动SSH Server了:

sudo service ssh start

我建议你马上测试一下是否能连接成功:

ssh 你希望登录的用户名@localhost -p22

如果不是很有必要,我不建议你直接root用户登录。

如果出现类似Connection refused的错误信息,那么说明服务可能没有开启,你可以通过类似sudo service ssh status的命令来检查;如果出现类似密钥交换失败的错误,请尝试检查密钥对;如果出现认证失败的错误,你可能需要检查一下配置文件中的认证策略,例如:

不管怎样,这些都应该能在Google上找到答案,我就不多说了。

如果能成功建立连接,那么说明大部分工作已经完成了。你可以回到CLion中,简单设置一下连接信息就可以使用了。你可以从Settings->Build, Execution, Deployment->Toolchains进入,也可以直接两下Shift来Search Everywhere,然后直接搜索Toolchains并进入:

如果检测都正确,那么你应该已经可以开始正常使用CLion了。

如果重启电脑后SSH Server没有启动,那么你可能会看到类似的错误信息:

很简单,启动SSH Server后,再点按Reload CMake Project就可以了:

MinGW,通过MSYS2

在这之前,我建议你先去了解一下MinGW、MinGW-w64还有MSYS2。

这也是个不错的方案(特别是和GUI相关的时候)。首先你要下载并安装MSYS2,根据文档快速开始,这应该没有什么问题。

然后安装工具链:

选好路径就可以开始工作了:

程序员也应当非常重视警告(Warning)。CLion可能会对超出版本范围的工具提示警告,不管是更新还是更旧。这都说明没有经过足够的测试来保证该版本的兼容性。如果你后续在调试阶段出现问题,那么可能要安装合适版本的调试器。

MinGW,通过直接在线/离线安装mingw-w64

你可以前往Mingw-w64 – GCC for Windows 64 & 32 bits [mingw-w64]了解更多信息。然后根据实际情况选择安装方式。如果你希望在没有网络的考试环境部署,那么直接离线安装可能是比较好的方案。

这个比较简单,其实没什么好说的,只要在CLion的工具链选项中指定你的安装/解压后的mingw-w64的路径就可以了。默认在线安装的话,CLion应该可以直接自动检测到。

Cygwin

也是个不错的方案,前往Cygwin查看更多信息。也是类似的下载安装,不过由于Cygwin并没有自带包管理,所以你需要在安装过程中就安装工具链,而没法像MSYS2那样。

记得在这里安装CMake、make、gcc-core、gcc-g++和gdb

其实CLion文档中只要求安装gcc-g++、make和gdb。

同样的,安装成功后在CLion中的工具链选项中配置好就要可以使用了。

小结

对了,不管你使用什么工具链,只要某些路径中涉及了非ASCII字符,例如中文用户名,那么可能会出现各种难以预料的错误。即使你在设置中更改了用户名,个人文件夹也不会改变,就算试图更改注册表也不一定稳妥。你可以使用各种链接的操作来试图避开,不过我觉得还是重新安装系统来得好一点。

调试?

毫无疑问,调试(Debug)在编写程序的过程中几乎是不可缺少的。如果你在上面配置工具链时Debugger已经可以工作,那么就不再需要额外的工作了。而如果你调用了Visual Studio的工具链,那么可能不能启动调试。(至少我上次看文档的时候还是不支持的,不过印象中可以调用其它Debugger的样子)

在CLion中进行调试也非常简单,基本的调试只需要点按编辑器左侧的行数就可以打下断点,然后点按右上角Debug图标启动调试即可。同样,你也可以使用快捷键Ctrl+F8来打断点、Shift+F9来启动调试。

启动调试之后,像Step Over、Step Into、Step Out之类的我觉得你应该自己去了解了。此外,调试不应该只停留在查看变量的阶段。例如,如果你在调试一个递归或者哪怕仅仅只是若干函数调用的程序,那么查看栈帧(Frames)可能会有所帮助。如果有些UI中没有实现的功能,你可以直接在GDB窗口中自行输入命令。

当然,我不希望你错过这个强大的功能:Evaluate Expression…(Alt+F8)

你可以利用这个功能,在当前现场像脚本语言一样执行你所需要的语句,这在很多时候都非常有用

智慧树时间:你可能不知道的IntelliJ/CLion

想到一个写一个吧……

Do not use mouse in IntelliJ, mouse is the evil.

Hadi Hariri

你知道吗?复制(Ctrl+C)、剪切(Ctrl+X)和粘贴(Ctrl+V)整行代码并不需要手动选中,不选中就是选中整行。想要重复一行?直接Ctrl+D吧。

这看起来很简单,对吗?你是否厌倦了还要移动右手去调整方向键才能补全例如ifwhile等语句的花括号?我想你还需要Complete Current Statement(Ctrl+Shift+Enter)。

Before
After

想为一个语句加上if吗?别再手动加括号了,直接对选中的语句使用Surround With…(Ctrl+Alt+T)吧!

什么?你说很多变量名起得不好,想改很麻烦,还怕有副作用?别慌,使用Refactor->Rename(Shift+F6),把这些让人头疼的问题交给IDE去分析就好了。相信我,它会做得又快又好的。

嗯……IntelliJ/CLion已经是一个成熟的IDE了,按下Generate(Alt+Ins)就可以帮你生成代码了。

代码经过几手修改,格式乱七八糟?试试Reformat Code(Ctrl+Alt+L)或许会有所帮助的。

糟糕,我的代码上出现了一个黄色/红色的灯泡。别慌,这说明IDE可能想要帮助你!触发Show Intention Actions(Alt+Enter)来看看吧。说不定,这就是以后你最经常使用的快捷键之一了。

找不到想要的东西吗?Search Everywhere(连续的两下Shift)和Run Anything(连续的两下Ctrl)或许能帮到你。

随便说了几个,更多精彩请自行翻阅每天都会弹出的Tip of the Day。对了,安装Presentation Assistant插件试试吧,我敢打赌你不会后悔的。

别人家的小朋友都有缩略代码的滚动条

很简单,安装CodeGlance插件就可以了。

这个主题天天看,看腻了,有没有猛男专用的颜色

喵?安装Material Theme UI插件看看有没有你需要的?在设置里你想怎么搞颜色都可以。

图标包自5.0.0+后的版本被分离出来了,下载Atom Material Icons就可以了。

Compare with Clipboard

什么?你说你又过不了OJ?它说Wrong Answer的时候,别再自己人工对比了!复制样例数据,回到你的程序的输出窗口,让IDE帮你对比它们吧。

Search with Google

遇到了问题?不要睡大觉,直接问Google吧!直接将选中的文字送到搜索引擎上!

以前在右键菜单里可以直接触发的,刚发现2020.1似乎移除了这个选项。不过我们仍然可以在Settings->Keymap中设置快捷键,或者在Settings->Appearance & Behavior->Menus and Toolbars里重新加回来。

不同Project设置不同的工具链?

你或许曾经有过这样的困扰,为了不同的用途,你安装了多个工具链。但是你发现每次切换项目似乎都要切换工具链?不,你只需要在Settings->Build, Execution, Deployment->CMake中设置工具链就可以了。

拷贝到Word后,行间距有违和的白色?

试着在这段代码前面加上一个回车吧,你会回来感谢我的。

Material Theme UI 更新5.0+后图标包不再有效?

JetBrains全家桶(IntelliJ IDEA、CLion等)开始升级2020.1了,如果你正在使用Material Theme UI插件的话,更新5.0.0+后会发现图标包风格还是原生的样子。并且刚更新后会有小弹窗:

如果去插件介绍页可以看到更新历史中写了:

据此,图标包迁移到了Atom Material Icons – Plugins | JetBrains。可以直接在IDE的插件页中搜索并安装。

De1CTF 2020 推文

好吧有点破事水……想了想还是把标题改了

ERQKGDWRP3SBHQQ3MTGBIYAZE4KJG5M3HCKRERQCIYWRP2KBHTGBGDMZE4KBH5M3GDABERQJIBWRP2IBH%D3%D3%D3YXNDBDJDATRYMHYUYC6VYDOGNDRVUSIYBDQ3YXNFMDJDAENYMC6WVDOGNDMRESIYVDM3YXNZNDJDATNQM3%D3%D3%D

大佬

5Yir6Zeu5oiRIOiHquW3seeci+WbvueJhw==

XCTF小秘

简单签到MISC,随便写了一个:

import com.google.common.io.BaseEncoding
import org.apache.commons.text.StringEscapeUtils
import java.net.URLDecoder
import java.nio.charset.StandardCharsets

fun main() {
    val str1First = "ERQKGDWRP3SBHQQ3MTGBIYAZE4KJG5M3HCKRERQCIYWRP2KBHTGBGDMZE4KBH5M3GDABERQJIBWRP2IBH%D3%D3%D3"
    val str1Second = "YXNDBDJDATRYMHYUYC6VYDOGNDRVUSIYBDQ3YXNFMDJDAENYMC6WVDOGNDMRESIYVDM3YXNZNDJDATNQM3%D3%D3%D"
    val str1 = buildString {
        str1First.zip(str1Second).forEach { (first, second) ->
            append(first).append(second)
        }
    }
    val str1UrlDecoded = URLDecoder.decode(str1, "UTF-8")
    println("Step1: $str1UrlDecoded")
    val str1Base32Decoded = String(BaseEncoding.base32().decode(str1UrlDecoded), StandardCharsets.UTF_8)
    println("Step2: $str1Base32Decoded")
    val str1UnicodeUnescaped = StringEscapeUtils.unescapeHtml4(str1Base32Decoded)
    println("Step3: $str1UnicodeUnescaped")
    val str2 = "5Yir6Zeu5oiRIOiHquW3seeci+WbvueJhw=="
    println(String(BaseEncoding.base64().decode(str2), StandardCharsets.UTF_8))
}

为了混合两个字符串,Kotlin标准库的zip还是挺有意思的。

关于Unicode编码,找到了这个:Unicode Escape Formats,所以形如&#x0000;的转为字符串就直接StringEscapeUtils.unescapeHtml4()处理。

引用:
arraylist – Merge two strings in kotlin – Stack Overflow

Docker 容器内连接宿主机localhost

昨天有点特殊需求,需要整一个frp(frpc)的容器。这个东西肯定要去连公网的frps,接受外来的请求后转发给内网。然后发现要转发到内网还好,但是要转发到宿主机的localhost时候就有点问题了,翻了一下文档和问答,大概是这样的:

对于Linux上的Docker,可以直接在docker run时指定:

--network="host"

或者在docker-compose.yml中:

network_mode: "host"

这种方式挺适合现在这个应用场景的。

如果是默认的网桥模式,那么可以在容器中访问宿主机对应docker网桥的IP地址。对于Windows或是Mac的Docker(版本18.03之后,据引用中的链接),可以直接连接主机名host.docker.internal

引用:
nginx – From inside of a Docker container, how do I connect to the localhost of the machine? – Stack Overflow
Use host networking | Docker Documentation
Compose file version 3 reference | Docker Documentation
Networking in Compose | Docker Documentation

分数四则运算(结构)

时间限制: 1 Sec 内存限制: 128 MB

题目描述

分数的分子和分母可用一个结构类型来表示。
编写实现两个分数加(addFS),减(subFS),乘(mulFS),除(divFS)的函数(要求计算结果分数是简化的),以及打印一个分数(printFS),计算两个整数最大公约数的函数(getGCD)。
注意:不能定义全局变量

输入

测试数据的组数 t
第一组第一个分数
第一组第二个分数
第二组第一个分数
第二组第二个分数
……

输出

第一组两个分数的和
第一组两个分数的差
第一组两个分数的积
第一组两个分数的商
第二组两个分数的和
第二组两个分数的差
第二组两个分数的积
第二组两个分数的商
……

样例输入

3
1/2
2/3
3/4
5/8
21/23
8/13

样例输出

7/6
-1/6
1/3
3/4

11/8
1/8
15/32
6/5

457/299
89/299
168/299
273/184

提示

求两数a、b的最大公约数可采用辗转相除法,又称欧几里得算法,其步骤为:

  1. 交换a, b使a > b;
  2. 用a除b得到余数r,若r=0,则b为最大公约数,退出;
  3. 若r不为0,则用b代替a, r代替b,此时a,b都比上一次的小,问题规模缩小了;
  4. 继续第2步。

解决方案

想想化简部分还是可以抽成一个inline函数的,但是懒得改了。

#include <iostream>

class Fraction {
public:
    Fraction() = default;

    Fraction(int numerator, int denominator) : numerator(numerator), denominator(denominator) {}

    Fraction operator+(const Fraction &rhs) {
        int numerator_tmp = this->numerator * rhs.denominator + rhs.numerator * this->denominator;
        int denominator_tmp = this->denominator * rhs.denominator;
        int gcd_tmp = gcd(numerator_tmp, denominator_tmp);
        return {numerator_tmp / gcd_tmp, denominator_tmp / gcd_tmp};
    }

    Fraction operator-(const Fraction &rhs) {
        return (*this + Fraction(-rhs.numerator, rhs.denominator));
    }

    Fraction operator*(const Fraction &rhs) {
        int numerator_tmp = this->numerator * rhs.numerator;
        int denominator_tmp = this->denominator * rhs.denominator;
        int gcd_tmp = gcd(numerator_tmp, denominator_tmp);
        return {numerator_tmp / gcd_tmp, denominator_tmp / gcd_tmp};
    }

    Fraction operator/(const Fraction &rhs) {
        int numerator_tmp = rhs.denominator, denominator_tmp = rhs.numerator;
        return (*this * Fraction(numerator_tmp, denominator_tmp));
    }

    friend std::istream &operator>>(std::istream &is, Fraction &rhs) {
        is >> rhs.numerator;
        is.get();
        is >> rhs.denominator;
        return is;
    }

    friend std::ostream &operator<<(std::ostream &os, const Fraction &rhs) {
        if (rhs.denominator < 0) {
            return os << -rhs.numerator << '/' << -rhs.denominator;
        } else {
            return os << rhs.numerator << '/' << rhs.denominator;
        }
    }

private:
    int numerator, denominator;

    static int gcd(int lhs, int rhs) {
        int tmp;
        while (rhs != 0) {
            tmp = rhs;
            rhs = lhs % rhs;
            lhs = tmp;
        }
        return lhs;
    }
};

int main() {
    size_t T;
    std::cin >> T;
    while (T--) {
        Fraction a{}, b{};
        std::cin >> a >> b;
        std::cout << a + b << std::endl
                  << a - b << std::endl
                  << a * b << std::endl
                  << a / b << std::endl
                  << std::endl;
    }
    return 0;
}