TCP/IP网络技术基础重点
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
用TCP/IP进行网际互连
——客户-服务器编程与应用1.客户-服务器模式
在分布式计算中,一个应用程序被动地等待,而另一个应用程序通过请求启动通信的模式,称为客户-服务器模式。
其中:发起通信的应用程序称为客户,等待接收客户通信请求的程序称为服务器。
客户和服务器分别指两个应用程序,但在实际工作中,我们也将运行服务器程序的计算机简称为服务器。
2.充当客户的服务器
在服务器计算某个请求的响应时,它可能需要访问其他的网络服务。
因此,服务器也可能充当客户。
在一个有许多可供使用的服务器的网络环境中,经常可以发现某个应用的服务器对另一个应用则是客户。
3.并发
是指真正或表面呈现的同时计算。
真正的并发:DNA计算机、多处理器(Multiprocessing)计算机;表面呈现的并发:通过分时(time sharing)机制,使得单个处理器在多个任务之间足够快地切换,以致从表面上这些计算似乎是在同时进行的,从而获得并发的效果。
4.进程vs线程
进程:是执行程序的实例,是具有一定独立功能的程序关于某个数据集合上的一次运行活动,而不是程序的静态版本。
进程包括一段地址空间和至少一个执行的线程。
线程最重要的信息时一个指令指针,它指明该进程正在执行的地址。
线程:是进程中执行运算的最小单位,亦即执行处理机调度的基本单位。
关系:线程是进程的一个组成部分,线程由进程创建,因此一个进程中至少存在一个线程,线程还可以创建其它线程;进程是资源分配和保护的基本单位,线程只能在进程的地址空间活动,线程只能使用其所在进程的资源;线程作为基本的调度单位,其状态有:就绪、运行、阻塞等;进程中所有线程共享进程的存储空间和分配资源。
线程的特征:动态创建、并发执行、抢先、私有局部变量、共享全局变量、
共享文件描述符、协调和同步函数。
线程的优点:更高的效率:上下文交换的额外开销减少;共享的存储器:通信开销减少,且更容易构造监控系统。
补充:创建和撤消线程的开销非常小:不需要向系统请求独立的地址空间及进行相关的地址空间复制(例如父子进程),因此创建和撤销线程系统的开销要远小于进程。
线程的缺点:由于线程间共享存储器和进程状态,一个线程的动作可能对同一进程中的其他线程产生影响:线程安全;健壮性。
进程中有多个线程在同时运行,而这些线程可能会同时运行一段代码。
如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
多个线程操作同一个数据段时,用相应的互斥机制,避免数据段中的数据错误。
5.局部和全局变量的共享
如果多个线程在同一个进程内执行,则它们各自拥有一份独立的局部变量的副本,且都可以共享进程的全局变量副本。
fork()函数:将运行的程序分为两个几乎完全一样的进程。
用户执行该并行程序时,OS创建一个含单线程的进程执行代码;当线程执行到fork()调用时,OS 复制进程,每个进程都启动一个从代码的同一位置开始执行的线程,这两个进程中的线程继续执行,就像两个用户同时启动了该应用程序的两个副本。
调用fork()函数的进程称为父进程, fork()函数新创建的进程称为子进程。
虽然一个进程可以包含多个线程,但fork()函数新创建的进程均为单线程的;fork函数要向它的调用者返回一个值。
返回给原来进程的值与返回给新创建进程的值是不同的。
在新创建的进程里,fork返回零;在原来的进程里,fork返回一个小的正整数来标识(pid)新创建的进程。
#include<stdib.h> #include<stdio.h> int sum;
main(){
int i;
sum=0;
fork();
for (i=1;i<=5;i++)
{ printf(“The value of I is %d\n”, i);
fflush(stdout); //在printf()后使用fflush(stdout)的作用是立刻将要输出的内容输出。
sum+=i;}
printf(“The sum is %d\n”,sum);
exit(0);
}
#include<stdio.h>
int sum;
main(){
int pid;
pid=fork();
if(pid!=0)
{ printf(“The original process print s this.\n”);
}
else{
printf(“The new process prints this.\ n”);
}
exit(0);
6.接口的功能
分配用于通信的本地资源;指定本地和远程通信端点;(客户端)启动连接;(客户端)发送数据报;(服务器端)等待连接到来;发送或接收数据;判断数据何时到达;产生紧急数据;处理到达的紧急数据;从容终止连接;处理来自远程端点的连接终止;异常终止通信;处理错误情况或连接异常终止;连接结束后释放本地资源。
7.套接字API
TCP/IP标准没有规定应用软件和TCP/IP协议软件接口的细节,而只是建议了所需的功能,并允许系统设计者选择有关API的具体实现细节。
套接字API提供了许多综合的功能,这些功能支持使用众多可能的协议来进行网络通信。
套接字调用将所有的TCP/IP协议看作一个单一的协议族,表示为PF_INET。
套接字调用让应用程序使用所要求的服务的类型来指明操作,而不是指明具体协议的名字。
8.套接字描述符vs文件描述符
文件描述符可通过系统函数open()创建,代表一个被打开的文件;套接字:就像文件一样,每个套接字也是由一个小整数标识,称为套接字描述符。
应用程序可以调用系统函数socket()来创建套接字。
操作系统为每个运行的进程维护一张单独的描述符表,文件描述符与套接字描述符都在该描述符表中分配,因此,在一个应用进程中不能同时存在具有相同值的文件描述和套接字描述符。
9.在程序中使用套接字调用
客户端:Step1:客户创建套接字;Step2:调用connect连接服务器;Step3:交互时,使用send发送请求,使用recv接收应答;Step4:当连接结束时,客户调用close断开连接。
服务器端:Step1:服务器创建套接字;Step2:调用bind指明它所使用的本
地(公开的)协议端口;Step3:调用listen设置连接等待队列的长度,之后进入循环;Step4:在循环中,调用accept进行等待,直到有一个连接请求到达为止;Step5:有连接请求到达时,使用recv接收请求,使用send发送应答,从而实现与客户的交互;Step6:当连接结束时,调用close断开连接。
之后,服务器回到accept调用,在那里等待下一个连接请求的到达。
10.查找域名
客户必须使用sockaddr_in结构指明服务器的地址,但用户在启动程序时输入的服务器信息一般为服务器的IP地址或者是服务器的域名。
套接字API提供了两种例程库inet_addr和gethostbyname来执行上述转换功能。
inet_addr():用于将用户输入的用点分十进制表示的IP地址转换为用二进制表示的32位的IP地址。
gethostbyname():用于将用户输入的服务器的域名转换为用二进制表示的32位的IP地址,当它调用成功,将返回一个hostent结构,若失败,则返回0。
hostent结构在netdb.h中声明,包含有用二进制表示的32位的IP地址。
struct hostent *gethostbyname(const char *name);name参数:含有协议名的字符串的地址。
Struct socketaddr_in{u_char sin_len;u_short sin_famil y;u_short sin_port;struct in_addr sin_addr;char sin_zero[8]};
Struct hostent{char *h_name;char **h_aliases;int h_addrtype;int h_length;char **h_addr_list;}#define h_addr h_addr_list[0]
serv_addr.sin_addr=*((struct in_addr*)hptr->h_addr);//强制转换为struct sockaddr_in结构中的struct in_addr 结构,因为struct sockaddr_in 结构用struct in_addr 结构的变量sin_addr存储IP地址。
11.由名字查找某个熟知端口
客户软件查找特定服务的协议端口号,则需要调用getservbyname。
struct servent *getservbyname(const char *name, const char *proto) ;
name参数:要查询的服务名称,如telnet,http,smtp等;proto参数:要查询的协议,如tcp,udp等。
没有找到返回NULL,否则返回一个指向servent结构的指针。
Struct servent{char *s_name;char **s_aliases;int s_port;char *s_proto;}
由名字查找协议:客户或服务器将协议名映射成分配给该协议协议号,通过调用getprotobyname来按协议名查找其对应的协议号。
struct protoent *getprotobyname(const char *name);调用成功,则返回一个protoent类型的结构地址,失败则返回0。
Struct protoent{char *p_name;char **p_aliases; int p_proto;};
12.分配套接字
协议族不止一个协议,第一个参数指明协议族,第二个指明要求的服务,第三个指明某个特定的协议。
#include<sys/types.h>
#include<sys/socket.h>
int s;
s=socket(PF-INET,SOCK_STREAM,0);
13.将TCP套接字连接到某个服务器
系统调用connect允许TCP套接字发起连接。
Connec强迫执行了开始时的三次握手。
除非它建立了连接,或者TCP到达超时门限并放弃建立连接,否则对connec 的调用时不会返回的。
int connect(int sockfd, struct sockaddr* serv_addr, int addrlen);首先,它对指明的头阿姐子进行检测,以保证它是有效的并且没有创建连接。
第二,它将第二个参数给出的端点地址填入此套接字中。
第三,若此套接字还没有本地端点地址,它便为连接选择一个(IP地址和协议端口号)。
第四,它发起一个TCP连接并返回一个值,一次告诉调用者连接是否成功。
14. 构成连接的过程:
/* connectsock.c - connectsock */
extern int errno;
int errexit(const char *format, ...);
int connectsock(const char *host, const char *service, const char *transport ) /*
* Arguments:
* host - name of host to which connection is desired
* service - service associated with the desired port
* transport - name of transport protocol to use ("tcp" or "udp")
*/
{
struct hostent *phe; /* pointer to host information entry */
struct servent *pse; /* pointer to service information entry */
struct protoent *ppe; /* pointer to protocol information entry*/
struct sockaddr_in sin; /* an Internet endpoint address */
int s, type; /* socket descriptor and socket type */
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
if ( pse = getservbyname(service, transport) )
sin.sin_port = pse->s_port;
else if ((sin.sin_port=htons((unsigned short)atoi(service))) == 0) errexit("can't get \"%s\" service entry\n", service);
if ( phe = gethostbyname(host) )
memcpy(&sin.sin_addr, phe->h_addr, phe->h_length);
else if ( (sin.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE ) errexit("can't get \"%s\" host entry\n", host);
if ( (ppe = getprotobyname(transport)) == 0)
errexit("can't get \"%s\" protocol entry\n", transport);
if (strcmp(transport, "udp") == 0)
type = SOCK_DGRAM;
else
type = SOCK_STREAM;
s = socket(PF_INET, type, ppe->p_proto);
if (s < 0)
errexit("can't create socket: %s\n", strerror(errno));
if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0)
errexit("can't connect to %s.%s: %s\n", host, service,
strerror(errno));
return s;
}
/* errexit.c - errexit */
Int errexit(const char *format, ...)
{
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
exit(1);
}
15.循环服务器vs并发服务器
循环服务器(iterative server):描述在一个时刻只处理一个请求的一种服务器实现;并发服务器(concurrent server):描述在一个时刻可以处理多个请求的一种服务器。
并发服务器这个术语是指服务器是否并发的处理多个请求,
而不是指下层的实现是否使用了多个并发执行线程。
使用循环方法实现的服务器易于构建和理解,但这样的结果会使其性能很差,因为这样的服务器要使客户等待服务;相反,以并发方法实现的服务器难于设计和构建,但却有较好的性能。
16.面向连接的vs无连接的访问
连接性(connectivity)问题是传输协议的中心,而客户使用这个传输协议访问某个服务器。
提供TCP一种面向连接的传输服务,使用TCP的服务器是面向连接的服务器,提供UDP一种无连接的传输服务,使用UDP的服务器是无连接的服务器。
当我们考虑各种服务器实现策略的优缺点时,设计者必须记住,所使用的应用协议可能会限制某些或者所有的选择方案。
17.无状态vs有状态服务器
服务器维护的与客户交互活动有关的信息称为状态信息。
不保存状态信息的服务器称为无状态服务器(Stateless Server),反之称为有状态服务器(Stateful Server)。
18.TCPvsUDP
TCP语义:点到点通信,建立可靠连接,可靠交付,具有流控的传输,双工传输,流模式。
优点:由于TCP提供了传输可靠性保障,因此TCP服务器编程实现简单。
缺点:要求对每个连接都有一个单独的套接字,TCP在空闲的连接上根本不发送任何分组,需要耗费大量服务器资源。
UDP语义:多对多通信,不可靠服务,缺乏流控制,报文模式。
优点:允许从一个套接字上与多个主机进行通信,因此,服务器资源耗费较小。
缺点:不能利用传输层提供可靠的投递,而通过超时和重传机制来获得可靠性可能十分困难。
选择传输协议:如果不考虑应用协议所要求的语义,设计者就不能在面向连接和无连接的传输协议间做出选择。
19.循环的、面向连接的服务器的算法
1.创建套接字并将其绑定到他所提供服务的熟知端口上;
2.将给端口设置为被动模式,使其准备为服务器所用;
3.从该套接字上接受下一个连接请求,获得该连接的套接字;
4.重复地读取来自客户的需求,构造响应,按照应用协议向客户发回响应;
5.当与某个特定客户完成交互时,关闭连接,并返回步骤3以接受新的连接。
用INADDR_ANY绑定熟知端口:套接字接口定义了一个特殊的常量INADDR_ANY,它可以代替IP地址。
INADDR_ANY指明了一个通配地址(wildcard address),它与该主机的任何一个IP地址都匹配。
使用INADDR_ANY使得在多接口机上的单个服务器可以接受这样的通信,即传入数据的目的地址时该主机的任一个IP地址。
当为套接字指明本地端点时,服务器使用INADDR_ANY以取代某个特定的IP 地址,这就允许套接字接收发给该机器的任一个IP地址的数据报。
20.循环的、无连接的服务器的算法
1.创建套接字并将其绑定到所提供服务的熟知端口上;
2.重复地读取来自客户的请求,构造响应,按照应用协议向客户发回响应。
21.并发的、无连接的服务器的算法
主1 创建套接字并将其绑定到所提供服务的熟知地址上,让该套接字保持为未连接的;
主2 反复调用recvfrom接受来自客户的下一个请求,创建一个新的从线程(可能在一个新进程中)来处理响应。
从1 从来自主进程的特定请求以及到该套接字的访问开始;
从2 根据应用协议构造应答,并用sendto将该应答发回客户;
从3 退出(即,从线程在处理完一个请求后便终止)。
22.并发的、面向连接服务器的算法
主1 创建套接字并将其绑定到所提供服务的熟知地址上。
让该套接字保持非连接;
主2 将该端口设置为被动模式,使其准备为服务器所用;
主3 反复调用accept以便接受来自客户的下一个连接请求,并创建新的从线程或进程来处理响应。
从1 由主进程传递来的连接请求(即针对连接的套接字)开始;
从2 用该连接与客户进行交互:读取请求并发回响应;
从3 关闭连接并退出。
在处理完来自客户的所有请求后,从线程就退出。
23.使用单线程获得表面上的并发性
1.创建套接字并将其绑定到所提供服务的熟知端口上。
将该套接字加到一个表
中,该表中的项是可以进行I/O的描述符;
2.使用select在已有的套接字上等待I/O;
3.如果最初的套接字准备就绪,使用accept获得下一个连接,并将这个新的
套接字加入到表中,该表中的项是可以进行I/O的描述符;
4.如果是最初的套接字以外的某些套接字准备就绪,就使用recv或read获得
下一个请求,构造响应,用send或write将响应发回给客户;
5.继续按照以上的步骤2进行处理。
24.各服务器所适用的场合
循环的和并发的:循环的服务器容易设计、实现和维护,但是并发的服务器可以对请求提供更快的响应。
如果请求处理时间很短而且循环方案产生的响应时间对用户来说已经足够快了,那么就可以使用循环实现的方法。
真正的和表面上的并发:只有一个线程的服务器依靠异步I/O管理多个连接;而多线程的实现(不管是多个单线程的进程,还是一个进程有多个线程)允许操作系统自动提供并发性。
如果创建线程或切换环境的开销很大,或者服务器必须在多个连接之间共享或交换数据,那么可以使用单线程的方案。
如果使用线程的开销不大,而且服务器必须在多个连接之间共享或交换数据,那么可以使用多线程的方案。
面向连接的和无连接的:只有应用协议处理可靠性问题或每个客户访问它的服务器都是在同一局域网进行的,这时才使用无连接的传输。
只要客户和服务器被广域网所分隔,就要使用面向连接的传输。
25.多线程并发服务器vs多进程并发服务器
多线程并发服务器特点:具有较少的上下文切换开销和共享的存储器能力。
创建和撤销线程以及线程间的通信开销小。
但多线程一般缺乏健壮性,且需要考虑线程安全,故增加了编程的复杂性。
多进程并发服务器特点:各进程有其独立的地址空间,可靠性高,但进程调度开销大,无法资源共享,进程间通信开销大。
多线程并发服务器:服务功能相对较简单,服务器重启代价相对较小且客户端请求较多并且客户端平均请求连接时间短的应用。
多进程并发服务器:客服端请求在同一时刻相对较少,服务器负荷相对较轻,且对服务器的稳定性要求较高的应用。
26.使用单线程获得表面上的并发性
为什么:多线程并发并不能有效解决由于网络I/O阻塞造成的服务器性能下降的问题,但是单线程并发可以通过异步I/O来解决;响应所需开销中,I/O占了主导地位,服务器采用异步I/O(单线程)来提供客户间表面上的并发可提高效率。
什么时候:在服务器仅需要很少处理的情况下、线程或进程的创建开销十分昂贵的情况下、服务器在多个连接中共享信息。
27.设计多协议服务器的动机
为每个协议使用一个单独的服务器,其主要优点是便于控制,而其主要缺点就是重复:软件管理和排错变得冗长乏味、软件更改在多个服务器之间需要同步、多个服务器消耗过多资源。
采用多协议服务器:允许所有代码封装在一个程序里,消除了重复,容易协调各种变化;由一个单执行线程构成,可以扩展成允许并发处理TCP连接,并发处理请求;对系统资源的要求比单独的服务器要少。
多协议服务器:允许单一过程相应服务请求,而不必关心请求来自UDP还是TCP;共享代码使维护更容易,保证多种协议所提供服务的一致性。
并发协议服务器:在对于每个请求,服务器执行的计算很少的情况下,采用单个循环的服务器就足够了;当每个请求要求更多计算的情况下,一个多协议服务器可以创建新的线程或进程,并发处理每个TCP连接,同时还循环地处理UDP的请求。
28.超级服务器
提供多协议的多服务服务器称为超级服务器(Super server)静态配置:配置发生在服务器开始执行的时候,配置信息放置在一个服务器启动时可以读取的文件中,当配置信息发生改变时,管理员需要重启服务器;动态配置:配置发生在服务器运行的时候,服务器不必重启就可以重新定义他所提供的各种服务。
29.预分配
要想控制时延,限制最大并发数,并且使得服务器在进程创建时间较长(进程创建时间大于请求处理时间)时仍能维持高吞吐量,一个最直接的技术就是预
分配并发进程或线程。
使用预分配技术,即程序员在编写服务器程序时,即在它开始执行时就预先创建N个从线程或进程。
所有的从线程或进程都等待请求到达,而当一旦有请求到达时,一个等待的从线程或进程就开始执行并处理该请求,在处理完成后,从线程或进程并不退出,而是返回并继续等待下一个请求的到达。
优点是操作系统用于创建从线程或进程的额外开销较小,并可以有效减少时延。
缺点是程序员必须对资源的使用相当小心,这是因为从线程或进程持续运行,而每次处理完请求之后并不释放内存,虽然每次占用的内存空间很小,但当时间过长之后,仍可能最终耗尽所有的内存空间。
30.延时分配
从线程/进程的延迟分配技术。
并不具体指定使用循环还是并发技术,而是通过服务器先测量处理的开销,然后再依据实际情况来动态选用循环或并发的处理模式。
服务器循环处理每个请求:在请求开始处理时设置计时器,若计时器到时仍未处理完该请求则创建从进程,在从进程里接着处理该请求。
特点:如到达请求所需时长短,或到达请求非法则在循环方式下进行处理;当到达请求所需时长大于计时器设置时长时,才采用并发方式进行处理。
31.预分配vs延时分配
预分配提高了请求到达前服务器的并发等级、延迟分配提高了请求到达后服务器的并发等级;两者基于同一原理:通过把服务器的并发等级从当前活跃的请求数目中分离出来,设计人员可获得灵活性并提高服务器效率。
两种技术结合的实现方案:开始采用延迟分配,当遇到处理时间开销大的请求,服务器将创建从进程处理。
当这个从进程对该请求处理完成后,并不关闭;而是采用预分配的方式,该从进程继续运行,等待下一个请求到达。
32.隧道技术
是指一种利用公共网络(如:TCP/IP网络)的基础设施在其它网络(如:X.25网络)之间传递数据的方式。
使用隧道传递的数据可以是使用不同协议封装的数据包,隧道协议将这些其他协议封装的数据包重新加密并封装在新的数据包中发送。
新的报头提供了路由信息,从而使得被封装的数据包能够在隧道的两个端点之间通过公共互联网络进行路由传输。
被封装的数据包在公共互联网络上传递时
所经过的逻辑路径称为隧道。
一旦到达网络终点,被封装的数据包将被解包并转发到最终目的地。
33.应用网关与隧道技术的比较
应用网关定义:需要在受限制的环境下设计客户-服务器软件时,需要依靠一种特别的技术来克服这种连通性约束,这种技术通过在一些中间机器上附加一些应用程序,让这些应用程序在客户和它所期望的服务器之间传递信息。
提供这种服务的中间程序称为应用网关。
应用网关的优点:程序员创建应用网关时可以不必修改计算机的操作系统。
另外,应用网关允许现存的网络系统继续运行而不会受到影响。
应用网关的缺点:需要程序员为每个服务单独构造应用网关,另外,还需要额外的硬件资源。
隧道技术的优点:出现新服务时不需要任何改变,不需要程序员为每个服务单独构造的隧道。
隧道技术的缺点:需要对连接两个网络系统的网关上的操作系统进行修改。
即:该操作系统必须支持隧道技术。
附1.主要套接字API
socket():应用程序调用Socket()函数创建一个新的套接字,这个新的套接字可用于网络通信。
该调用返回这个新创建的套接字的描述符,失败返回-1。
int socket(int family, int type, int protocol);family参数:指明应用程序所需要使用的协议族,TCP/IP协议族为PF_INET;
Type参数:指明所需要的服务类型,TCP为SOCK_STREAM,UDP为SOCK_DGRAM;Protocol参数:使用的协议号,或用0表示给定族和类型的默认协议号。
connect():在创建新的套接字后,客户程序调用connect()函数以便同远程服务器建立主动(Active)连接;成功则返回0,失败返回-1。
int connect(int sockfd, struct sockaddr* serv_addr, int addrlen);sockfd参数:套接字描述符;serv_addr参数:远程服务器socket所使用的地址,包括所使用的端口号和IP地址;addrlen参数:指明远程服务器socket地址的长度。
inet_addr()函数:将一个点分十进制表示的ip地址字符串转换成一个等价的二进制表示的地址。
send():客户与服务器都使用send()函数在TCP连接上发送数据。
不同的是,客户发送的是请求,而服务器发送的是应答。
返回实际上发送出的字节数,差错返回-1。
int send(int sockfd, const void* msg, unsigned int msgLength, int fl ags);sockfd参数:本地建立的套接字描述符;msg参数:一个存放应用程序要发送数据的缓冲区;msgLength参数:实际要发送的数据的字节数;flags参数:控制比特,是否接受外带数据和是否预览报文。
recv():客户与服务器都使用recv()函数从TCP连接上接收数据。
不同的是,客户接收的是应答,而服务器接收的是请求。
返回实际上接收的字节数,出错返回-1。
int recv(int sockfd, void* rcvBuffer, int bufferLength, int flags); close():客户或服务器一旦结束使用某个套接字,则调用close()函数来将该套接字撤销。
若只有一个进程使用该套接字,则close将立即终止连接并撤销该套接字。
若有多个进程共享该套接字,则close将把该套接字的引用数减1,当引用数减到0。