做六天回民

我像以往一样上了食堂三楼,这边虽然是侧门,但标注着“紧急出口”的字样,很少会出现“拒客”的情况,显然今天和三年来都不一样了。突然想到门口那张告示,字比往常写的秀气得多,也就没去细看。这下赶忙跑到门口,略览了一下:看到“三楼…1至6…走南门”等字样,心想原来食堂也知道我7号才回家,说什么也要开放到6号再说。满心欢喜地从另一边跑上去,远远又看见大门紧闭…旁边的清真坐了熙攘坐了几个人,无奈去了回民店。

在西大以来就很少去这里的清真,因为和十几年来的印象实在差得很远,简直就是其它店的缩略版。饭毕不得不重新去“审阅”门外那张通告了。

原文居然是“三楼值班食堂1-6日 清真餐厅 请走南门”。靠,好不容易在学校过几天清净日子,居然要被逼成回民了!

Comments

大切にするよ

突然想起09剧场版8月可能会出dvd,于是翻了翻dygod,还未发布,不过好在迅雷上片源还算是比较充足. 剧情么基本上是近30年前的翻版,记忆中似乎没多大区别.非要挑出”不同”的话可能只有本作的主题歌《大切にするよ》了…wikipedia.jp上的口碑貌似还是不错的.

大切にするよ 作詞:柴咲コウ 作曲:市川淳 あなたは今どこに住んでるかな 元気でいるのかな 寒くはないかな 困った顔しないで 投げだしたりしないで 後戻りはしないで 暮らしててね、笑っててね…そして いつかはあなたも大人になって 出会った人と恋に落ちる そんなときにも どこかで手を合わせ祈ってる あなたの指定席、変わってないかな 前みたいに外を ながめているかな たまにしかってくれた たまに笑わせてくれた 離れても遠くから 心の瞳(め)で見てくれてた 今も… あなたがそこに生きてるだけで それだけで 私も生きられる いつか私も1人で立てるようになるかな 誰もがいつしか時間の旅終えて 次の場所へ歩く そして気づいてゆく 見えない繋がれた手に いつかはあなたも言葉じゃなくて 愛する人に愛伝える そんなときにも どこかで手を合わせ祈ってる

Comments

小巷十日

自搬来恰好一年,由于平时不在家的缘故,很少在附近逛。对这里的印象始终是“乱哄哄”的,人在路上都是挤避着走。

要不是文灯老头号称“亲临”西安,估计还要在家再坐禅一个月,不至于每天破纪录地六点多起床赶安定门的次班21路,也就不会为了赶一辆公交车一大早穿行在古怪的小巷中了。

谁知道到了长大雁塔才发现上了当,文灯先是把诬蔑自己“八十高龄”的同行竞争者骂了个遍,所谓“校长”也在一旁不停鼓动大家向“大师”的亲临面授致谢(提到“大师”,不禁让人联想到李敖的《十七岁》一书…)。果不其然,“大师”随后即说自己俗务缠身,也就来两天意思一下,紧接着会有替补跟上…要说“替补”也有些来头了,成天这么给擦屁股就没什么其它想法么?

似乎我的确不太适合上大课,更何况还是三百多应届毕业生参加的“学术报告会”,实在让人难以适应。好不容易年轻“替补”带动了两天,后来居然又成了集体看录像,看来学生实在是好唬弄。

再说这个校区,使用的年头是有点久了,但也不至于下点雨校园成了汪洋一片吧,难怪我第一次来居然看见有的宿舍台子上晾着胶鞋…胶鞋啊…这玩意现在还用得着啊?

除了每天十多个小时在雁塔晨钟下度过,剩下就只是一条长有一百多米的小巷了。尽管只有十天体验,还是总结了如下初步感悟:狼烟三两屡,狗屎七八坨,无害皆人畜,臭气不可闻……

出了最后一道巷口,本部科学楼已在西南城墙的那边若隐若现了。

Comments

Introducing the Google Chrome OS

 (from hanyi.name on Twitter)Seems that we have been missed some reprinting for several months,while just 12 hours ago Google INTRODUCED its browser-based OS named Google Chrome OS on its official blog.No doubt the new project will stimulate open source community,or its competitors(e.g. MS)?We believe that tendency will be clear soon…Here’s the article by Sundar Pichai from blogspot.

 It’s been an exciting nine months since we launched the Google Chrome browser. Already, over 30 million people use it regularly. We designed Google Chrome for people who live on the web — searching for information, checking email, catching up on the news, shopping or just staying in touch with friends. However, the operating systems that browsers run on were designed in an era where there was no web. So today, we’re announcing a new project that’s a natural extension of Google Chrome — the Google Chrome Operating System. It’s our attempt to re-think what operating systems should be.

Google Chrome OS is an open source, lightweight operating system that will initially be targeted at netbooks. Later this year we will open-source its code, and netbooks running Google Chrome OS will be available for consumers in the second half of 2010. Because we’re already talking to partners about the project, and we’ll soon be working with the open source community, we wanted to share our vision now so everyone understands what we are trying to achieve.

Speed, simplicity and security are the key aspects of Google Chrome OS. We’re designing the OS to be fast and lightweight, to start up and get you onto the web in a few seconds. The user interface is minimal to stay out of your way, and most of the user experience takes place on the web. And as we did for the Google Chrome browser, we are going back to the basics and completely redesigning the underlying security architecture of the OS so that users don’t have to deal with viruses, malware and security updates. It should just work.

Google Chrome OS will run on both x86 as well as ARM chips and we are working with multiple OEMs to bring a number of netbooks to market next year. The software architecture is simple — Google Chrome running within a new windowing system on top of a Linux kernel. For application developers, the web is the platform. All web-based applications will automatically work and new applications can be written using your favorite web technologies. And of course, these apps will run not only on Google Chrome OS, but on any standards-based browser on Windows, Mac and Linux thereby giving developers the largest user base of any platform.

Google Chrome OS is a new project, separate from Android. Android was designed from the beginning to work across a variety of devices from phones to set-top boxes to netbooks. Google Chrome OS is being created for people who spend most of their time on the web, and is being designed to power computers ranging from small netbooks to full-size desktop systems. While there are areas where Google Chrome OS and Android overlap, we believe choice will drive innovation for the benefit of everyone, including Google.

We hear a lot from our users and their message is clear — computers need to get better. People want to get to their email instantly, without wasting time waiting for their computers to boot and browsers to start up. They want their computers to always run as fast as when they first bought them. They want their data to be accessible to them wherever they are and not have to worry about losing their computer or forgetting to back up files. Even more importantly, they don’t want to spend hours configuring their computers to work with every new piece of hardware, or have to worry about constant software updates. And any time our users have a better computing experience, Google benefits as well by having happier users who are more likely to spend time on the Internet.

We have a lot of work to do, and we’re definitely going to need a lot of help from the open source community to accomplish this vision. We’re excited for what’s to come and we hope you are too. Stay tuned for more updates in the fall and have a great summer.

Posted by Sundar Pichai, VP Product Management and Linus Upson, Engineering Director

テルーの唄

夕闇迫る雲の上 いつも一羽で飛んでいる 鷹はきっと悲しかろう 音も途絶えた風の中 空を掴んだその翼 休めることはできなくて

心を何にたとえよう 鷹のようなこの心 心を何にたとえよう 空を舞うよな悲しさを

雨のそぼ降る岩陰に いつも小さく咲いている 花はきっと切なかろう 色も霞んだ雨の中 薄桃色の花びらを 愛でてくれる手もなくて

心を何にたとえよう 花のようなこの心 心を何にたとえよう 雨に打たれる切なさを

人影絶えた野の道を 私とともに歩んでる あなたもきっと寂しかろう 虫の囁く草原を ともに道行く人だけど 絶えて物言うこともなく

心を何にたとえよう 一人道行くこの心 心を何にたとえよう 一人ぼっちの寂しさを

暫定リンク http://www.pmsinfirm.org/…dom/oricon037.mp3

心を何にたとえよう…

Comments

温馨提示

请不要点击评论中的链接,本站对大量来自外站的robots评论内容概不负责,在此建议用户采用安全可靠的web浏览器,并安装反病毒软件和即时更新病毒库,我们不会再关闭评论功能。

另注:Google chrome的安全限制主要针对页尾的itsun统计,由于雅虎统计已正式关闭,我们不得不更换统计工具,目前为止我们相信该统计代码的功能和流程是安全的。

10月18日更新:经确认itsun被挂马…我们已经紧急撤下了有关代码,目前可以继续正常使用。近期几乎没有机会能维护网站,我们最早将于2010年1月启动magic cube project。

2月1日更新:我们取消了文章more tag设置,以利于seo,因此首页显示可能将恢复到过去比较冗长的局面。

2月3日测试:syntaxhighlighting显示。

#include <iostream>

int main()
{
 std::cout<<"Hello world!"<<endl;
 return 0;
}


Comments

从极限编程说起

实际上这里所说的eXtreme Programming(XP)并不是指某种软件工程方法学,但我们不得不先讨论其意义。

诚然,XP被认为是目前最有成效的敏捷软件开发方法之一,其核心在于将严格定义的规则、流程和相关文档分散成若干小规模过程,并借助动态进化设计实现灵巧的轻量级开发。

表面上,XP的目标是降低用户需求变更所造成的成本增量。这似乎与人们通常所说的“未雨绸缪”有所抵触,也和传统的开发过程之间存在很大出入。然而从社会学的角度来说,XP满足了一种社会变化机制,毕竟历史看起来并不是“照本演绎”的,尽管软件开发存在用户需求,但无论是出于主观原因还是客观原因,这些“需求”并不是一成不变的,XP在面临此类状况时所体现出来的效果,显然要优于其它一些传统的软件开发方法。

现在考虑一个现实中的案例,如果我们的实习期从2009年7月12日正式启动,直到10月下旬,那么摆在面前的就是一个极其复杂的组合调度问题,因为毕竟没有人会幻想剩余最后可怜的两个月能够力挽狂澜。

许多先行者告诉我们执行严格的water-fall方案才有可能到达彼岸,但很明显本案例并不适用这种经典方法。 因而我们需要敏捷,此处敏捷的原因并非需求变化的莫测,而是突发条件的难料,这就迫使我们更加灵巧、轻量地思考这一问题。

但不应忘记,XP方法之所以被称作eXtreme,它期望开发者能够在小规模过程中真正突破eXtreme,并最终实现积流以江、汇江成海。

我并不认为XP缺乏严谨,就如专家定义的XP价值标准:沟通、简单、回馈、勇气和尊重,这些也是我们能最终取得成功的关键因素。

注:一年前的“掩帘向学”仿佛近在咫尺,尽管只持续十多天的思考,却似乎打通了一条向往之路,如今终于步入“关键模块”阶段。鉴于此我不得不逐步减少交流频道的更新,令人惭愧的是一年多来几乎没有一个原创性的连载系列文章能够最终完成,希望将来能有机会继续坐在图书馆里完成它们,这恐怕就是用户当前抽象化的最终需求之一了。

十五载奇侠传,仙剑不灭

记得五年前,电视剧版在verycd论坛里发布的时候,我实在等不及joyo上预定好的dvd,趁着过年看完了抢先版。尽管剧情多少令人有些出乎意料,却不失对“仙剑”从多种角度的诠释。但由于实在偏离游戏主线,两盒dvd到货后就再没有开封了。

从那时起,就期待三代能够同样移植电视了。事实上当初“仙剑”能自三代起重新笼络人心,不仅仅是因为仿日式游戏的风格,更重要的是,它对弥补系列游戏的世界观具有非凡的意义。一年后首部外传的问世,仅仅能做到媲美前作,远未达到超越的程度。

至于两年前的四代,虽说突破了传统的“蜀山仙剑”故事,并创作出令人眼前一亮的剧本,但可能是出于某种原因,制作方并未打算构建对“仙剑”格外重要的新世界观,显出并不愿意重新涉足此作的意思。不好说仙迷对电视剧版等了多长时间,只数年过去,电视剧竟已拍了第二部,我也已经在西大混迹了三年,好不令人唏嘘。

校园网半年前阉割p2p,迅雷从那以后外网下载速度首次超过了3mb/s,不一会三十七集全数下完。看之前给自己规定争取每天一集,一个月后搞定,然而不过三天时间就完成了这些工作量。编剧果然接受了前作观众对电视剧本改动过繁的意见,基本上延续了“轮回”的三代主题。电视固然不能讲究“游戏性”,但单就剧情来说,电视在某种程度上甚至超越了游戏本身,这恰恰是真正十几年仙迷所乐道的。

然而不知道是没留意时间环节还是别的什么原因,电视剧并未做足前期宣传。再加上一些哗众取宠的游戏网站枉聚集一帮宅男,以令人颇为遗憾的言论搏出位,欺骗点击率。更有人甚至将游戏与一些日式游戏比较,趁机攻击还未辉煌即几凋零的国产PC游戏业。

无论如何,尽管《仙剑奇侠传三》电视剧版尚未首播,但凭借目前客观存在的全民关注度,以及一些主创演员的迅速飘红,我们还有什么理由不去期待“问情篇”呢?

MP77的UNIX课件笔记(12)

本章介绍socket网络编程,socket是当前计算机网络中最流行的编程接口,同时也是UNIX系统应用中必不可少的关键模块之一。我们会介绍计算机网络的基本概念和原理,但仍然需要读者具备一定的计算机网络通信基础知识。在后继连载中,会经常引用本文讨论的内容。

1 socket网络编程概述

1.1 socket概述

网络协议规定了两台计算机之间进行数据交换的共同规则,包括交换数据的格式和动作序列。但并不规定在一台计算机内利用网络协议传输信息的应用程序和实现这些协议的协议实体之间的程序接口。

UNIX中传输层和传输层以下的协议在操作系统内核中实现,那么,就必须规定一种应用程序使用内核的这些网络功能的方法。UNIX总是习惯于将设备和其他机制组织成文件的方式,然后通过文件描述符像访问普通文件那样访问它们。UNIX访问网络也用文件描述符引用一个特殊文件的方法。网络机制要比终端和管道复杂得多,所以,还提供了一组施加在这种特殊文件描述符上的系统调用实现网络所必需的功能。这组函数,就是应用程序和网络之间的接口。

应用程序与网络之间接口有socket和TLI。socket最先由BSD 4.3提供,TLI(tansport layer interface)由AT&T的System V提供。除此之外,还有其他的几种接口,但没有被程序员普遍接受。

对于socket和TLI,多数程序员更偏爱socket,在System V和其他的UNIX系统中也提供了socket编程界面。Windows系统中也有类似的Winsock接口,几乎所有函数都兼容。

最流行的网络协议是TCP/IP,socket提供的编程接口可以使用TCP/IP协议。BSD UNIX设计的socket编程界面是一种通用的网络编程界面,充分考虑了各种网络,例如:IPX网络,X.25网络,ISO的传输层协议TP4,IBM的SNA,以及UNIX内的进程之间通信等。但是,许多系统并不提供所有这些网络支持,有的系统甚至只允许TCP/IP协议的socket编程。

在运行TCP/IP协议的计算机中,一般都支持软件虚拟的IP数据报自环接口loopback。如果试图和IP地址等于自己地址的计算机通信,或者与地址127.0.0.1的计算机通信,数据不会发送到网络上,而是通过内核实现的虚拟的自环接口loopback,将数据回环到计算机自身。利用这一点,可以在一个计算机的多个进程之间进行通信。

现代的许多软件设计,同一台计算机内的进程之间通信也使用socket方式,这使得系统有很大的灵活性,因为需要的时候,只要将两个进程分布到不同的计算机上运行就可以了,而不需要更改程序。

1.2 TCP和UDP协议

TCP/IP对应用程序提供的服务主要有两种:

1、一种是面向连接的可靠的数据流传输TCP,另一种是无连接不可靠数据报传输UDP。

2、应用程序员在使用TCP/IP网络编写通信程序之前,应当首先在TCP和UDP协议之间作出选择,它们决定了由系统提供的通信可靠性。

1.3 基本网络体系结构

在ISO(International organization for Standards)定义的网络体系结构OSI(Open System Interconnection)开放系统互连模型中,计算机网络被定义为七层结构模型。从底向上分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。目前广泛使用的Internet协议簇结构表示如下:

介质层包括IEEE802.3(Ethernet)、802.4(token bus)、802.5(token ring)及其它;

网络层以Internet protocal为主,ARP、RARP、ICMP和IGMP为辅结构;

传输层主要分为TCP协议和UDP协议;

应用层建立了以TCP为基础的FTP、TELNET、SMTP、SNMP,DNS(Domain Name Server)则建立在TCP和UDP之上,另外一些如RPC、NFS、XDR建立在纯粹的UDP协议之上。

计算机网络的通信过程不属于本文讨论范围,如有必要可自行查阅相关文献。

1.4 网络字节次序

不同计算机厂商在计算机内部存储整数的方法会有不同。有的厂商将整数的低位字节放在最低地址处,这种安排叫Little Endian,而有的厂商正好相反,高位字节放在内存的低地址处,它们的字节顺序安排叫Big Endian。

网络通信时,总是从内存的低地址开始传输连续的若干字节,因此,网络软件为了保证各计算机之间的互联性,要求所有的数据按统一的字节顺序传输,这就是规定的网络字节顺序。网络字节顺序的规定与Big Endian 相同。

UNIX中htons,htonl两个库函数,分别将短整数和长整数从主机字节次序转换到网络字节次序。相应地,ntohs和ntohl把网络字节顺序转换到主机字节顺序。

在socket的网络系统调用和库函数的结构体参数中的整数,一般也要求按网络字节顺序排列。为了源程序的可移植性,即使所用的UNIX中主机字节顺序与网络字节顺序正好吻合,也不要省略掉所必须的htons、htonl、ntohs和ntohl。

2 TCP客户端/服务器程序

2.1 Clinet/Server结构概述

socket编程接口,无论使用TCP还是UDP协议,都是一种client/server风格的软件结构。client/server结构的协议软件包括客户端软件和服务器端软件。

以文件传送协议FTP为例,UNIX提供的ftp命令就是客户端软件,在提供文件传送服务的远程计算机上,运行服务器端软件。这些服务器端的软件,在UNIX中由“精灵(daemon)”进程inetd控制,当TCP连接到达时,inetd创建ftpd服务进程负责与客户端软件的ftp通信,以完成文件传送操作,文件传送结束后,ftpd进程结束。

事实上,UNIX设计的socket机制,不仅仅是面向TCP/IP协议的,而是面向所有的网络通信,包括SPX/IPX,X.25,SNA,甚至充分考虑了可扩展性,支持将来可能出现的其他协议。IPv6比socket出现得晚,但是仍然继续沿用socket机制的这层“壳”,内核扩充了IPv6支持后,应用程序员就可以写基于IPv6的socket风格的通信程序了。

2.2 仿照文件的操作模式

访问文件有一组函数,而且这些函数调用的先后顺序也有一定的规则:

先open得到文件描述符fd,然后可以执行read和write访问文件内容,另有一些可以施加在fd上的函数,如:lseek定位文件指针,fcntl设置close-on-exec标志或者对文件的记录加锁,fstat获得文件的状态,等等。

使用管道时,就不再用open获得文件描述符,而是用pipe一次获得两个文件描述符。

使用socket的情况类似:

先用socket创建一个文件描述符,在这个文件描述符上,先施行一些connect,bind,listen等操作控制建立TCP连接,然后才能使用read和write收发数据。

通信过程中可使用fcntl、setsockopt和getpeername等函数,获得一些通信的状态,或者设置一些与通信有关的参数。最终,用close关闭连接。

这些函数调用的先后顺序,也遵循一定的规则。

2.3 网间进程通信需解决的问题

1、网间进程标识问题

单机上不同进程可以用进程号唯一标识。但在网络环境下,各主机独立分配的进程号是不能唯一标识该进程的。

2、多重协议的识别问题

操作系统支持的网络协议众多,不同协议的工作方式、地址格式都不同, TCP/IP协议引入了下列几个概念解决多重协议的识别问题:端口、地址、网络字节序、连接、半相关、全半相关等。

接下来我们将对以上概念逐一做一介绍。

2.3.1 Ports

把网络地址和端口号信息放在一个结构中,也就是套接字地址结构。大多数套接字系统调用都需要一个指向套接字地址结构的指针作为参数,并以此来传递地址信息。每个协议族都定义它自己的套接字地址结构,套接字地址结构都以“sockaddr_”开头,并以每个协议族名中的两个字母作结尾。

端口包括一些数据结构和I/O缓冲区。进程通过系统调用和某端口建立连接后,传输层传给该端口的数据都被相应的进程接收,相应进程发给传输层的数据都从该端口输出。

在TCP/IP协议的实现中,端口操作类似于一般I/O操作,进程获取一个端口相当于获取本地的唯一的I/O文件,可以用一般的读/写原语访问。类似于文件描述符,每个端口都有一个叫端口号的整数描述符,以区别不同的端口。由于TCP/IP传输层的两个协议TCP和UDP是两个完全独立的软件模块,因此各自端口号也相互独立。如TCP和UDP的端口号可以相同,两者并不冲突。

2.3.2 端口的分配

(1)全局分配:是一种集中式分配,由一个公认的中央机构根据用户的需要进行统一分配,并将结果公布于众;

(2)本地分配( 动态连接): 进程在需要访问传输层时,向本地操作系统提出申请,操作系统返回本地唯一的端口号,进程再通过合适的系统调用将自己和该端口连接起来(binding)。

TCP/IP端口号分配综合了两种方式。 TCP/IP是将端口号分为两部分,少量的作为保留端口(<256),以全局的方式分配给服务进程。因此,每一个标准服务器都拥有一个全局公认的端口叫周知口,即使在不同的机器上,其端口号也相同。如HTTP中80、TELNET中的23等。剩余的为自由端口,是以本地方式进行分配。

/etc/services文件包含了服务名、端口号和协议名。若机器提供新服务,则需要在该文件中建立一项。

2.3.3 地址

网络通信中的两个进程是分别在两个不同的机器上。两台机器可以位于不同的网络,这些网络通过互连设备(网关、网桥、路由器)连接,因此需要三级寻址(网络地址、主机地址、进程标识)。

某一主机可与多个网络相连,必须指定一特定网络地址;

网络上每一台主机应有其唯一的地址;

每一主机上的每一进程应有在该主机上的唯一标识符。

通常主机地址由网络ID和主机ID组成,在TCP/IP协议中用32位整数值表示;TCP和UDP均使用16位端口号标识用户进程。

2.3.4 连接

两个进程间的通信链路称为连接,连接在内部表现为一些缓冲区和一组协议机制。

2.3.5 半相关和全半相关

1、半相关(half-association)

网络中用一个三元组(the triple)可以在全局中唯一标识一个进程(协议,本机地址,本地端口号)。这样的一个三元组叫做半相关。

2、全半相关

一个完整的网间进程通信需要两个进程组成,并且只能使用同一种高层协议,也就是说TCP无法和UDP通信,因此一个完整的网间进程通信需要一个五元组来标识(协议,本机地址,本地端口号,远地地址,远地端口号)。

这样一个五元组,叫做一个相关(association),即两个协议相同的半相关才能组合成一个合适的相关,或完全指定组成一连接。

2.3.6 服务方式

1、面向连接服务

面向连接服务是电话系统服务模式的抽象,即每一次完整的数据传输都要经过建立连接,使用连接,终止连接的过程。在数据传输过程中,各数据分组不携带目的地址,而使用连接号(connect ID)。本质上,连接是一个管道,收发数据不但顺序一致,而且内容相同。TCP协议提供面向连接的虚电路。

2、无连接服务

无连接服务是邮政系统服务的抽象,每个分组都携带完整的目的地址,各分组在系统中独立传送。无连接服务不能保证分组的先后顺序,不进行分组出错的恢复与重传,不保证传输的可靠性。UDP协议提供无连接的数据报服务。

2.3.7 客户机/服务器模式

TCP/IP允许程序员在两个应用程序之间建立通信并来回传送数据,提供一种对等通信,可以是同一台机器上,也可以是不在同一台机器上。TCP/IP指明了数据是如何进行通信的,但并没有规定如何组织这些应用程序。

实践中网间进程通信的主要模式是客户机/服务器模式,即客户机向服务器发出请求,服务器在接收到请求后提供相应的服务。客户与服务器的作用是非对称的,服务器进程一般是先于客户启动,并一直随系统运行而存在,直到被终止。

客户机/服务器模式的建立基于以下两点:

(1)网络的目的是共享,从而让拥有众多资源的主机提供服务,让资源较少的客户请求服务;

(2)网间进程通信完全是异步的,在通信的进程间需要一种机制建立联系,以便为二者的数据交换提供同步。

客户机/服务器模式操作过程中采取的是以下主动请求方式:

1、服务器端

(1)打开一个通信信道,并告知本地主机将在某一公共地址端口(如Http中80、Telnet中的23)上接受用户的请求;

(2)等待客户请求到达端口;

(3)若接收到重复请求服务,则处理该请求并发送应答信号;若接收到并发服务请求,则要建立子进程来处理这个客户的请求,服务完成后,关闭子进程与客户的通信链路,并终止子进程;

(4) 跳至(2)步;

(5) 关闭服务器。

2、客户端

(1)打开一个通信信道,并连接到服务器所在主机的特定端口;

(2)向服务器发出服务器请求报文,等待并接受应答;

(3)请求结束后关闭与服务器的通信链路并终止此进程。

从以上描述过程可以看出:

客户与服务器的作用是非对称的,因此编码不同。

服务器进程一般是先于客户请求启动。只要系统运行,进程就一直存在,直到正常终止或者强迫终止。

客户软件不必处理并发性,因此比服务器程序简单得多。

2.3.8 TCP协议的实现机制

UDP协议是无连接的不可靠的协议,而TCP是面向连接的,所谓面向连接,是指在数据传输开始前建立一个数据通道,这个通道在整个数据传输过程中都保证通畅,到传输结束才关闭这一通道。

一个典型的TCP协议双方通信的过程是:

(1)获得对方IP地址和端口号。

(2)在本地主机上选择一个IP地址和端口号。

(3)建立连接。

(4)传输数据 这时数据就好象是直接从发送方顺序流出到接收方的一样,与普通的文件流操作没有什么不同。

(5)断开连接。

2.4 TCP网络通信程序构建

2.4.1 建立TCP连接

为了建立一条可靠的连接,TCP采用3次握手:

(1)服务器首先执行被打开的连接操作:socket, bind, listen, accept,然后服务器阻塞,等待客户端的连接。

(2)客户端执行主动打开的连接操作:socket,connect,同时客户端向服务器发送SYN类型的数据段,其中包括客户端的序列号。

(3)服务器接收到这个SYN数据段后,也发送一个SYN类型的数据段,其中包括服务器的序列号和对上一个SYN的确认。

(4)客户端在接收到这个确认后,就发送了对服务器SYN的确认,完成客户端的连接。

(5)服务器接收到这个确认,完成服务器的连接。

2.4.2 面向连接的SOCKET编程

套接字根据使用的协议不同可以分很多种,这里主要介绍两种:TCP套接字和UDP套接字。

Socket套接字对于不同的对象存在相应合理的解释。对于内核来说,socket标记了通信的终点,而对于应用程序来说,socket是一个文件描述符,这种文件描述符指定了应用程序写入/读取信息的位置。

显然,套接字描述符和文件描述符形式上基本一致,但数据结构上存在很大区别。

文件描述符在前文中已经有详细讨论,我们曾提到每个进程均含有一张文件描述符表,表内的数据结构存储了文件的相关信息。

套接字接口为网络通信增加了一个新的抽象,即套接字。当进程调用socket后,操作系统就分配了一个新的数据结构以便保存通信所需的信息,并在文件描述符表中填入了一个新的条目,该条目含有一个指向这个数据结构的指针。尽管套接字内部的数据结构有许多字段,在系统创建套接字后,大多数字段中的值并没有填上。在套接字能够被使用之前,创建该套接字的应用程序必须用其他系统调用把套接字数据结构中的这些信息填上。

套接字一旦创建,应用程序就必须指定如何使用它,套接字本身是完全通用的,可以用来进行任意方式的通信。例如,服务器可以将套接字配置为等待传入连接,而客户可以将其配置为发起连接。

如果服务器将套接字配置为等待传入连接,就称此套接字套接字为主动套接字;反之,客户用来发起连接的套接字就称为被动套接字。

使用TCP的客户和服务器各自使用套接字的一种调用序列。

客户创建套接字,调用connect连接服务器,交互时,使用send(或者write)发送请求,使用recv(或者read)接收应答。当使用连接结束时,客户调用close。

服务器使用bind指明使用的本地(熟知)协议端口,调用listen设置连接等待队列的长度,之后便进入循环。在循环中,服务器调用accept进行等待,直到下一个连接请求到达为止,它使用recv和send(或read和write)同客户交流,最后使用close终止连接。之后,服务器回到accept调用,在那里等待下一个连接。

2.4.3 使用TCP时客户和服务端通信流程

服务端:socket->bind->listen->accept->read->write->close

客户端:socket->connect->write->read->close

2.4.4 套接字用到的具体数据结构

1、通用套接字地址数据结构(/usr/include/sys/socket.h)

struct sockaddr /struct to hold an address /

{

unsigned short sa_familly; /address family/

char sa_data[14]; /protocol address/

};

sa_familly为协议族,指出通信协议类型,对于internet域的地址族为AF-INET。

sa_data存贮实际的地址。

2、IPV4套接字地址数据结构

在实际中为了方便处理,每个协议在上面通用定义的基础上改成自己的套接字地址结构,这些结构均以“sockaddr_”开头,并以对应每个协议族的唯一后缀结束。

对于我们关心的Internet(IPV4)域,我们有专用的套接字地址结构sockaddr_in结构,它定义在中。

struct sockaddr_in

{

short int sin_fammily; / address family Ipv4 is AF_INET/

unsigned short sin_port; /port number/

struct in_addr sin_addr; /internet address IP address/

unsigned char sin_zero[8]; /same size as struct sockaddr/

};

struct in_addr

{

unsigned long s_addr; /32-bit IP address , network byte/

};

2.4.5 套接字用到的基本系统调用

1、socket系统调用,用来获得一个socket描述符。

include

include

int socket(int domain, int type, int protocol);

domain是指存放通信进程的区域,通常使用的domains包括:

AF_UNIX for communication between processes on one system;

AF_INET (IPv4) for communication between processes on the same or different systems using the DARPA standard protocols(IP/UDP/TCP)

AF_INET6 (IPv6)

AF_LOCAL (Unix domain)

type:通信的类型SOCK-STREAM(TCP)、SOCK-DGRAM(UDP)、SOCK-RAW。

Protocol:一般为0,除非使用原始套接口。

2、bind系统调用

bind为套接字指定本地地址,它包含了IP地址和协议端口号,服务器主要由bind来指明熟知的端口号,它在此熟知的端口号等待连接。以下是一段bind程序演示:

include

include

include

define MYPORT 3490

main()

{ int sockfd; struct sockaddr_in my_addr; sockfd = socket(AF_INET, SOCK_STREAM, 0); /需要错误检查 / my_addr.sin_family = AF_INET; / host byte order / my_addr.sin_port = htons(MYPORT); / short, network byte order / my_addr.sin_addr.s_addr = inet_addr(“132.241.5.10”); bzero(&(my_addr.sin_zero),; / zero the rest of the struct / / don’t forget your error checking for bind(): / bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));

处理自己的 IP 地址和端口的 时候,有些工作是可以自动处理的。 my_addr.sin_port = 0; / 随机选择一个没有使用的端口 / my_addr.sin_addr.s_addr = INADDR_ANY; / 用自己的IP地址 / 通过将0赋给 my_addr.sin_port,告诉 bind() 自己选择合适的端 口。

将 my_addr.sin_addr.s_addr 设置为 INADDR_ANY,告诉 它自动填上它所运行的机器的 IP 地址。没有将INADDR_ANY转 换为网络字节顺序!INADDR_ANY 实际上就 是 0!即使你改变字节的顺序,0依然是0。 my_addr.sin_port = htons(0); / 随机选一没有使用的端口 / my_addr.sin_addr.s_addr = htonl(INADDR_ANY);

/ 使用自己的IP地址 /

3、connect系统调用

connect系统调用于在一个指定的socket上建立一个连接。

int connect(int sockfd, struct sockaddr *serv_addr,int addrlen);

在创建套接字后,客户程序connect以便同远程服务器建立主动的连接,connect的一个参数允许客户指明远程端点,它包括IP地址以及协议端口号。一旦建立了连接,客户就可以向它传送数据了。

include

include

include

define DEST_IP “132.241.5.10”

define DEST_PORT 23

main()

{

int sockfd;

struct sockaddr_in dest_addr; / 目的地址/ sockfd = socket(AF_INET, SOCK_STREAM, 0); / 错误检查 / dest_addr.sin_family = AF_INET; / host byte order /

dest_addr.sin_port = htons(DEST_PORT); / short, network byte order / dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);

bzero(&(dest_addr.sin_zero),; / zero the rest of the struct / / don’t forget to check error / connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr));

4、listen系统调用

系统调用使一个套接字进入监听状态,仅被TCP服务器调用

include

int listen(int sockfd, int backlog);

在创建套接字后,直到应用程序采取进一步行动以前,它既不是主动的(由客户使用)也不是被动的(由服务器使用)。面向连接的服务器用listen将一个套接字置为被动的模式,并使其准备接受传入连接。大多数服务器由无限循环构成。该循环可以接受传入的下一个连接,然后对其进行处理,完成后便返回准备接受下一个连接,正处于忙的服务器有可能又来了一个连接请求。为保证不丢失连接请求,服务器必须给listen传递一个参数,告诉操作系统对某个套接字上的连接请求进行排队。因此,listen的一个参数指明某个套接字将被置为被动的模式,而另一个参数将指明套接字所使用的队列长度。

5、accept系统调用

int accept(int sockfd, struct sockaddr addr, int addrlrn);

accept返回非负描述字表示成功,出错将返回-1。如果成功,则返回值用来标识新建立的连接。

参数addr为返回客户进程协议地址,参数addrlen为返回客户进程协议地址的长度。

对TCP套接字,服务器用socket创建一个套接字,用bind指明本地端口地址,用listen将其置为被动的模式,用accept以获取传入连接请求,accept的一个参数指明一个套接字,将从该套接字上接受连接。

accept为每一个新连接创建了一个新的套接字,并将这个新套接字的描述符传给调用者。服务器只对这个新的连接使用该套接字,而原来的套接字接受其他的连接请求。服务器一旦接受了一个连接后,它就可以在这个新的套接字上传送数据。在使用完这个新的套接字后,服务器将关闭该套接字。

include

include

include

define MYPORT 3490 /用户接入端口/

define BACKLOG 10 / 多少等待连接控制/

main()

{

int sin_size; int sockfd, new_fd; / listen on sock_fd, new connection on new_fd / struct sockaddr_in my_addr; / 地址信息 / struct sockaddr_in their_addr; / connector’s address information / sockfd = socket(AF_INET, SOCK_STREAM, 0); / 错误检查/ my_addr.sin_family = AF_INET; / host byte order / my_addr.sin_port = htons(MYPORT); / short, network byte order / my_addr.sin_addr.s_addr = INADDR_ANY; / auto-fill with my IP / bzero(&(my_addr.sin_zero),; / zero the rest of the struct / / don’t forget your error checking for these calls: / bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)); listen(sockfd, BACKLOG); sin_size = sizeof(struct sockaddr_in); new_fd = accept(sockfd, &their_addr, &sin_size);

6、send和recv系统调用(read和write)

int send( int sockfd,void *buf ,int len,int flags);

int recv( int sockfd,void *buf ,int len,int flags);

在大多数UNIX系统,程序员可以用read代替recv,用write代替send。对TCP和UDP套接字来说,他们的语义是一样的。把 flags 设置为 0 就可以了。

下列调用将导致进程阻塞:

1、accept()系统调用,若没有连接请求,则被阻塞。

2、read()系统调用。

3、write()系统调用。

7、Closesocket

Comments

MP77的UNIX课件笔记(11)

3 进程与文件描述符

3.1 内核中的文件打开结构

我们已经在前文中介绍过有关文件描述符的概念。在OS文件系统中,为了提高文件访问的效率,在访问一个文件时,将文件的inode节点读入内核内存,在整个文件访问期间使用内存中的inode节点。由于同一个进程可以访问多个文件,多个进程可以同时访问文件,因此在内核中构建了“活动文件目录AFD”active file directory,文件描述符即该文件目录的索引。

从内核的角度来看,AFD是一个三级存储结构,它包括了如下几个部分:

1、进程PCB的user结构中有一整型数组u_ofile,记录当前进程打开的文件。所谓的文件描述符fd,实际上就是user结构中u_ofile数组的下标值。每个进程有一个u_ofile数组。当然,无论是动态分配还是静态分配,系统不可能允许这个数组无限大,这就是每个进程最多可以打开的文件个数限制。

2、u_ofile数组中的元素值,是一个整数,这个整数是file数组的一个下标。file定义在/usr/include/sys/file.h中,主要包含以下几个域:

struct file{

char f_flag; //开启文件的读写操作要求

char f_count; //引用计数

int f_inode; //内核中inode数组的下标,可找到已读入内核中的文件inode节点

offset_t f_offset; //文件读写位置指针,系统在此记录文件读写位置

}

3、内存inode表在整个系统也只有一张,是外存中inode的缓冲。在内存inode中也有一个引用计数字段,统计有多少个file结构引用它。

在具体的OS实现中,尽管可能使用了更复杂的引用类型,但整体三级结构的框架是不变的。

内存AFD三级存储结构的构建,与文件描述符的有关操作密切相关。

例如open调用,其实质是增加了活动文件目录,在三级表格中增加原始条目。在open层次上已经将设备虚拟得跟普通磁盘文件一样。

又例如管道操作pipe,网络通信socket,都是创建文件描述符,系统把它们虚拟得跟普通文件一样,随后利用read、write像访问普通磁盘文件一样访问管道,或者在网络连接上收发数据。fork导致的子进程继承也会增加条目。

而close调用实质是AFD的删除操作,直接消除u_ofile项,根据引用计数,可能会引发file,inode结构的释放。进程正常地或者异常地终止,内核都会根据进程user结构中u_file的记载,自动关闭已打开的所有文件。

3.2 文件描述符的继承和复制

3.2.1 文件描述符的继承

根据上节介绍的AFD原理,我们很容易能理解fork和exec对已打开文件的影响。这里仍有必要对此进行进一步说明。

fork创建子进程后,子进程继承父进程已打开的所有文件描述符。具体做法就是,子进程user结构中的u_ofile是父进程这个数组的复制。为了防止随后各自独立执行的父子进程close调用会带来的影响,进行了这样的复制后,所有打开文件描述符对应file结构中的f_count都加1。这样,父子进程可以独立地关闭各自的文件,而对另一个进程不产生影响。这种做法还使得父子进程共用文件的读写位置。

由于fork后父进程的文件描述符被子进程继承,因此子进程不需要再次执行open调用,就可以直接使用这些文件描述符,由于exec系统调用不会创建任何进程,执行exec系统调用也不影响文件描述符。

shell程序正是利用了文件描述符的继承机制,向子进程自动文件描述符0、1、2,分别表示标准输入、标准输出和标准错误输出,确保该进程的上述操作均关联至当前tty。

3.2.2 close-on-exec标志

在有的情况下,我们希望在执行exec时自动关闭某些文件描述符。这就需要为已经打开的文件设置close-on-exec标志。

内核为每个文件描述符记录了一个文件描述符标志字,标志字的第0比特是close-on-exec标志。默认情况下,该标志位被清除,因此如果要求文件描述符在exec时自动关闭,必须取出这个文件描述符的标志字,将第0比特设置为1,标志字的其他比特保持原值,然后重新设置文件描述符的标志字。

获取文件描述符标志字,使用系统调用函数fcntl,函数原型如下:

include

int fcntl(int fd,int cmd,…);

fcntl有很多功能,这里用到的仅仅是获取和设置文件描述符控制字的功能,后面的文件和记录锁定,以及设置无阻塞I/O时,还会用到这个函数。

flags = fcntl(fd,F_GETFD,0);

flags |= FD_CLOEXEC;

fcntl(fd,F_SETFD,flags);

上述程序演示了获取文件描述符关键字、置位close-on-exec标志,然后重新设置文件描述符控制字。F_GETFD和F_SETFD都是中定义的宏。为了程序的可读性,不直接操作第0比特,而是使用宏FD_CLOEXEC,清除该标志应当使用下面的语句:

flags &= ~FD_CLOEXEC;

3.2.3 文件描述符的复制

fork在创建新进程时复制所有文件描述符,如果只需要复制一个文件描述符,需要使用到System call为dup2。

int dup2(int fd1,int fd2);

复制文件描述符fd1到fd2,fd2可以是空闲的文件描述符,如果fd2是已打开的文件,则先关闭原先的fd2,如果fd1不是有效的描述符,则不关闭fd2,调用失败。dup2的返回值为-1时,标志调用失败。

3.3 管道操作

3.3.1 创建管道

进程使用fork创建子进程后,父子进程就有各自独立的存储空间,互不影响。两个进程之间交换数据就不可能像进程内的函数调用那样,通过传递参数或者使用全局变量实现,必须通过其它的方式。

管道是一种历史悠久的进程间通信机制,在shell中通常使用元字符|连接两个命令,就是基于管道机制而实现的。

管道创建后会在内核中生成一个管道对象,进程可以得到两个文件描述符,然后程序就像访问文件一样访问管道。write调用将数据写入管道,read调用从管道中读出写入的内容。读入的顺序和写入的顺序相同。

int pipe(int pfd[2]);

当创建管道失败时,pipe返回-1。创建管道成功后,获得两个文件描述符pfd[0]和pfd[1],分别用于读管道和写管道。这样如果进程向pfd[1]写入数据,那么就会从pfd[0]顺序读出来。

管道实现的基本思路是,当使用fork创建子进程后,文件描述符被继承,这样父进程冲pfd[1]写入的数据,子进程就可以从pfd[0]读出,从而实现父子进程之间的通信。

一般情况下,父子进程就可以关闭不再需要的文件描述符。

3.3.2 管道读写操作

对于写操作write来说,由于管道是内核中的一个缓冲区,缓冲区不可能无限大,或者说管道不可能长度无限。若管道已满,则write操作会导致进程被阻塞,直到管道另一端read将已进入管道的数据取走后,内核才把阻塞在write的写端进程唤醒。管道容量依赖于Unix系统的实现,一般至少为4096B。

管道的读操作分三种情况。

第一种情况,管道为空,则read调用会将进程阻塞,而不是返回0.进程会一直等待到管道写端向管道写入了数据,才会醒来,read调用返回。类似的情况还有终端读,以及网络通信socket读,在终端没有按键,或者网络上尚未有数据到达的时候,read一样会将进程睡眠等待,而不是返回0。

第二种情况,管道不为空,返回读取的内容,read调用的形式为:

n = read(fd,buf,m);

read的第三个参数m是最多可以读取的字节数。如果管理中实际有n个字节,那么如果m>=n,则读n个;如果m

第三种情况,管道写端已关闭,则返回0。类似的,终端文件和网络socket,终端上按ctrl+D键或者网络连接被关闭,read也是返回0。

两个独立的进程对管道的读写操作,如果未写之前,读先行一步,那么,操作系统内核在系统调用read中让读端进程睡眠,等待写端送来数据。同样,如果写端的数据太多或者写得太快,读端来不及读,管道满了之后操作系统内核就会在系统调用write中让写端进程睡眠,等待读端独奏数据。这种同步机制,在读写速度不匹配时不会丢失数据。

3.3.3 管道的关闭

只有所有进程中引用管道写端的文件描述符都关闭了,读端read调用才返回0。

关闭读端,不再有任何进程读,则导致写端write调用返回-1。errno被设为EPIPE,在写端write函数退出前进程还会收到SIGPIPE信号,默认处理是终止进程,该信号可以被捕捉。

3.3.4 管道通信应注意的问题

1、管道传输的是一个无记录边界的字节流。写端的一次write所发送的数据,读端可能需要多次read才能读取,如一次写64KB数据。也有可能写端的多次write所发送的数据,读端一次就全部读出积压在管道中的所有数据。使用TCP协议的网络socket操作也存在同样的问题。

2、父子进程需要双向通信时,应采用两个管道。父子进程只使用一个管道进行双向数据传送时会存在问题导致数据流混乱。

3、父子进程使用两个管道传递数据,安排不当就有可能产生死锁。死锁出现的原因是,如果父进程一次性将若干处理请求写至管道A,然后读管道B等待这个请求的处理结果。子进程先读管道A得到处理请求,但是每次只从管道A中读走一个请求,将处理结果写到管道B。如果因为某个处理请求的数据过大,写管道A满而导致父进程被阻塞,而子进程因要向父进程写回一个体积较大的数据而导致写管道B也被阻塞,这时死锁出现。

4、管道的缺点,管道是半双工的通信通道,数据只能在一个方向上流动,且只限于父子进程或同祖先进程间通信,而且没有保留记录边界。

3.3.5 命名管道

命名管道允许没有共同祖先的不相干进程访问一个FIFO管道。首先用命令:

mknod pipe0 p

创建一个文件,pipe0是文件名,p是文件类型标识。

这时在文件系统中就存在一个命名管道,向这个文件中写入数据,就是向管道内写数据,从这个文件中读取数据,就是从管道中读取数据。

发送者调用:

fd = open(“pipe0”,O_WRONLY);

write(fd,buf,len);

接受者调用:

fd = open(“pipe0”,O_RDONLY);

len = read(fd,buf,sizeof buf );

总的来说,管道是最早用于进程之间通信的手段,包括后来增加的命名管道。而Unix从System V开始增强了进程之间的通信机制IPC(inter-process communication),提供了消息队列message、信号量semaphore和共享内存share memory等多种通信方式,限于篇幅我们不可能一一列举,读者也可以根据需要随时查阅相关资料。

Comments