下载的时候为什么会产生上行? 上行如何限制下载速度
引言 在进行TCP下载的时候, 观察网络, 会发现同时会有"很大"的上行, 比如我的Steam下载 下载速度大概为1000Mbps的时候, 我会产生20Mbps的上行. 同时qBittorrent的[Options]-[Speed]中有一个选项[对传送总开销进行速度限制] 在开启了这个选项, 并且设定一个小于你最大上行的速度限制后, 会发现当你下载速度变高后, 其他种子的上行会随之降低. 这些现象为什么发生, 是本文要探讨的内容. TCP 无论是HTTP下载, 还是BT下载, 所依赖的都是TCP协议. 要了解上述现象, 就要先了解这些行为所依赖的协议. 根据RFC的定义, 当我们下载的时候, 每当Server端发送给我们一个TCP报文段, 我们就要回应一个没有数据的TCP报文段(ACK) 表示已经完全收到并请求下一个报文段(暂且不考虑Cumulative Acknowledgment和Delayed Acknowledgment). 举例 具体举例来说: 本地网络设置的MTU是1480, 则Server端TCP报文中, IP头+最大的数据量=1480. 使用iperf3进行测试(IPv4) Server端发送给我们的内容则为: 名称 报文大小 说明 数据(Payload) 1480-32-20=1428 MTU限制了最大数据 IP头 20 IPv4 TCP头 32 因为包含了OPTION, 所以变大了 Eth头 14 目标MAC, 源MAC, 以太网类型 FCS 4 帧校验序列 则我们接收到的一个以太网帧大小为MTU+ETH头+FCS(帧校验序列)=1480+14+4=1498Byte. 我们的ACK只会包含TCP头,IP头,Eth头, 而不会包含数据, 所以ACK的大小为32+20+14+4=70Byte. 则我们的上行速率会是实际下载数据速率的$\frac{70}{1498}=4.672897196 \%$ 这是在每一个ACK包都对应了一个TCP包情况下的最大占用上行. 在实际的网络请求中, 我们会有累积确认 (Cumulative Acknowledgment)和延迟确认 (Delayed Acknowledgment), 这两种机制会使得我们的ACK包的数量小于接收到的TCP包的数量. ...
为什么第一个怪是Kakka会导致问号出怪概率提升? 从源码的角度解析杀戮尖塔
引言 无论是在中文论坛还是英语论坛, 都会看到一个说法 即如果第一个怪物是Cultist(邪教徒Kakka), 则有80%的概率, 你的下一个事件(问号)会变成战斗, 导致你无法使用捏奥的悲恸白嫖到精英. 这个让我很是奇怪, 在我之前的认知中, 杀戮尖塔遇到什么怪物, 问号碰到什么事件, 都是已经注定好的行为, 为什么会有Bug会让第一支怪物, 影响到后续的问号事件呢? 所幸杀戮尖塔是用Java写的, 所以我们可以很直接的反编译他的Jar文件, 看到源代码 源代码获取方式 打开杀戮尖塔文件夹, 里面会有一个名为desktop-1.0.jar的文件, 使用任意反编译工具即可获取源代码. 这里我使用的是JD-GUI, 将其解析之后获得到源码 生成怪物和事件的关系 生成怪物 其中生成怪物的代码在desktop-1.0.jar.src\com\megacrit\cardcrawl\dungeons\Exordium.java protected void generateMonsters() { generateWeakEnemies(3); generateStrongEnemies(12); generateElites(10); } 这里是Exordium, 即第一章序幕也就是第一层, 会生成3个弱怪, 12个强怪, 10个精英. 这里可以证明, 3个弱怪之后就是强怪池. 其中生成弱怪的代码 protected void generateWeakEnemies(int count) { ArrayList<MonsterInfo> monsters = new ArrayList<>(); monsters.add(new MonsterInfo("Cultist", 2.0F)); monsters.add(new MonsterInfo("Jaw Worm", 2.0F)); monsters.add(new MonsterInfo("2 Louse", 2.0F)); monsters.add(new MonsterInfo("Small Slimes", 2.0F)); MonsterInfo.normalizeWeights(monsters); populateMonsterList(monsters, count, false); } 其中MonsterInfo中, 第一个参数为怪物名称, 第二个是生成权重, 这里面四个怪物的权重是一样的, 所以理论上生成的概率是相同的. ...
算法&&数学: 拥有最小平均数的切片
问题Problem 在codility看到的题目, 题目内容如下 题目内容 英文题目 A non-empty array A consisting of N integers is given. A pair of integers $(P, Q)$, such that $0 ≤ P < Q < N$, is called a slice of array A (notice that the slice contains at least two elements). The average of a slice $(P, Q)$ is the sum of $A[P] + A[P + 1] + … + A[Q]$ divided by the length of the slice. To be precise, the average equals $(A[P] + A[P + 1] + … + A[Q]) / (Q − P + 1)$. ...
记一次Python MRO多继承使用
引言 之前只是看书的时候看过Python多继承的MRO顺序, 从来没在代码里面具体实践过, 今天终于遇到了一次. 在写NWU.ICU的过程中, 有课程评价一项, 建立Model的时候, 为了实现标记删除, 继承了自定义的SoftDeleteModel模型, class Review(SoftDeleteModel): .... pinyin = models.TextField(verbose_name='拼音', blank=True) search_vector = SearchVectorField(null=True) objects = SearchManager() SoftDeleteModel如下 class SoftDeleteManager(models.Manager): def get_queryset(self): return super().get_queryset().filter(is_deleted=False) class SoftDeleteModel(models.Model): is_deleted = models.BooleanField(default=False) deleted_at = models.DateTimeField(null=True, blank=True) objects = SoftDeleteManager() all_objects = models.Manager() def soft_delete(self): self.is_deleted = True self.deleted_at = timezone.now() self.save() soft_delete_signal.send(sender=self.__class__, instance=self) def restore(self): self.is_deleted = False self.deleted_at = None self.save() class Meta: abstract = True 概括就是删除的时候标记is_deleted为true, 并且重写了get_queryset方法, 使得model.objects.get()时, 让已被标记删除的数据不被查询到. 这本来工作好好的, 但为了引入拼音模糊搜索功能, 我在Review模型, 加了几行, 也就是上文里面的 pinyin = models.TextField(verbose_name='拼音', blank=True) search_vector = SearchVectorField(null=True) objects = SearchManager() 使得我的model.objects.get()一直可以获取到已经被标记删除的数据. 解决方案 其中SearchManager如下 class SearchQuerySet(models.QuerySet): def search(self, query, query_table_name): ... class SearchManager(models.Manager): def get_queryset(self): return SearchQuerySet(self.model, using=self._db) def search(self, query, page_size=10, current_page=1): ... search_results = self.get_queryset().search(query, query_table_name) return ... 原因很明显, objects = SearchManager()中get_queryset的覆盖了我在SoftDeleteModel定义的objects的原有方法. ...
数学: ab+1整除a^2+b^2, 证明(a^2+b^2)/(ab+1)是完全平方数
问题 设$a$和$b$为正整数, 且$ab+1$整除$a^2+b^2$. 证明$\frac{a^2+b^2}{ab+1}$是完全平方数. 前置知识 完全平方数 如果$a\in N^*$(正整数), 如果$a^2=b$, 那么我们称$b$为完全平方数. 整除 为了智械危机的快速来临, 在这里讲解一下整除:) 如果$\frac{b}{a}=c$, 且$c\in N^*$, 那么我们称a可以整除b. 解题思路 请先尝试一下自己解题, 五分钟没有思路的情况下, 再接着往下看. 证明思路 相信大家还记得高中数学的"韦达定理"(Vieta’s theorem), 其出自于一个处理数论的证明技巧: 韦达跳跃(Vieta jumping), 除了韦达定理外, 还包括了另一个部分, 无穷递增法(method of infinite descent) 韦达定理描述为: 在二元一次方程中, 两个根的和,积与abc(abc为二元一次方程的项)的关系 假设一元二次方程$ax^2+bx+c=0$有两个实数根, 设其为$x_1,x_2$, 则$x_1+x_2=-\frac{b}{a}$, $x_1*x_2=\frac{c}{a}$ 具体推导过程不表. 无穷递增法是一种反证法, 用自然语言表述为 假设方程有解, 并假设X为最小解, 接着再从X出发, 尝试推导出一个更小的解Y. 若能推导成功, 则与刚才假设的"X为最小解"矛盾, 因此证明此方程无解. 回到最开始的问题中, $ab+1$可以整除$a^2+b^2$, 因此$\frac{a^2+b^2}{ab+1}$是一个正整数, 设其为$k$. 接着我们假设有正整数$a,b$满足$\frac{a^2+b^2}{ab+1}=k$, 而$k$不是完全平方数. 最后, 假设在所有满足条件2的正整数中, 有一组是$a_1,b_1$, 他们拥有最小的和, 其中$a_1>b_1$. 因此, 如果我们能证明, 如果有一组数可以比比$a_1,b_1$还要小, 那么我们条件2的假设:$k$不是完全平方数, 就不成立了, 即证明了$k$是完全平方数. ...
Android JetPack Compose 使用Room Flow的一些小坑
引言 在写一个小的Android Compose App时, 遇到了一个小问题 先看下行为 你的浏览器不支持视频标签. 我的收藏列表是根据收藏名称从数据库查询到的(不要问为什么这么建立数据库, 我也知道是屎山, 这里只探讨这个Flow的行为), 可以看到, 在我们更新了收藏的名称之后, 收藏列表消失了. 其中收藏列表的渲染由FavorItem(selectedFavorItemListInTmp)实现. 具体获取收藏列表实现如下 @OptIn(ExperimentalMaterial3Api::class) @Composable fun AddFavorScreen( queryUserViewModel: QueryUserViewModel, ) { val selectedFavorItemList by queryUserViewModel.selectedFavorItemList.collectAsState() var selectedFavorItemListInTmp by remember(selectedFavorItemList) {mutableStateOf(selectedFavorItemList)} val favorName by queryUserViewModel.favorName.collectAsState() var favorNameTmp by remember(favorName) { mutableStateOf(favorNameFromOtherPage ?: favorName) } LaunchedEffect(Unit) {queryUserViewModel.queryFavorItems()} //Some code change favorNameTmp Button(onClick = { queryUserViewModel.saveUserFavor( selectedFavorItemListInTmp, favorNameTmp, remarkTmp ); }) { Text("保存") } FavorItem(selectedFavorItemListInTmp) } @Composable fun FavorItem(userList: List<ItemDetailState>) { ... } class QueryUserViewModel(){ val favorName = MutableStateFlow("") val remark = MutableStateFlow("") val selectedFavorItemList = MutableStateFlow<List<ItemDetailState>>(emptyList()) fun queryFavorItems() { val favorName = favorName.value viewModelScope.launch { remark.value = UserFavorRepository.queryFavorRemark(favorName) ?: "" UserFavorRepository.queryFavorItems(favorName).collect { user -> selectedFavorItemList.value = user.map { UserDataClassToItemDetailState(it) } }} _queryState.value = SaveState.Success } fun saveUserFavor( selectedFavorItemList: List<ItemDetailState>, favorName: String, favorRemark: String ) { viewModelScope.launch { UserFavorRepository.saveUserFavor(selectedFavorItemList, favorName, favorRemark) } } } 原因 为什么收藏列表为空? 我们使用的数据库为SQLite, 兼容层为Room ...