Notes on UNPv1 Ch.5
#1 一般的私有服务器绑定端口时应该应该使用5001~49151区间内的端口. 0~1023为熟知端口, 1024~5000为传统BSD程序使用的临时端口, 49141~65535为一般系统分配时使用的临时端口. (Page.122)
#2 子进程在结束时会向父进程发送信号SIGCHLD, 父进程应使用wait*等函数来获取子进程的终止状态信息, 否则子进程会变成僵尸(APUE有介绍...忘记了). (Page.129)
#3 理所当然的, 信号既可以由内核产生(如SIGCHLD等等信号对应不同的情况), 也可以有进程产生(kill函数). (Page.129)
#4 一般来说大部分信号都可以通过sigaction函数设定一个指定的handler(可以用SIG_DEF重置, SIG_IGN设为忽略: 信号来了就像没来一样, "slow system call"不会被中断)来捕获. 但是SIGIO, SIGPOLL和SIGURG仍然需要额外的操作来捕获这些信号. 而SIGKILL和SIGSTOP不能被忽略(貌似连捕获都不能?). (Page.129)
#5 signal函数一般会重启被中断的"slow system call"(利用SA_RESTART, 但是有些系统调用绝大部分系统都不会重启, 如select), 但是由SIGALRM引起的中断不会被重启. (Page.130, 134)
#6 可以设置mask来阻止信号的到来, 但是被阻止的信号不是被删除, 而是被保存起来, 等到mask解除之后, 信号仍然会发送到进程中. 如果有多个相同的信号SIGXXX发送到一个设置了mask的进程, 那么只会保留这个信号SIGXXX的一个副本, 也就是说, 解除mask后进程只会收到一个SIGXXX. (Page.131)
#7 捕获一个信号进入信号处理函数, 在处理过程中如果又来了一个相同的信号, 那么这个新的信号会被阻塞(如先后两个子进程结束, 那么就先后发送两个SIGCHLD). 可以设置成不阻塞(SA_NODEFER). (Page.131)
#8 僵尸状态的目的是为父进程维护子进程的终止信息: 进程号, 终止状态, 资源使用情况(CPU time, 内存等). (Page.132)
#9 "slow system call"是指那些可能会一直阻塞的系统调用(如accept, 对socket或pipe的read和write等等). 对磁盘的读写虽然可能会暂时的阻塞, 但是磁盘IO不是"slow system call". "slow system call"在阻塞是如果进程捕捉到信号并用处理函数处理, 那么"slow system call"可能会返回EINTR(如果被重启了就不会这样). (Page.134)
#10 connect不能重启, 如果connect返回EINTR, 那么应该用select之类的函数等待TCP三次握手完成. 如果是其他错误, 那么就只能close这个sockfd, 重新socket和再次connect. (Page.135)
#11 wait函数用于并行TCP服务器处理结束的子进程不可行, 因为wait不可以在非阻塞模式下工作. 如果多个子进程同时结束, 会出现#6中提到的只有一个SIGCHLD被保留的情况. 所以每次收到SIGCHLD, 应该以循环的方式把当前所有结束了的子进程的信息全部用非阻塞waitpid获取完. (Page.139)
#12 对收到RST的链接accept, errno = ECONNABORTED; write, errno = EPIPE; read, errno = ECONNREST. (Page.140, 142, 144)
#13 一方收到FIN后, 仍然可以向对方发送数据, 但是可能会收到RST. 如果对方使用shutdown来关闭发送(SHUT_WR), 那么本机可以继续发送而不收到RST; 但是如果对方通过close来关闭socket(发送FIN), 或者shutdown+SHUT_RD(这样不会发送FIN), 这种情况下继续发送数据就会收到RST, 如果close或shutdown+SHUT_RD时, 机子还有数据在缓存没有取出, 一样发送RST. (Page.141)
#14 对接收到RST的socket继续进行写操作的话, 会引发SIGPIPE, 无论是否捕捉这个信号, 写操作都会失败, errno=EPIPE. 对收到FIN的socket可以写, 但是对收到RST的socket不可以写. (Page. 143)
#15 一般通过心跳包(主动发送数据确定对方)或SO_KEEPALIVE(一定时间内没有数据交互关闭链接)来确定对方是否崩溃. (Page.145)
#16 使用结构体作为数据发送存在3个问题: 字节排序问题, 类型实现问题, 结构题对齐问题. (Page.150)
c++异常处理 笔记若干
1. 抛出异常后, 当前正在执行的代码会立刻中断执行, 转到最近的与异常类型相匹配的catch模块中执行, 当catch模块执行完毕之后, 直接从catch模块后面的代码继续执行.
2. 如果一个函数会抛出异常, 函数内部抛出了一个异常, 并且函数内部没有对这个异常进行捕捉, 那么函数会立刻终止, 甚至没有返回, 就如过goto了一样, 而异常则抛出给调用此函数的代码来处理.
3. 任何类型的异常的异常都可以抛出, 简单的从int, 复杂到类结构, 都没有问题.
4. throw语句如果抛出的是一个已经定义的类实例(结构实例), 那么会导致以这个实例为参数的拷贝构造函数的调用:
#include <stdio.h> class ThrowTest { public: ThrowTest(){} ThrowTest(const ThrowTest& tt) { puts("Copy Constructor is called."); } }; int main() { try { ThrowTest tt; throw tt; } catch(ThrowTest) { puts("inside catch"); } return 0; }
输出为
Copy Constructor is called.
Copy Constructor is called.
inside catch
这里会输出两个"Copy Constructor is called."是因为catch也会调用到拷贝构造函数, 这个等下再说明.
而如果不是已经定义好的类实例(即直接调用构造函数使用临时值), 那么就只是调用该函数. (这是为了避免实例生存期结束而导致的错误?)
5. catch的模块参数只能是一个, 作用和一般的函数参数一样, 也分按值传递和按引用传递, 所以在笔记4的例子代码的输出结果是调用了两次拷贝构造函数. 如果使用
catch(Exception& e) { //some code //...
的方法, 则不会再调用一次构造函数.
6. try模块里面定义的局部变量只能在try模块里使用, 在catch里面也不可以使用, 这个和一般的花括号一样. catch也是类似.
7. 在try模块里面可以抛出不同类型的异常, 通过不同catch模块指定不同类型的异常来捕获, 注意如果抛出的是派生类的异常, catch的异常类型是基类, 那么照样可以捕获这个异常, 但是反过来则不行. 所以在安排catch模块时要注意顺序, 如果基类catch模块先于派生类catch模块, 将导致派生类类型的异常被截断无法正确捕捉.
8. 如果不需要理会异常的具体内容, 可以不指明catch模块参数的参数名, 只写出类型即可. 如
catch(Exception) { //some code //...
9. 函数抛出异常应指明指明异常说明(抛出列表). 抛出多种不同类型的异常用逗号隔开, 如果不指明异常说明的话等于可以抛出任何异常(等价于后面说的"...."), 用没有内容的空括号表示不抛出任何异常.
returnType function(someArgs) throw ();//no throw returnType function(someArgs) throw (exType);//only one type returnType function(someArgs) throw (exType1, exType2, ..., exTypeN);//multi type returnType function(someArgs) throw (...);//all type returnType function(someArgs) ;//all type
10. 用 ... (3个句号) 可以抛出任何异常和捕获任何异常.
11. 如果函数抛出了不在异常说明中列出的类型的异常, 会调用unexpected函数, unexpected函数的默认处理是调用terminate函数结束程序, 但是也可以用set_unexpected函数改变其行为. 如果抛出了异常没有被catch, 那么直接调用terminate函数.
12. 异常的抛出可以递归, 就是有f1, f2, f3 3个函数, f1调用f2, f2调用f3, f3抛出异常, f2不处理, 那么f2也结束, 异常交给f1处理.
vim自带的一些颜色主题
默认的vim语法高亮不是很好看, 网上也有很多配色脚本提供(如http://www.vim.org和http://vimcolorschemetest.googlecode.com/svn/html/index-c.html), 但是问题在于这些脚本并不是个个都能用, 有些看图示好像很漂亮, 但是实际上用起来效果根本不是这样.
偶然在找配色脚本时发现原来vim自己本身就带着一些主题, ubuntu 10.04下放在了/usr/share/vim/vim72/colors下面. 这些主题是: blue.vim default.vim desert.vim evening.vim morning.vim pablo.vim shine.vim torte.vim darkblue.vim delek.vim elflord.vim koehler.vim murphy.vim peachpuff.vim ron.vim slate.vim zellner.vim
只要在vim下输入
colorscheme evening "或者其他脚本名, 注意不要有.vim后缀.
或者
colo evening "这个是简写
就可以切换主题了. 当然你也可以直接在.vimrc里面写.
如果找到了一些合适的主题, 也可以放在上面说到的文件夹里面使用.
个人觉得evening这个主题还不错.
c++不被继承的函数及有关笔记
First of all, 一切测试都是基于单继承, 同时不涉及到虚函数等多态的内容.
在C++的继承里面, 父类有5种函数是不会被"继承"的(但是仍然可以调用, 所谓不被继承, 就是如果不重定义这些函数, 就不可以在外部由继承类通过任何方式调用这些函数).
1. 构造函数
2. 析构函数
3. 拷贝构造函数
4. 赋值操作符(=)函数
5. 私有函数
首先是第一种, 如果在子类的构造函数中不显式地调用父类的构造函数, 那么子类的构造函数会隐式地调用父类的默认构造函数(无参数的那个). 即
class A { public: A(){puts("A is created.");} private: int a; }; class B : public A { public: B(){puts("B is created.");} //B() : A(){puts("B is created.");} private: int b; };
无论类B的构造函数是哪一个, 输出的结果都是
A is created.
B is created.
而对于第二种, 析构函数, 那么则不必显示调用父类的析构函数, 子类的析构函数会隐式地调用父类的析构函数. 即
class A { public: ~A(){puts("A is deleted.");} private: int a; }; class B : public A { public: ~B(){puts("B is deleted.");} private: int b; };
在B析构的时候, 都会输出
B is deleted.
A is deleted.
(注意析构的顺序和构造的顺序相反)
而对于第三和第四种, 则需要显式地去调用父类的函数, 否则不会自动调用,
第三种的调用方法是
Derived(const Derived& d) : Base(d) { //... something to init about Derived except Base }
实际上如果没有了Base(d)个地方, 拷贝构造函数会自动调用父类的默认构造函数(和普通构造函数的做法一样).
第四种的调用方法是
const Derived& operator =(const Derived& d) { Base::operator =(d); //...do the rest of copy. }
第五种就不用说了.
总的来说, 对于这种不能"继承"的函数, 除了析构函数和私有函数这种不用(能)调用的函数外, 在重定义的时候最好都是显式地去调用父类的对应函数, 贪一时便利而去利用语言本身的缩略, 首先需要掌握许多语言的细节(这还没考虑编译器的不同实现, 万一某些编译器编译出来的拷贝构造函数会自动调用父类的呢?), 其次就是会导致程序不好阅读和不利日后的维护.
已经没有 iostream.h 了
作为一个大一才学计算机, 而且在一个不入流的学校的更加不入流的计算机学院学计算机的人, 我就和大部分中国的未来码农一样, 一开始学的是c语言, 准确来说, 我们学院更前卫一点, 学的是c++. 而又不能免俗地, 看到的第一段代码就是在一个黑乎乎的terminal里面输出一个类似"Hello World!"的傻逼难产程序(我操).
因此iostream.h给我的印象非常深刻, 在很长一段时间里面, 我先c++程序的第一行代码就是#include <iostream.h>. 后来又慢慢见识到命名空间, 那时候见到介绍说, 使用命名空间是新标准, 原来用.h的iostream慢慢会被淘汰, 于是也没有想那么多, 就改用using namespace std 了. 再到后来, 开始做些acm的题目, 发现用printf的格式化输出比较方便, 而且比较高效(这里又涉及到了输出缓存以及输出处理顺序的问题, 话说现在都没有完全搞懂), 于是就慢慢转向使用printf, 用多了又发现用stdio.h在zoj上内存的占用比用iostream的少了4k, 最后iostream就几乎没有用过了.
今天回顾下c++的内容时, 脑海中一闪iostream和iostream.h到底有什么区别, 遂baidu了一下(学了3年多计算机还分不清此二者区别, 真是惭愧). 发现原来这两个东西果然是不同的文件, 而且iostream.h已经被c++指明是不支持的东东了...
在unbuntu 10.04下find了一下, 果然真没找到iostream.h这个文件, iostream倒是找到了. 又写了个吐"Hello World!"的傻逼难产程序试验了一下, 使用iostream.h的版本果然是编译失败了(gcc 4.4.3 报iostream.h: No such file or directory).
别了, iostream.h, 我在c++里面初恋般的记忆.
[转]我不是天才
我对计算机很在行。至少在同龄人中是这样的。请相信我说的,我听到很多赞扬:“哦,你真是一个天才!”那么,看起来我确实是个计算机天才。或者 … ?
让我们做过实验:把我所有的跟计算机相关的知识加起来,除以我学习这些知识所花的所有时间。得出来的是我学习的“速度”。然后对我的那些不够“专业”的朋友做同样的算法。谁的成绩会最好?如果我是计算机天才,我应该是学得最快的一个,是吗?
事实上,我相信我的成绩将会远低于一个”普通“人的水平。我在计算机前花费了大量的时间,常常不是在学习新东西或开发什么特别的东西。见鬼,总计起来我比众多的非技术爱好者”浪费“了太多的时间。这听起来像个天才吗?
我深信对计算机我没有天才。
有的只是热情。我对计算机,对计算机科学有热情,对编程,对创造,对分享,对帮助他人等等有热情。我之所以愿意对它花费这么多的时间是因为:它有趣,我喜欢它;这是我想要做的东西。这就是我决定做这行的原因。
事实上,我还清楚的记得第一次和计算机编程遭遇的情形。我的父亲一直在研究计算机系统管理工作,我一直不明白他日复一日的在做什么。他的工作距离我很远。然而,有一天他为一个客户写一个小程序,有一部分工作是在家里做的。我根本不清楚程序是用来干吗的,也不知道爸爸如何让它们运行的,我只是坐在他身旁看他工作。没有提问,没有试图理解(他一直工作),但就这么盯着已经让我产生了足够的兴趣。
我的热情就这样被点燃。突然的我就开始尝试着使用FrontPage,HTML,JavaScript(大多是从别处拷贝粘贴一些代码),PHP和MySQL。我一直在寻找一个更好的免费主机服务,四处在网上寻找我可以安装和改进的PHP脚本。我是如此的喜欢HotScript!那些年我完全沉浸在实验的快乐中,不需要理解这些技术是如何的一种原理。
年幼时就开始做这些事情的一个最大的好处是没有人来评价你。人们都认为你是为了好玩,即使有些东西超出了你的年龄范围,人们也不会捅破你做出的美丽泡泡,他们会让你继续研究实验。
一旦你长大了,人们开始对你有所期望。“你不是唱歌的料。”“你想学绘画!放弃吧!”“溜冰?小伙,你在浪费你的时间!”他们也许不会这么直接的说,可一旦你做出的事情超出了人们的意料,那些持怀疑态度的人就会出现在你面前。人们会鼓励你应该做一些“新尝试”,可一旦这”尝试“被认为很难,你就被认为是在”浪费时间“。
你可以环顾四周看看同龄人中有谁比你会玩吉他,会溜冰,或会唱歌。你如何才能像他们那样优秀?他们一定有这方面的天赋!但事实是,他们只是比你更早的学习它们,或者花了比你更多的时间在上面。
人是如此的容易放弃;有时整个世界好像都在反对你做的事情。那么,忘掉世界上还有其他的人吧。世上没有天才,总有一天你也能像你心中的”天才们“一样成为天才。
就让热情带你前进。