生命是一种长期而持续的累积过程

 

【文章作者是清华电机系彭明辉老师】

许多同学应该都还记得联考前夕的焦虑:差一分可能要掉好几个志愿,甚至于一生的命运从此改观!到了大四,这种焦虑可能更强烈而复杂:到底要先当兵,就业,还是先考研究所?

我 就经常碰到学生充满焦虑的问我这些问题。可是,这些焦虑实在是莫须有的!生命是一种长期而持续的累积过程,绝不会因为单一的事件而毁了一个人的一生,也不 会因为单一的事件而救了一个人的一生。属于我们该得的,迟早会得到;属于我们不该得的,即使侥幸巧取也不可能长久保有。如果我们看清这个事实,许多所谓人 生的重大抉择就可以淡然处之,根本无需焦虑。而所谓人生的困境,也往往当下就变得无足挂齿。

我自己就是一个活生生的例子。从一进大学就决定不再念研究所,所以,大学四年的时间多半在念人文科学的东西。毕业后工作了几年,才决定要念研究所。硕士毕业后,立下决心:从此不再为文凭而念书。谁知道,世事难料,当了五年讲师后,我又被时势所迫,整装出国念博士。

出 国时,一位大学同学笑我:全班最晚念博士的都要回国了,你现在才要出去?两年后我从剑桥回来,觉得人生际遇无常,莫此为甚:一个从大一就决定再也不钻营学 位的人,竟然连硕士和博士都拿到了!属于我们该得的,哪样曾经少过?而人生中该得与不该得的究竟有多少,我们又何曾知晓?从此我对际遇一事不能不更加淡 然。当讲师期间,有些态度较极端的学生会当面表现出他们的不屑;从剑桥回来时,却被学生当做不得了的事看待。这种表面上的大起大落,其实都是好事者之言, 完全看不到事实的真相。从表面上看来,两年就拿到剑桥博士,这好像很了不起。但是,在这两年之前我已经花整整一年,将研究主题有关的论文全部看完,并找出 研究方向;而之前更已花三年时间做控制方面的研究,并且在国际著名的学术期刊中发表论文。而从硕士毕业到拿博士,期间七年的时间我从不停止过研究与自修。 所以,这个博士其实是累积了七年的成果,或者,只算我花在控制学门的时间,也至少有五年),根本也没什么好惊讶的。

常人不从长期而持续的累积过程来看待生命因积蓄而有的成果,老爱在表面上以断裂而孤立的事件夸大议论,因此每每在平淡无奇的事件上强做悲喜。可是对我来讲,当讲师期间被学生瞧不起,以及剑桥刚回来时被同学夸大本事,都只是表象。

事实是:我只在乎每天二十四小时点点滴滴的累积。拿硕士或博士只是特定时刻里这些成果累积的外在展示而已,人生命中真实的累积从不曾因这些事件而终止或加添。常有学生满怀忧虑的问我:

老师,我很想先当完兵,工作一两年再考研究所。这样好吗?

很好,这样子有机会先用实务来印证学理,你念研究所时会比别人了解自己要的是什么。

可是,我怕当完兵又工作后,会失去斗志,因此考不上研究所。

那你就先考研究所好了。

可是,假如我先念研究所,我怕自己又会像念大学时一样茫然,因此念的不甘不愿的。

那你还是先去工作好了!

可是……”

我 完全可以体会到他们的焦虑,可是却无法压抑住对于这种话的感慨。其实,说穿了他所需要的就是两年研究所加两年工作,以便加深知识的深广度和获取实务经验。 先工作或先升学,表面上大相迳庭,其实骨子里的差别根本可以忽略。在朝三暮四这个成语故事里,主人原本喂养猴子的橡实是早上四颗下午三颗,后来改为朝三暮 四,猴子就不高兴而坚持改回到朝四暮三。其实,先工作或先升学,期间差异就有如朝三暮四与朝四暮三,原不值得计较。但是,我们经常看不到这种生命过程中长 远而持续的累积,老爱将一时际遇中的小差别夸大到攸关生死的地步。

最 讽刺的是:当我们面对两个可能的方案,而焦虑的不知何所抉择时,通常表示这两个方案可能一样好,或者一样坏,因而实际上选择哪个都一样,唯一的差别只是先 后之序而已。而且,愈是让我们焦虑得厉害的,其实差别越小,愈不值得焦虑。反而真正有明显的好坏差别时,我们轻易的就知道该怎么做了。可是我们却经常看不 到长远的将来,短视的盯著两案短期内的得失:想选甲案,就舍不得乙案的好处;想选乙案,又舍不得甲案的好处。如果看得够远,人生常则八,九十,短则五,六 十年,先做哪一件事又有什么关系?甚至当完兵又工作后,再花一整年准备研究所,又有什么了不起?当然,有些人还是会忧虑说:我当完兵又工作后,会不会因为 家累或记忆力衰退而比较难考上研究所?

我 只能这样回答:一个人考不上研究所,只有两个可能:或者他不够聪明,或者他的确够聪明。不够聪明而考不上,那也没什么好抱怨的。假如你够聪明,还考不上研 究所,那只能说你的决心不够强。假如你是决心不够强,就表示你生命中还有其他的可能性,其重要程度并不下于硕士学位,而你舍不得丢下他。既然如此,考不上 研究所也无须感到遗憾。不是吗?

人生的路这么多,为什么要老斤斤计较著一个可能性?我高中最要好的朋友,一生背运:高中考两次,高一念两次,大学又考两次,甚至连机车驾照都考两次。毕业后,他告诉自己:我没有人脉,也没有学历,只能靠加倍的诚恳和努力。现在,他自己拥有一家公司,年收入数千万。

一个人在升学过程中不顺利,而在事业上顺利,这是常见的事。有才华的人,不会因为被名校拒绝而连带失去他的才华,只不过要另外找适合他表现的场所而已。反过来,一个人在升学过程中太顺利,也难免因而放不下身段去创业,而只能乖乖领薪水过活。福祸如何,谁能全面知晓?

我 们又有什么好得意?又有什么好忧虑?人生的得与失,有时候怎么也说不清楚,有时候却再简单不过了:我们得到平日累积的成果,而失去我们不曾努力累积的!所 以重要的不是和别人比成就,而是努力去做自己想做的。功不唐捐,最后该得到的不会少你一分,不该得到的也不会多你一分。

好 像是前年的时候,我在往艺术中心的路上遇到一位高中同学。他在南加大当电机系的副教授,被清华电机聘回来开短期课程。从高中时代他就很用功,以第一志愿上 台大电机后,四年都拿书卷奖,相信他在专业上的研究也已卓然有成。回想高中入学时,我们两个人的智力测验成绩分居全学年第一,第二名。可是从高一我就不曾 放弃自己喜欢的文学,音乐,书法,艺术和哲学,而他却始终不曾分心,因此两个人在学术上的差距只会愈来愈远。反过来说,这十几二十年我在人文领域所获得的 满足,恐怕已远非他所能理解的了。我太太问过我,如果我肯全心专注于一个研究领域,是不是至少会赶上这位同学的成就?我不这样想,两个不同性情的人,注定 要走两条不同的路。不该得的东西,我们注定是得不到的,随随便便拿两个人来比,只看到他所得到的,却看不到他所失去的,这有什么意义?

有 次清华电台访问我:老师你如何面对你人生中的困境?我当场愣在那里,怎么样都想不出我这一生什么时候有过困境!后来仔细回想,才发现:我不是没有过困境, 而是被常人当作困境的境遇,我都当作一时的际遇,不曾在意过而已。刚服完兵役时,长子已出生却还找不到工作。我曾焦虑过,却又觉得迟早会有工作,报酬也不 至于低的离谱,不曾太放在心上。念硕士期间,家计全靠太太的薪水,省吃俭用,对我而言又算不上困境。一来,精神上我过的很充实,二来我知道这一切是为了让 自己有机会转行去教书(做自己想做的事)。三十一岁才要出国,而同学正要回系上任教,我很紧张(不知道剑桥要求的有多严),却不曾丧气。因为,我知道自己过去一直很努力,也有很满意的心得和成果,只不过别人看不到而已。我没有过困境,因为我从不在乎外在的得失,也不武断的和别人比高下,而只在乎自己内在真实的累积。

我 没有过困境,因为我确实了解到:生命是一种长期而持续的累积过程,绝不会因为单一的事件而有剧烈的起伏。同时我也相信:属于我们该得的,迟早会得到;属于 我们不该得的,即使一分也不可能加增。假如你可以持有相同的信念,那么人生于你也会是宽广而长远,没有什么了不得的困境,也没有什么好焦虑的了。

 

 

linux-Tcp IP协议栈源码阅读笔记(转)


出处: blog.csdn.net/cz_hyf/archive/2006/02/19/602802.aspx

 

一.linux内核网络栈代码的准备知识
 
1. linux内核ipv4网络部分分层结构
 

BSD socket层: 这一部分处理BSD socket相关操作,每个socket在内核中以struct socket结构体现。这一部分的文件
 
主要有:/net/socket.c /net/protocols.c etc

INET socket层:BSD socket是个可以用于各种网络协议的接口,而当用于tcp/ip,即建立了AF_INET形式的socket时,
 
还需要保留些额外的参数,于是就有了struct sock结构。文件主要
 
有:/net/ipv4/protocol.c /net/ipv4/af_inet.c /net/core/sock.c etc

TCP/UDP层:处理传输层的操作,传输层用struct inet_protocol和struct proto两个结构表示。文件主要
 
有:/net/ipv4/udp.c /net/ipv4/datagram.c /net/ipv4/tcp.c /net/ipv4/tcp_input.c /net/ipv4//tcp_output.c /net/ipv4/tcp_minisocks.c /net/ipv4/tcp_output.c /net/ipv4/tcp_timer.c
 
etc  
     
IP层:处理网络层的操作,网络层用struct packet_type结构表示。文件主要有:/net/ipv4/ip_forward.c
ip_fragment.c ip_input.c ip_output.c etc.

数据链路层和驱动程序:每个网络设备以struct net_device表示,通用的处理在dev.c中,驱动程序都在/driver/net目
 
录下。
 
2. 两台主机建立udp通信所走过的函数列表
 
^
|       sys_read                fs/read_write.c
|       sock_read               net/socket.c
|       sock_recvmsg            net/socket.c
|       inet_recvmsg            net/ipv4/af_inet.c
|       udp_recvmsg             net/ipv4/udp.c
|       skb_recv_datagram       net/core/datagram.c
|       -------------------------------------------
|       sock_queue_rcv_skb      include/net/sock.h
|       udp_queue_rcv_skb       net/ipv4/udp.c
|       udp_rcv                 net/ipv4/udp.c
|       ip_local_deliver_finish net/ipv4/ip_input.c
|       ip_local_deliver        net/ipv4/ip_input.c
|       ip_recv                 net/ipv4/ip_input.c
|       net_rx_action           net/dev.c
|       -------------------------------------------
|       netif_rx                net/dev.c
|       el3_rx                  driver/net/3c309.c
|       el3_interrupt           driver/net/3c309.c

==========================

|       sys_write               fs/read_write.c
|       sock_writev             net/socket.c                    
|       sock_sendmsg            net/socket.c
|       inet_sendmsg            net/ipv4/af_inet.c
|       udp_sendmsg             net/ipv4/udp.c
|       ip_build_xmit           net/ipv4/ip_output.c
|       output_maybe_reroute    net/ipv4/ip_output.c
|       ip_output               net/ipv4/ip_output.c
|       ip_finish_output        net/ipv4/ip_output.c
|       dev_queue_xmit          net/dev.c
|       --------------------------------------------
|       el3_start_xmit          driver/net/3c309.c
V
 
 
3. 网络路径图、重要数据结构sk_buffer及路由介绍
 
    linux-net.pdf 第2.1章 第2.3章 第2.4章
    
4. 从连接、发送、到接收数据包的过程
 
    linux-net.pdf 第4、5、6章详细阐述
 
 
二.linux的tcp-ip栈代码的详细分析
 
1.数据结构(msghdr,sk_buff,socket,sock,proto_ops,proto)
 
bsd套接字层,操作的对象是socket,数据存放在msghdr这样的数据结构:
 
创建socket需要传递family,type,protocol三个参数,创建socket其实就是创建一个socket实例,然后创建一 个文件描述符结构,并且互相建立一些关联,即建立互相连接的指针,并且初始化这些对文件的写读操作映射到socket的read,write函数上来。
 
同时初始化socket的操作函数(proto_ops结构),如果传入的type参数是STREAM类型,那么就初始化为 SOCKET->ops为inet_stream_ops,如果是DGRAM类型,则SOCKET-ops为inet_dgram_ops。对于 inet_stream_ops其实是一个结构体,包含了stream类型的socket操作的一些入口函数,在这些函数里主要做的是对socket进行 相关的操作,同时通过调用下面提到的sock中的相关操作完成socket到sock层的传递。比如在inet_stream_ops里有个 inet_release的操作,这个操作除了释放socket的类型空间操作外,还通过调用socket连接的sock的close操作,对于 stream类型来说,即tcp_close来关闭sock
释放sock。
 
创建socket同时还创建sock数据空间,初始化sock,初始化过程主要做的事情是初始化三个队列,receive_queue(接收到 的数据包sk_buff链表队列),send_queue(需要发送数据包的sk_buff链表队列),backlog_queue(主要用于tcp中三 次握手成功的那些数据包,自己猜的),根据family、type参数,初始化sock的操作,比如对于family为inet类型的,type为 stream类型的,sock->proto初始化为tcp_prot.其中包括stream类型的协议sock操作对应的入口函数。
 
在一端对socket进行write的过程中,首先会把要write的字符串缓冲区整理成msghdr的数据结构形式(参见linux内核 2.4版源代码分析大全),然后调用sock_sendmsg把msghdr的数据传送至inet层,对于msghdr结构中数据区中的每个数据包,创建 sk_buff结构,填充数据,挂至发送队列。一层层往下层协议传递。一下每层协议不再对数据进行拷贝。而是对sk_buff结构进行操作。
 
inet套接字及以下层 数据存放在sk_buff这样的数据结构里:
 
路由:
    
    在linux的路由系统主要保存了三种与路由相关的数据,第一种是在物理上和本机相连接的主机地址信息表,第二种是保存了在网络访问中判断一个网络地址应该走什么路由的数据表;第三种是最新使用过的查询路由地址的缓存地址数据表。
    1.neighbour结构  neighbour_table{ }是一个包含和本机所连接的所有邻元素的信息的数据结构。该结构中有个元素是neighbour结构的数组,数组的每一个元素都是一个对应于邻机的 neighbour结构,系统中由于协议的不同,会有不同的判断邻居的方式,每种都有neighbour_table{}类型的实例,这些实例是通过 neighbour_table{}中的指针next串联起来的。在neighbour结构中,包含有与该邻居相连的网络接口设备net_device的 指针,网络接口的硬件地址,邻居的硬件地址,包含有neigh_ops{}指针,这些函数指针是直接用来连接传输数据的,包含有 queue_xmit(struct * sk_buff)函数入口地址,这个函数可能会调用硬件驱动程序的发送函数。
 
    2.FIB结构 在FIB中保存的是最重要的路由规则,通过对FIB数据的查找和换算,一定能够获得路由一个地址的方法。系统中路由一般采取的手段是:先到路由缓存中查找 表项,如果能够找到,直接对应的一项作为路由的规则;如果不能找到,那么就到FIB中根据规则换算传算出来,并且增加一项新的,在路由缓存中将项目添加进 去。
    3.route结构(即路由缓存中的结构)
 
 
 
数据链路层:
  
   net_device{}结构,对应于每一个网络接口设备。这个结构中包含很多可以直接获取网卡信息的函数和变量,同时包含很多对于网卡操作的函数,这些 直接指向该网卡驱动程序的许多函数入口,包括发送接收数据帧到缓冲区等。当这些完成后,比如数据接收到缓冲区后便由netif_rx(在net/core /dev.c各种设备驱动程序的上层框架程序)把它们组成sk_buff形式挂到系统接收的backlog队列然后交由上层网络协议处理。同样,对于上层 协议处理下来的那些sk_buff。便由dev_queue_xmit函数放入网络缓冲区,交给网卡驱动程序的发送程序处理。
 
   在系统中存在一张链表dev_base将系统中所有的net_device{}结构连在一起。对应于内核初始化而言,系统启动时便为每个所有可能支持的网 络接口设备申请了一个net_device{}空间并串连起来,然后对每个接点运行检测过程,如果检测成功,则在dev_base链表中保留这个接点,否 则删除。对应于模块加载来说,则是调用register_netdev()注册net_device,在这个函数中运行检测过程,如果成功,则加到 dev_base链表。否则就返回检测不到信息。删除同理,调用
unregister_netdev。
 
 
2.启动分析
 
    2.1 初始化进程 :start-kernel(main.c)---->do_basic_setup(main.c)---->sock_init(/net/socket.c)---->do_initcalls(main.c)
 
void __init sock_init(void)
{
 int i;
 
 printk(KERN_INFO "Linux NET4.0 for Linux 2.4\n");
 printk(KERN_INFO "Based upon Swansea University Computer Society NET3.039\n");
 
 /*
  * Initialize all address (protocol) families. 每一项表示的是针对一个地址族的操作集合,例如对于ipv4来说,在net/ipv4/af_inet.c文件中的函数 inet_proto_init()就调用sock_register()函数将inet_families_ops初始化到属于IPV4的 net_families数组中的一项。
  */
 
 for (i = 0; i < NPROTO; i++)
  net_families[i] = NULL;  
 
 /*
  * Initialize sock SLAB cache.初始化对于sock结构预留的内存的slab缓存。
  */
 
 sk_init();
 
#ifdef SLAB_SKB
 /*
  * Initialize skbuff SLAB cache 初始化对于skbuff结构的slab缓存。以后对于skbuff的申请可以通过函数kmem_cache_alloc()在这个缓存中申请空间。
  */
 skb_init();
#endif
 
 /*
  * Wan router layer.
  */
 
#ifdef CONFIG_WAN_ROUTER 
 wanrouter_init();
#endif
 
 /*
  * Initialize the protocols module. 向系统登记sock文件系统,并且将其安装到系统上来。
  */
 
 register_filesystem(&sock_fs_type);
 sock_mnt = kern_mount(&sock_fs_type);
 /* The real protocol initialization is performed when
  *  do_initcalls is run. 
  */

 /*
  * The netlink device handler may be needed early.
  */
 
#ifdef CONFIG_NET
 rtnetlink_init();
#endif
#ifdef CONFIG_NETLINK_DEV
 init_netlink();
#endif
#ifdef CONFIG_NETFILTER
 netfilter_init();
#endif
 
#ifdef CONFIG_BLUEZ
 bluez_init();
#endif
 
/*yfhuang ipsec*/
#ifdef CONFIG_IPSEC            
 pfkey_init();
#endif
/*yfhuang ipsec*/
}
 
 
    2.2 do_initcalls() 中做了其它的初始化,其中包括
 
                协议初始化,路由初始化,网络接口设备初始化
 
(例如inet_init函数以_init开头表示是系统初始化时做,函数结束后跟 module_init(inet_init),这是一个宏,在include/linux/init.c中定义,展开为 _initcall(inet_init),表示这个函数在do_initcalls被调用了)
 
    2.3 协议初始化
此处主要列举inet协议的初始化过程。
 
static int __init inet_init(void)
{
 struct sk_buff *dummy_skb;
 struct inet_protocol *p;
 struct inet_protosw *q;
 struct list_head *r;
 
 printk(KERN_INFO "NET4: Linux TCP/IP 1.0 for NET4.0\n");
 
 if (sizeof(struct inet_skb_parm) > sizeof(dummy_skb->cb)) {
  printk(KERN_CRIT "inet_proto_init: panic\n");
  return -EINVAL;
 }
 
 /*
  * Tell SOCKET that we are alive... 注册socket,告诉socket inet类型的地址族已经准备好了
  */
  
   (void) sock_register(&inet_family_ops);
 
 /*
  * Add all the protocols. 包括arp,ip、ICMP、UPD、tcp_v4、tcp、igmp的初始化,主要初始化各种协议对应的inode和socket变量。
 
其中arp_init完成系统中路由部分neighbour表的初始化
 
ip_init完成ip协议的初始化。在这两个函数中,都通过定义一个packet_type结构的变量将这种数据包对应的协议发送数据、允许发送设备都做初始化。

  */
 
 printk(KERN_INFO "IP Protocols: ");
 for (p = inet_protocol_base; p != NULL;) {
  struct inet_protocol *tmp = (struct inet_protocol *) p->next;
  inet_add_protocol(p);
  printk("%s%s",p->name,tmp?", ":"\n");
  p = tmp;
 }
 
 /* Register the socket-side information for inet_create. */
 for(r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
  INIT_LIST_HEAD(r);
 
 for(q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
  inet_register_protosw(q);
 
 /*
  * Set the ARP module up 
  */
 
 arp_init();
 
   /*
    * Set the IP module up
    */
 
 ip_init();
 
 tcp_v4_init(&inet_family_ops);
 
 /* Setup TCP slab cache for open requests. */
 tcp_init();

 /*
  * Set the ICMP layer up
  */
 
 icmp_init(&inet_family_ops);
 
 /* I wish inet_add_protocol had no constructor hook...
    I had to move IPIP from net/ipv4/protocol.c :-( --ANK
  */
#ifdef CONFIG_NET_IPIP
 ipip_init();
#endif
#ifdef CONFIG_NET_IPGRE
 ipgre_init();
#endif
 
 /*
  * Initialise the multicast router
  */
#if defined(CONFIG_IP_MROUTE)
 ip_mr_init();
#endif
 
 /*
  * Create all the /proc entries.
  */
#ifdef CONFIG_PROC_FS
 proc_net_create ("raw", 0, raw_get_info);
 proc_net_create ("netstat", 0, netstat_get_info);
 proc_net_create ("snmp", 0, snmp_get_info);
 proc_net_create ("sockstat", 0, afinet_get_info);
 proc_net_create ("tcp", 0, tcp_get_info);
 proc_net_create ("udp", 0, udp_get_info);
#endif  /* CONFIG_PROC_FS */
 
 ipfrag_init();
 
 return 0;
}   
module_init(inet_init);
                                                 
 
     2.4 路由初始化(包括neighbour表、FIB表、和路由缓存表的初始化工作)
 
            2.4.1 rtcache表 ip_rt_init()函数 在net/ipv4/ip_output中调用,net/ipv4/route.c中定义
 
            2.4.2 FIB初始化 在ip_rt_init()中调用 在net/ipv4/fib_front.c中定义
 
           2.4.3 neigbour表初始化  arp_init()函数中定义 
 
     2.5 网络接口设备初始化
             
             在系统中网络接口都是由一个dev_base链表进行管理的。通过内核的启动方式也是通过这个链表进行操作的。在系 统启动之初,将所有内核能够支持的网络接口都初始化成这个链表中的一个节点,并且每个节点都需要初始化出init函数指针,用来检测网络接口设备。然后, 系统遍历整个dev_base链表,对每个节点分别调用init函数指针,如果成功,证明网络接口设备可用,那么这个节点就可以进一步初始化,如果返回失 败,那么证明该网络设备不存在或是不可用,只能将该节点删除。启动结束之后,在dev_base中剩下的都是可以用的网络接口设备。
 
            2.5.1 do_initcalls---->net_dev_init()(net/core /dev.c)------>ethif_probe()(drivers/net/Space.c,在netdevice{}结构的init中调 用,这边ethif_probe是以太网卡针对的调用)
 
 
 
3.网络设备驱动程序(略)
        
 
4.网络连接
 
     4.1 连接的建立和关闭
 
            tcp连接建立的代码如下:
                    server=gethostbyname(SERVER_NAME);
                    sockfd=socket(AF_INET,SOCK_STREAM,0);
                    address.sin_family=AF_INET;
                    address.sin_port=htons(PORT_NUM);
                    memcpy(&address.sin_addr,server->h_addr,server->h_length);
                    connect(sockfd,&address,sizeof(address));
 
       连接的初始化与建立期间主要发生的事情如下:
                      
       1)sys_socket调用:调用socket_creat(),创建出一个满足传入参数family、type、和 protocol的socket,调用sock_map_fd()获取一个未被使用的文件描述符,并且申请并初始化对应的file{}结构。
        
       2)sock_creat():创建socket结构,针对每种不同的family的socket结构的初始化,就需要调用不同 的create函数来完成。对应于inet类型的地址来说,在网络协议初始化时调用sock_register()函数中完成注册的定义如下:
        struct net_proto_family inet_family_ops={
                PF_INET;
                inet_create
        };所以inet协议最后会调用inet_create函数。
        
       3)inet_create: 初始化sock的状态设置为SS_UNCONNECTED,申请一个新的sock结构,并且初始化socket的成员ops初始化为 inet_stream_ops,而sock的成员prot初始化为tcp_prot。然后调用sock_init_data,将该socket结构的变 量sock和sock类型的变量关联起来。
 
       4)在系统初始化完毕后便是进行connect的工作,系统调用connect将一个和socket结构关联的文件描述符和一个 sockaddr{}结构的地址对应的远程机器相关联,并且调用各个协议自己对应的connect连接函数。对应于tcp类型,则 sock->ops->connect便为inet_stream_connect。
 
 
       5)inet_stream_connect: 得到sk,sk=sock->sk,锁定sk,对自动获取sk的端口号存放在sk->num中,并且用htons()函数转换存放在sk-& gt;sport中。然后调用sk->prot->connect()函数指针,对tcp协议来说就是tcp_v4_connect()函 数。然后将sock->state状态字设置为SS_CONNECTING,等待后面一系列的处理完成之后,就将状态改成 SS_CONNECTTED。
 
       6) tcp_v4_connect():调用函数ip_route_connect(),寻找合适的路由存放在rt中。ip_route_connect找两 次,第一次找到下一跳的ip地址,在路由缓存或fib中找到,然后第二次找到下一跳的具体邻居,到neigh_table中找到。然后申请出tcp头的空 间存放在buff中。将sk中相关地址数据做一些针对路由的变动,并且初始化一个tcp连接的序列号,调用函数tcp_connect(),初始化tcp 头,并设置tcp处理需要的定时器。一次connect()建立的过程就结束了。
 
       连接的关闭主要如下:
 
        1)close: 一个socket文件描述符对应的file{}结构中,有一个file_operations{}结构的成员f_ops,它的初始化关闭函数为sock_close函数。
 
        2)sock_close:调用函数sock_release(),参数为一个socket{}结构的指针。
 
        3)sock_release:调用inet_release,并释放socket的指针和文件空间
 
        4)inet_release: 调用和该socket对应协议的关闭函数inet_release,如果是tcp协议,那么调用的是tcp_close;最后释放sk。
 
        4.2 数据发送流程图
 
 
 
各层主要函数以及位置功能说明:
        1)sock_write:初始化msghdr{}结构 net/socket.c
        2)sock_sendmsg:net/socket.c
        3)inet_sendmsg:net/ipv4/af_net.c
        4)tcp_sendmsg:申请sk_buff{}结构的空间,把msghdr{}结构中的数据填入sk_buff空间。net/ipv4/tcp.c
        5)tcp_send_skb:net/ipv4/tcp_output.c
        6)tcp_transmit_skb:net/ipv4/tcp_output.c
        7)ip_queue_xmit:net/ipv4/ip_output.c
        8)ip_queue_xmit2:net/ipv4/ip_output.c
        9)ip_output:net/ipv4/ip_output.c
        10)ip_finish_output:net/ipv4/ip_output.c
        11)ip_finish_output2:net/ipv4/ip_output.c
        12)neigh_resolve_output:net/core/neighbour.c
        13)dev_queue_xmit:net/core/dev.c
 
 
        4.3 数据接收流程图
 
各层主要函数以及位置功能说明:
 
        1)sock_read:初始化msghdr{}的结构类型变量msg,并且将需要接收的数据存放的地址传给msg.msg_iov->iov_base.      net/socket.c
        2)sock_recvmsg: 调用函数指针sock->ops->recvmsg()完成在INET Socket层的数据接收过程.其中sock->ops被初始化为inet_stream_ops,其成员recvmsg对应的函数实现为 inet_recvmsg()函数. net/socket.c
        3)sys_recv()/sys_recvfrom():分别对应着面向连接和面向无连接的协议两种情况. net/socket.c
        4)inet_recvmsg:调用sk->prot->recvmsg函数完成数据接收,这个函数对于tcp协议便是tcp_recvmsg net/ipv4/af_net.c
        5)tcp_recvmsg:从网络协议栈接收数据的动作,自上而下的触发动作一直到这个函数为止,出现了一次等待的过程.函 数tcp_recvmsg可能会被动地等待在sk的接收数据队列上,也就是说,系统中肯定有其他地方会去修改这个队列使得tcp_recvmsg可以进行 下去.入口参数sk是这个网络连接对应的sock{}指针,msg用于存放接收到的数据.接收数据的时候会去遍历接收队列中的数据,找到序列号合适的.
        但读取队列为空时tcp_recvmsg就会调用tcp_v4_do_rcv使用backlog队列填充接收队列.
        6)tcp_v4_rcv:tcp_v4_rcv被ip_local_deliver函数调用,是从IP层协议向INET Socket层提交的"数据到"请求,入口参数skb存放接收到的数据,len是接收的数据的长度,这个函数首先移动skb->data指针,让它 指向tcp头,然后更新tcp层的一些数据统计,然后进行tcp的一些值的校验.再从INET Socket层中已经建立的sock{}结构变量中查找正在等待当前到达数据的哪一项.可能这个sock{}结构已经建立,或者还处于监听端口、等待数据 连接的状态。返回的sock结构指针存放在sk中。然后根据其他进程对sk的操作情况,将skb发送到合适的位置.调用如下:
 
        TCP包接收器(tcp_v4_rcv)将TCP包投递到目的套接字进行接收处理. 当套接字正被用户锁定,TCP包将暂时排入该套接字的后备队列(sk_add_backlog).这时如果某一用户线程企图锁定该套接字 (lock_sock),该线程被排入套接字的后备处理等待队列(sk->lock.wq).当用户释放上锁的套接字时 (release_sock,在tcp_recvmsg中调用),后备队列中的TCP包被立即注入TCP包处理器(tcp_v4_do_rcv)进行处 理,然后唤醒等待队列中最先的一个用户来获得其锁定权. 如果套接字未被上锁,当用户正在读取该套接字时, TCP包将被排入套接字的预备队列(tcp_prequeue),将其传递到该用户线程上下文中进行处理.如果添加到sk->prequeue不成 功,便可以添加到 sk->receive_queue队列中(用户线程可以登记到预备队列,当预备队列中出现第一个包时就唤醒等待线程.)   /net/tcp_ipv4.c
 
        7)ip_rcv、ip_rcv_finish:从以太网接收数据,放到skb里,作ip层的一些数据及选项检查,调用 ip_route_input()做路由处理,判断是进行ip转发还是将数据传递到高一层的协议.调用skb->dst->input函数指 针,这个指针的实现可能有多种情况,如果路由得到的结果说明这个数据包应该转发到其他主机,这里的input便是ip_forward;如果数据包是给本 机的,那么input指针初始化为ip_local_deliver函数./net/ipv4/ip_input.c
 
        8)ip_local_deliver、ip_local_deliver_finish:入口参数skb存放需要传送到上层 协议的数据,从ip头中获取是否已经分拆的信息,如果已经分拆,则调用函数ip_defrag将数据包重组。然后通过调用 ip_prot->handler指针调用tcp_v4_rcv(tcp)。ip_prot是inet_protocol结构指针,是用来ip层登 记协议的,比如由udp,tcp,icmp等协议。 /net/ipv4/ip_input.c
 

 

 

XMPP协议介绍

网上摘录

1、什么是XMPP ?
XMPP的前身是Jabber,一个开源形式组织产生的网络即时通信协议。XMPP目前被IETF国际标准组织完成了标准化工作。标准化的核心结果分为两部分;
核心的XML流传输协议
基于XML流传输的即时通讯扩展应用
XMPP的核心XML流传输协议的定义使得XMPP能够在一个比以往网络通信协议更规范的平台上。借助于XML易于解析和阅读的特性,使得XMPP的协议能够非常漂亮。
XMPP的即时通讯扩展应用部分是根据IETF在这之前对即时通讯的一个抽象定义的,与其他业已得到广泛使用的即时通讯协议,诸如AIM,QQ等有功能完整,完善等先进性。

2、XMPP的基本网络结构是怎样的?
XMPP中定义了三个角色,客户端,服务器,网关。通信能够在这三者的任意两个之间双向发生。服务器同时承担了客户端信息记录,连接管理和信息的路由功 能。网关承担着与异构即时通信系统的互联互通,异构系统可以包括SMS(短信),MSN,ICQ等。基本的网络形式是单客户端通过TCP/IP连接到单服 务器,然后在之上传输XML。

3、XMPP通过TCP传什么了?
传输的是与即时通讯相关的指令。在以前这些命令要么用2进制的形式发送(比如QQ),要么用纯文本指令加空格加参数加换行苻的方式发送(比如MSN)。而 XMPP传输的即时通讯指令的逻辑与以往相仿,只是协议的形式变成了XML格式的纯文本。这不但使得解析容易了,人也容易阅读了,方便了开发和查错。而 XMPP的核心部分就是一个在网络上分片断发送XML的流协议。这个流协议是XMPP的即时通讯指令的传递基础,也是一个非常重要的可以被进一步利用的网 络基础协议。所以可以说,XMPP用TCP传的是XML流。

 

--------------------------------------------------------------------------------------------

来源: 云风的BLOG blog.codingnow.com/2008/11/xmpp.html

我最常用的 IM 是 google talk ,本身就实现了标准的 XMPP Client 和 XMPP Server 协议;而我们的 网易 popo 也实现了 XMPP 的 s2s 网关。我想研究一下 XMPP 是个不错的选择。

花了一整天的时间,把 XMPP 核心协议 仔细通读了一遍,收获颇多。原来以为 XMPP 是个可怕的巨无霸。我对 XML 原本也没有太多好感。最后,看法有所改变。

其实,XMPP 仅仅是定义了一个网络服务间相互通讯的协议。它已经把服务间需要关心的东西减少到了最少。具体的应用每家服务提供商可以随意扩展。popo 在制作新版本时,我曾多次建议采用已有的标准协议,再此基础上开发自己的东西。当时或许大家都认为标准协议容易促手促脚,我当时也没啥研究,没有多言。今 天看来,我更觉得这是一个决策失误。本来我们有一个很好的机会,利用 popo 联系起网易的各种服务,现在这条路将走的更为艰辛。其实,XMPP 定义的东西,即使自己去设计也会定义出类似的一套来。而把各种网络服务互通本该是发展的重点,为 IM Client 增添专有花哨的特性就有些舍本逐末了。更为恼火的是,popo 到现在也没有一个很好的非 Windows 平台解决方案。怎能让诸多把握着互联网上部分话语权的技术人士接受?(或者,同在杭州的 IT 圈子,popo 的开发人员是不是应该看看支付宝的同行们做了些什么?)

谈谈我对 XMPP 的粗浅理解。这些仅仅建立在我对 RFC3920 的一天阅读的基础上,难免会有错误,不足以做技术参考。

XMPP 抽象出一个在互联网上唯一的对象实体,用 JID 来表达。通常一个 JID 由三部分组成,node@domain/resource 。比 email 的表达形式多了一个 /resource 。这是因为 email 地址本身虽然可以表达一个实体,都是往往不够表达这个实体下的具体服务。就好比一个 ip 地址可以表示一台机器,但是我们还需要 port 号来表达这台机器具体提供的服务一样。

用过 gtalk 的人应该很喜欢 gtalk 可以在不同的地方同时登陆这个不错的特性。用过以后,才能体会,无论是 qq 还是 msn 还是 popo ,只允许一个登陆是多么愚蠢的设定。gtalk 其实遵守了标准的 XMPP 协议,它用来区别一个帐号(一般是一个 gmail 邮件地址)的多处登陆,正是利用了不同的 resource 标识。

XMPP 规范的最重要的一条通信协议就是,如何把消息从一个 JID 发送到另一个 JID (message)。这有点像 email 协议,但不同的是,它强调了实时性和安全性(虽然不是必须的)。因为 JID 可以在不同的 domain 下,这就需要 domain 间相互协作。对于 IM 网络来说(XMPP 远不只用于 IM 协议),就是不同的 IM 服务间互通。

对于 domain 下的 xmpp 服务的发现,利用了 DNS 协议的一些功能。xmpp 的 s2s 服务提供位置,放在了 DNS 的 SRV 记录里。你可以用 nslookup 做个试验,启动 nslookup ,输入 set type=SRV

然后查询 _xmpp-server._tcp.gmail.com 你会发现 gmail.com 的 xmpp s2s 服务地址已经端口号 5269 。同样,也可以查询 _xmpp-server._tcp.163.com_xmpp-server._tcp.popo.163.com 查到网易 popo 的 xmpp 中转服务器地址。

btw, 查询 _xmpp-client._tcp.gmail.com 可以查到 gtalk 的 client 登陆地址,而网易 popo 则没有提供 xmpp client 登陆点。

按 RFC3920 所述,在 xmpp server 互联的时候,会优先尝试获取 domain 的 SRV 记录,如果失败就直接去连默认的 6259 端口。然后就可以开始握手协议。

xmpp 比较强调 s2s 的安全性,所以推荐的握手都是建立在 TLS 层之上,使用 SASL 认证。TLS 层需要服务器有一个数字证书,为了安全可信,建议是找个根证书签名。不过自己签名也行,只需要服务器缓存证书即可。握手过程在 RFC3920 中描述的非常细致,可以按照其编码,问题不大。需要注意的是,这里的 XML 流格式要求很精确,不允许传输多余的东西。我一度认为采用 XML 会导致协议的实现上非常臃肿,其实不然。采用 XML 只是一个表象,适合人阅读和调错而已。RFC 中特别要求不去实现 XML 中的某某特性就是一例。我们不应该为了 XML 而去 XML 。

其实 XMPP 的 c2s 和 s2s 并无太大区别,s2s 做的人手我想是因为开源项目和开源库比较少吧。而开源的 client 实现则是一大堆。c2s 和 s2s 的通讯都是基于那几条协议而已,s2s 的实现难点在于握手比较复杂(其实 c2s 也一样,只是很多库帮你做好了)。c2s 是共享一个 tcp 连接做双向通讯;而 s2s 则是用两条 TCP 连接。两条连接也一定程度上避免了 s2s 的欺骗,当然真正的安全来至于 TLS 和 SASL 的保障。DNS 毕竟是一个很脆弱的东西。

除了点对点消息外,XMPP 定义了消息的组播。也就是一个 JID 可以以自己的名义发布消息 (presence)。而服务器来决定该发给谁。发送目标是由订阅消息决定的。其它多个 JID 可以订阅某个 JID 的消息。对于 IM 来说,最常用的就是上线下线等状态变化消息了。

第三条即是对某个 JID 的状态进行设置和获取 (iq)。于 IM 应用来说,设置签名,昵称,状态等都依赖于它。

XMPP 的核心协议无非规定了以上三种通讯协议,此外规范了服务器间互连的握手认证方案。然后给出了一些错误信息的表述方法。稍微了解过之后,很容易编写。如果希 望重造轮子的话,对于 C 语言开发者来说,最繁琐的可能是 XML 的解析于生成。我自己稍微考察了一下,有个叫 LoudMouth 的库还不错。

如果实现 s2s 网关的话,有些细节做起来可能很麻烦,比如查询 DNS 的 SRV 记录。这个在 jabberd 1.x 里其实有独立的模块实现好了,取来用即可 (见 dnsrv) 。而 TLS SASL 层的实现则早就有现成的开源库了。

实现一个 jabber server 或许比你想象的还简单。in.jabberd 居然只用 600 多行 C 代码就从零实现了一个 jabber 服务器。当然功能非常的简陋了。

                     

 

-------------------------------------------------------------------------------------------------

规范:

核心的XML流传输协议 rfc3920
基于XML流传输的即时通讯扩展应用 rfc3921

英文不好的人还可以查看wiki.jabbercn.org/index.php 这是xmpp的中文网站,翻译了主要的xmpp协议。

另外,还有一篇tech.163.com/07/0627/16/3I0Q86FG000917GT.html

 

 

命名的强制类型转换

《C++ Primer》

 

The general form for the named cast notation is the following:

命名的强制类型转换符号的一般形式如下:

     cast-name<type>(expression);

cast-name may be one of static_cast, const_cast, dynamic_cast, or reinterpret_cast. type is the target type of the conversion, and expression is the value to be cast. The type of cast determines the specific kind of conversion that is performed on the expression.

其中 cast-namestatic_castdynamic_castconst_castreinterpret_cast 之一,type 为转换的目标类型,而 expression 则是被强制转换的值。强制转换的类型指定了在 expression 上执行某种特定类型的转换。

dynamic_cast

A dynamic_cast supports the run-time identification of objects addressed either by a pointer or reference. We cover dynamic_cast in Section 18.2 (p. 772).

dynamic_cast 支持运行时识别指针或引用所指向的对象。对 dynamic_cast 的讨论将在第 18.2 节中进行。

const_cast

A const_cast, as its name implies, casts away the constness of its expression. For example, we might have a function named string_copy that we are certain reads, but does not write, its single parameter of type char*. If we have access to the code, the best alternative would be to correct it to take a const char*. If that is not possible, we could call string_copy on a const value using a const_cast:

const_cast ,顾名思义,将转换掉表达式的 const 性质。例如,假设有函数 string_copy,只有唯一的参数,为 char* 类型,我们对该函数只读不写。在访问该函数时,最好的选择是修改它让它接受 const char* 类型的参数。如果不行,可通过 const_cast 用一个 const 值调用 string_copy 函数:

     const char *pc_str;
     char *pc = string_copy(const_cast<char*>(pc_str));

Only a const_cast can be used to cast away constness. Using any of the other three forms of cast in this case would result in a compile-time error. Similarly, it is a compile-time error to use the const_cast notation to perform any type conversion other than adding or removing const.

只有使用 const_cast 才能将 const 性质转换掉。在这种情况下,试图使用其他三种形式的强制转换都会导致编译时的错误。类似地,除了添加或删除 const 特性,用 const_cast 符来执行其他任何类型转换,都会引起编译错误。

static_cast

Any type conversion that the compiler performs implicitly can be explicitly requested by using a static_cast:

编译器隐式执行的任何类型转换都可以由 static_cast 显式完成:

     double d = 97.0;
     // cast specified to indicate that the conversion is intentional
     char ch = static_cast<char>(d);

Such casts are useful when assigning a larger arithmetic type to a smaller type. The cast informs both the reader of the program and the compiler that we are aware of and are not concerned about the potential loss of precision. Compilers often generate a warning for assignments of a larger arithmetic type to a smaller type. When we provide the explicit cast, the warning message is turned off.

当需要将一个较大的算术类型赋值给较小的类型时,使用强制转换 非常有用。此时,强制类型转换告诉程序的读者和编译器:我们知道并且不关心潜在的精度损失。对于从一个较大的算术类型到一个较小类型的赋值,编译器通常会 产生警告。当我们显式地提供强制类型转换时,警告信息就会被关闭。

A static_cast is also useful to perform a conversion that the compiler will not generate automatically. For example, we can use a static_cast to retrieve a pointer value that was stored in a void* pointer (Section 4.2.2, p. 119):

如果编译器不提供自动转换,使用 static_cast 来执行类型转换也是很有用的。例如,下面的程序使用 static_cast 找回存放在 void* 指针中的值(第 4.2.2 节):

     void* p = &d; // ok: address of any data object can be stored in a void*
     // ok: converts void* back to the original pointer type
     double *dp = static_cast<double*>(p);

When we store a pointer in a void* and then use a static_cast to cast the pointer back to its original type, we are guaranteed that the pointer value is preserved. That is, the result of the cast will be equal to the original address value.

可通过 static_cast 将存放在 void* 中的指针值强制转换为原来的指针类型,此时我们应确保保持指针值。也就是说,强制转换的结果应与原来的地址值相等。

reinterpret_cast

A reinterpret_cast generally performs a low-level reinterpretation of the bit pattern of its operands.

reinterpret_cast 通常为操作数的位模式提供较低层次的重新解释。

A reinterpret_cast is inherently machine-dependent. Safely using reinterpret_cast requires completely understanding the types involved as well as the details of how the compiler implements the cast.

reinterpret_cast 本质上依赖于机器。为了安全地使用 reinterpret_cast,要求程序员完全理解所涉及的数据类型,以及编译器实现强制类型转换的细节。

As an example, in the following cast

例如,对于下面的强制转换:

     int *ip;
     char *pc = reinterpret_cast<char*>(ip);

the programmer must never forget that the actual object addressed by pc is an int, not a character array. Any use of pc that assumes it's an ordinary character pointer is likely to fail at run time in interesting ways. For example, using it to initialize a string object such as

程序员必须永远记得 pc 所指向的真实对象其实是 int 型,而并非字符数组。任何假设 pc 是普通字符指针的应用,都有可能带来有趣的运行时错误。例如,下面语句用 pc 来初始化一个 string 对象:

     string str(pc);

is likely to result in bizarre run-time behavior.

它可能会引起运行时的怪异行为。

The use of pc to initialize str is a good example of why explicit casts are dangerous. The problem is that types are changed, yet there are no warnings or errors from the compiler. When we initialized pc with the address of an int, there is no error or warning from the compiler because we explicitly said the conversion was okay. Any subsequent use of pc will assume that the value it holds is a char*. The compiler has no way of knowing that it actually holds a pointer to an int. Thus, the initialization of str with pc is absolutely correctalbeit in this case meaningless or worse! Tracking down the cause of this sort of problem can prove extremely difficult, especially if the cast of ip to pc occurs in a file separate from the one in which pc is used to initialize a string.

pc 初始化 str 这个例子很好地说明了显式强制转换是多么的危险。问题源于类型已经改变时编译器没有提供任何警告或错误提示。当我们用 int 型地址初始化 pc 时,由于显式地声明了这样的转换是正确的,因此编译器不提供任何错误或警告信息。后面对 pc 的使用都假设它存放的是 char* 型对象的地址,编译器确实无法知道 pc 实际上是指向 int 型对象的指针。因此用 pc 初始化 str 是完全正确的——虽然实际上是无意义的或是错误的。查找这类问题的原因相当困难,特别是如果 ippc 的强制转换和使用 pc 初始化 string 对象这两个应用发生在不同文件中的时候。

Advice: Avoid Casts

建议:避免使用强制类型转换

By using a cast, the programmer turns off or dampens normal type-checking (Section 2.3, p. 44). We strongly recommend that programmers avoid casts and believe that most well-formed C++ programs can be written without relying on casts.

强制类型转换关闭或挂起了正常的类型检查(第 2.3 节)。强烈建议程序员避免使用强制类型转换,不依赖强制类型转换也能写出很好的 C++ 程序。

This advice is particularly important regarding use of reinterpret_casts. Such casts are always hazardous. Similarly, use of const_cast almost always indicates a design flaw. Properly designed systems should not need to cast away const. The other casts, static_cast and dynamic_cast, have their uses but should be needed infrequently. Every time you write a cast, you should think hard about whether you can achieve the same result in a different way. If the cast is unavoidable, errors can be mitigated by limiting the scope in which the cast value is used and by documenting all assumptions about the types involved.

这个建议在如何看待 reinterpret_cast 的使用时非常重要。此类强制转换总是非常危险的。相似地,使用 const_cast 也总是预示着设计缺陷。设计合理的系统应不需要使用强制转换抛弃 const 特性。其他的强制转换,如 static_castdynamic_cast,各有各的用途,但都不应频繁使用。每次使用强制转换前,程序员应该仔细考虑是否还有其他不同的方法可以达到同一目的。如果非强制转换不可,则应限制强制转换值的作用域,并且记录所有假定涉及的类型,这样能减少错误发生的机会。

Advice: Use the C++ Versions of C Library Headers

 

Advice: Use the C++ Versions of C Library Headers

建议:采用 C 标准库头文件的 C++ 版本

In addition to facilities defined specifically for C++, the C++ library incorporates the C library. The cctype header makes available the C library functions defined in the C header file named ctype.h.

C++ 标准库除了定义了一些选定于 C++ 的设施外,还包括 C 标准库。C++ 中的头文件 cctype 其实就是利用了 C 标准库函数,这些库函数就定义在 C 标准库的 ctype.h 头文件中。

The standard C headers names use the form name.h. The C++ versions of these headers are named cnamethe C++ versions remove the .h suffix and precede the name by the letter c. Thec indicates that the header originally comes from the C library. Hence, cctype has the same contents as ctype.h, but in a form that is appropriate for C++ programs. In particular, the names defined in the cname headers are defined inside the std namespace, whereas those defined in the .h versions are not.

C 标准库头文件命名形式为 name 而 C++ 版本则命名为 cname ,少了后缀,.h 而在头文件名前加了 c 表示这个头文件源自 C 标准库。因此,cctype 与 ctype.h 文件的内容是一样的,只是采用了更适合 C++程序的形式。特别地,cname 头文件中定义的名字都定义在命名空间 std 内,而 .h 版本中的名字却不是这样。

Ordinarily, C++ programs should use the cname versions of headers and not the name.h versions. That way names from the standard library are consistently found in the std namespace. Using the .h headers puts the burden on the programmer to remember which library names are inherited from C and which are unique to C++.

通常,C++ 程序中应采用 cname 这种头文件的版本,而不采用 name.h 版本,这样,标准库中的名字在命名空间 std 中保持一致。使用 .h 版本会给程序员带来负担,因为他们必须记得哪些标准库名字是从 C 继承来的,而哪些是 C++ 所特有的。

 

例如:

目录/usr/include/c++/4.3内包含若干C标准库的替代,如cstring(对应于string.h)

/** @file cstring
 *  This is a Standard C++ Library file.  You should @c #include this file
 *  in your programs, rather than any of the "*.h" implementation files.
 *
 *  This is the C++ version of the Standard C Library header @c string.h,
 *  and its contents are (mostly) the same as that header, but are all
 *  contained in the namespace @c std (except for names which are defined
 *  as macros in C).
 */

 

 

头文件用于声明而不是用于定义

《C++ Primer, 4th》

----------------------------------------------------------------------

Headers Are for Declarations, Not Definitions
头文件用于声明而不是用于定义

When designing a header it is essential to remember the difference between definitions, which may only occur once, and declarations, which may occur multiple times (Section 2.3.5, p. 52). The following statements are definitions and therefore should not appear in a header:

当设计头文件时,记住定义和声明的区别是很重要的。定义只可以出现一次,而声明则可以出现多次(第 2.3.5 节)。下列语句是一些定义,所以不应该放在头文件里:

     extern int ival = 10;      // initializer, so it's a definition
     double fica_rate;          // no extern, so it's a definition

Although ival is declared extern, it has an initializer, which means this statement is a definition. Similarly, the declaration of fica_rate, although it does not have an initializer, is a definition because the extern keyword is absent. Including either of these definitions in two or more files of the same program will result in a linker error complaining about multiple definitions.

虽然 ival 声明为 extern,但是它有初始化式,代表这条语句是一个定义。类似地,fica_rate 的声明虽然没有初始化式,但也是一个定义,因为没有关键字 extern同一个程序中有两个以上文件含有上述任一个定义都会导致多重定义链接错误

Because headers are included in multiple source files, they should not contain definitions of variables or functions.

因为头文件包含在多个源文件中,所以不应该含有变量或函数的定义。

There are three exceptions to the rule that headers should not contain definitions: classes, const objects whose value is known at compile time, and inline functions (Section 7.6 (p. 256) covers inline functions) are all defined in headers. These entities may be defined in more than one source file as long as the definitions in each file are exactly the same.

对于头文件不应该含有定义这一规则,有三个例外。头文件可以定义类、值在编译时就已知道的 const 对象和 inline 函数(第 7.6 节介绍 inline 函数)。这些实体可在多个源文件中定义,只要每个源文件中的定义是相同的。

These entities are defined in headers because the compiler needs their definitions (not just declarations) to generate code. For example, to generate code that defines or uses objects of a class type, the compiler needs to know what data members make up that type. It also needs to know what operations can be performed on these objects. The class definition provides the needed information. That const objects are defined in a header may require a bit more explanation.

在头文件中定义这些实体,是因为编译器需要它们的定义(不只是声明)来产生代码。例如:为了产生能定义或使用类的对象的代码,编译器需要知道组成该类型的数据成员。同样还需要知道能够在这些对象上执行的操作。类定义提供所需要的信息。在头文件中定义 const 对象则需要更多的解释。

Some const Objects Are Defined in Headers
一些 const 对象定义在头文件中

Recall that by default a const variable (Section 2.4, p. 57) is local to the file in which it is defined. As we shall now see, the reason for this default is to allow const variables to be defined in header files.

回想一下,const 变量(第 2.4 节)默认时是定义该变量的文件的局部变量。正如我们现在所看到的,这样设置默认情况的原因在于允许 const 变量定义在头文件中。

In C++ there are places where constant expression (Section 2.7, p. 62) is required. For example, the initializer of an enumerator must be a constant expression. We'll see other cases that require constant expressions in later chapters.

在 C++ 中,有些地方需要放置常量表达式(第 2.7 节)。例如,枚举成员的初始化式必须是常量表达式。在以后的章节中将会看到其他需要常量表达式的例子。

Generally speaking, a constant expression is an expression that the compiler can evaluate at compile-time. A const variable of integral type may be a constant expression when it is itself initialized from a constant expression. However, for the const to be a constant expression, the initializer must be visible to the compiler. To allow multiple files to use the same constant value, the const and its initializer must be visible in each file. To make the initializer visible, we normally define such consts inside a header file. That way the compiler can see the initializer whenever the const is used.

一般来说,常量表达式是编译器在编译时就能够计算出结果的表达式。当 const 整型变量通过常量表达式自我初始化时,这个 const 整型变量就可能是常量表达式。而 const 变量要成为常量表达式,初始化式必须为编译器可见。为了能够让多个文件使用相同的常量值,const 变量和它的初始化式必须是每个文件都可见的。而要使初始化式可见,一般都把这样的 const 变量定义在头文件中。那样的话,无论该 const 变量何时使用,编译器都能够看见其初始化式。

However, there can be only one definition (Section 2.3.5, p. 52) for any variable in a C++ program. A definition allocates storage; all uses of the variable must refer to the same storage. Because, by default, const objects are local to the file in which they are defined, it is legal to put their definition in a header file.

但是,C++ 中的任何变量都只能定义一次(第 2.3.5 节)。定义会分配存储空间,而所有对该变量的使用都关联到同一存储空间。因为 const 对象默认为定义它的文件的局部变量,所以把它们的定义放在头文件中是合法的。

There is one important implication of this behavior. When we define a const in a header file, every source file that includes that header has its own const variable with the same name and value.

这种行为有一个很重要的含义:当我们在头文件中定义了 const 变量后,每个包含该头文件的源文件都有了自己的 const 变量,其名称和值都一样。

When the const is initialized by a constant expression, then we are guaranteed that all the variables will have the same value. Moreover, in practice, most compilers will replace any use of such const variables by their corresponding constant expression at compile time. So, in practice, there won't be any storage used to hold const variables that are initialized by constant expressions.

当该 const 变量是用常量表达式初始化时,可以保证所有的变量都有相同的值。但是在实践中,大部分的编译器在编译时都会用相应的常量表达式替换这些 const 变量的任何使用。所以,在实践中不会有任何存储空间用于存储用常量表达式初始化的 const 变量。

When a const is initialized by a value that is not a constant expression, then it should not be defined in header file. Instead, as with any other variable, the const should be defined and initialized in a source file. An extern declaration for that const should be made in the header, enabling multiple files to share that variable.

如果 const 变量不是用常量表达式初始化,那么它就不应该在头文件中定义。相反,和其他的变量一样,该 const 变量应该在一个源文件中定义并初始化。应在头文件中为它添加 extern 声明,以使其能被多个文件共享。

 

 

终于见到OSD-Lyrics了

真是惭愧,直到今天才终于用上OSD-Lyrics

效果真是非常地赞,嘻嘻

自己只是写了个简单的搜狗歌词下载,连另一个歌词下载引擎:千千,都是tigersoldier给写的,艾...

 

.........................

 

要怎么办呢...

 

 

 

理解__getattr__, __setattr__

 

The following methods can be defined to customize the meaning of attribute access (use of, assignment to, or deletion of x.name) for class instances.

object.__getattr__(self, name)

Called when an attribute lookup has not found the attribute in the usual places (i.e. it is not an instance attribute nor is it found in the class tree for self). name is the attribute name. This method should return the (computed) attribute value or raise an AttributeError exception.

Note that if the attribute is found through the normal mechanism, __getattr__() is not called. (This is an intentional asymmetry between __getattr__() and __setattr__().) This is done both for efficiency reasons and because otherwise __getattr__() would have no way to access other attributes of the instance. Note that at least for instance variables, you can fake total control by not inserting any values in the instance attribute dictionary (but instead inserting them in another object). See the __getattribute__() method below for a way to actually get total control in new-style classes.

object.__setattr__(self, name, value)

Called when an attribute assignment is attempted. This is called instead of the normal mechanism (i.e. store the value in the instance dictionary). name is the attribute name, value is the value to be assigned to it.

If __setattr__() wants to assign to an instance attribute, it should not simply execute self.name = value — this would cause a recursive call to itself. Instead, it should insert the value in the dictionary of instance attributes, e.g., self.__dict__[name] = value. For new-style classes, rather than accessing the instance dictionary, it should call the base class method with the same name, for example, object.__setattr__(self, name, value).

 

例子(Python Recipe6.1):

 

#!/usr/bin/python

class Temperature(object):
    coefficients = {'c': (1.0, 0.0, -273.15), 'f': (1.8, -273.15, 32.0),
                    'r': (1.8, 0.0, 0.0)}
    def __init__(self, **kwargs):
        # default to absolute (Kelvin) 0, but allow one named argument,
        # with name being k, c, f or r, to use any of the scales
        try:
            name, value = kwargs.popitem()
        except KeyError:
            # no arguments, so default to k=0
            name, value = 'k', 0
        # error if there are more arguments, or the arg's name is unknown
        if kwargs or name not in 'kcfr':
            kwargs[name] = value             # put it back for diagnosis
            raise TypeError, 'invalid arguments %r' % kwargs
        setattr(self, name, float(value))
    def __getattr__(self, name):
        # maps getting of c, f, r, to computation from k
        print '__getattr__: %s' % str(name)
        try:
                        eq = self.coefficients[name]
        except KeyError:
            # unknown name, give error message
            raise AttributeError, name
        return (self.k + eq[1]) * eq[0] + eq[2]
    def __setattr__(self, name, value):
        # maps settings of k, c, f, r, to setting of k; forbids others
        print '__setattr__: name = %s, value = %s' % (str(name), str(value))
        if name in self.coefficients:
            # name is c, f or r -- compute and set k
            eq = self.coefficients[name]
            self.k = (value - eq[2]) / eq[0] - eq[1]
        elif name == 'k':
            # name is k, just set it
            object.__setattr__(self, name, value)
        else:
            # unknown name, give error message
            raise AttributeError, name
    def __str__(self):
        # readable, concise representation as string
        return "%s K" % self.k
    def __repr__(self):
        # detailed, precise representation as string
        return "Temperature(k=%r)" % self.k

if __name__ == '__main__':
        t = Temperature(f=70)
        print t.__dict__
        print t.c
        print t.f
        print t.k

输出为:

simplyzhao@simplyzhao-laptop:~/code/python$ ./recipe2ed_6_1.py
__setattr__: name = f, value = 70.0
__setattr__: name = k, value = 294.261111111
{'k': 294.26111111111106}
__getattr__: c
21.1111111111
__getattr__: f
70.0
294.261111111


 

 

 

 

"yield" in recursive generator

来源: www.javaeye.com/topic/338111

 

二叉树的中序遍历,递归的genertor(想当然的解法,和遍历二叉树的算法完全一样)

  1. class tree_t:  
  2.     def __init__(self, data, left = None, right = None):  
  3.         self.left = left  
  4.         self.data = data  
  5.         self.right = right  
  6.   
  7. left = tree_t("left")  
  8. right = tree_t("right")  
  9. root = tree_t("root", left, right)  
  10.   
  11. def infix_iterate(tree):  
  12.     if tree.left:  
  13.         infix_iterate(tree.left)  
  14.     yield tree  
  15.     if tree.right:  
  16.         infix_iterate(tree.right)  
  17.   
  18. for tree in infix_iterate(root):  
  19.     print tree.data



二叉树的中序遍历,递归的genertor(正确的解法)

  1. class tree_t:  
  2.     def __init__(self, data, left = None, right = None):  
  3.         self.left = left  
  4.         self.data = data  
  5.         self.right = right  
  6.   
  7. left = tree_t("left")  
  8. right = tree_t("right")  
  9. root = tree_t("root", left, right)  
  10.   
  11. def infix_iterate(tree):  
  12.     if tree.left:  
  13.         for child in infix_iterate(tree.left):  
  14.             yield child  
  15.     yield tree  
  16.     if tree.right:  
  17.         for child in infix_iterate(tree.right):  
  18.             yield child  
  19.   
  20. for tree in infix_iterate(root):  
  21.     print tree.data



第1个例子没有任何问题,很直观。但在第2个例子时,发现程序只输出一行代码root,仔细看了下参考资料,才注意到正确的递归调用和我想当然的不一样:

  1. 想当然的写法:  
  2. if tree.left:  
  3.     infix_iterate(tree.left)  
  4.   
  5. 正确的写法:  
  6. if tree.left:  
  7.     for child in infix_iterate(tree.left):  
  8.         yield child  


不知道为什么python的递归generator没有采用例子2中那样直观的写法?是实现困难的原因吗?

 

 

Python里带有yield表达式(*)的函数是generator function,而调用一个generator function总是返回一个iterator;iterator不调用其next()方法是不会执行的;iterator总是通过yield表达式来提供next()方法的返回值,自然,谁调用next()方法谁就会获得返回值。

在 楼主的第二种写法里,那两个if语句里的调用并不是“递归调用”,而只是单纯的获得了两个iterator,并且没有让它们执行;generator function要真的“递归”起来,就得像第三种写法那样,在generator function的函数体里获得了一个iterator之后,遍历这个iterator里的值并逐个yield返回出去。for...in语句所做的正好就是拿到一个iterable然后逐个遍历里面的值,所以正好适合用在这个场景。

要是直接带第二种写法上加点东西就能看到得到的iterator里发生的事:

Python代码
  1. class tree_t:  
  2.     def __init__(self, data, left = None, right = None):  
  3.         self.left = left  
  4.         self.data = data  
  5.         self.right = right  
  6.   
  7. left = tree_t("left")  
  8. right = tree_t("right")  
  9. root = tree_t("root", left, right)  
  10.   
  11. def infix_iterate(tree):  
  12.     if tree.left:  
  13.         print (infix_iterate(tree.left).next().data)  
  14.     yield tree  
  15.     if tree.right:  
  16.         print (infix_iterate(tree.right).next().data)  
  17.   
  18. for tree in infix_iterate(root):  
  19.     print tree.data  


当然这样不能用于遍历就是了,只是用来说明generator function里调用了“自己”得到的是什么。

 

 

Rate your life

This Is My Life, Rated
Life: 7.1
Mind: 7
Body: 6.9
Spirit: 5.9
Friends/Family: 6.2
Love: 5.4
Finance: 5.2
Take the Rate My Life Quiz

 

看到别人都在测试当前的生活状态,我也赶紧去测试一下,嘻嘻,结果如上图

整体成绩还不错

暑假快乐,目标是在家吃胖点...