Telnet详解

1. 概述

Telnet协议是TCP/IP协议族中的一员,是Internet远程登陆服务的标准协议。Telnet协议的目的是提供一个相对通用的,双向的,面向八位字节的通信方法,允许界面终端设备和面向终端的过程能通过一个标准过程进行互相交互。应用Telnet协议能够把本地用户所使用的计算机变成远程主机系统的一个终端。

Telnet协议具有如下的特点:

1. 适应异构

为了使多个操作系统间的Telnet交互操作成为可能,就必须详细了解异构计算机和操作系统。比如,一些操作系统需要每行文本用ASCII回车控制符(CR)结束,另一些系统则需要使用ASCII换行符(LF),还有一些系统需要用两个字符的序列回车-换行(CR-LF);再比如,大多数操作系统为用户提供了一个中断程序运行的快捷键,但这个快捷键在各个系统中有可能不同(一些系统使用CTRL+C,而另一些系统使用ESCAPE)。如果不考虑系统间的异构性,那么在本地发出的字符或命令,传送到远地并被远地系统解释后很可能会不准确或者出现错误。因此,Telnet协议必须解决这个问题。

为了适应异构环境,Telnet协议定义了数据和命令在Internet上的传输方式,此定义被称作网络虚拟终端NVT(Net Virtual Terminal)。它的应用过程如下:

  •  对于发送的数据:客户机软件把来自用户终端的按键和命令序列转换为NVT格式,并发送到服务器,服务器软件将收到的数据和命令,从NVT格式转换为远地系统需要的格式;
  • 对于返回的数据:远地服务器将数据从远地机器的格式转换为NVT格式,而本地客户机将将接收到的NVT格式数据再转换为本地的格式。

2. 传送远地命令 

我们知道绝大多数操作系统都提供各种快捷键来实现相应的控制命令,当用户在本地终端键入这些快捷键的时候,本地系统将执行相应的控制命令,而不把这些快捷键作为输入。那么对于Telnet来说,它是用什么来实现控制命令的远地传送呢?

Telnet同样使用NVT来定义如何从客户机将控制功能传送到服务器。我们知道USASCII字符集包括95个可打印字符和33个控制码。当用户从本地键入普通字符时,NVT将按照其原始含义传送;当用户键入快捷键(组合键)时,NVT将把它转化为特殊的ASCII字符在网络上传送,并在其到达远地机器后转化为相应的控制命令。将正常ASCII字符集与控制命令区分主要有两个原因:

  •  这种区分意味着Telnet具有更大的灵活性:它可在客户机与服务器间传送所有可能的ASCII字符以及所有控制功能;
  •  这种区分使得客户机可以无二义性的指定信令,而不会产生控制功能与普通字符的混乱。

3. 数据流向 

将Telnet设计为应用级软件有一个缺点,那就是:效率不高。这是为什么呢?下面给出Telnet中的数据流向:

数据信息被用户从本地键盘键入并通过操作系统传到客户机程序,客户机程序将其处理后返回操作系统,并由操作系统经过网络传送到远地机器,远地操作系统将所接收数据传给服务器程序,并经服务器程序再次处理后返回到操作系统上的伪终端入口点,最后,远地操作系统将数据传送到用户正在运行的应用程序,这便是一次完整的输入过程;输出将按照同一通路从服务器传送到客户机。

因为每一次的输入和输出,计算机将切换进程环境好几次,这个开销是很昂贵的。还好用户的键入速率并不算高,这个缺点我们仍然能够接受。

4. 强制命令  

我们应该考虑到这样一种情况:假设本地用户运行了远地机器的一个无休止循环的错误命令或程序,且此命令或程序已经停止读取输入,那么操作系统的缓冲区可能因此而被占满,如果这样,远地服务器也无法再将数据写入伪终端,并且最终导致停止从TCP连接读取数据,TCP连接的缓冲区最终也会被占满,从而导致阻止数据流流入此连接。如果以上事情真的发生了,那么本地用户将失去对远地机器的控制。

为了解决此问题,Telnet协议必须使用外带信令以便强制服务器读取一个控制命令。我们知道TCP用紧急数据机制实现外带数据信令,那么Telnet只要再附加一个被称为数据标记(date mark)的保留八位组,并通过让TCP发送已设置紧急数据比特的报文段通知服务器便可以了,携带紧急数据的报文段将绕过流量控制直接到达服务器。作为对紧急信令的相应,服务器将读取并抛弃所有数据,直到找到了一个数据标记。服务器在遇到了数据标记后将返回正常的处理过程。

5.  选项协商 

由于Telnet两端的机器和操作系统的异构性,使得Telnet不可能也不应该严格规定每一个telnet连接的详细配置,否则将大大影响Telnet的适应异构性。因此,Telnet采用选项协商机制来解决这一问题。

Telnet选项的范围很广:一些选项扩充了大方向的功能,而一些选项制涉及一些微小细节。例如:有一个选项可以控制Telnet是在半双工还是全双工模式下工作(大方向);还有一个选项允许远地机器上的服务器决定用户终端类型(小细节)。

Telnet选项的协商方式也很有意思,它对于每个选项的处理都是对称的,即任何一端都可以发出协商申请;任何一端都可以接受或拒绝这个申请。另外,如果一端试图协商另一端不了解的选项,接受请求的一端可简单的拒绝协商。因此,有可能将更新,更复杂的Telnet客户机服务器版本与较老的,不太复杂的版本进行交互操作。如果客户机和服务器都理解新的选项,可能会对交互有所改善。否则,它们将一起转到效率较低但可工作的方式下运行。所有的这些设计,都是为了增强适应异构性,可见Telnet的适应异构性对其的应用和发展是多么重要。

2. 原理

Telnet协议的主体由三个部分组成:

  • 网络虚拟终端(NVT,Network Virtual Terminal)的定义;
  • 操作协商定义;
  • 协商有限自动机;

2.1. 网络虚拟终端(NVT)

2.1.1. NVT工作原理

顾名思义,网络虚拟终端(NVT)是一种虚拟的终端设备,它被客户和服务器所采用,用来建立数据表示和解释的一致性。

2.1.2. NVT的定义

1. NVT的组成

网络虚拟终端NVT包括两个部分:

  •  输出设备:输出远程数据,一般为显示器
  •  输入设备:本地数据输入

2. 在NVT上传输的数据格式

在网络虚拟终端NVT上传输的数据采用8bit字节数据,其中最高位为0的字节用于一般数据,最高位为1的字节用于NVT命令

3. NVT在TELNET中的使用

TELNET使用了一种对称的数据表示,当每个客户机发送数据时,把它的本地终端的字符表示影射到NVT的字符表示上,当接收数据时,又把NVT的表示映射到本地字符集合上。

在通信开始时,通信双方都支持一个基本的NVT终端特性子集(只能区分何为数据,何为命令),以便在最低层次上通信,在这个基础上,双方通过NVT命令协商确定NVT的更高层次上的特性,实现对NVT功能的扩展。

在TELNET中存在大量的子协议用于协商扩展基本的网络虚拟终端NVT的功能,由于终端类型的多样化,使得TELNET协议族变得庞大起来。

2.2. 操作协商

2.2.1. 为什么要协商操作选项

当定义了网络虚拟终端设备后,通信的双方就可以在一个较低的层次上实现数据通信,但基本的NVT设备所具有的特性是十分有限的,它只能接收和显示7位的ASCII码,没有最基本的编辑能力,所以简单的NVT设备是没有实际应用意义的;为此TELNET协议定义了一族协议用于扩展基本NVT的功能,目的是使NVT能够最大限度地达到用户终端所具有的功能。

为了实现对多种终端特性的支持,TELNET协议规定在扩展NVT功能时采用协商的机制,只有通信双方通过协商后达成一致的特性才能使用,才能赋予NVT该项特性,这样就可以支持具有不同终端特性的终端设备可以互连,保证他们是工作在他们自己的能力以内。

2.2.2. 操作协商命令格式

TELNET的操作协商使用NVT命令,即最高位为1的字节流,每条NVT命令以字节IAC(0xFF)开始。原理如下:

只要客户机或服务器要发送命令序列而不是数据流,它就在数据流中插入一个特殊的保留字符,该保留字符叫做“解释为命令”(IAC  ,Interpret As Command) 字符。当接收方在一个入数据流中发现IAC字符时,它就把后继的字节处理为一个命令序列。下面列出了所有的Telnet NVT命令,其中很少用到。

表1   TELNET 命令

名称 编码 说明
EOF 236 文件结束符
SUSP 237 挂起当前进程
ABORT 238 中止进程
EOR 239 记录结束符
SE 240 子选项结束
NOP 241 空操作
DM 242 数据标记
BRK 243 终止符(break)
IP 244 终止进程
AO 245 终止输出
AYT 246 请求应答
EC 247 终止符
EL 248 擦除一行
GA 249 继续
SB 250 子选项开始
WILL 251 选项协商
WONT 252 选项协商
DO 253 选项协商
DONT 254 选项协商
IAC 255 字符0XFF

 

其中常用的TELNET选项协商如下:

  • WILL (option code) 251 指示希望开始执行,或者确认现在正在操作指示的选项。
  • WON’T (option code) 252 指出拒绝执行或继续招待所指示的选项。
  • DO (option code) 253 指出要求对方执行,或者确认希望对方执行指示的选项。
  • DON’T (option code) 254 指出要求对方停止执行,或者确诊要求对方停止执行指示的选项。

那么对于接收方和发送方有以下几种组合:

表2   TELNET 选项协商的六种情况

发送者 接收者 说明
WILL DO 发送者想激活某选项,接受者接收该选项请求
WILL DONT 发送者想激活某选项,接受者拒绝该选项请求
DO WILL 发送者希望接收者激活某选项,接受者接受该请求
DO DONT 发送者希望接收6者激活某选项,接受者拒绝该请求
WONT DONT 发送者希望使某选项无效,接受者必须接受该请求
DONT WONT 发送者希望对方使某选项无效,接受者必须接受该请求

选项协商需要3个字节:IAC,然后是WILL、DO、WONT或DONT;最后一个标识字节用来指明操作的选项。常用的选项代码如下:

表3   TELNET 选项代码

选项标识 名称 RFC
1 回应(echo) 857
3 禁止继续 858
5 状态 859
6 时钟标识 860
24 终端类型 1,091
31 窗口大小 1,073
32 终端速率 1,079
33 远端流量控制 1,372
34 行模式 1,184
36 环境变量 1,408

通常情况下,客户机向服务器发送字符而服务器将其回显到用户的终端上,但是,如果网络的时延回引起回显速度太慢,用户可能更愿意让本地系统回显字符。在客户机允许本地系统回显前,它要向服务器发送以下序列:

IAC  DONT ECHO

服务器收到请求后,发出3个字符的响应:

IAC WONT ECHO

表示服务器已经按请求同意关闭回显。

2.3. 子选项协商

除了“打开”或“关闭”以外,有些选项还需要更多的信息,例如对于指明终端类型来说,客户必须发送一个字符串来标识终端类型,所以要定义子选项协商。

RFC 1091定义了终端类型的子选项协商。举个例子:

客户发送字节序列来请求打开选项:

<  IAC,WILL,24>

24是终端类型的选项标识符。如果服务器同意该请求,响应为:

<  IAC,DO,24 >

接着服务器发送

<  IAC,SB,24,1,IAC,SE>请求客户给出其终端类型。

SB是子选项开始命令,下一个字节24表示该子选项为终端类型选项。下一个字节1表示:发送你的终端类型。客户的响应为:

<  IAC,SB,24,0,’I',’B',’M',’P',’C', IAC,SE>

第四个字节0的含义是“我的终端类型为”。

3. 实现

整个协议软件分为三个模块,各模块的功能如下:

1. 与本地用户的输入/输出模块:处理用户输入/输出;

2. 与远地系统的输入/输出模块:处理与远程系统输入/输出;

3. TELNET协议模块:实现TELNET协议,维护协议状态机。

telnet客户机要做两件事:

  • 读取用户在键盘上键入的字符,并通过tcp连接把他们发送到远程服务器上
  • 读取从tcp连接上收到的字符,并显示在用户的终端上

[转载]::[Linux] ls和size命令以及程序内存映像、磁盘映像的理解

下午试验一个小程序来着,用到size 命令,后来发现只是一个空程序体的话,size 命令仍然显示编译出的a.out 的bss 段大小有四个字节,于是去google 了下size 命令的相关用法之类,没想到找出来一篇关于程序内存映像和磁盘映像等的不错的说明。又给自己扫盲了。一共有两个帖子觉得内容很犀利,组织一下贴过来备份下。

第一个帖子详细说了可运行程序的磁盘映像、内存映像、进程地址空间的内容和对应关系。以及ls 命令和 size 命令的输出:(对文章不做更改,只对个别错别字有旁注,表示对原作者版权的尊重,)

———————————————————— 转载一,start —————————————————————————

GNU/Linux平台的C开发及运行环境

文章来源: 唯一    发表日期: 2007-8-11    访问次数: 1036
链接:
http://onlyone.hpsbhq.com/showitemend.asp?endid=78&leiclass=Linux

本文介绍在GNU/Linux环境下一个C程序由源代码到程序,到加载运行,最后终止的过程。同时以此过程为载体,介绍GNU/Linux平台下软件开发工具的使用。
本文以我们最常见的hello, world!为例:

 

 

#include

main ()

{

      printf(“hello, world!\n”);

}

 

   

 

C程序生成


 

 

下图是一个由C源代码转化为可执行文件的过程:

 

代码编辑: 比较流行的编辑器是GNU Emacs和vim。Emacs具有非常强大的功能,并且可扩展。

 

编译:包括编译预处理,编译,汇编,连接过程。它们都可以通过GCC来实现。关于GCC,可以参考我关于GCC的笔记


C编译器将源文件转换为目标文件,如果有多个目标文件,编译器还将它们与所需的库相连接,生成可执行模块。当程序执行时,操作系统将可执行模块拷贝到内存中的程序映象。

程序又是如何执行的呢?执行中的程序称之为进程。程序转化为进程的步骤如下:

 

1,  内核将程序读入内存,为程序镜像分配内存空间。

2,  内核为该进程分配进程标志符(PID)。

3,  内核为该进程保存PID及相应的进程状态信息。

经过上述步骤,程序转变为进程,即可以被调度执行。

 

上述的hello, world程序实际是不规范的,POSIX规定main函数的原型为:

 

 

int main( int argc, char *argv[])

 

argc是命令行参数的个数,argv是一个指针数组,每个指针元素指向一个命令行参数。

 

 

e.g:  $ ./a.out arg1 arg2

argc = 4

argv[0] = ./a.out   argv[1] = arg1  argv[2] = arg2


 

 

 C程序的开始及终止

 


 

 

   

程序的运行:

唯一入口:exec函数族(包括execl, execv, execle, execve, execlp, execvp)


程序开始执行时,在调用main函数之前会运行C启动例程,该例程将命令行参数和环境变量从内核传递到main函数。

 

程序的终止:有8种途径:

正常终止

1,    从main返回。

2,    调用exit。

3,    调用_exit或_Exit。

4,    从最后一个线程的开始例程返回。

异常终止

5,    调用abort。

6,    接收到一个终止信号。

7,    对最后一个线程发出的取消请求做出响应。

 

_exit与_Exit的区别        :前者由POSIX定义,后者由ISO C定义。

exit与_exit, _Exit的区别:前者在退出时会调用由用户定义的退出处理函数,而后两者直接退出. (关于退出处理函数atexit(), 参考APUE2, P182.)


另外, 调用exit()或_Exit()需要包含, 调用_exit()需要包含.

 


要退出程序,除了return只能在main中调用外,exit, _exit, _Exit可以在任意函数中调用。

 

main函数最后调用return (0); 与调用exit (0)是等价的。


程序中调用exit时,exit首先调用注册的退出处理函数(通过atexit注册),然后关闭所有的文件流。

 

在程序运行结束时,main函数会向调用它的父进程(shell)返回一个整数值,称之为返回状态。该数值由exit或return定义。如果没有显示地调用它们,程序还是会正常终止,但返回数值不确定(以前面的hello, world程序为例,返回值为13,实际上是printf函数的字符个数)。
$ gcc -Wall -o hello hello.c
$ ./hello
$ echo $?             (echo $? 用于在bash中查看子程序的返回值)
13


程序映象


 

我们已经了解了一个可执行模块(executable module)是怎样由源代码生成的. 那么, 执行这个程序时, 又是怎样的情况呢? 下面介绍一个位于磁盘中的可执行程序是如何被执行的.


(1) 程序被执行时, 操作系统将可执行模块拷贝到内存的程序映像(program image)中去.

(2) 正在执行的程序实例被称为进程: 当操作系统向内核数据结构中添加了适当的信息, 并为运行程序代码分配了必要的资源之后, 程序就变成了进程. 这里所说的资源就包括分配给进程的地址空间和至少一个被称为线程(thread)的控制流.

上面只是大而化之地介绍了程序是如何转化为进程的, 这里关注的是内存程序映像. 在第(1)步中, 操作系统将可执行模块由硬盘拷贝到内存的程序映像中, 程序映像的一般布局如下图:

 

从低地址到高地址依次为下列段:

1, 代码段:即机器码,只读,可共享(多个进程共享代码段)。

2, 数据段:储存已被初始化了的静态数据。

3, 未初始化的数据段(也被称为BSS段):储存未始化的静态数据。

4, 堆:储存动态分配的内存.

5, 栈:储存函数调用的上下文, 动态数据.

 

另外, 在高地址还储存了命令行参数及环境变量.

 

程序代码(text)段一般是在进程之间共享的. 比如一个进程fork出一个子进程时, 父子进程共享text段, 子进程获得父进程数据段, 堆, 栈的拷贝.

 

 

磁盘映像, 内存映像, 地址空间之比较

前面提到, 可执行程序首先被操作系统从磁盘中拷贝到内存中, 还要为进程分配地址空间. 加上已经介绍的内存程序映像, 这就有三种关于可执行程序的存储组织了:

磁盘: 可执行文件段    内存: 内存程序映像  进程: 进程地址空间

下标列出了它们之间的对应关系:

 内存程序映像 进程地址空间
可执行文件段
 code(text)
code(text)
 code(text)
 data  data data
bss    data  bss
 heap data
-
stack
stack
-

内存程序映像和进程地址空间之比较

(1) 它们的代码段和栈相互对应.
(2) 内存程序映像的data, bss, heap对应到进程地址空间的data段. 也就是说, data, bss, heap会位于一个连续的地址空间中, code和stack可能位于另外的地址空间. 这就可以针对不同的段实现不同的内存管理策略: code段所在的地址空间可以是”只能被执行的”, data, bss, heap所在的地址空间是不可执行的…

正因为内存程序映像中的各段可能位于不同的地址空间中, 它们不一定位于连续的内存块中. 操作系统将程序映像映射到地址空间时, 通常将内存程序映像划分为大小相同的块(也就是page, 页). 只有该页被引用时, 它才被加载到内存中. 不过对于程序员来说, 可以视内存程序映像在逻辑上是连续的.

内存程序映像和可执行文件段之比较

(1) 明显, 前者位于内存中, 后者位于磁盘中.
(2) 内存程序映像中的code, data, bss段分别对应于可执行文件段中的code, data, bss段.
(3) 堆栈在可执行文件段中是没有的, 因为只有程序被加载到内存中运行时才会被分配堆栈.
(4) 虽然可执行文件段中包含了bss, 但bss并不被储存在位于磁盘中的可执行文件中.

使用file, ls, size, strip命令来查看相关信息

我们利用下面3个简单的例子来理清上述概念:

 (1) array1.c

 

int a[50000] = {1, 2, 3, 4};      /*  被显式初始化为非0的静态数据  */

int main(void) { 
   a[0] = 3;
   return 0;
}

 

 

(2) array2.c

 

int b[50000]; /* 未被显式初始化的静态数据 */
int main(void) {
   b[0] = 3;
   return 0;
}

 

 

(3) array3.c

int c[50000] = {0,0,0,0}; /* 被显式初始化为0的静态数据 */
int main(void) {
   c[0] = 3;
   return 0;
}

 

 

array1.c中, 数组a被显式初始化为非0.

array2.c中, 数组b未被显式初始化, 但由于它是静态变量, 所以被编译器初始化为默认的值: b中所有元素被初始化为0.

array3.c中, 数组c的所有元素被显式地初始化为全0.

 

$ gcc -Wall -o init array1.c

$ gcc -Wall -o noinit array2.c

$ gcc -Wall -o init-0 array3.c

 

使用ls命令, 查看磁盘文件大小:

$ ls -l init noinit init-0

-rwxr-xr-x 1 zp zp 209840 2006-08-21 15:56 init
-rwxr-xr-x 1 zp zp   9808 2006-08-21 15:57 init-0
-rwxr-xr-x 1 zp zp   9808 2006-08-21 15:57 noinit

我们发现array1.c 生成的init可执行文件比array2.c, array3.c生成的要大大约200000字节. 而array2.c 和array3.c生成的可执行文件在大小上是一样的!

 

严格地说, 上述内存程序映像中的”未初始化的静态数据”应该改称为”被初始化为全0的静态数据”: 被程序员显式地初始化为0或被编译起隐式地初始化为默认的0. 而且, 只有程序被加载到内存中时, 被初始化为全0的静态数据所对应的内存空间才被分配, 同时被赋予0值.

 

使用size命令, 查看内存程序映像信息:

$ size init noinit init-0

 text    data          bss         dec         hex    filename
822  200272        4        201098   3118a   init
822     252       200032  201106   31192   noinit
822     252       200032  201106   31192   init-0


size命令显示内存程序映像中的text, data, bss三个段大小, 以及这3个段大小之和的十进制和十六进制表示. (由于堆栈是在程序执行时动态分配的, size无法显示它们的大小.  可以使用ps命令查看进程地址空间信息. )

 

通过size命令, 我们可以得知如下事实:

1, 不管静态数据是否被初始化, 加载到内存中的程序映像大小是不变的. 它们之间的区别只是data和bss段大小的不同( 影响磁盘文件的大小).

2, 由于size不计算堆栈大小, 所以ls命令和size命令类出的磁盘程序映像大小和内存程序映像大小应该是一样的, 但通过上面的ls和size命令输出我们发现:

(1) 若静态变量被初始化为非0, 磁盘映像要大于内存映像.

(2) 若静态变量被初始化为全0, 磁盘影响(映像)要小于内存影响(映像).


这是因为:

(1) 位于磁盘中的可执行程序中不关(光)包含上面类出的磁盘映像的内容(code, data, bss), 它还包括: 符号表, 调试信息, 针对动态库的链接表等内容. 但这些内容在程序被执行时是不会被加载到内存中的.

使用file命令可以查看可执行文件的信息. 使用strip命令可以删除可执行程序中的符号表:
$ strip init; ls -l init
-rwxr-xr-x 1 zp zp 205920 2006-08-21 16:41 init
虽然符号表被删除了, 但init中还有其他信息, 所以仍比内存镜像大.)

(2) 静态变量被初始化为全0时(不管是程序员显式地初始化还是被编译器初始化为默认的0), 这一过程是在程序被加载到内存中时进行的, 数据无非位于data和bss段中, 所以它们是否被初始化为全0对于size来说, 内存映像总的大小是不变的, 但由于磁盘映像中不包含bss的值, 所以此时磁盘映像可能小于内存映像(如果bss段大于符号表, 调试信息, 链接表等的大小).

size命令不光可以查看最终生成的可执行文件的内存映像信息, 还可以查看可.o目标文件.

 

进程地址空间的数据段还包括了堆, 即内存程序映像中的堆. 堆一般用作动态分配内存. ( malloc(), calloc(), realloc(), free()).

———————————————————— 转载一,end ————————————————————————

而在转载二里面的答帖中,更是有高手给出了为什么ls 命令和size 命令有区别的间接解说:

———————————————————— 转载二,start ———————————————————————
链接:http://bbs.chinaunix.net/archiver/?tid-1719587.html

服务器、客户机代码

#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 4321
#define BACKLOG 1
#define MAXRECVLEN 1024
int main(int argc, char *argv[])
{
char buf[MAXRECVLEN];
int listenfd, connectfd; /* socket descriptors */
struct sockaddr_in server; /* server’s address information */
struct sockaddr_in client; /* client’s address information */
socklen_t addrlen;
/* Create TCP socket(AF_INET, SOCK_STREAM, 0)) == -1)
{cket */
if ((listenfd = so
/* handle exception */
perror(“socket() error. Failed to initiate a socket”);
exit(1);
}

/* set socket option */
int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1)
{
/* handle exception */
perror(“Bind() error.”);
exit(1);
}

if(listen(listenfd, BACKLOG) == -1)
{
perror(“listen() error. \n”);
exit(1);
}
addrlen = sizeof(client);
while(1){
if((connectfd=accept(listenfd,(struct sockaddr *)&client, &addrlen))==-1)
{
perror(“accept() error. \n”);
exit(1);
}
struct timeval tv;
gettimeofday(&tv, NULL);
printf(“You got a connection from client’s ip %s, port %d at time %ld.%ld\n”,inet_ntoa(client.sin_addr),htons(client.sin_port), tv.tv_sec,tv.tv_usec);

int iret=-1;
while(1)
{
iret = recv(connectfd, buf, MAXRECVLEN, 0);
if(iret>0)
{
printf(“%s\n”, buf);
}else
{
close(connectfd);
break;
}
/* print client’s ip and port */
send(connectfd, buf, iret, 0); /* send to the client welcome message */
}
}
close(listenfd); /* close listenfd */
return 0;
}
———————————————————————————————–
/* buf will store received text */
struct hostent *he; /* structure that will get information about remote host */
struct sockaddr_in server;

if (argc != 2)
{
printf(“Usage: %s \n”,argv[0]);
exit(1);
}

if((he=gethostbyname(argv[1]))==NULL)
{
printf(“gethostbyname() error\n”);
exit(1);
}

if((sockfd=socket(AF_INET,SOCK_STREAM, 0))==-1)
{
printf(“socket() error\n”);
exit(1);
}
bzero(&server,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr = *((struct in_addr *)he->h_addr);
if(connect(sockfd, (struct sockaddr *)&server, sizeof(server))==-1)
{
printf(“connect() error\n”);
exit(1);
}
  
  char str[] = “horst\n”
if((num=send(sockfd,str,sizeof(str),0))==-1){
printf(“send() error\n”);
exit(1);
}
if((num=recv(sockfd,buf,MAXDATASIZE,0))==-1)
{
printf(“recv() error\n”);
exit(1);
}
buf[num-1]=’\0′;
printf(“server message: %s\n”,buf);
close(sockfd);
return 0;
}

C语言结构体扫盲

定义与声明

——————————————————————

结构体的定义如下所示,struct为结构体关键字,tag为结构体的标志,member-list为结构体成员列表,其必须列出其所有成员;variable-list为此结构体声明的变量。

/*——————————————————————*/

struct tag{
member-list;
}variable-list;

/*—————————————————————–*/

1、结构体基本定义:

//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct {
    int a;
    char b;
    double c;
} s1;
=============================================================================

2、定义的同时进行变量声明

//同上声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE{
    int a;
    char b;
    double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3; 
=============================================================================

3、使用typeof

//也可以用typedef创建新类型
typedef struct{
    int a;
    char b;
    double c; 
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;
=============================================================================
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct {
    int a;
    char b;
    double c;
} s1;
//同上声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE{
    int a;
    char b;
    double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3; 
//也可以用typedef创建新类型
typedef struct{
    int a;
    char b;
    double c; 
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;

c语言通过函数地址调用函数

函数名即为函数指针,也是其地址。

只要定义一个函数指针(按照要调用的函数形式进行定义),将要调用的函数名赋予它,即可调用。

#include <stdio.h>

typedef void(*pfun)(int x); // 指针函数:指向void返回,带参数int参数的函数

void output(int x)
{
    printf("x: %d\n", x);
}

int main()
{
    pfun p = output; // 将output函数指针赋予p
    p(10); // 通过函数指针调用函数
    return 0;
}

用户态和内核态

就像世界上的人并不平等一样,并不是所有的程序都是平等的。世界上有的人占有资源多,有的人占有资源少,有的人来了,别人得让出资源,有的人则专门为别人让出资源。程序也是这样,有的程序可以访问计算机的任何资源,有的程序则只能访问非常受限的少量资源。而操作系统作为计算机的管理者,自然不能和被管理者享受一样的待遇,它应该享有更多的方便或特权。为了区分不用程序的不同权利,人们发胆了内核和用户态的概念。

那么什么是内核态,什么是用户态呢?只要想一想现实生活中,处于社会核心的人与处于社会边缘的人有什么区别就能明白处于核心的人拥有的资源多!因此,内核态就是拥有资源多的状态,或者说访问资源多的状态,我们也称之为特权态。相对来说,用户态就是非特权态,在此种状态下访问的资源将受到限制。如果一个程序运行在特权态,则该程序就可以访问计算机的任何资源,即它的资源访问权限不受限制。如果一个程序运行在用户态,则其资源需求将受到各种限制。

例如,如果要访问操作系统的内核数据结构,如进程表,则需要在特权态下才能办到。如果要访问用户程序里的数据,则在用户态下就可以了。

由于内核态的程序可以访问计算机的所有资源,这种程序的可靠性和安全性就显得十分重要。试想如果一个不可靠的程序在内核态下修改了操作系统的各种内核数据结构,结果会怎样呢?整个系统有可能崩溃。而运行于用户态的程序就比较简单了,如果其可靠性和安全性出了问题,其造成的损失只不过是让用户程序崩溃,而操作系统将继续运行。

很显然,内核态和用户态各有优势:运行在内核态的程序可以访问的资源多,但可靠性、安全性要求高,维护管理都较复杂;用户态程序访问的资源受限,但可靠性、安全性要求低,自然编写维护起来都较简单。一个程序到底应该运行在内核态还是用户态取决于其对资源和效率的需求。

一般来说,一个程序能够运行于用户态,就应该让它运行在用户态。只在迫不得已的情况下,才让程序运行于内核态。只要看看一个国家的治理就清楚了。我们拿什么标准来判断什么事情应该归国家领导管理。凡是牵扯到计算机本体根本运行的事情都应该在内核态下执行,只与用户数据和应用相关的东西则放在用户态执行。另外,对时序要求特别高的事情,也应该在内核态做。你有没有想过,国家领导出门怎么不塞车呢?

那么什么样的功能应该在内核态下实现呢? 首先,CPU管理和内存管理都应该在内核态实现。这些功能可不可以在用户态下实现呢?当然能,但是不太安全。就像一个国家的军队(CPU和内存在计算机里的地位就相当于一个国家的军队的地位)交给老百姓来管一样,是非常危险的。所以从保障计算机安全的角度来说,CPU和内存的管理必须在内核态实现。

诊断与测试程序也需要在内核态下实现。因为诊断和测试需要访问计算机的所有资源,否则怎么判断计算机是否正常呢?就像中医治病,必须把脉触摸病人。你不让中医触摸,他怎么能看病呢(当然,很多人认为中医是伪科学,根本治不了病,本书对此问题不做讨论)?输入输出管理也一样,因为要访问各种设备和底层数据结构,也必须在内核态实现。

对于文件系统来说,则可以一部分放在用户态,一部分放在内核态。文件系统本身的管理,即文件系统的宏数据部分的管理,必须放在内核态,不然任何人都可能破坏文件系统的结构;而用户数据的管理,则可以放在用户态。编译器、网络管理的部分功能、编辑器用户程序,自然都可以放在用户态下执行。图3.8描述的是Windows操作系统的内核态与用户态的界线。

(点击查看大图)图3.8Windows操作系统的内核态与用户态的界线

何为RTS Threshold和Fragmentation Threshold

何为RTS Threshold和Fragmentation Threshold

RTS Threshold是Request to Send的缩写,它规定了低频射频信号的包大小,设置的越小,那么相同数据就需要越多的包来发送。它一般在两个设备进行传输前会进行握手操作来确定RTS包大小,包大小从高到低尝试,确定后就以这个大小来传输数据。
本文中遇到的问题就是路由器的RTS包大小超过了网卡所能承受的最大值,导致网卡和路由器联系频频掉包,导致断线。

Fragmentation Threshold设置的是最大包长度,如果数据超过这个长度,就必须被分包传输

安装apue.h文件

1、下载源码

wget http://www.apuebook.com/src.3e.tar.gz

2、解压

tar -zxvf src.3e.tar.gz

3、文件复制

cp ./apue.2e/include/apue.h /usr/include/

4、复制error.c

cp ./apue.2e/lib/error.c /usr/include/

5、编辑/usr/include/apue.h文件

在文件最后#endif前加上包含error.c的代码:#include “error.c”;

进程和线程(转自阮一峰博客)

进程(process)和线程(thread)是操作系统的基本概念,但是它们比较抽象,不容易掌握。

最近,我读到一篇材料,发现有一个很好的类比,可以把它们解释地清晰易懂。

1.

计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。

2.

假定工厂的电力有限,一次只能供给一个车间使用。也就是说,一个车间开工的时候,其他车间都必须停工。背后的含义就是,单个CPU一次只能运行一个任务。

3.

进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。

4.

一个车间里,可以有很多工人。他们协同完成一个任务。

5.

线程就好比车间里的工人。一个进程可以包括多个线程。

6.

车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。

7.

可是,每间房间的大小不同,有些房间最多只能容纳一个人,比如厕所。里面有人的时候,其他人就不能进去了。这代表一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。

8.

一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫“互斥锁”(Mutual exclusion,缩写 Mutex),防止多个线程同时读写某一块内存区域。

9.

还有些房间,可以同时容纳n个人,比如厨房。也就是说,如果人数大于n,多出来的人只能在外面等着。这好比某些内存区域,只能供给固定数目的线程使用。

10.

这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做“信号量”(Semaphore),用来保证多个线程不会互相冲突。

不难看出,mutex是semaphore的一种特殊情况(n=1时)。也就是说,完全可以用后者替代前者。但是,因为mutex较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计。

11.

操作系统的设计,因此可以归结为三点:

(1)以多进程形式,允许多个任务同时运行;

(2)以多线程形式,允许单个任务分成不同的部分运行;

(3)提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。

(完)

ubuntu安装nginx

1.到nginx官网下载源码包。最好下载稳定版本,nginx官网http://www.nginx.org/

2.安装nginx依赖包运行命令:

sudo apt-get install libssl-dev
sudo apt-get install libpcre3 libpcre3-dev

3.解压下载的nginx源码包。检查编译环境 ./configure –prefix=/usr/local/nginx

4.编译安装 make && make install

5.到nginx安装目录执行启动命令:/usr/local/nginx/sbin/nginx

关闭命令:/usr/local/sbin/nginx -s stop (停止)
重启命令:/usr/local/sbin/nginx -s reload (重启)

启动之后浏览器中http://localhost/查看是否成功。

安装还可以进行在线安装:

安装命令:sudo apt-get install nginx

接下来系统将提示进行下载安装

6.nginx配置文件所在目录/usr/local/nginx/conf/nginx.conf

配置文件详解如下:

user www-data;
worker_processes 4;
pid /run/nginx.pid;

events {
worker_connections 768;
# multi_accept on;
}

http {

##
# Basic Settings
##

sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;

# server_names_hash_bucket_size 64;
# server_name_in_redirect off;

include /etc/nginx/mime.types;
default_type application/octet-stream;

##
# Logging Settings
##

access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;

##
# Gzip Settings
##

gzip on;
gzip_disable “msie6″;

# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

##
# nginx-naxsi config
##
# Uncomment it if you installed nginx-naxsi
##

#include /etc/nginx/naxsi_core.rules;

##
# nginx-passenger config
##
# Uncomment it if you installed nginx-passenger
##

#passenger_root /usr;
#passenger_ruby /usr/bin/ruby;

##
# Virtual Host Configs
##

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}

#mail {
#    # See sample authentication script at:
#    # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
#
#    # auth_http localhost/auth.php;
#    # pop3_capabilities “TOP” “USER”;
#    # imap_capabilities “IMAP4rev1″ “UIDPLUS”;
#
#    server {
#        listen     localhost:110;
#        protocol   pop3;
#        proxy      on;
#    }
#
#    server {
#        listen     localhost:143;
#        protocol   imap;
#        proxy      on;
#    }
#}

详细看的话这里只是一些基本的配置

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;

可见文件/etc/nginx/sites-enabled/default也是一份重要的配置文件。我这里把内容贴在这里

# You may add here your
# server {
#    …
# }
# statements for each of your virtual hosts to this file

##
# You should look at the following URL’s in order to grasp a solid understanding
# of Nginx configuration files in order to fully unleash the power of Nginx.
# http://wiki.nginx.org/Pitfalls
# http://wiki.nginx.org/QuickStart
# http://wiki.nginx.org/Configuration
#
# Generally, you will want to move this file somewhere, and start with a clean
# file but keep this around for reference. Or just disable in sites-enabled.
#
# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
##

server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;

root /usr/share/nginx/html;
index index.html index.htm;

# Make site accessible from http://localhost/
server_name localhost;

location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
# Uncomment to enable naxsi on this location
# include /etc/nginx/naxsi.rules
}

# Only for nginx-naxsi used with nginx-naxsi-ui : process denied requests
#location /RequestDenied {
#    proxy_pass http://127.0.0.1:8080;
#}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
#error_page 500 502 503 504 /50x.html;
#location = /50x.html {
#    root /usr/share/nginx/html;
#}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
#    fastcgi_split_path_info ^(.+\.php)(/.+)$;
#    # NOTE: You should have “cgi.fix_pathinfo = 0;” in php.ini
#
#    # With php5-cgi alone:
#    fastcgi_pass 127.0.0.1:9000;
#    # With php5-fpm:
#    fastcgi_pass unix:/var/run/php5-fpm.sock;
#    fastcgi_index index.php;
#    include fastcgi_params;
#}

# deny access to .htaccess files, if Apache’s document root
# concurs with nginx’s one
#
#location ~ /\.ht {
#    deny all;
#}
}

# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
#    listen 8000;
#    listen somename:8080;
#    server_name somename alias another.alias;
#    root html;
#    index index.html index.htm;
#
#    location / {
#        try_files $uri $uri/ =404;
#    }
#}

# HTTPS server
#
#server {
#    listen 443;
#    server_name localhost;
#
#    root html;
#    index index.html index.htm;
#
#    ssl on;
#    ssl_certificate cert.pem;
#    ssl_certificate_key cert.key;
#
#    ssl_session_timeout 5m;
#
#    ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
#    ssl_ciphers “HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES”;
#    ssl_prefer_server_ciphers on;
#
#    location / {
#        try_files $uri $uri/ =404;
#    }
#}

————————————————————————————————————————————————–

ubuntu安装Nginx之后的文件结构大致为:

所有的配置文件都在/etc/nginx下,并且每个虚拟主机已经安排在了/etc/nginx/sites-available下

启动程序文件在/usr/sbin/nginx

日志放在了/var/log/nginx中,分别是access.log和error.log

并已经在/etc/init.d/下创建了启动脚本nginx

默认的虚拟主机的目录设置在了/usr/share/nginx/www

ubuntu文件系统

/   根目录

    │
    ├boot/     启动文件。所有与系统启动有关的文件都保存在这里
     │    └grub/   Grub引导器相关的文件
    │
    ├dev/       设备文件
    ├proc/      内核与进程镜像
    │
    ├mnt/      临时挂载
    ├media/   挂载媒体设备
    │
    ├root/      root用户的$HOME目录
    ├home/
    │    ├user/   普通用户的$HOME目录
    │    └…/
    │
    ├bin/      系统程序
    ├sbin/      管理员系统程序
    ├lib/      系统程序库文件
    ├etc/     系统程序和大部分应用程序的全局配置文件
    │   ├init.d/  SystemV风格的启动脚本
    │   ├rcX.d/  启动脚本的链接,定义运行级别
    │   ├network/   网络配置文件
    │   ├X11/     图形界面配置文件
    │
    ├usr/
    │   ├bin/     应用程序
    │   ├sbin/   管理员应用程序
    │   ├lib/     应用程序库文件
    │   ├share/   应用程序资源文件
    │   ├src/     应用程序源代码
    │   ├local/
    │   │     ├soft/     用户程序
    │   │     └…/     通常使用单独文件夹
    │   ├X11R6/   图形界面系统
    │
    ├var/        动态数据
    │
    ├temp/        临时文件
    ├lost+found/   磁盘修复文件

为什么Linux不需要磁盘碎片整理

linux系统

如果你是个Linux用户,你可能听说过不需要去对你的linux文件系统进行磁盘碎片整理。也许你注意到了,在Liunx安装发布包里没有磁盘碎片整理的工具。为什么会这样?

为了理解为什么Linux文件系统不需要磁盘碎片整——而Windows却需要——你需要理解磁盘碎片产生的原理,以及Linux和Windows文件系统它们之间工作原理的不同之处。

什么是磁盘碎片

很多Windows用户,甚至是没有经验的用户,都深信经常对文件系统进行碎片整理会提高计算机的速度。但并不是很多人知道这其中的原委。

简单的说,磁盘驱动器上有很多扇区,每个扇区都能存放一小段数据。文件,特别是大文件的存储需要占用很多不同的扇区。假设现在你有很多个文件存在的文件系统里,每个文件都会被存储在一系列连续的扇区里。后来你更新了其中的一个文件,它的体积变大了。文件系统会尝试把文件新增的部分存放到紧邻原始文件的扇区里。可不幸的是,它周边已经没连续的足够扇区空间了,文件需要被分割成数段——这些都在自动进行的。当从磁盘上读取这个文件时,磁盘磁头需要跨越数个不同的物理位置来读取各个扇区——这样会使速度降低。

磁盘碎片整理就是小心的移动这些小文件块来减少碎片,让每个文件都能连续的分布在磁盘上。

windows系统

当然,如果是固态硬盘,那情况又不同了,固态硬盘没有机械移动,不应该进行碎片整理——对一个U盘进行碎片整理通常会降低它的寿命。在最新版的Windows系统里,你实际上不需要关心系统上的碎片——Windows会自动替你整理。

Windows文件系统的工作原理

微软老的FAT文件系统——最后一次使用是在Windows 98 和 Window ME上,可如今的U盘上还在使用它——并不会智能的管理文件。当你把一个文件存入FAT文件系统里时,系统会尽量的把它存到靠近磁盘开始的地方。当你存入第二个文件时,它会紧接着第一个文件——一个接着一个。当原始文件体积变大后,它们一定会产生碎片。根本没有留给它们增长的空间。

微软新的NTFS文件系统——使用在装有Windows XP和2000的PC机上——稍微智能了一点。它会在磁盘上在文件周围分配一些“缓冲”的空闲空间,尽管如此,任何Windows用户都会告诉你,经过一段时间的使用后,NTFS文件系统还是会形成碎片。

由于这些文件系统的工作原理,它们注定需要进行碎片整理来保持高性能。微软在它最新的视窗系统里通过在后台运行一个磁盘碎片整理进程来解决这个问题。

windows磁盘碎片整理

Linux文件系统的工作原理

Linux的ext2, ext3, 和 ext4 文件系统 ——Ubuntu和大多数最新的Linux发布版中使用的是ext4——采用了一种更聪明的方法来存放文件。与把多个文件并排放在磁盘上不同,Linux文件系统把所有文件都分散到了整个磁盘上,每两个文件之间都留有相当巨大的空闲空间。当文件被修改、体积增加时,它们通常有足够的空间来扩展。一旦有碎片产生时,文件系统会尝试移动整个文件来消除碎片,所以你不需要一个碎片整理工具。

linux文件系统

因为这种工作方式,当磁盘快要装满时,你开始会发现有碎片开始产生。如果已用空间超过95%(甚至80%),你会发现碎片开始变多。但不管怎样,这个文件系统的设计会使正常情况下不产生碎片。

如果你在Linux系统上遇到了磁盘碎片问题,你很可能需要一个更大的硬盘了。如果你真想整理一个文件系统,这最简单的方式也许是最可靠的方式:把文件从这个分区里考出,删除这些文件,然后考回这些文件。当你把文件考回硬盘时,文件系统会智能为文件分配存储空间。

你可以使用 fsck 命令来查看Linux文件系统上的磁盘碎片情况——在输出结果里寻找“non-contiguous inodes”信息。