半连接与全连接队列
参考:《UNIX网络编程》、hping3命令使用、小林网络、51CTO
内核为每个监听套接字维护两个队列:未完成连接队列和已完成连接队列 。前者又称半连接队列、SYN队列,后者又称全连接队列、accept 队列。
半连接队列:服务器收到客户端发来的 SYN 报文后,将该连接存入此队列中。这些连接处于 SYN_RCVD 状态。
全连接队列:服务端收到第三次握手的 ACK 后,该连接被内核从半连接队列转移到全连接队列,此时已经完成三次握手,处于 ESTABLISHED 状态。当调用 accept 函数时,该连接将从全连接队列中移除。
backlog
“backlog的含义从未有过正式的定义”,不同的 UNIX 操作系统对其有不同的实现 。这里只以笔者的环境 Ubuntu 16.04 进行说明。
其他版本的相关说明请参见《UNP》第 3 版 84 页。
先说结论:
在 Linux 内核 2.2 之后,backlog 参数影响全连接队列的长度, tcp_max_syn_backlog 作为系统变量则影响半连接队列的长度 。
为什么说“影响”而不是“决定”?因为两个队列长度的具体 ...
错误处理与包裹函数
参考书籍:《UNIX网络编程》《UNIX环境高级编程》
errno
只要在一个 系统调用 中有错误发生,全局变量 errno 就会被自动设置为一个特定的正值,用来反馈具体错误,而函数本身则会返回 -1,以说明函数发生了错误;如果函数返回正值,即没有错误发生,则 errno 没有定义。errno 包含在 errno.h 中。
如果函数没有出错,则之前的 errno 可能不会被清除(未定义);只有发生错误时,才会覆盖之前的错误。
errno 不会为 0,这与多线程的 errno 处理有关。
这是因为:线程函数(以 pthread_ 开头的函数)遇到错误时不会设置标准 Unix 的 errno 变量,而是将 errno 的值以函数返回值的形式交给调用者。也就是说,返回值大于 0 则说明发生了错误,那没有发生错误呢?自然也就是返回 0 了。
1234int n;if((n=pthread_mutex_lock(&ndoen_mutex))!=0){fprintf(stderr,"error:%s",strerror(n));}
你看 ...
TIME_WAIT与SO_REUSEADDR
运行环境:Ubuntu 16.0.4
无论是否开启 SO_REUSEADDR ,客户端主动断开连接后,无需等待 TIME_WAIT 即可重连,因为客户端每次 bind 的端口都不一样。由此可见, TIME_WAIT 是对端口而言 。
不管是服务器还是客户端,只要是主动断开连接的,都会有 TIME_WAIT 。
如不开启 SO_REUSEADDR,服务器端主动断开连接,则必须等待 TIME_WAIT 后才可重新 bind 该端口,原因见下文。
注意,必须是要在 accept 或 connect 成功返回后(即连接成功后)断连或终止程序,才会有 TIME_WAIT ;仅仅 bind 但未连接成功,终止程序后是不会 TIME_WAIT 的 。
注意,如果 bind 指定端口不成功,则会自动 bind 其他任意端口;
SO_REUSEADDR 生效的前提条件是开启时间戳,即令 /proc/sys/net/ipv4/tcp_timestamps 为 1(默认也为1)。
实操发现即使关闭时间戳,服务器也能立刻重新绑定端口,原因尚不明确。
123456789101 ...
Socket网络编程重点
初学Socket网络编程的过程中,发现此部分学习有大量细节需要掌握,因此笔记不可忽略!
Socket编程需要基础网络知识作为前置内容,该部分内容可参见计算机网络基础知识笔记
主要参考文章:《UNIX网络编程卷一》《TCP/IP网络编程》
本文只记录重点内容和个人理解,系统学习请移步《UNIX网络编程》
123456789101112131415161718192021222324252627#include<stdio.h>#include<unistd.h>#include<sys/socket.h>#include<arpa/inet.h>#include<string.h>int main(){ int servSock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); sockaddr_in servAddr; memset(&servAddr,0,sizeof(servAddr)); //<string.h> servA ...
关于CHATGPT的一些想法
近一个月以来,国内乃至全世界的焦点都汇聚到了chatgpt之上。被chatgpt刷屏后,我个人也不禁产生了一些思考。
chatgpt对我们的生活将造成哪些影响?
很难说。《未来世界的幸存者》中有这样一个观点:未来几十年内,人工智能将使许多传统岗位消失,如客服、信用评估员、市场分析师等;但有些职业是人工智能暂时很难取代的,如画家或律师等艺术性或人文性较强的工作,原因很容易知道,一言以蔽之,机器始终无法替代人类的灵光一闪。之前我也对此观点深信不疑,直到看见了下面这些新闻:
要知道,该书出版于2018年,至今仅过了短短五年…所以,你很难想象科技的发展速度,谁知道下一个五年将会是什么样呢?就chatgpt而言,虽然目前它的回答尚有较大的瑕疵,但风口已经打开,如周鸿伟所说——“搭不上ChatGPT的企业可能将被淘汰”,大量资本的注入势必会大大增强其性能,所以它的潜力仍然无法预估。不过,有一点我们必须清楚:如果想要避免未来被人工智能取代,就必须从事技术含量高、重复性低、可量化性低的工作。当然,公务员也许不错。
话说回来,也许不用对未来AI是否会造成失业潮而过度担心呢:
本人浅薄地认为,AI ...
深度思维-读书总结
前几日,我父亲醉酒后拉着我谈心,不知道是酒后起兴还是愁绪积压太久,向我倾诉了他对前半生的无尽悔恨,悔恨学生时代没有自制力(初中名列前茅,中考前被朋友带着打牌,三个月不眠,因此失利;高中仍名列前茅,又被朋友拉着打乒乓,没下课就跑去占台,耗费大量时间);悔恨早年赚到钱时,没能听朋友劝告进入房地产;悔恨后来发家时,没能及时在深圳买房…父亲微仰着头,泪光不停地闪烁着,“在那个疯狂的、满地是钱的年代,无数个机会从我们面前闪过,然而我一个也没曾抓住。”
父亲的这些话,令我感触颇多。那个撒钱的年代,为什么机会遍地,却不曾抓住过?当然,任何一件事都有其时代的局限性,马后炮总是容易的。但话说回来,不依旧有很多人脱颖而出,改变了自己的命运吗?有人说他们靠的是胆量,或说凭的是运气。不可否认,这两者差一个都不行,但笔者认为,更重要的是——认知。与认知同义的,还有我们常说的眼界、格局等。最近有句话在网上传得很火——凭运气赚到的钱,总会凭实力亏掉,这句话的背后也许就体现了认知的重要性。如著名畅销书籍《穷爸爸富爸爸》中提到的,为什么一些运动员、演员等红极一时,短时间内赚到了普通人几辈子都赚不到的钱,而十几年甚至几年 ...
更安全的用户进程?
本节分支:data_limit_3gb
在实现用户进程一文的文尾,笔者留下了一个思考题:既然要求用户不能直接访问内核,那为什么不将用户代码段的界限设置为 3GB 呢?正如之前所演示的那样,如果用户代码段的界限为 4GB,则用户就可以随意修改内核,包括直接访问显存:
其实笔者也不知道准确的答案,我粗略参考了 Linux 0.11 的代码,发现 Linux 0.11 似乎也是直接将段界限设置为 4GB 。至于规范的防止用户修改内核的方式,咋们以后遇上了再说,现在我们来看看到底能不能通过将数据段界限改为 3GB 来防止用户直接修改内核数据。
首先将用户数据段的段界限改为 0xbfffffff :
123//文件说明:tss.c//函数说明:tss_init()*((struct gdt_desc*)0xc0000938) = make_gdt_desc((uint32_t*)0, 0xbffff, GDT_CODE_ATTR_LOW_DPL3, GDT_ATTR_HIGH);
还要修改 syscall:
12345678910111213//.....uint32_t _syscall ...
系统调用
本节分支:printk
系统调用与API
之前很长一段时间,笔者都将系统调用和 API 函数混为一谈,实际上两者有较大区别。
API (Application Programming Interface,应用程序接口) ,其主要功能是提供通用功能集,程序员通过调用 API 对应用程序进行开发,可以减轻编程任务。
API 可以简单的理解为一个通道或者桥梁,是一个程序和其他程序进行沟通的媒介,本质上一个函数。比如我们想往屏幕上打印字符,显然,如果自己从头实现,则需要了解显卡、汇编等知识,无疑相当麻烦。而且 C 库中早就为我们准备了打印函数,即 printf,你只需要按它的要求传入参数就行,无需了解 printf 内部实现。所以 printf 也可以称为 API 。说白了,接口,就是指两个不同程序之间交互的地方 ,就这么简单。
而系统调用是一种特殊的接口,通过这个接口,用户可以访问内核空间,进而实现一些只有内核才能完成的操作,比如屏幕打印、内存申请(malloc)等。
那么这两者有什么区别呢?严格来说,两者没有直接关系,但一般而言,系统调用一般封装在 API 中,但不是所有 API ...
printf加强-支持short与long long
本文前置内容:printf 底层剖析及可变参数探究
本文参考文章:x86环境下将64位整数转换为字符串 - 仲夏夜的乙醇 ,使用位运算代替取模
本节对应分支:printk-enhanced
上节说到 Linux 0.11 的 printk 不支持输出 short (不是不支持,而是表现得和 int 相同)和 long long,本节修改代码来支持 %hd 和 %lld 。
本以为很简单,只需像之前那样对 short 或 long long 数值不断除以基数并取模,依次得到数字字符,然后组成字符串即可。没想到的是,Bochs 的 32 位 x86 环境不支持 64 位除法和取模运算 ,也就是说无法支持以下操作:
1234long long a = 10;long long b = 2;long long c = a / b;long long d = a % c;
否则会报错,如下:
12undefined reference to `__divdi3'undefined reference to `__moddi3'
__divdi3 和 __moddi3 是 ...
printf底层剖析及可变参数探究
本文前置内容:可变参数列表,函数调用约定
本文参考文章:《你必须知道的495个C语言问题》《Linux内核完全注释》《操作系统真相还原》printf-菜鸟教程
本节对应分支:printk
概览
相信每一位 C 选手写下的第一句代码都是下面这句经典的 Hello World 吧?
12345int main(){ printf("hello world!"); return 0;}
理所应当的,其中的 printf 函数也成为了咋们认识的第一个函数。对笔者个人而言, printf 是一个熟悉而陌生的函数,说熟悉是因为它伴随了我整个 C 语言的学习生涯;说陌生是因为学习过程中一直对它存疑,模模糊糊,始终没能一探究竟,不知道各位读者是否也是像笔者一样呢?记得 C 语言萌新阶段时,我时常吐槽 printf 中的那些格式符,如 %d、%s、%c、%x 等,乱七八糟的,实在是太难记啦!入门阶段时,我赞叹 printf 强大的格式处理能力,比如左右对齐、输出宽度、输出精度等;进阶阶段时,我又开始疑惑 printf 是如何做到参数可变的,但因为基 ...