分享
 
 
 

stevens那本socket的第一部分的简要翻译

王朝other·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

以前被流放到乡下,无聊时翻的,昨天从盘上翻出来,觉得从来没在网上贴过,埋没了可惜,就灌到CSDN来了,高手不要笑话我,转贴也不用注明了

第一部分:TCP/IP

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

Client-Server的概念:

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

譬如WWW Server和浏览器,FTP Server和各式各样的FTP工具,等等。

一般意义上的Server提供一种特定的服务,运行在后台,并没有直接

和用户交互的界面、命令行等等,它响应Client的服务请求,并把Client

需要的数据返回给Client,当然,其中可能要经过数据库操作或计算等等。

Client是客户端程序,直接与用户或操作员发生交互,与远程的服务

器上的或本地的后台Server通信,完成用户的请求。一个Client在任何时

刻只能和一个确定的Server通信,而Server,可以同时处理多个Client。

Client-Server是编写网络程序的一般模式(在某些情况下可能会有

更复杂的结构,譬如一个应用程序既做Server又做Client)。

应用程序的通信必须通过网络协议,本文中只讨论TCP/IP协议族。

本文不按照ISO的OSI七层网络分层结构,而是把OSI结构中最上面的

三层(应用层、表示层、会话层)统称应用层,把OSI结构中的最下面两

层(链路层、物理层)统称链路层。这样,我们的分层结构就是:

┏━━━━━┯━━━━━━━━━━━━━━━━━┓

┃应用层│与用户交互的应用程序和服务┃

┠─────┼─────────────────┨

┃传输层│TCP & UDP ┃

┠─────┼─────────────────┨

┃网络层│IPv4 & IPv6 ┃

┠─────┼─────────────────┨

┃链路层│网卡驱动程序、网卡本身、传输介质┃

┗━━━━━┷━━━━━━━━━━━━━━━━━┛

Client和Server不一定要在同一网段,可以是通过广域网连接的。当

然中间必然要经过路由器。

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

一个Daytime的TCP Client例子:

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

1 #include "unp.h"

2 int

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

4 {

5 int sockfd,n;

6 char recvline[MAXLINE + 1];

7 struct sockaddr_in servaddr;

8 if ( argc != 2 )

9 err_quit("usage: %s <IP address>", argv[0] );

10 if( (sockfd = socket( AF_INET, SOCK_STREAM, 0) < 0 )

11 err_sys("socket error");

12 bzero( &servaddr, sizeof(servaddr) );

13 servaddr.sin_family = AF_INET;

14 servaddr.sin_port = htons(13);

15 if ( inet_pton( AF_INET, argv[1], &servaddr.sin_addr) <= 0)

16 err_quit("inet_pton error for %s", argv[1]);

17 if ( connect( sockfd, (SA *)&servaddr, sizeof(servaddr) ) <= 0 )

18 err_sys("connet error");

19 while( (n = read( sockfd, recvline, MAXLINE)) > 0 ){

20 recvline[n] = 0;

21 if ( fputs( recvline, stdout) == EOF )

22 err_sys("fputs error");

23 }

24 if ( n < 0 )

25 err_sys("read error");

26 exit(0);

27 }

这个程序运行后的结果大致如下:

# a.out 206.62.226.35

Fri Jan 12 14:27:52 1999

这个27行的程序中有许多细节问题,我们先大概看一下:

1:unp.h是我们自己的头文件,里面有一些宏定义,和一些include

语句。

2 - 3:命令行参数

10 - 11:创建一个TCP socket,所谓TCP socket,其实就是一个Internet

(AF_INET)的stream(SOCK_STREAM) socket,注意Internet的大写

的I,这样的写法指互连的网络,而不一定要是因特网;stream即

字节流的意思。与之对应的是SOCK_DATAGRAM,数据报的socket,

即:UDP。

12 - 16:填写socket的地址结构,填入server端的IP地址和要连接的端口

号(port_number)。我们先是通过bzero把整个结构的内容置为0,

把地址族address family填为:AF_INET,地址族是内核需要的一

个参数,尽管我们的程序都是用AF_INET,但实际上内核可以处理

多种地址结构,一个典型的例子是:AF_UNIX,只限于本地进程间

通信的socket地址结构。端口号赋为我们需要的服务的端口号,

在本例中是Daytime的端口:13,IP地址赋为命令行的参数值,由

于在这个地址结构中这些值都有特定的格式,所以我们用库函数:

htons:host to network short 和

inet_pton:presentation to numeric

来转换格式。

注意:对于某些早期的IPv4,不支持inet_pton,改用:inet_addr。

17 - 18:connect调用实现和servaddr结构中指定的IP & port的连接,注意

connect的第三个参数,这是servaddr结构的大小,这个参数也是

内核为了处理多种地址结构所必需的。

#define SA struct sockaddr_in

这样的写法不使程序行过长。

19 - 25:读取server的返回,并输出到屏幕(stdout)。

这里要注意,由于TCP是字节流协议,server返回的字符串可能是在

一个TCP段(TCP segment)内,也有可能是在很多个TCP段内,所以

我们在从TCP socket中读取数据的时候总是把read放在循环内,直

到read返回0(对方关闭了socket)或者返回小于0的数(error)。

在本例子中,是由于server关闭了它的socket,使得我们程序中的

read返回0,这种形式也存在于HTTP中;但还有其他的协议,譬如FTP

和SMTP,是在每个记录尾部标记回车换行,还有一种情况,是在每个

记录头部先声明此记录的长度,SUN RPC和DNS就是这样的。

最后要说明的是:TCP本身不提供记录边界的服务,应用程序如果需

要知道一个记录是否结束,它必须自己想办法,譬如象上面这三种。

26:程序结束。UNIX会关闭所有没有关闭的文件句柄,包括socket。

东西太多了,慢慢来,不急。

==========

协议无关性

==========

我们上面这个程序是建立在IPv4的基础上的,如果我们要它在IPv6下工作,我

们要做如下改动:

7 struct sockaddr_in6 servaddr;

10 if( (sockfd = socket( AF_INET6, SOCK_STREAM, 0) < 0 )

13 servaddr.sin6_family = AF_INET6;

14 servaddr.sin6_port = htons(13);

15 if ( inet_pton( AF_INET6, argv[1], &servaddr.sin6_addr) <= 0)

也就是所有用到AF_INET或者servaddr.sin_addr的地方。以后会有一个叫:

getaddrinfo的调用,可以让我们编写出与协议无关的程序。

人们总是习惯于说:169.xici.net.cn的HTTP服务,而不是说:10.74.32.74的

80端口。所以我们必须要能把域名和服务转换成系统调用能接受的形式,有这样的

函数:gethostbyname和getservbyname。

========

出错处理

========

我们不可能每个程序都这样写:

if ( (sockfd = socket ( AF_INET, SOCK_STREAM, 0) < 0 )

err_sys("socket error");

我们这样:

int

Socket( int family, int type, int protocol)

{

int n;

if ( (n = socket( family, type, protocol )) < 0 ){

err_sys("socket error");

return n;

}

这样做的目的是使程序看起来不那么长。

每次系统调用出错时,系统调用返回-1,UNIX的全局变量errno被赋值,其值在

<sys/errno.h>中有详细的描述。

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

一个Daytime的TCP Server例子:

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

我们来自己写个Server吧:

1 #include "unp.h"

2 #include <time.h>

3 int

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

5 {

6 int listenfd, connfd;

7 struct sockaddr_in servaddr;

8 char buff[MAXLINE];

9 time_t ticks;

10 listenfd = Socket( AF_INET, SOCK_STREAM, 0 );

11 bzero( &servaddr, sizeof(servaddr) );

12 servaddr.sin_family = AF_INET;

13 servaddr.sin_addr.s_addr = htons(INADDR_ANY);

14 servaddr.sin_port = htons(13);

15 Bind( listenfd, (SA *)&servaddr, sizeof(servaddr) );

16 Listen( listenfd, LISTENQ );

17 for( ; ; ) {

18 connfd = Accept( listenfd, (SA *)NULL, NULL );

19 ticks = time( NULL );

20 snprintf( buff, sizeof(buff), "%.24s\r\n", ctime(&ticks) );

21 Write( connfd, buff, strlen(buff) );

22 Close( connfd );

23 }

24 }

11 - 15:通过填写地址结构和调用bind,把端口13和listenfd绑定;INADDR_ANY

的意思是允许listenfd接受任何地址来的连接请求,如果我们不希望这

样,我们也可以指定listenfd可以接受请求的地址。随后讨论。

16:调用listen,listenfd进入listening状态,LISTENQ是告诉内核可以为

连接请求开多大的队列,最大值一般是5。

17 - 21:accept是个阻塞调用,程序执行到这里,就停下,等待连接请求,当连

接请求到达,并且TCP的三段握手完成后,accept返回,返回一个新的

文件句柄:connfd,对每个不同的连接,accept返回不同的connfd,与

listenfd不同,listenfd只用于等待连接请求,而对每一个已经实现的

连接,我们是用这个连接的connfd来通信,交换数据。

snprintf是为了防止恶意攻击而采取的手段,如果用sprintf的话,大量

的服务请求很容易导致缓存溢出,与此类似有危险的函数还有:gets,

strcat,strcpy,通常用:fgets,strncat,strncpy来代替。

同上,这个程序也是与协议有关的,我们同样可以使用getaddrinfo来使得它与协

议无关。

这个server只能同时处理一个服务请求,在处理这个请求的过程中(尽管这个时间

非常短)不能接受新的请求,为了能够并发的处理client的服务请求,我们要用到UNIX

的系统调用:fork,为每个连接创建一个子进程,处理完后子进程自然死亡,还有一种

办法是事先就生成许多子进程,处理完后可以循环使用,后者一般不常用。

如果我们要让这个server长时间的运行在后台,我们一般会再改动些代码,来配合

UNIX下的superserver:inetd,这个以后再说。我们现在可以简单的用&把它放到后台。

当然,如果真要运行的话,端口不能用13,它已经被系统里正宗的Daytime server先用

了,我们必须换一个。

========

常用命令

========

netstat、ifconfig、ping,是最常用的三个检查网络状态的命令。可以查看man联

机文档获得详细用法。

大致用法是:

netstat

-i:查看网络接口,譬如:lo0、eth0

-r:查看路由表

-a:查看所有socket状态

-n:以数字形式显示

ifconfig

后面跟系统中定义的接口名,查看接口的详细资料

ping

后面跟IP地址,通过ICMP协议,检查网络的连通性;也可以通过广播地址,了解一

个网段内主机的运行情况;可以通过-s指定ICMP包的大小,来检查线路质量。

======

IP简介

======

IP层提供的是一种无连接无确认的数据报发送服务,IP层尽最大努力向目的地发送

数据报,但并不保证数据完整正确的到达,可靠性必须由上层协议来完成,譬如TCP,如

果使用UDP,则由应用程序自己负责。

IP层的核心内容:数据的分段、重装,网间中继和路由选择。

IPv4的结构:(IPv6就算了、、、里面好多东西还没定型)

0 3 4 7 8 15 16 31

┏━━━━┯━━━━┯━━━━━━━━┯━━━━━━━━━━━━━━━━┓ ──

┃ version│ header │ type of service│ total length ( in bytes ) ┃ ↑

┃ (4) │ length ││┃ │

┠────┴────┴────────┼─┬─┬─┬──────────┨ │

┃ identification │0 │DF│MF│ fragment offset ┃

┠─────────┬────────┼─┴─┴─┴──────────┨ 20

┃time to live (TTL)│ protocol │ header checksum ┃ bytes

┠─────────┴────────┴────────────────┨

┃ 32-bit source IPv4 address ┃ │

┠───────────────────────────────────┨ │

┃ 32-bit destination IPv4 address ┃ ↓

┠───────────────────────────────────┨ ──

┋ options ( if any ) ┋

┠───────────────────────────────────┨

┋ ┋

┋ data ┋

┋ ┋

┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

具体含义如下:

version:4位版本号,值为4(IPv4)

header length:整个IP头的长度,包括任选项(options),单位是四字节(32-bits),

由于这个域是4位,最大值为15,所以,IP头的最大长度为:60字节,

这60字节包括了20个字节的固定头部,40字节的任选项。

type of service:8位服务类型字段,由三位先导位(无定义)和四个服务类型位和

一个保留位(必须为0)组成,我们可以通过socket的IP_TOS选项

来设定服务类型:要求尽量大的传输量还是尽量小的延迟,或者是

尽量大的可靠性、尽量小的开销。

total length:16位,整个IP包的长度,包括了头部,我们可以算出,实际的载荷长

度是本字段的值减去四倍的header length的值。这个字段的一个不经

常提及的作用是:通常链路层都有一个最小数据帧长度,譬如802.3网

有个64字节的限制(计算机网络213-214),有可能一个合法的IP包不

能满足这个要求,本地链路层就会添加填充字段,以满足物理需要,远

端链路层照样会把这些填充字段交给它的IP层,对方IP层就要使用这个

长度值来判断有效数据的长度。

identification:16位,对于每个IP包都赋予不同的值,用于分段、重装。

DF & MF:do not fragment,和 more fragment的意思,这两个位和后面的13位的frag-

ment offset都用于分段和重装。

time to live: 由发送方赋值,在数据报投递的过程中,被每一个经过路由器减一,

一旦这个值被减到0,数据报被丢弃,这个字段的最大值是255,缺省

值一般是64,我们可以通过socket的IP_TTL和IP_MULTICAST_TTL选项

来设定它的值。

protocol:指明IP包中有效载荷字段内的内容使用的协议:1 (ICMPv4)、2 (IGMPv4)、

6 (TCP)、17 (UDP)、等等。

header checksum:头部(包括任选项)校验和。

address:源端和目的端IP地址。

options:一些路由选择、时戳、安全性的信息。最大40字节。可以通过getsockopt、

setsockopt的IPPROTO_IP、IP_OPTIONS来读取和设置。

关于IP地址的表示方法,我们用 :206.62.226.67/26 来表示主机206.62.226.67,它

的掩码为:255.255.255.192 (26个1),这个网段的网络地址为:206.62.226.64/26,广播

地址为:206.62.226.127。我们不再理会所谓的A、B、C类的划分。

环路地址:凡是发往地址127/8的IP包都不送往链路层,而是直接做为本地IP层的输入;

通常用这个地址来调试程序,宏定义为:INADDR_LOOPBACK127.0.0.1(这个最常用)。

multihomed host的定义:

1、有多个接口的主机,譬如两块802.3网卡(可以在同一个网段),或者是一块802.3

网卡和一块.25网卡,或者是一块802.3网卡和一个拨号备份线路。

2、在同一接口上设置的别名,使用不同的IP地址。(这个我不大懂,没配过)

===========

ICMPv4 简介

===========

ICMP:Internet Control Message Protocol

ICMP是建立在IP之上的,用于在主机、路由器之间传递出错和控制信息,通常是由

TCP/IP软件本身使用,但也有应用程序使用ICMP,譬如:Ping、Traceroute。

0 7 8 15 16 31

┏━━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━━━━━━━┓

┃ type │ code │ checksum ┃

┠─────────┴────────┴────────────────┨

┋ ┋

┋ ( remainder depends on type and code ) ┋

┋ ┋

┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

我们要关心的是应用程序能获得哪些ICMP消息,什么消息指明什么错误,错误原因是如

何返回到我们的程序的:

┏━━┯━━┯━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┓

┃type│code│ Description │ Handled by or errno ┃

┠──┼──┼─────────────────┼──────────────┨

┃ 0 │ 0 │ echo reply │user process (ping) ┃

┠──┼──┼─────────────────┼──────────────┨

┃ 3 │ │ destination unreachable: │ ┃

┃ │ │ │ ┃

┃ │ 0 │ network unreachable │EHOSTUNREACH ┃

┃ │ 1 │ host unreachable │EHOSTUNREACH ┃

┃ │ 2 │ protocol unreachable │ECONNREFUSED ┃

┃ │ 3 │ port unreachable │ECONNREFUSED ┃

┃ │ 4 │ fragmentation needed │EMSGSIZE ┃

┃ │ │ but DF bit set │ ┃

┃ │ 5 │ source route failed │EHOSTUNREACH ┃

┃ │ 6 │ destination network unknown │EHOSTUNREACH ┃

┃ │ 7 │ destination host unknown │EHOSTUNREACH ┃

┃ │ 8 │ source host isolated(obsolete)│EHOSTUNREACH ┃

┃ │ 9 │ destination network │EHOSTUNREACH ┃

┃ │ │ administratively prohibited │ ┃

┃ │ 10 │ destination host │EHOSTUNREACH ┃

┃ │ │ administratively prohibited │ ┃

┃ │ 11 │ network unreachable for TOS │EHOSTUNREACH ┃

┃ │ 12 │ host unreachable for TOS │EHOSTUNREACH ┃

┃ │ 13 │ commuication administratively │ ┃

┃ │ │ prohibited │(ignored) ┃

┃ │ 14 │ host precedence violation │(ignored) ┃

┃ │ 15 │ precedence cutoff in effect │(ignored) ┃

┠──┼──┼─────────────────┼──────────────┨

┃ 4 │ 0 │ source quench │kernel for TCP ┃

┃ │ │ │ignored by UDP ┃

┠──┼──┼─────────────────┼──────────────┨

┃ │ │ redirect: │ ┃

┃ │ │ │ ┃

┃ 5 │ 0 │ redirect for networkk │kernel updates routing table┃

┃ │ 1 │ redirect for host │kernel updates routing table┃

┃ │ 2 │ redirect for TOS and network │kernel updates routing table┃

┃ │ 3 │ redirect for TOS and host │kernel updates routing table┃

┠──┼──┼─────────────────┼──────────────┨

┃ 8 │ 0 │ echo request │kernel generates reply ┃

┠──┼──┼─────────────────┼──────────────┨

┃ 9 │ 0 │ router advertisement │user process ┃

┠──┼──┼─────────────────┼──────────────┨

┃10 │ 0 │ router solicitation │user process ┃

┠──┼──┼─────────────────┼──────────────┨

┃ │ │ time exceeded: │ ┃

┃11 │ │ │ ┃

┃ │ 0 │ TTL equals 0 during transit │user process ┃

┃ │ 1 │ TTL equals 0 during reassembly│user process ┃

┠──┼──┼─────────────────┼──────────────┨

┃ │ │ parameter problem: │ ┃

┃12 │ │ │ ┃

┃ │ 0 │ IP header bad (catchall error) │ENOPROTOOPT ┃

┃ │ 1 │ required option missing │ENOPROTOOPT ┃

┠──┼──┼─────────────────┼──────────────┨

┃13 │ 0 │ timestamp request │kernel generates reply ┃

┠──┼──┼─────────────────┼──────────────┨

┃14 │ 0 │ timestamp reply │user process ┃

┠──┼──┼─────────────────┼──────────────┨

┃15 │ 0 │ information request (obsolete) │(ignored) ┃

┠──┼──┼─────────────────┼──────────────┨

┃16 │ 0 │ information reply (obsolete) │user process ┃

┠──┼──┼─────────────────┼──────────────┨

┃17 │ 0 │ address mask request │kernel generates reply ┃

┠──┼──┼─────────────────┼──────────────┨

┃18 │ 0 │ address mask reply │user process ┃

┗━━┷━━┷━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┛

所有标注:user process的地方,都可以通过raw socket(原始套接字)获得。

原始套接字:直接面向IP层的socket:SOCK_RAM。

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

TCP/IP协议族简介

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

TCP/IP协议族中最重要的两个协议就是字面上这两个:TCP和IP,此外还有

UDP、ICMP、IGMP、ARP、RARP、BPF、DLPI。

ICMP我们已经介绍过了,下面简单说一下其他几个协议:

IGMP:Internet Group Message Protocol,用于多点广播。

ARP:Address Resolution Protocol,实现IP地址和链路层物理地址的映

射关系,只在链路层是广播形式的时候需要,譬如:802.3(Ethernet)、802.4

(token bus)、802.5(token ring)、FDDI等等,在点对点连接的情况下不需要。

RARP:Reverse Address Resolution Protocol,实现从链路层物理地址

到IP地址的映射,通常用于无盘工作站的启动过程。

BPF:BSD Packet Filter

DLPI:Data Link Provider Interface

上面这两个是直接和链路层通信的协议,只有Linux提供了一种特殊的叫做

:SOCK_PACKET的socket可以利用这两个协议。

值得注意的是:在IPv6中,ICMP、IGMP、ARP、RARP都由一个叫做ICMPv6的

协议取代,使用的也不再是IPv4、而是IPv6。

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

TCP& UDP 简介

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

这两个协议是我们的五层结构中的传输层协议,也是我们编写socket程序的

切入点,有必要详细介绍,不过再详细也不如自己去看书。

UDP:User Datagram Protocol,用户数据报协议

提供无连接的数据报服务,即不保证数据送达目的地。它只是简单的把应用

程序的数据交给IP层,IP层把数据封装进IP包,发往目的地,没有确认!如果有

必要,应用程序必须自己负责处理确认、超时、重传、流控等。

每一个 UDP的数据报都有一个长度,我们可以把它看做是一个记录,如果数

据完整无错的到达了目的地,对方可以知道记录的长度,这点和TCP不同。

UDP 还有个好处是,在Client和Server之间没有建立连接,对于只需一个包

就完成的事务,不需要烦琐的三段握手和四个包的断开确认,Client可以在向一

个Server发送完数据后马上使用刚才这个socket向另外一个Server发送,同样,

Server的同一个socket也可以顺序接收不同的Client的数据。

TCP:Transfer Control Protocol

传输控制协议,提供面向连接的、全双工的、可靠的、顺序的、无重复的字

节流服务,TCP 协议把数据以通信双方所能接受的最大发送长度为单位把用户数

据分段发送,TCP 协议不保持数据的记录边界;TCP 内部自己有处理诸如确认、

超时、重传、流控等的机制。

面向连接:要使用TCP 通信,就必须先建立连接,然后才能交换数据,最后

还要断开连接。

可靠性: TCP向对方发送数据的时候,它会等待一个确认,如果没有收到确

认,TCP将重发一遍,继续等待确认,一定次数的重传后,TCP会放弃,并返回出

错信息。TCP中有个叫RTT:round-trip time 的算法,可以知道确认大概在什么

时候到达,RTT ,在一个LAN上可能是微秒级的,在一个WAN上可能是秒级的,而

且这个值是动态的,在某个时刻,TCP认为RTT是一秒,但是30秒后,TCP 可能就

认为RTT是两秒,全由网络当时的负荷等决定,TCP可以算出下一个确认应该等待

多长时间。通常从第一次发送到最后放弃,是4分钟到10分钟。

有序性: TCP会为它发送的每个字节标记编号。譬如,应用程序要发送一

个2K长的数据,使得TCP把它们分做两个段(所谓段,就是TCP交给IP的数据单位)

发送,那么TCP给第一个段的编号就是1-1024,第二个段的编号号就是 1025-2048,

如果数据没有按顺序到达,或者是出现了重复(由于无必要的重传引起的),对

方的TCP会根据编号排列,丢弃副本,把正确的数据交给应用程序,

流控: TCP总是告诉它的另一端,它当前能够接受多少个字节的数据,这个

值叫做活动窗口,在任一时刻,活动窗口的值总是当前接收缓冲区的空闲总和,

窗口的值是动态的,当数据到达,窗口值变小,当应用程序读取后,窗口值变大,

如果接收缓冲区已满而应用程序不读取,则窗口值为 0,这时,在接收任何数据

之前,必须等待程序把当前的数据从缓冲中取走。

全双工:程序可以在同一个TCP连接同时接收和发送数据,TCP给每个方向的

传送保留不同的活动窗口值、序列号等。(UDP也可以使用同一个 socket发送和

接收)

______________________________________________________________________

TCP连接的建立

TCP建立的时候要经过如下4个步骤:

1、Server必须准备接受一个连接请求,通过:socket、bind、listen调用,

这个过程叫做被动打开(passive open)。

2、Client通过调用connect主动打开(active open),这使得Client端的TCP

发送一个SYN段(synchronize),告诉Server,我(Client)发送数据的初始序列号,

通常这个SYN段不包含任何数据,只有IP头,TCP头,有可能有TCP选项(即将谈到)。

3、Server给Client一个确认,并且也要给Client一个SYN段,告诉Client它

的初始序列号,和它的TCP选项(和Client发过来的选项通常不一样,全双工嘛),

通常这个ACK和SYN是放在一个段里面发送的。

4、Client确认Server的SYN。

如图:

client server

connect┃ SYN J ┃

(active open)┠───────────→┨

┃ ┃accept

┃ ACK J+1, SYN K ┃

connect return┠←───────────┨

┃ ┃

┃ ACK K+1 ┃

┠───────────→┨accept return

┃ ┃

client的初始序列号是J,server的初始序列号是K,ACK J+1 的意思是:已

经收到J,下一个期望的是J+1。

这里的J、K就是包的序列号,下面那个图的M、N也是这个意思,很显然,cl-

ient的初始序列号就是J,server的初始序列号就是K。

______________________________________________________________________

TCP的选项

1、MSS:Maximum Segment Size,最大段长度,我们可以通过TCP_MAXSEG的

定义,通过getsockopt、setsockopt来读取或设置它,通常这个值与链路层的最

大帧长有关系,通过适当的设定,可以减少或是防止IP层的数据分段。

2、活动窗口大小, TCP 可以向连接的另外一端建议的最大活动窗口大小是

65535字节,因为TCP头部中这个字段的宽度是16位,最大只能到65535 ,但是在

一些速度很快的网络上(或者是时延比较大的地方:卫星),这个值不够用,于

是新的定义是把这个值左移14位,那么最大值就是65535乘以2的14次方,等于:

1073725440字节,够了。为了与早期没有这个选项的TCP兼容:TCP可以提出它建

议的窗口大小,但它只有在对方也提出这个选项的时候才对自己的窗口大小做调

整。宏定义:SO_RCVBUF。

3、Timestamp,时戳,多用于高速网,为了防止假丢包的错误,编程人员对

此无需了解。

______________________________________________________________________

TCP连接的释放要经过如下四个步骤:

1、首先调用close的一端(又叫active close),发送一个带FIN标志的TCP包,

表示数据发送结束。

2、收到FIN的一端(又叫:passive close),对FIN包做确认,在给应用程序

的接收缓冲的最后放入一个end-of-file;因为收到FIN就意味着在这个连接上不

可能再收到任何数据了。

3、一段时间后,应用程序收到end-of-file,调用close,被动关闭一端的

TCP发送FIN包。

4、主动关闭的一方收到FIN后,做确认。

如图:我们假设是client做active close。

client server

┃ FIN M ┃

close ┠───────────→┨

┃ ┃read return 0

┃ ACK M+1 ┃

┠←───────────┨

┃ ┃

┃ FIN N ┃

┠←───────────┨

┃ ┃

┃ ACK N+1 ┃

┠───────────→┨accept return

┃ ┃

通常需要四个包才能完全释放一个TCP连接,但有时候FIN M是随着数据一起

发送的,而ACK M+1和FIN N一起发送(应用程序很快就收到了end-of-file,调用

了close)。

在确认M+1后,在发送FIN N之前,有可能有数据从passive close一方发向

active close一方,这叫做:half-close,我们以后在讲shutdown调用的时候再

详细讨论。

______________________________________________________________________

TCP的状态

这个东西比较重要,因为netstat -a最后一列显示的就是这十一种状态中的

某一个。

首先我们定义CLOSED状态,就是socket的出发点,也是终点,在netstat -a

中是不可能显示出来的,因为netstat -a是不显示一个CLOSED的socket的,我们

可以把这个状态想的抽象一点,或者你也可以认为没有这个状态。

一个最普通的是ESTABLISHED状态,就是连接已经建立,正使用的状态。

由于active open的一方不一定是active close,也可能是passive close,

所以我们把open和close分开来说,首先说open过程中的状态:

active open一方:发送了SYN J后从 CLOSED 进入 SYN_SEND 状态,再收到

ACK J+1、SYN K、发送ACK K+1后,进入ESTABLISHED状态。

passive open一方:调用listen后从CLOSED进入:LISTEN状态,收到SYN J

后发送ACK J+1、SYN K,进入SYN_RCVD状态,再收到ACK K+1后进入ESTABLISHED

状态。

断开连接的时候:

active close一方:发送 FIN M后进入 FIN_WAIT_1 状态,收到ACK M+1后

进入FIN_WAIT_2状态,再收到FIN N后,发送ACK N+1,进入TIME_WAIT 状态(马

上详细讲这个TIME_WAIT)。

passive close一方:收到FIN M,发送ACK M+1,进入CLOSE_WAIT状态,当

应用程序调用了close后,发送FIN N,进入LAST_ACK状态,收到最后一个ACK

N+1后,回到CLOSED状态。

这里有一种特殊情况,就是双方同时调用close,这种情况很常见,因为TCP

不保持记录边界,当一个程序采用了先发送长度给对方的方法来标记记录的时候,

很经常出现双方同时关闭socket的情况:发送的发完就调用close,接收的先收到

长度,然后再收到长度指定的那么长的数据后,想都不想就close、、、

这样的话,有可能在发送了FIN后,还没收到ACK,先收到一个FIN,这个时候,

我们叫它:CLOSING状态,这种情况下,双方都还要发送、接收ACK,最后都进入

TIME_WAIT。

TIME_WAIT:每次active close的一方都要进入这个状态,等什么呢?

1、active close的一方也是发送最后一个ACK的一方,如果这个ACK丢失的话,

对方会再发送FIN,它就是要等这个不一定到来的FIN,如果不等待就进入CLOSED,

而ACK确实丢失,那么对方就会再发FIN,但是它永远收不到ACK了,对方的应用程

序就会收到一个error。

2、等待所有还在途中的IP包消亡。我们考虑这样一个极端的例子,它不是不

可能的:我们在206.62.226.63上ftp到198.69.10.2,本地的端口号是临时的,我

们假设它是1500,那么这个连接就是<206.62.226.63.1500 , 198.69.10.2.21>,

这个写法我们马上详细讲,在这个连接建立以后的某个时候,某个路由器或者是

某条线路出了毛病,延迟立即变大,在TIMEOUT后,包被重发,对方收到,然后这

个时候,关闭连接,假设没有TIME_WAIT状态的话,本地的临时端口号1500被释放,

然后马上就有另外一个用户与198.69.10.2的FTP端口连接,它的临时端口号也是

1500,那么,这个连接再次出现:<206.62.226.63.1500 , 198.69.10.2.21>,好,

这个时候,在线路或者是路由器出问题的时候被耽搁的包到了、、、出错、、、。

由以上两个原因我们可以看出,TIME_WAIT的时间应该能够等到那个不一定来

的FIN,还要能让所有在途中的IP包消亡:应该是2MSL,MSL:Maximum Segment

Lifetime,就是一个IP包能在网络上存活的最大时间,关于这个MSL,RFC1122 的

建议是2分钟,而BSD socket实现的时候是用的30秒。这个时间主要由IP包头部的

TTL和一些经验值决定。

如果在TIME_WAIT的过程中,ACK丢失,而对方重发的最后一个FIN又不断丢失,

直到TIME_WAIT结束,那么对方的应用程序是会收到一个 error,不过对于这种情

况,我们已经无能为力了。

TIME_WAIT时间到后,进入CLOSED状态。

______________________________________________________________________

TCP的端口号

我们要注意这么几点:

1、系统保留端口:1-1023,只有超级用户的进程才可以把这个范围里面的端

口和它的socket绑定作为server。

2、临时端口:1024-5000,这些是给本地上的client 进程用的,系统临时给

这些client进程分配这个范围内的端口号。我们要注意,如果我们的 server用了

这个范围内的端口号,那么,它不一定能在任何时候都运行成功(放在rc.d里面是

可以)。这个范围现在看来已经很小,不够用了,所以Solaris把它的范围移到了

32768-65535 ,还有一些版本的UNIX把上限5000改为50000。

3、有些client进程必须要使用保留端口号,譬如rlogin、rsh ,它们通过一

个叫rresvport的调用来获得513-1023中的未用端口号,这个调用从大的数值开始

一个一个试,直到遇到没有被占用的,一般情况下1023 是没有被占用的,也就是

说rresvport一般是返回1023。

________________________________________________________________________

socket pair:就是这种写法:<206.62.226.63.1500 , 198.69.10.2.21>

IP地址后面的数字表示当地的端口号,这就表示一对socket,它有四个元素组成:

本地IP、本地端口号、远端IP、远端端口号,一个socket pair在它所处的网络上

是唯一的,譬如因特网,它可以标识一个唯一确定的TCP连接。

尽管UDP不是面向连接的协议,但我们也可以定义UDP的socket pair,当然,

这没什么意义。

为了理解socket pair,我们考虑这样的情况:server是198.69.10.2的ftpd

进程,它绑定的端口是21,然后,206.62.226.63的一个用户与这个server建立了

连接,它获得的临时端口号是1500,这时,就有了个socket pair:< 198.69.10.

2.21 , 206.62.226.63.1500>,然后问题出来了,198.69.10.2上的端口21不是在

listen吗?那如果新的连接请求来了怎么办?回想那个Daytime的server的源码,

用于listen的socket是listenfd,而与client通信的socket是connfd,这么说吧:

在198.69.10.2上这时确实是有两个看上去一样的socket:IP地址一样,端口号一

样,但是,我们确定一个TCP连接是要一个socket pair,于是,如果有发给198.

69.10.2.21的包,TCP内核会检查包的来源,如果是206.62.226.63.1500来的,就

把包送给已经建立了连接的那个socket,如果不是,就送给那个在listen的socket。

更极端一点,如果server是并发的(ftpd确实是的),在处理第一个连接的时候生

成了一个子进程,然后从206.62.226.63又来了一个连接请求,然后它再开一个子

进程,这个socket pair譬如说是:< 198.69.10.2.21 , 206.62.226.63.1501>,

那么,以后凡是发给198.69.10.2.21的包,198.69.10.2上的TCP内核都会检查来

源,如果是206.62.226.63.1501来的,就交给第二个子进程的connfd,如果是206

.62.226.63.1500来的,就交给第一个子进程的connfd,如果都不是,就交给 --

listenfd。

________________________________________________________________________

IP层所受的物理限制和对TCP的影响:

1、IPv4包最大不能超过65535字节,包括头部,因为长度字段只有16位。

2、IPv6包最大不能超过65575字节,因为它的长度字段不包括头部的40字节。

3、MTU:Maximum Transmission Unit,由链路层决定的最大帧长,Ethernet

的是1500字节,PPP的MTU是可协商的,SLIP的是296字节、、、。

IPv4需要最少有MTU值为68字节的链路层,IPv6要576字节。

4、路径最小MTU,是指从一台主机到另一台主机中间所有链路的最小MTU,现

在这个值一般是Ethernet的1500字节。这个路径最小MTU,是有方向性的,从A到B

的值和从B到A的值可能不一样,因为路由可能不同。

5、当IP要在一个MTU比当前要发送数据的长度小的接口上发送包的时候,IP

就会把数据分段,这些被分段的数据在没有到达最终目的地之前不被重装。

对于IPv4,主机和路由器都可以进行包的分段,而在IPv6中,路由器不允

许对要它转发的包进行分段,而是简单的返回错误信息:Packet too big。(要

说明的是,如果是路由器本身产生的包,譬如由telnetd产生的,因为很多路由器

都要通过局域网来进行配置,直接接终端太麻烦;这些包是可以被路由器分段的)

6、如果“DF”(Don't fragment)被置的话,IPv4中的主机和路由器都不能对

包进行分段,而是返回:fragmentation needed but DF bit set 的ICMP 包或出

错信息。DF位的这个特性被用于找出路径最小MTU,当然IPv6也可以做这件事情了,

因为它的路由器本来就不能分段。

7、最小重装缓冲区(Minimum Reassembly Buffer Size)大小,IPv4中为576字

节,IPv6为1500字节,我们无法得知一个给定的IPv4主机是否能接收一个577 字节

的包。

8、TCP的MSS,已经说过了,这里要指出的是,它的值一般是路径最小MTU减去

TCP头部长度和IP头部长度,譬如,路径最小MTU是Ethernet的1500字节的时候,使

用IPv4的TCP提出的MSS就是1460,如果用的是IPv6的话,就是1440。

________________________________________________________________________

TCP的输出过程的简要描述:

每个TCP socket都有一个发送缓冲,我们可以通过setsockopt的SO_SNDBUF来改

变它的大小,当应用程序调用了write,内核就把所有的数据从应用程序的内存空间

拷贝到socket的发送缓冲,如果空间不够的话(有可能是应用程序的数据太多,同

时socket的发送

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有