版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、第3章 UDP套接字与原始套接字的编程,3.1 概述 3.2 UDP套接字编程 3.3 连接UDP套接字的功能 3.4 UDP编程中的错误检测及处理方法 3.5 UDP套接字在OICQ服务中的应用 3.6 原始套接字 3.7 服务器编程模型 习题,3.1 概述,Internet协议簇支持一个面向无连接的传输协议 用户数据报协议(UDP,User Datagram Protocol)。UDP协议向应用程序提供了一种发送经过封装的IP数据报的方法,而且不需要在发送方和接收方之间建立连接就可以进行数据报通信。 UDP协议与TCP协议提供的服务不同,所以基于UDP协议的应用程序同基于TCP的应用程序有
2、不相同的地方,它们的编程模型也不相同。图3-1给出了典型的UDP客户机/服务器程序的函数调用模型。,图3-1 UDP客户机/服务器程序的编程模型,在前面的章节中,已经介绍了基于TCP套接字编程的函数调用模型,比较TCP和UDP编程模型可以看出,UDP协议不需要事先在客户机、服务器程序之间建立连接。服务器端在调用socket函数生成一个UDP套接字后,利用bind函数将套接字与本地IP、端口号绑定,然后,服务器端调用recvfrom函数等待接收由客户端发送来的数据。客户机首先调用socket函数创建一个UDP套接字,然后利用sendto函数将请求包发送至服务器端;服务器端收到请求包后,根据请求进
3、行处理,调用sendto函数将处理结果作为应答数据发送给客户机。客户机调用recvfrom函数接收服务器端发送来的应答数据。当通信结束后,客户机调用close函数关闭UDP套接字,而服务器端可以保留已建立的UDP套接字继续与其他客户机进行数据通信。,3.2 UDP套接字编程,3.1节中已介绍了基于UDP协议网络编程的一般模型,其中涉及到了在套接字编程中曾简单介绍过的数据报发送和接收函数sendto和recvfrom,下面我们列出可用于数据报发送、接收的高级套接字函数。这些函数是:,#include #include #include int sendto(int fd,char *buf,in
4、t len,int flags,struct sockaddr *toaddr,int addrlen); int recvfrom(int fd,char *buf,int len,int flags,struct sockaddr *fromaddr, int addrlen); int sendmsg(int fd,struct msghdr *msgp,int flags); int recvmsg(int fd,struct msghdr *msgp,int flags);,UDP套接字在通信中发送和接收的数据是以数据报为单位的。当应用程序调用函数sendto发送数据时,首先应将数据
5、封装生成一个UDP数据报,然后发送;当应用程序调用函数recvfrom接收数据时。UDP协议将返回一个完整的UDP数据报数据内容。在使用UDP套接字进行编程时,我们必须注意以下几个问题:,(1) UDP套接字在发送数据时不会因发送缓冲区而出现阻塞。UDP协议没有专门为UDP套接字设置发送缓冲区,当应用程序通过调用函数sendto来发送数据时,该函数将要发送的数据从用户缓冲区拷贝到系统缓冲区,然后返回。UDP协议进一步把数据封装成一个UDP数据报,然后将这个UDP数据报传送给低层的IP协议,从而完成UDP数据报的发送任务。UDP协议是不可靠的协议,它没有必要保留已经发送的UDP数据报内容。所以,
6、UDP套接字只有一个发送缓冲区大小,而这个大小就是可以发送的UDP数据报的最大长度。如果应用程序发送的数据量大于这个限制值,函数sendto将以错误返回,错误类型是EMSGSIZE。UDP套接字的发送缓冲区大小是不会发生变化的,所以,只要应用程序保证调用函数sendto发送的数据量小于这个限制值,发送操作总能够成功。因此,应用程序使用UDP套接字发送数据时,不会因发送缓冲区而出现阻塞。,图3-2 UDP套接字接收缓冲区,UDP套接字的接收缓冲区的大小是有限制的,当接收到新的UDP数据报时,如果这个UDP套接字的接收缓冲区队列已经满了,那么UDP协议将丢弃这个数据报,并且不向发送方返回任何错误信
7、息。这种操作也是由UDP协议不保证接收数据的可靠性的特点所决定的。 (3) UDP服务器采用循环服务器的工作方式,不会被某一个客户机独占,但客户机可能被阻塞。UDP通信模式中,服务器一般采用循环服务器工作模式。在服务器与客户机之间不需要建立连接,UDP服务器能够交替地处理来自多个客户机的请求,这就意味着服务器在前后两次循环处理的请求可以是不同客户机的请求,任何一个客户机都无法独占服务器。,(4) 发送数据时需指定接收方的地址。UDP套接字是面向无连接的套接字的,所以在套接字数据结构中不会保存接收方的IP地址及其端口号。如果应用程序要发送数据,就需要在调用发送函数sendto的同时指定接收方的地
8、址。当应用程序接收数据报时,如果需要知道发送者的地址,则可以在调用接收函数recvfrom中提供空间由内核来填充;如果不关心对方的地址,则可以将函数recvfrom的参数from设置为空指针NULL,同时也必须将参数addrlen设置为NULL。,(5) 在需要多点传送数据时,使用UDP套接字。目前,TCP协议不支持多点传送数据,因为,如果要使用TCP协议进行多点传送的话,就必须要为每一个传送建立一个连接。所以,在需要多点传送数据时就往往采用UDP协议。,/*函数udps_respon负责处理数据通信*/ void udps_respon(int sockfd) int n; char msg
9、1024; int addrlen; struct sockaddr_in addr;,for(;) n=recvfrom(sockfd,msg,1024,0,(struct sockaddr*) ,/*以下为主程序部分*/ int main(int argc,char*argv) int sockfd; struct sockaddr_in addr; /* 创建一个UDP数据报类型的套接字 */ sockfd=socket(AF_INET,SOCK_DGRAM,0);,if(sockfd0) fprintf(stderr,Socket error); exit(1); bzero(,/*
10、服务器为套接字绑定一个端口号 */ if(bind(sockfd,(struct sockaddr*) ,这是一个简单的UDP服务器,它不需要在通信前与客户机建立固定连接,直接使用UDP套接字来接收客户机发送的UDP数据报。但是,在接受客户机请求前,服务器必须设置自己的公认的地址和端口号。它一次只能处理一个客户机的请求,而不能并发的处理多个客户机的请求,所以它是一个典型的循环服务器。它不被一个客户端所独立占有,能够交替处理多个客户机的请求,当一个客户机出现错误时不会影响服务器对来自其他客户机请求的处理。,3.2.2 UDP客户机编程示例 客户机: #include #include #incl
11、ude #include #define SERVER_PORT 8080,/*函数udps_requ负责处理数据通信*/ void udpc_requ(int sockfd,const struct sockaddr_in* addr,int len) char buf1024; int n;,for(;fgets(buf,1024,stdin)!=NULL;) /* 向服务器端发送数据 */ sendto(sockfd,buf,strlen(buf),0,addr,len); /* 接收服务器端的回应 */ n=recvfrom(sockfd,buf,1024,0,NULL,NULL);
12、bufn = 0; fputs(buf,stdout); ,/*主程序部分*/ int main(int argc,char*argv) int sockfd; struct sockaddr_in addr; if(argc!=3) fprintf(stderr, usage:client ipaddr port); exit(1); ,/* 创建一个UDP数据报类型的套接字 */ sockfd=socket(AF_INET,SOCK_DGRAM,0); if (sockfd 0) fprintf(stderr,Socket error); exit(1); ,/* 调用通信函数进行数据通信
13、 */ udpc_requ(sockfd, 这是一个简单的UDP客户端程序。由于采用面向无连接的通信模式,因此它不需要跟服务器端建立连接,直接在函数sendto中指定服务器端的地址,调用sendto函数向服务器发送数据。同时,可以接收来自服务端的应答数据报。当数据传输发生错误时,服务端不会有阻塞的危险,但是客户端可能会因为数据报在传输过程中的丢失而在调用函数recvfrom处阻塞。因为UDP协议不能够保证数据可靠到达,所以,对于可能遇到的问题或错误,用户应在程序中加以处理。,3.3 连接UDP套接字的功能,1连接UDP套接字的建立 UDP套接字也可以调用connect函数,调用的方法和流式套接
14、字相同,但其调用的结果与流式套接字调用connect函数的结果不同。它没有三次握手过程,因为UDP协议中不需要在发送和接收方之间建立连接。UDP套接字只是记录了目的方的IP地址和端口号,这些信息被包含在调用connect函数的套接字中,并在调用后立即返回给进程。我们把调用connect函数后的UDP套接字称为连接UDP套接字,把未调用connet函数的UDP套接字称为未连接UDP套接字。,UDP套接字调用connect函数后,将检查每个到达的数据报,UDP协议将数据报中的目的地址与connect函数的套接字中保存的IP地址进行比较,二者一致时该套接字接收这个数据报,反之丢弃这个数据报。UDP连
15、接套接字具有以下一些特点。 (1) 由于连接套接字已经记录了该套接字相应的目的地址,因此发送数据时可以不用指定服务器的目的地址。UDP协议将自动根据保存的地址填充要发送的UDP数据报。 (2) 对于连接套接字,UDP协议在内核中检查连接套接字收到的数据报,并使得连接套接字只接收那些来自目的地址的UDP数据报。,UDP协议检查每个到达的数据报,根据数据报的目的端口号选择接收套接字,UDP协议检查该套接字是否是连接套接字。如果这个套接字为UDP未连接套接字,则协议将数据报存放在该套接字接收缓冲区队列中。如果这个套接字为UDP连接套接字,则协议将数据报的目的IP地址与套接字保存的IP地址比较,只有在
16、相同时,才将数据报存放在套接字的接受缓冲区队列中,否则,丢弃。因此,当UDP客户机只与一个服务器通信时,调用函数connect,将这个UDP套接字转化为UDP连接套接字,保证只接收这个服务器的信息。,2数据报发送以及错误返回情况 UDP协议在进行数据报通信时,可能会有以下几种情况发生: (1) 数据报成功到达服务器端,并且被服务器接收。 (2) 数据报成功到达服务器端,但是服务器端的UDP套接字接收缓冲区已满,此时服务器端将自动丢弃这个数据报,并且不向客户机返回任何错误信息。,(3) 数据报成功到达服务器端,但是数据报指定的目的端口上没有接收此数据报的进程。此时服务器端上的UDP协议将丢弃这个
17、数据报,并且向客户机返回一个ICMP错误报文,通知客户机在目的端口上没有接收进程。当这个ICMP消息到达客户机UDP协议之后,UDP协议将向客户机报告这个错误。我们调用函数sendto发送数据,该函数只要将数据报发送至目的系统的缓冲区就完成调用返回,而ICMP错误报文是在sendto()函数调用完成之后返回的,错误的产生和发现时间是不一致的,所以该错误被称为异步错误。对于UDP套接字,当这个错误到达时,如果客户机正在进行系统调用,则系统将返回一个ECONNRESET类型的错误。对于未连接UDP套接字,Linux系统也返回ECONNRESET。,(4) 数据报未成功到达服务器。这种情况又分为两种
18、: 如果数据报因为目的地址不可到达而在网络中被丢弃,并且这个数据报的传送经过了路由器,那么传送路径上的某个路由器将向客户机UDP协议返回ICMP错误消息,通知发送端目的地址不可到达。客户机UDP协议接收到这个ICMP错误消息之后,如果客户机正在进行系统调用,则这个系统调用将以ENETUNREACH或EHOSTUNREACH类型的错误返回。 如果数据报在网络中传输时,由于字节发生错误而被路由器丢弃,或者由于路由器的缓冲区满,该数据报也将被丢弃,而且发送端也不会得到任何返回的错误信息。,在进行基于UDP套接字编程时,我们应根据实际情况去选择使用连接UDP套接字还是未连接UDP套接字。通常客户机进程
19、只与一个服务器进程通信时,使用连接套接字比较方便,这样可以避免服务器端接收来自其他客户端的UDP数据报。当服务器进程需要同多个客户端进程进行数据报通信,并且是循环服务的方式时,使用未连接套接字。这是一般采用的原则,最终采用什么方法还应根据具体要求加以变通。,3连接UDP套接字的取消 连接UDP套接字的取消与TCP套接字的关闭不同,它不像后者有专门的close套接字函数来关闭连接,连接UDP套接字只需再次调用connect函数,使用一个非法的套接字地址对这个套接字调用connect函数,执行此调用,connect函数套接字将会丢弃原来保存的地址。设置套接字地址的地址簇为AF_UNSPEC,则co
20、nnect函数调用以错误返回,其错误类型为EAFNOSUPPORT,表示UDP协议不支持AF_UNSPEC类型的套接字地址。这样,这个UDP连接套接字的地址信息被清除,可以取消连接UDP套接字。,取消连接UDP套接字的操作如下: struct sockaddr_in addr; int sockfd; addr.sin_family = AF_UNSPEC; connect(sockfd,(struct sockaddr *) 对一个连接UDP套接字再次调用connect函数,可以完成以下两个任务: (1) 断开已连接套接字。 (2) 指定新的IP地址和端口号,即创建一个新的连接。,3.4 U
21、DP编程中的错误检测及处理方法,1UDP协议不保证数据报可靠到达 如果应用程序要求实现传送的UDP数据报可靠地到达接收方,我们必须在应用程序中检测并处理各种可能的错误。例如,采用数据重传和超时重发来实现:发送方保存需要发送的数据报,接收方应用程序接收到数据报之后,向发送者返回一个确认数据报,然后发送方才将这个数据报从缓冲区中释放出去。因为发送的数据报和对方返回的确认数据报都有可能丢失,所以,如果发送者在指定的时间内没有收到接收方的确认信息,将重新发送这个数据报。,调用alarm()函数是最常用的超时控制方法,编程也比较简单。因为信号可以中断函数的阻塞,而alarm()函数可以在设定的时限到达时
22、发出SIGALRM信号,所以我们可以在程序中捕获SIGALRM信号,唤醒用户进程作下一步的操作。在Linux内核2.420,i386体系源程序代码中,alarm()函数所对应的系统调用函数名称是sys_alarm(),在linux/kernel/timer.c 中实现,函数定义如下: asmlinkage unsigned long sys_alarm(unsigned int seconds),注意,该函数的定时单位是秒,当参数设为0时函数将取消定时操作。alarm()函数与进程相关,而与文件描述符或者说是与输入输出通道无关。当我们使用多个输入输出通道时,我们可能无法区分究竟在哪个通道上发生
23、了阻塞。 当超时到达时,alarm()函数将发出信号SIGALRM,这个信号的默认操作是终止进程,这就意味着如果我们不捕获SIGALRM,我们的程序在阻塞一段时间后便会自动结束。当我们捕获了SIGALRM信号并处理后,一般情况下会给阻塞函数返回EINTR。 此外,还有一点需要引起注意:alarm()是一次性函数,而不是周期性函数。,下面我们给出处理这种错误时的部分示例: #include void sig_handler(); int main() int timde_out,n; struct sigaction act; int sockfd;,char msg100,buff100; s
24、truct sockaddr_in addr; /* 创建一个UDP数据报类型的套接字 */ sockfd=socket(AF_INET,SOCK_DGRAM,0); if(sockfd0) fprintf(stderr,Socket error); exit(1); ,bzero( ,act.sa_handler = sig_handler; sigemptyset(,for (;) timde_out = 0; /* 设立信号机制,发送后等待20 s后,若无返回,则认为发送超时,重新发送 */ alarm(20); /* 读应答 */ n=recvfrom(sockfd,buf,size(
25、buf),0,(struct sockaddr*),if (n0 , /* for */ /* main */ void handler() /*信号处理函数*/ timed_out = 1; ; proc_timeout() /* 重新发送数据 */ ;,由上面的程序可见,利用alarm()函数实现操作控制的方法比较简单:首先设置信号SIGALRM的处理函数,在调用读函数之前,调用alarm()函数设置在超时到达时发送信号SIGALRM。如果读函数被信号SIGALRM中断,则表示超时到达。 2UDP协议不保证数据报顺序到达 因为UDP协议是面向无连接的,该协议不保证数据报能够顺序到达,这就意
26、味着所有的UDP数据报并不能按照发送的先后顺序到达接收方被处理。如果应用程序要求数据报必须是按照顺序到达并处理的,我们就要在设计UDP报文时对每个发送的数据报进行顺序编号。接收方在接收到一个数据报之后,根据数据报中的数据序号将其放入缓冲区的合适的位置,符合时才处理这个数据报,否则等待接收顺序靠前的数据报。,3UDP协议没有流量控制 UDP协议本身不提供流量控制功能,虽然UDP协议为每个套接字建立了一个接收缓冲区队列,接收到的数据报被拷贝到该队列中,但是如果在通信过程中数据报发送速度大于接收速度,当套接字接收缓冲区满后,UDP协议将丢弃之后到达的数据报,从而造成大量的数据报丢失。针对这种情况,我
27、们可以在确保UDP数据报可靠到达的基础上进行如下的流量控制: 发送方应用程序创建一个发送缓冲区队列,每发送一个数据报,首先将它拷贝到该队列中,然后再发送给接收方。每个数据报在接收到接收方返回的确认信息前将一直保存在这个缓冲区队列中。如果发送缓冲区队列已满,则暂停发送新的数据报,直到缓冲区队列再次出现空间为止。通过这种方法可以实现简单的流量控制。,3.5 UDP套接字在OICQ服务中的应用,UDP协议经常使用于语音、图像、文字等格式的数据传输中,很多即时通信程序都使用UDP套接字编程来实现数据通信,例如,目前使用的最广泛的聊天程序OICQ。本节我们将给出两个代码片段,来分别说明OICQ服务器端和
28、客户机程序的基本实现原理。,先来看实现发送数据的客户端程序: #include #include #include #include #include #include #include ,#define SERVER_PORT 8003 #define MSG_BUF_SIZE 512 int port = SERVER_PORT; void main() int sockfd; int count = 0; int flag; char bufMSG_BUF_SIZE; struct sockaddr_in address;,/* 创建一个数据报类型的套接字 */ if(sockfd =
29、socket(AF_INET,SOCK_DGRAM,0) = -1) fprintf(stderr,socket error); exit(1); memset(,/* 发送数据 */ do sprintf(buf,packet%dn,count); if(count30) sprintf(buf,over); flag = 0; ,sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr*) 服务器端程序: #include #include #include #include #include #include #include ,#define S
30、ERVER_PORT 8003 #define MSG_BUF_SIZE 512 int port = SERVER_PORT; /* ip地址表示本机 */ char *hostname = ; void main() ,int sinlen; int port = SERVER_PORT; char messageMSG_BUF_SIZE; int sockfd; struct sockaddr_in sin; struct hostent *server_host_name; if(sockfd = socket(PF_INET,SOCK_DGRAM,
31、0) = -1) fprintf(stderr,socket error); exit(1); ,server_host_name = gethostbyname(hostname); bzero( ,/* 接收数据 */ while(1) sinlen = sizeof(sin); recvfrom(sockfd,message,256,0,(struct sockaddr *) ,上面两个程序经编译调试通过后,在一台主机分别运行接收程序和发送程序,就可以看到通过UDP协议的数据通信过程了。当然,也可以在两台机器上启动两个不同的进程来运行,只要修改上述程序中的IP地址,就可以实现客户机和服务
32、器的通信模拟。,3.6 原始套接字,3.6.1 原始套接字定义 使用流式套接字或数据报套接字,应用程序可以实现基于TCP协议或UDP协议的数据交互。这种交互属于比较高层次的网络通信方式,它向程序员屏蔽了TCP、UDP和IP数据包的具体格式,简化了编程工作;但同时也限制了应用程序对通信协议的支持范围,降低了用户对数据的操作能力,影响了编程的灵活性。原始套接字则支持我们直接对IP数据包进行操作,可以允许用户访问ICMP和IGMP等多种协议的数据包,允许用户访问内核不处理的IP数据包,允许用户读写包括首部在内的IP数据包,允许用户基于IP层开发新的高层通信协议。,原始套接字的使用分为三个步骤:原始套
33、接字的创建、属性的设置以及数据的发送和接收。 1原始套接字的创建 将函数socket(int domain,int type,int protocol ) 中的参数type设为SOCK_RAW,并将参数protocol设为某种指定类型(如IPPROTO_ICMP、IPPROTO_IGMP、IPPROTO_IP等),我们就可以创建一个原始套接字。,#include . int sockfd sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP); . 标准的协议类型通常由系统头文件定义,在Linux2.4.20内核中,它们位于第2347行。protocol等于I
34、PPROTO_ICMP,表示这是一个使用ICMP协议工作的原始套接字;protocol等于IPPROTO_IGMP,表示这是一个使用IGMP协议工作的原始套接字;protocol等于IPPROTO_IP,表示这个原始套接字可以接收内核送达的任何类型的IP数据包。 原始套接字直接发送和接收IP数据包,是一种面向无连接的套接字,而且它只能由超级用户或系统管理员来创建。,无论是否设置了IP_HDRINCL属性,原始套接字接收的都是整个IP数据包,即我们的接收缓存区中的数据包含IP数据包的首部。 3数据发送和接收 端口是TCP、UDP等传输层协议中的概念,在原始套接字中不存在端口。原始套接字通过IP地
35、址来识别主机。可以使用sendto()、sendmsg()、recvfrom()和recvmsg()函数,来收发数据包;也可以先调用connect()、bind()函数绑定对方或本地地址,然后再使用write()、writev()、send()、read()、readv()和recv()等函数来收发数据。,2原始套接字属性的设置 IP数据包由首部和数据实体组成,如果没有对原始套接字设置IP_HDRINCL属性,则在发送数据时,数据缓存区中存放的是IP数据包的数据实体部分;如果设置了IP_HDRINCL属性,则数据发送缓存区中存放的是整个IP数据包,包括IP数据包的首部。IP_HDRINCL是通
36、过调用函数setsockopt()来进行设置的: int optval=1; if(setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, ,3.6.2 ICMP协议中原始套接字的应用 在第1章中,我们已经知道ICMP(Internet消息控制协议)是TCP/IP协议簇的一个组成部分,主要作用是通过传递网络故障、网络拥塞、路由错误、中间主机崩溃及重启等信息,来协调路由器、源主机和中间主机之间的工作。 使用ICMP协议通信时,一般不设置IP_HDRINCL选项,在数据发送缓存区仅仅填写ICMP数据包,不需要考虑IP首部;但在接收数据时,接收缓存区内存放的是IP首部加
37、ICMP数据包,所以首先必须找到ICMP数据包的起始位置,然后才能取出ICMP数据包进行处理。通常我们采用结构来读写ICMP消息的固定长度部分,包括描述IP数据包首部的结构和描述ICMP数据包首部的结构。我们可以自行定义这些结构,也可以采用系统头文件中的结构定义:iphdr和icmphdr。,iphdr描述了IP数据包的首部,在Linux2.4.20内核中,它们位于文件的第116136行,其代码及注释如下: struct iphdr #if defined(_LITTLE_ENDIAN_BITFIELD) _u8 ihl:4,/* 首部长度,以4字节为单位进行计量 */ version:4;
38、/* 版本 */ #elif defined (_BIG_ENDIAN_BITFIELD) _u8 version:4, ihl:4;,#else #error Please fix #endif _u8 tos;/* 服务类型 */ _u16 tot_len;/* 数据包总长 */ _u16 id;/* 标识 */ _u16 frag_off;/* 标识位和碎片偏移 */ _u8 ttl;/* 生存时间(time to live) */,_u8 protocol; /* 协议:TCP、UDP、ICMP等 */ _u16 check; /* 首部校验和 */ _u32 saddr; /* 源I
39、P地址 */ _u32 daddr; /* 目的IP地址 */ ; icmphdr描述了ICMP数据包的首部,在Linux2.4.20内核中,它们位于文件的第6681行,其代码如下:,struct icmphdr _u8 type; _u8 code; _u16 checksum; union struct _u16 id; _u16 sequence; echo;,_u32 gateway; struct _u16 _unused; _u16 mtu; frag; un; ;,下面我们以一段Ping程序代码片断为例,来说明 如何使用原始套接字实现ICMP协议的数据交互。在 这个例子中,源主机
40、向目的主机发出回显请求(ICMP_ECHO,type=0,code=0),目的主机返回回显响应(ICMP_ECHOREPLY,type=8,code=0),相关的数据包格式如图3-3所示。其中,标识符是源主机的进程号,序列码用来标识发出回显请求的次序,时间戳表示数据包发出的时刻,通过比较回显响应时刻和源主机当前时刻的差值,可以测出ICMP数据包的往返时间。在这个例子中,用户进程一共向目的主机发送三次回显请求。,图3-3 ICMP回显请求和响应的 数据包格式,#include . int nTimeout=0;/* 超时标志 */ void recv_process(char* buf);/*
41、处理接收到的数据 */ short CheckSum(short *buf,int nSize)/* 计算校验和 */, unsigned long chksum=0; short* buf2=buf; chksum=*buf2;buf2+;buf2+; for(int i=2;i16)+(chksum ,void FillIcmpHdr(char *pIcmpHdr,int nDataSize) /* 填充ICMP数据包 */ icmphdr *pIcmph; static int nSeq=0; pIcmph=(icmphdr*)pIcmpHdr; pIcmph-type=ICMP_ECH
42、O; pIcmph-code=0; pIcmph-un.echo.id=htons(getpid( ); pIcmph-un.echo.sequence=htons(nSeq);nSeq+; pIcmph-checksum=htons(CheckSum(short*)pIcmpPack,nDataSize); ,void sigalrm_handler(int sig) nTimeout=1; int main(int argc,char* argv )/* 主程序入口 */ unsigned long buf64; int sockfd; struct sockaddr_in addr1;
43、struct sigaction act;,timeval tv; act.sa_handler=sigalrm_handler; /* 设置超时信号捕获函数 */ act.sa_mask=0; act.sa_flags=0; sigaction(SIGALRM, /* 创建原始套接字 */ if(sockfd0),exit(1); bzero( while(n3), alarm(5);/* 设置超时值为5 s */ gettimeofday(,n=recvfrom(sockfd,buf,128,0,(struct sockaddr*) ,函数recv_process()用来处理对方主机返回的
44、回显响应数据包,这个数据包内含有IP数据首部,必须将其过滤掉。recv_process()的主要代码如下: void recv_process(char *buf) /* 填充ICMP数据包 */ iphdr *pIph; icmphdr *pIcmph; int iplen,icmplen; pIph=(iphdr*)buf; /* 确定IP数据包起始位置 */,iplen=ip-ihltype!=ICMP_ECHOREPLY|pIcmph-un.echo.id!=htons(getpid( ) exit(3); pl=(unsigned long*)pIcmph; ptv1=(timeva
45、l*)(pl+2); gettimeofday(tv2,NULL); tv2=GetInterval(*ptv1,tv2);/* 编制函数,计算时间差值 */ ./* 显示接收结果 */ ,IGMP(Internet组管理协议)是另一种最常使用原始套接字进行操作的协议,这个协议用来协调多播路由器与主机之间的工作:多播路由器使用IGMP协议来查询多播组内有哪些主机;主机则在加入和退出多播组时使用IGMP协议向路由器发出通告,或者使用IGMP协议响应多播路由器的查询。 与ICMP协议类似,IGMP数据包也是嵌入在IP数据包内进行传输的,我们可以通过调用sockfd=socket(AF_INET,S
46、OCK_RAW,IPPROTO_IGMP)创建一个支持协议的原始套接字。IGMP协议中原始套接字的使用方法与ICMP协议中原始套接字使用方法基本一致,只是协议的具体格式有所差别,其编程方法可参阅前面有关套接字编程的例子。,3.6.3 IP_HDRINCL选项 当对原始套接字设置了IP_HDRINCL属性后,我们就可以对IP数据包的首部进行操作,可以对封装在IP数据包内的任何协议(如TCP、UDP等)进行操作,也可以定制自己的协议首部。在创建原始套接字时,应将协议类型设为IPPROTO_IP(值为0): int sockfd sockfd=socket(AF_INET,SOCK_RAW, IPP
47、ROTO_IP);,下面是一个构造TCP数据包的函数,其中用到的tcphdr结构由linux2.4.20内核头文件的第2356行定义。 int send_syn(char *buf,int nSize,unsigned short port,struct sockaddr addr) iphdr *pIph; tcphdr *pTcph; bzero(buf,nSize); pIph=(iphdr*)buf; pTcph=(tcphdr*)(buf+sizeof(iphdr);,pTcph-source=htons(9090); /* 填写TCP数据首部 */ pTcph-dest=port;
48、 pTcph-seq=random( ); pTcph-doff=5; pTcph-syn=1; pIph-version=4; /* 填写IP数据首部 */,pIph-ihl=sizeof(iphdr)2; pIph-tot_len=sizeof(iphdr)+sizeof(tcphdr); pIph-ttl=128; pIph-protocol=IPPROTO_TCP; pIph-saddr=random( ); pIph-daddr=addr-sin_addr; pTcph-check=ChkSum(buf); /* 计算整个数据包的校验和 */ return *(pIph-tot_le
49、n); /* 返回需要发送的数据总长 */ ,主程序可以不断地调用这个函数,然后向服务器发送。这将引发用户进程与服务器的三次握手过程(syn=1)。由于用户进程送出的源地址是一个随机数,几乎全都是不可达地址,因此三次握手将无法完成。服务器由于侦听套接字的连接队列满而被阻塞,从而引起服务失效。,3.7 服务器编程模型,3.7.1 循环服务器 1UDP循环服务器 基于UDP的编程常采用循环服务器的编程模式,循环服务器的实现较为简单:UDP服务器每次从套接字上读取一个客户端的请求并处理请求,然后将结果返回给客户机。循环服务器的处理过程如图3-4所示。,图3-4 循环服务器处理过程,循环服务器的程序处
50、理方法为: int sockfd; struct sockaddr_in addr, clientaddr; int n, addrlen; char buf512; if (sockfd = socket(AF_INET SOCK_DGRAM 0) 0 ) printf(socket error.n); exit(1); ,bzero( ,for ( ; ; ) addrlen =sizeof(clientaddr); n=recvfrom(sockfd buf sizeof(buf) 0 (struct sockaddr *),else if (n0) printf(recvfrom er
51、ror: %sn strerror(errno); continue; doit(sockfd); */ 处理请求 */ sendto(sockfd buf sizeof(buf) 0 (struct sockaddr *) ,由于基于UDP的通信是面向无连接的,因此没有一个客户机可以独占服务器,只要处理过程不是死循环,服务器对于每一个客户机的请求总是能够满足的。 图3-5为一个小型控制系统示例,所用计算机都工作在端口PORT1上,计算机1负责从工业现场采集数据,对原始数据进行初步处理(滤波、去伪等)后原始发送给计算机2(服务器),计算机2对数据进行处理(滤波、修正、转换等),然后将结果以广播
52、形式发送出去。收到结果数据后,计算机1和计算机2不作处理,计算机3判断是否需要报警,计算机4将结果送去显示,计算机5将数据存档。如何区分数据来源则由应用层的数据协议来保证,例如,每个数据包中都标明数据包类型是原始数据、结果数据,还是其他数据。,图3-5 一个小型控制系统示例,计算机2的程序代码如下: #include #define PORT1 8989 #define COMPUTERX 55 . int main() char buf256; int sockfd; struct sockaddr_in addr_2; /* 计算机2的地址结构 */ struct
53、 sockaddr_in addr_x; /* 广播地址结构 */,sockfd=socket(AF_INET,SOCK_DGRAM,0); if(sockfd0) fprintf(stderr,Socket error); exit(1); bzero(,if(bind(sockfd,(struct sockaddr*),int nDataComeFrom=0; /* 用来存放数据包类型代码 */ int n=recvfrom(sockfd,buf,256,0,(struct sockaddr*) . /* 解析接收到的数据包,并将数据包类型代码放入nDataComeFrom */,if(n
54、DataComeFrom=1) process(buf, ,2TCP循环服务器 现在我们考虑基于TCP的服务器采用循环服务器工作模式的实现方法。TCP服务器接受一个客户端的连接,然后进行处理,直到完成这个客户机的所有请求后,断开连接。TCP循环服务器一次只能处理一个客户端的请求,只有在这个客户的所有请求都满足后,服务器才可以继续后面的请求。这样,如果有一个客户端占住服务器不放时,其他的客户机都不能工作了,因此,TCP服务器一般很少用循环服务器模型。只有当数据处理工作所需时间很短(如时钟服务)或者服务器只能为单一用户提供服务时才被使用,而且这时应该设置超时控制。,图3-6是一个使用TCP循环服务
55、的例子,这是一个通过网络控制的可移动机械手,服务器程序运行在机械手的移动平台上,用户可以通过网络远程操控机械手完成搬运物体的工作,这个机械手不允许多人同时操作。若采用UDP协议来实现这个系统,由于UDP协议的不可靠性,很可能出现后发出的指令被首先执行,先发出的指令被延后执行的状况,用户将感到机械手不可控制。当然,我们也可以在应用层的协议里加上时序控制,但那样将加大编程的难度。而采用TCP的循环模式,并对阻塞函数添加超时控制,情况将会比较理想。有关TCP循环服务的程序代码,可参见前面套接字的内容,这里不再赘述。,图3-6 一个使用TCP循环服务的例子,3.7.2 并发服务器 针对上述TCP循环服
56、务器的缺陷,人们提出了并发服务器的编程模型。并发服务器的思想是:每一个客户机的请求并不由服务器侦听进程直接处理,而是由服务器侦听进程创建一个自己的子进程负责处理服务请求,父进程仍负责侦听客户机的请求。并发服务器的处理流程如图3-7所示。,图3-7 并发服务器的处理流程,1TCP并发服务器 基本的TCP并发服务器采用这样的流程:先创建一个侦听套接字,等待客户机的请求,每当接受一个客户机请求时,就创建一个子进程,并在子进程中进行数据处理,父进程则继续等待新的客户机请求,直到服务程序满足退出条件。TCP并发服务器的程序处理方法为: int sockfd,newsockfd; struct socka
57、ddr_in addr;,if (sockfd = socket(AF_INET,SOCK_STREAM,0) 0 ) printf(socket error.n); exit(1); bzero(,if ( bind(sockfd,) 0 ) printf(bind error. n); exit(1); if (listen(sockfd,5) 0 ) printf(listen error. n); exit(1); ,for ( ; ; ) newsockfd = accept(sockfd,); if (newsockfd 0 ,if ( fork() = 0 ) close(soc
58、kfd); doit(newsockfd);/* 处理请求 */ exit(0); close(newsockfd); ,2UDP并发服务器 UDP协议虽然在套接字函数处不大容易阻塞,但如果对某一个客户机进行数据处理的时间过长,则服务器在这段时间内将不能接收其他客户机的请求,同时UDP协议又是不可靠的通信协议,不保证数据是否能够到达目的地址,所以就会造成数据包的丢失。这时可以采用并发的UDP服务结构:服务程序每收到一项请求,就单独为这个客户机创建一个进程,完成相应的数据处理任务,然后关闭套接字描述符。UDP并发服务器处理方法如下:,#include #define PORT1 8989 void sigchld_handler(int sig) while(waitpid(-1,NULL,WNOHANG)0) return; ,int main() char buf256; int sockfd; struct sockaddr_in addr;
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年雕塑创作委托合同协议
- 高中生运用植物生理学知识培育耐旱经济作物课题报告教学研究课题报告
- 初中化学气体制备装置的核磁共振气体分析技术研究课题报告教学研究课题报告
- 2026年珠海市兆征纪念学校拟招聘小学数学教师1名备考题库含答案详解
- 2025年法律咨询平台技术架构升级报告
- 海西州交通运输局2025年面向社会公开招聘编外工作人员的备考题库及参考答案详解1套
- 2026年山东外贸职业学院单招职业技能笔试备考试题及答案解析
- 2026年北京航空航天大学可靠性与系统工程学院聘用编科研助理F岗招聘备考题库及答案详解(新)
- 合肥经开投资促进有限公司2025年公开招聘备考题库附答案详解
- 2026年中山市东区中学公开招聘地理专任教师备考题库及答案详解(新)
- 驾驶安全文明驾驶培训课件
- 无人机应用技术专业开设论证报告
- 十五五特殊教育发展提升行动计划
- 2025年河南公务员遴选考试题库(附答案)
- 农商行数据安全管理办法
- 20.3课题学习 体质健康测试中的数据分析课件 2025年春人教版数学八年级下册
- 架梁安全培训课件
- 造价咨询项目工作实施方案
- 口腔门诊急救药箱配置与管理规范
- 中国石油天然气集团公司一级采购物资管理(2025)报告
- 2025至2030中国日本清酒行业市场发展现状及发展前景与投资报告
评论
0/150
提交评论