UNIX的网络通信初步_第1页
UNIX的网络通信初步_第2页
UNIX的网络通信初步_第3页
UNIX的网络通信初步_第4页
UNIX的网络通信初步_第5页
已阅读5页,还剩102页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第六章UNIX的网络通信初步,(1)UNIX操作系统为进程通信提供了相应设施,如管道(pipe)、命名管道(namedpipe)和软中断信号(signal),消息(message)、共享存储区(sharedmemory)和信号量(semaphore)等,但这些都只限于用在本机进程间的通信。(2)为了实现计算机全面联网与信息的异地处理,需要为用户构建Client/Server应用的通讯结构,通过网络接口编程,以解决不同主机进程间的通信问题。,6.1网络接口,在UNIX系统中,网络接口有两类:一类是源自BSDUNIX的Sockets(套接口),另一类是UNIXSystemV的TLI(TransmissionLayerInterface)。TLI是根据工业标准“ISO传输服务定义(ISO8072)”实现的,由于SVR3只包括了流以及TLI构建模块而并没有任何的如TCP/IP之类的协议,因此TLI具有与协议无关性,关键技术是定义了一组对许多传输协议公共的服务。目前TLI的修正版XTL在UNIX系统中仍然得到广泛的使用。SocketAPI是基于各种传输协议之上的,目前已经成为网络编程的既成事实标准。基于SocketsAPI的通用性,本章只讨论SocketsAPI的应用。,目前最通用的提供远程进程间通信的API是伯克利套接字(Berkeleysocket)接口。所谓的套接字是一种抽象数据结构,用以创建一条在没有相关联的进程间发送、接收消息的通道(连接点)。这些进程在通信前各自建立一个Socket,并通过对Socket的读写操作实现通信功能。当使用基于套接字的连接时,服务器端进程创建一个套接字,并把它映射到一个本地地址上,然后等待(监听)客户端的请求。客户端进程也创建自己的套接字,并确定服务器端的具体位置(比如主机名,端口号等)。依靠传输连接方式的应答,客户端进程就可以开始发送和接收数据,而不用管是否接收到服务器进程的正式确认(应答)。,每个套接字都有其类型和一个与之相连的进程。当应用程序创建套接字时,套接字系统调用返回句柄,即所谓套接字描述字,它和文件描述字是有所区别的:当文件描述字,由open命令创建时,它被耦合到特定的文件或设备;当套接字描述字由Socket命令创建时,它并不被耦合到任何位置。当套接字用作面向连接的网络传输接口时,应用程序可用bind命令将套接字明确地耦合到一个地址。当套接字用作无连接的网络传输接口时,应用程序可以在用sendto命令发送数据报时动态地提供地址。,6.1.1套接口的类型,UNIX提供下列四种类型的socket:数据流套接字(SOCK_STREAM),它提供双向的、面向连接的、可靠的、有序的并且不重复的无记录边界数据流。一对相连的流Socket提供几乎类似于管道的接口。流式socket针对于TCP服务应用,如文件传送协议(FTP)。数据流套接字采用TCP协议,这是个有连接的协议,在数据正式传输前必须建立连接,此连接是个稳定的双向线路,可以保证提供无错误的传送管道,因为只要封包在传送过程发生错误损毁、次序错乱或送错,TCP将会察觉问题并要求重新发送数据,因此适合在需要大量的数据传输并要求完全正确的状况时使用。,6.1.1套接口的类型,数据报套接字(SOCK_DGRAM),它也支持双向数据流,但数据以独立包形式被发送,无可靠性保证、无序、数据可能丢失或重复。数据报socket提供一个无连接服务,对应于无连接的UDP服务应用,如网络文件系统(NFS)、组播通信。数据报套接字采用UDP协议,这是个无连接的协议,发送主机直接将封包送至目的主机,无需事先建立连接。因为避免了建立连接所需的高代价,采用数据报方式效率较高,但数据报方式自身不能处理数据传输过程出现的错误,因此使用数据报方式的应用程序必须自己处理这些问题。一般在比较简单的网络应用程序中使用数据报方式。,6.1.1套接口的类型,原始套接字(SOCK_RAW),它提供对支持socket概念的基本通信协议的访问。该接口允许用户访问支持套接字抽象的底层通信协议,如IP、ICMP直接访问,常用于检验新的协议实现或访问现有服务中配置的新设备。顺序报套接字:这种类型的套接字类似数据流套接字,不同的是其传送的数据具有记录边界。,6.1.2套接口支持的协议,1套接字协议簇(family)(1)AF-UNIX:UNIXDomain协议,在该域中创建的套接字只能为同在一个主机的进程所用;(2)AF-INET:Internet协议,这个域里的套接字允许在不同主机上的不相关的进程间进行通讯;2套接字协议(Protocol)(1)TCP协议(传输控制协议):负责保证两台主机之间传输的分组到达目的地,保证分组以正确的顺序(确切的说,分组按正确的顺序重新编排)并无差错地到达目的地。当分组在两台主机间的路径上丢失时,TCP协议确保重传丢失的分组;(2)UDP协议(用户数据报协议):类似TCP协议,但是它是不可靠的。UDP不对分组进行检查、重新排序和重传;,6.1.2套接口支持的协议,6.1.3套接口地址结构,大多数套接口函数都需要一个指向套接口地址结构的指针作参数,而在实际应用中各协议的地址结构是不同的,例如IPv4是32位的,而IPv6则是128的。由于早期定义的原因,UNIX的系统函数都只支持通用的地址结构而无法区分特定协议的地址结构,因此在调用这些函数时必须将指向具体协议的套接口地址结构的指针类型转换成指向通用套接口地址结构的类型。如serv是IPv4的地址格式,可以通过(structsockaddr*)/*地址总长度*/u_shortsa_family;/*协议族,AF_xxx*/charsa_data14;/*具体协议地址*/;其中:sa_family为套接字协议簇类型;sa_data中存储具体的协议地址,不同的协议簇有不同的地址结构,如TCP/IP协议的套接字地址结构是在文件中定义的sockaddr_in结构,这个结构定义如下:,IPv4套接口地址结构:(在头文件中定义)structsockaddru_charsin_len;/*32位的IPv4地址总长度*/u_shortsin_family;/*协议族,AF_INET*/u_shortsin_port;/*协议端口号*/charsin_zero8;/*保留,置0*/;要注意的是,套接口的地址结构是按网络字节顺序而不是按主机存储字节顺序来存储的(网络字节是从高到低的顺序,而主机是从低到高的顺序),因此,在Internet上传输数据时就需要进行转换,否则就会出现数据不一致。下面是几个常用的字节顺序转换函数:htonl():把32位值从主机字节序转换成网络字节序ntohl():把32位值从网络字节序转换成主机字节序,通用套接口地址结构IPv4套接口地址结构,长度AF_INIET16位端口号sin_portsin_zero8,长度AF_INIET16位端口号sin_portsa_data16,6.2网络通信的系统函数,6.2.1建立网络连接的数据结构一个用户为了执行网络I/O操作,它首先要调用函数socket()。这个函数陷入内核后执行sys_socket()系统调用,后者为用户创建一个名为socket的数据结构,并对其进行一些简单的初始化工作,然后返回一个小的正整数,代表这个socket结构。这个小的正整数与文件描述符功能类似,所以把它称作套接字描述符。,6.2.1建立网络连接的数据结构,structsocketsocket_statestate;unsignedlongflags;structproto_ops*ops;structinode*inode;structfasync_struct*fasync_list;structfile*file;structspck*sk;wait_queue_head_twait;shorttype;unsignedcharpasscred;unsignedchartli;其中:state描述该套接字的状态信息;flags表示连接时的一些控制信息;ops指向oroto_ops结构类型的指针;inode是指向inode结构类型的指针;fasync_list存在异步进行处理的不同文件的指针;sk指向socket结构的指针;wait表示等待在该socket结构上的人物列表;type表示数据包的类型。,6.2.2网络连接的建立和关闭,1建立一个套接字描述符函数格式如下:intsocket(intfamily,inttype,intprotocol);socket()调用中有三个参数,第一个参数family是一个整数型的量,指定协议簇;第二个参数是type,表示套接字的类型;第三个参数protocol用来表示在指定协议族中使用哪种特定协议,大多情况下,该参数被设为0,让系统自己去选择基于协议族的协议。Socket()调用成功则返回一个正整数,即套接字描述符,用以标识该套接字;如果调用失败,会返回1,并设置全局变量errno为相应的错误类型。,Sockek()函数的执行流程,Socket(),Sys_socketcall(),Sys_socket(),Socket_create(),Socket_alloc(),Inet_creat(),Sk_alloc(),Sock_alloc()用来创建一个socket结构,Sk_alloc()用来创建一个sock结构,系统调用接口,Sock_create(.)sock_alloc();inet_creat();,例1:创建一个套接字对#include#include#include#include#includemain(void)intsock2,cpid,i;/*套接字对*/staticcharbufBUF_SZ;/*消息的临时缓冲区*/if(socketpair(PF_UNIX,SOCK_TREAM,0,sock)0)perror(“Generationerror”);exit(1);switch(cpid=(int)fork()case-1:perror(“Backfork”);exit(2);case0:/*子进程*/close(sock1);for(i=0;i10;i+=2)sleep(1);sprintf(buf,“c:%dn”,i);write(sock0,buf,sizeof(buf);read(sock0,buf,BUF_SZ);printf(“c%s”,buf);close(sock0);break;,default:/*父进程*/close(sock0);for(i=0;i10;i+=2)sleep(1);read(sock1,buf,BUF_SZ);printf(“p%s”,buf);sprintf(buf,“p:%dn”,i);write(sock1,buf,sizeof(buf);close(sock1);return0;,6.2.2网络连接的建立和关闭,2指定本机地址及端口bind()intbind(intsockfd,structsockaddr*my_addr,intaddrlen);/*0成功;-1出错*/其中:参数sockfd是调用socket()返回的套接字,参数my_addr是通用地址结构指针,参数addrlen是该结构的长度,常被设置为sizeof(structsockaddr)。该函数为套接字分配一个本地协议地址,对于IP协议来说是IP地址和TCP或UDP端口号的组合。bind()调用在UNIX域中用来联系套接字和它的名字(一个文件名),在因特网域用来将本地地址和套接字绑定在一起,包括IP地址和端口号。它是依据第二个参数的值的不同而不同的;套接字和本地地址的绑定采用组合的方式,如下表,注:INADDR-ANY在UNIX系统中被映射为0的常量,6.2.2网络连接的建立与关闭,使用bind函数时,可以用下面的赋值实现自动获得本机IP地址和随机获取一个没有被占用的端口号:my_addr.sin_port=0;/*系统随机选择一个未被使用的端口号*/my_addr.sin_addr.s_addr=INADDR_ANY;/*填入本机IP地址*/,6.2.2网络连接的建立与关闭,3客户启动连接connect()intconnect(intsockfd,structsockaddr*serv_addr,intaddrlen);/*返回:0成功;-1出错*/sockfd是调用socket()返回的套接字;serv_addr是包含远端服务器IP地址和端口号的通用地址结构的指针;addrlen是远端地址结构的长度。TCP客户用Connect函数建立一个与远端TCP服务器的连接,正是connect激发了TCP三次握手的连接过程。由于协议族总被包含在套接字地址结构的前两个字节中,并通过socket()调用与某个协议族相关。因此bind()和connect()无须协议作为参数。,connect(),用户空间,核心空间,sys_socketcall(),sys_connect(),inet_stream_coonect(),tcp_v4_connect(),ip_route_output(),ip_route_output_key(),ip_route_connect(),ip_output(),ip_route_optput_slow(),系统调用,传输层(TCP协议),网络层(IPv4或IPv6),6.2.2网络连接的建立与关闭,4监听连接listen()intlisten(intsockfd,intbacklog);/*返回:0成功;-1出错*/sockfd是调用socket()建立的套接字,它是一个socket调用成功后的返回值;backlog是指定在请求队列中允许的最大请求数,一般大于5的均设为5。进入的连接请求将在队列中等待accept()调用建立与客户的连接。该函数仅被TCP服务器调用,它总是使套接口处于被动的监听模式,并为该套接口建立一个输入数据队列,将到达的服务请求保存在此队列中,直到程序处理它们。如果一个服务请求到来时,输入队列已满,该套接口将拒绝连接请求并显示出错信息。listen()调用将一个尚未连接的主动套接字转换成为一个被动套接字,使其可以接收连接请求,因为由socket()调用所创建的套接字(主动套接字)只可以用来进行主动连接,不能接收连接的请求。,5服务器接受连接accept()intaccept(intsockfd,structsockaddr*addr,int*addrlen);/*返回:0成功;-1出错*/accept()调用从倾听套接字的连接队列中接收第一个连接,生成一个新的套接字来完成客户机的要求,原来的套接字继续监视网络,等待用户的连接。accept()调用的第一个参数sockfd是用来标识从哪个套接字中接收连接的,addr是一个指向客户方套接字地址结构的变量指针,该变量用来接收提出连接请求服务的客户的协议地址,指明某台主机从某个端口发出该请求;addr的确切格式由套接字创建时建立的地址族决定。addrten通常为一个指向值为sizeof(structsockaddr)的整型指针变量,指明客户方套接字地址结构的长度(字节数)。accept()只用于TCP服务器,且在调用前应该先调用过listen()。当listen()侦听到有连接请求到达时,accept()调用将请求连接队列上的第一个客户的套接字地址及长度放入addr和addrlen,并与该客户在sockfd建立连接。,6.2.2网络连接的建立与关闭,6关闭套接字close()close(intsockfd);参数sockfd是待关闭的套接字。close()是标准的关闭函数,在TCP服务中激发该套接字的连接关闭。函数功能只是对该套接字作“关闭标识”表明不可用,而连接的另一方还在试图发送排队的数据。只有当对方发现通信的套接字已不可用,自己也调用close()关闭本机的套接字,才真正地结束数据的发送。,6.2.3发送数据,UNIX内核为用户提供的发送数据的系统调用有5个,它们分别是:1)write():与文件系统中的write()完全一致;2)writev():与write功能相似,所不同的是writev可以在一次函数调用中写多个缓冲区(集中写)3)send():面向连接的发送数据过程(TCP);4)sendto():面向无连接的发送数据过程(UDP)5)sendmsg():直接使用msghdr结构发送数据。在功能上可以代替以上四个输出函数。,1发送数据的系统调用接口,write(),writev(),send(),sendto(),sendmsg(),sys_write(),sys_writev(),sys_socketcall(),sock_write(),sock_write(),sys_send(),sys_sendmsg(),sys_sendto(),sock_sendmsg(),inet_sendmsg(),系统调用,应用层,BSD套接字接口,INET套接字(传输层),2.从INTE协议层到IP层,inet_sendmsg(),tcp_sendmsg(),tcp_sendmsg()tcp_transmit_skb(),ip_queue_xmit(),网络层,BSD套接字接口,INET套接字(传输层),2.IP层到硬件层的数据发送过程,ip_queue_xmit2(),ip_output(),ip_finish_output(),ip_queue_xmit(),net/core/dev.c,IP层,dev_queue_xmit(),neigh_resolve_output_(),ip_finish_output2(),net/ipv4/ip_et/ipv4/ip_et/ipv4/ip_et/ipv4/ip_et/ipv4/ip_et/core/neighbour.c,数据链路与硬件层,4.硬件层的数据发送过程,dev_queue_xmit(),ei_start_xmit(),qdisc_run(),ei_start_xmit(),物理设备,硬件层,net/pkt_sched.h,硬件上有队列吗?,以太网卡(例如NE2000),6.2.4接收数据,UNIX内核为用户提供的接收数据的系统调用也是5个,它们分别是:1)read():与文件系统中的read()完全一致;2)readv():与read功能相似,所不同的是readv可以在一次函数调用中读多个缓冲区;3)recv():面向连接的接收数据过程(TCP);4)recvfrom():面向无连接的接收数据过程(UDP)5)recvmsg():直接使用msghdr结构来接收数据。在功能上可以代替以上四个输出函数。,1接收数据的系统调用接口,read(),readv(),recv(),recvfrom(),recvmsg(),sys_read(),sys_readv(),sys_socketcall(),sock_read(),sock_readv(),sys_recv(),sys_recvmsg(),sys_recvfrom(),sock_recvmsg(),inet_recvmsg(),系统调用,应用层,BSD套接字接口,INET套接字(传输层),2.硬件层接收数据分析,netif_rx(),ei_recieve(),ei_interrupt(),ip_rev(),net/core/dev.cdrivers/net/8390.cdrivers/net/8309.c,硬件层,net_rx_action(),IP层,物理设备,NE网卡,向队列写入数据,Backing接收数据队列,从队列中读出数据,2.硬件层接收数据分析,3.从IP层接收数据,tcp_v4_rcv(),ip_local_deliver_finish(),ip_local_deliver(),tcp_rev_established(),INET层,net_rx_action(),ip_rev(),ip_rcv_finish(),net/ipv4/tcp_et/ipv4/ip_et/ipv4/ip_et/ipv4/input.c,硬件层,tcp_v4_do_rcv(),receive_queue队列,IP层,4.从INTE层接收数据,6.3套接字编程方法,1面向连接的数据流套接字时序步骤流套接字的服务器进程和客户机进程在进行通信前必须建立一条连接,其中,初始化连接的是客户端进程,接收连接的进程是服务器端的进程。建立连接和通信的主要步骤如下:(1)服务进程首先调用Socket()创建一个流套接字;(2)服务进程调用bind()公开服务器地址,将服务器地址与套接字绑定在一起;(3)服务进程调用listen()将套接字转换成倾听套接字,此时该套接字可以接收来自客户机的请求;(4)通过accept()阻塞服务进程,此时该服务器进入一个无限循环,等待客户进程建立连接;(5)客户进程也通过Socket()创建一个流套接字,然后调用connect()与服务进程建立连接;(6)当客户进程的连接请求到达服务器后,服务进程进程被唤醒,生成一个新的套接字,服务进程用这个新的套接字按预先定义的协议调用read()和write()进行通信,处理客户进程的要求;而服务进程最早生成的套接字则继续用于监听网络上的服务请求;(7)处理完成后,服务进程和客户进程调用close()关闭这个连接和套接字;,服务器,Socket()、bind()、listen(),accept(),read(),write(),read(),close(),阻塞,等待客户机连接请求,coonect(),write(),read(),socket(),close(),客户,建立连接(TCP三路握手),发送数据请求,接收数据,通信结束、关闭连接,面向连接的数据流套接字通信模型,2面向连接的数据流套接字的典型编程方法,(1)服务器一方main(void)if(创建一个流套接字返回值0)出错提示;退出;if(命名套接字返回值0)出错提示;退出;if(监听连接请求返回值0)出错提示;退出;for(;)新的套接描述符=取得第一个连接请求返回代码;if(新的套接描述符0)出错提示;退出;接收数据信息;处理请求;将应答发送给客户机;关闭套接字;,(2)客户一方main(void)if(创建一个流套接字返回值0)出错提示;退出;if(连接服务器返回值0)出错提示;退出;for(;)发送数据信息;接收服务器方应答;关闭套接字;,6.4无连接的数据流套接字的编程方法,1无连接的数据流套接字时序步骤数据报套接字的服务器进程和客户机进程在进行通信前不用建立连接。通信的主要步骤如下:(1)服务进程首先调用Socket()创建一个数据报套接字;(2)服务进程调用bind()将服务器地址绑定在在这个套接字上;(3)通过recvfrom()阻塞服务进程,等待客户进程发来的请求;(4)客户机首先调用Socket()创建一个数据报套接字;(5)客户机进程调用bind()将客户机地址绑定在在此套接字上;(6)调用sendto(),客户机进程向服务进程发出请求;(7)服务进程接到客户机数据报后被唤醒,执行完客户机请求后调用sendto()将处理结果返回给客户机;(8)客户机调用recvfrom()接收服务进程返回的请求处理结果;(9)服务进程和客户进程调用close()撤消套接字;,2非连接的数据流套接字的通信模型,3非连接的数据流套接字的典型编程方法,(1)服务器一方main(void)if(创建一个数据报套接字返回值0)出错提示;退出;if(命名套接字返回值0)出错提示;退出;for(;)接收客户机数据报(请求);处理请求;将数据(结果)发送给客户机;关闭套接字;,3非连接的数据流套接字的典型编程方法,(1)客户端一方main(void)if(创建一个数据报套接字返回值0)出错提示;退出;if(命名该套接字返回值0)出错提示;退出;for(;)发送数据报(请求)给服务器;接收服务器应答;关闭套接字;,6.5基于客户/服务器模式的网络编程,1客户/服务器的工作流程(1)必须使用标准函数库实现系统调用,以陷入内核获得完成用户任务所需要的系统软、硬件资源;(2)系统调用都被内核入口点system_call函数截获,该函数根据所传递的参数确定应该执行哪些系统调用,并通过检查系统调用表,以确定相应的服务例程。最后把控制权转给该服务例程;(3)该服务过程立即和相关的内核代码模块建立联系。这些模块可能进一步需要和其他内核模块或者底层硬件通讯;(4)当系统调用结束后,结果按照相同的路径反方向返回。核心把控制交给用户程序;(5)如果该实例有某些错误的操作,如用户堆栈溢出,处理器将引发一个异常通知内核,有内核执行相应的处理程序来处理异常事件。,客户端,服务器端,解析域名生成服务器请求计算并显示服务内容和图像,Buffetallocted,Socketbuffer,客户程序,系统调用,Connect()recv()send()fputs(),TCP/IP协议,内核,NIC缓冲,网卡,解析域名生成服务器请求计算并显示服务内容和图像,Buffetallocted,Socketbuffer,TCP/IP协议,NIC缓冲,accept()recv()send()open()read(),文件系统RAMorDisk,服务器程序,内核,网卡,HTTPTCPIP,网络协议,Internet,操作系统支持Web服务器的工作流程,1客户/服务器的工作流程,在客户端,如果运行的是Web测览器,当向Web浏览器的“地址”窗口处输入http:/.或者“WWW.”等形式的网址并回车,就是由Web浏览器向web服务器发出的一次客户请求。该请求经过解析后,通过系统调用由用户态转为核心态执行。在核心态操作系统中的TCP/IP协议代码和网卡驱动程序控制网卡把请求发送到相应的网络上,等待Web服务器响应。当服务响应返回时,由网卡接收,并通过内核传送给客户程序。在服务器端,内核通过网卡从网络上接收Web请求,并通过系统调用传递给Web服务器。Web服务器根据此服务请求执行相应的服务过程,并由内核把结果放到网络上传送给客户。Web浏览器和Web服务器使用URL和URLConnection进行网络通信,这是一种较高层次的网络通信,为的是访问Internet上的资源。通常,用户也常常需要编写一个由操作系统提供的接口客户/服务器应用程序,由套接口实现的客户服务器应用程序是低级别的网络通信。因此,从程序员的观点来看,操作系统所提供的系统调用定义了应用程序和协议栈之间的接口。应用程序无论使用哪种通信协议进行网络通信,都必须申请同操作系统交互才能得到服务。,#include#include#include#include#include#include#include#includeintmain(intargc,char*argv)intsockfd,numb,port=8000;structsockaddr_ins;structhostent*host;charbuf100;if(argc!=2)fprintf(stderr,usage:%shostnamen,argv0);exit(1);if(!(host=gethostbyname(argv1)perror(errorinresolvinghostname);exit(1);sockfd=socket(AF_INET,SOCK_STREAM,0);if(sockfd0)exit(0);/*父进程退出,使shell成为前台进程*/setsid();/*第1子进程成为新会话和新进程组的领头进程的同时也失去控制终端*/*第1子进程执行下面的操作*/for(i=0;i0)/*接收来自服务器的字符,放在缓冲区buf*/bufn=0;if(fputs(buf,stdout)=EOF)perror(Errorfputsn);exit(1);if(n0)/*显示来自服务器的字符信息*/printf(nResponsefromserver:nn%sn,buf);exit(1);close(sockfd);/*通信目的完成,关闭与服务器的连接*/,5编制TCP服务器程序,客户机程序是一个单个用户运行来访问远程服务器的程序,它要求只经过很短的延时、最好没有延时就能得到答复。当从提供服务的主机的角度去考虑问题时,情况将变得非常复杂。因为分布在各地的用户可能在同一时刻访问同一台给定的服务器,每个用户都期望得到无时延的响应。为了尽最大可能满足所有用户的要求,提供应用服务的主机必须能够快速响应并处理多个请求,因此,服务器必须使用并发处理。由于用户对并发的需要使得服务器程序的设计、编程和维护都趋向复杂化,需要使用新的算法和新的编程技术。衡量一个服务器程序的标准同样是可靠性。好用和高吞吐率。下面先介绍一种面向连接的、循环串行处理客户请求的服务器算法和编程技术。它是服务器编程技术中最简单的,因此称它为一个简单的TCP服务器程序。,编制TCP服务器程序,(2)一个简单TCP服务器的编程方法所谓简单的TCP服务器程序是指一个单进程一次只处理一个客户请求的服务器程序。设计一个简单TCP服务器程序的步骤如下:l)调用socket()创建一个套接口。2)调用bind()把套接口绑定到自己所提供服务的知名端口上。3)调用listen()将套接口设置为被动模式,使之准备接受外来的连接请求。4)调用accept()接受下一个连接请求,并获得连接的一个新套接口。5)读取来自客户机的请求,并处理该客户请求,然后向客户发回响应数据。6)当与该客户完成通信时,关闭此次连接,并返回步骤等待接受下一个新的连接请求。,编制TCP服务器程序,(3)一个简单TCP服务器的实现以下给出的程序server.c是为client.c客户程序提供服务的服务器程序。程序server.c在接收到客户询问当前时间的请求之后,便处理这个请求,并将结果发回给客户。这个程序允许接纳20个服务请求的输入队列。它必须和client.c程序一道工作,先启动server.c,稍后启动client.c程序。#include#include#include#include#include#includeintport=8000;,voidmain()structsockaddr_inserveraddr,clientaddr;/*服务器和客户端地址信息*/intsockfd,temp_sockfd,clientaddr_size;charbuf16384;time_tticks;sockfd=socket(AF_INET,SOCK_STREAM,0);/*创建IPv4的数据流socket*/if(sockfd=-1)perror(calltosocket);exit(1);bzero(/*指定服务器用于监听的端口*/,if(bind(sockfd,(structsockaddr*),if(read(temp_sockfd,buf,16384)=-1)/*由子进程提供服务*/perror(calltoread);exit(1);printf(receivedfromclient:%sn,buf);ticks=time(NULL);sprintf(buf,%srn,ctime(,程序的分析,程序一开始先调用socket()创建一个套接口,然后调用bind()函数将知名端口13捆绑到这个套接回。程序中指定IP地址为INADDRANY,它意味着允许服务器在任意端口上接收来自客户的连接请求。通过调用listen()函数将此套接口转变成一个监听套接口,以便使内核开始监听连接到13号端口上的客户连接请求。socket()、bind()和listen()是所有TCP服务器监听客户连接所必须的“三步曲”。接着server.c进入一个无限循环等待来自客户的连接请求。一般情况下,服务器进程在调用accept()函数之后处于阻塞状态。等待着客户连接请求的到达。当TCP连接的三路握手信号结束时,accept()返回,它的返回值是内核为刚连接到服务器的客户创建的一个新的、已连接的套接口描述符特temp_sockfd。随后,函数read()使用这个套接口描述符接收来自客户端的服务请求;并打印这个信息。接着执行请求服务,调用time()函数获取当前时间和日期,并用函数ctime()将这个时间(秒数)转变成人们习惯的阅读格式。最后调用write()函数,将时间数据发回客户端。,程序的分析,程序一开始先调用socket()创建一个套接口,然后调用bind()函数将知名端口13捆绑到这个套接回。程序中指定IP地址为INADDRANY,它意味着允许服务器在任意端口上接收来自客户的连接请求。通过调用listen()函数将此套接口转变成一个监听套接口,以便使内核开始监听连接到13号端口上的客户连接请求。socket()、bind()和listen()是所有TCP服务器监听客户连接所必须的“三步曲”。接着server.c进入一个无限循环等待来自客户的连接请求。一般情况下,服务器进程在调用accept()函数之后处于阻塞状态。等待着客户连接请求的到达。当TCP连接的三路握手信号结束时,accept()返回,它的返回值是内核为刚连接到服务器的客户创建的一个新的、已连接的套接口描述符特temp_sockfd。随后,函数read()使用这个套接口描述符接收来自客户端的服务请求;并打印这个信息。接着执行请求服务,调用time()函数获取当前时间和日期,并用函数ctime()将这个时间(秒数)转变成人们习惯的阅读格式。最后调用write()函数,将时间数据发回客户端。,6并发服务器的设计,server.c程序称为面向连接的循环服务器或称为面向连接的迭代服务器。它一次只能处理一个客户,当有多个客户请求同时到达时,要用listen()函数中第二个参数,它是内核允许接入的最大排队数目。在这个队列中,内核每次返回一个给accept()函数。在这个例子中服务器的服务响应速度是非常快的,因为它在服务期间只执行了两个库函数。如果服务器服务的项目是费时的操作,其他排队等待的客户势必要等待很长时间,因此就必须重新寻求新的方式,以便能够同时为到达的多个客户请求进行服务。这种能够同时为多个客户进行服务的程序称为并发服务器。并发服务器能同时处理多个客户请求,下面给出的实例是在并发服务器编程中使用fork()函数为每个到达的客户派生一个子进程,由这个子进程处理客户的请求。除此之外,还有许多其他编写并发服务器的技术,比如使用线程代替fork()调用,或在服务器运行前预先执行fork(),创建一定数量的子进程,等等。,6并发服务器的设计,并发实现:可以通过进程并发或线程并发实现。并发进程是当服务器收到客户请求并accept()后,调用fork派生一个子进程来为该客户程序服务,自己则回到等待状态,准备接收下一个连接请求,子进程则在服务完成后退出。并发进程为每个客户均fork一个子进程,即每客户单进程服务,子进程可以即时派生,也可以预先派生一定的数量以备系统调用。并发线程是指当有客户连接时由主线程创建子线程为客户提供服务,这种方法的执行效率更高。下面只对并发进程设计作详细介绍。,(2)并发进程实现在介绍编写并发服务程序前,首先来了解一下服务器并发进程是如何实现的,也就是理解UNIX的fork函数。fork()是UNIX中派生子进程的唯一方法,在调用中系统将从父进程虚空间到子进程虚空间的拷贝,两个进程的代码段和用户数据段是完全相同的,并且两个进程的系统数据段也几乎相同。但是它们有各自的数据结构且进程标志符是不同的。调用格式为:pid_tfork(pid);/*返回:在子进程中为0;在父进程中为子进程的ID;-1出错*/,fork函数调用一次却返回两个不同的值,是因为fork()以后的某个时刻,子进程创建后当前运行的仍然是父进程。因此,子进程的上下文被保存,并进入到就绪队列中等待调度。因此,子进程的上下文被保存,并进入到就绪队列中等待调度。当父进程运行结束后将会返回子进程的标识符,而父进程也将从核心态转化成用户态。当子进程得到调度并投入运行后,由于子进程与父进程的代码段相同,子进程同样也调用fork(),只不过不真正创建子进程,只是返回一个0值,然后子进程在自己的虚空间中运行。我们可以通过返回值是否为0判断当前进程是父进程还是子进程。值得注意的是,父进程在fork之前打开的所有描述字在fork后均与子进程共享,并发服务程序设计正是利用这一特性实现的。当服务器accept连接,并调用fork后,已经连接的套接口就在父进程与子进程间得到共享,此时套接口描述符中访问计数项的记录为2,表示该套接口被两个进程访问。此后父进程关闭,访问计数减为1,因此,子进程还在继续访问该套接口并为该口的连接客户提供服务。只有再次close,计数变为0该套接口的连接才真正被关闭。,#include#include#include#include#include#includeintport=8000;voidmain()structsockaddr_inserveraddr,clientaddr;/*服务器和客户端地址信息*/intsockfd,temp_sockfd,clientaddr_size;charbuf16384;time_tticks;pid_tpid;sockfd=socket(AF_INET,SOCK_STREAM,0);/*创建IPv4的数据流socket*/if(sockfd=-1)perror(calltosocket);exit(1);,bzero(,while(1)/*服务器无限循环等待客户连接请求*/temp_sockfd=accept(sockfd,(structsockaddr*),编译并运行client.c和serverf.c,显示执行结果。$gcc-oclientclient.c$gcc-oserverfserverf.c先运行serverf服务器程序。屏幕显示结果如下:$./serverfAccoptingconnectionsReceivedfromclient:Whatisthetimeanddate打开第二个窗口,运行client客户程序。屏幕显示结果下:$./clientSendingmessageWhatisthetimeanddatatoserver.sentmessagewaitforresponseMonDec911:20:422002,6.6UDP套接字编成的基本方法,用户数据报协议UDP是在传输性能上比TCP更低级的协议,它和TCP协议存在着本质的差异。因为UDP不面向连接所以它不提供由保证的信息传输,更不提供有保证的传输错误提示。除此之外,UDP也不能保证消息以被发送时的顺序到达。而TCP是面向连接的,因此它提供可靠的宇节流等许多保证。但是在某些情况下,使用TCP协议不一定是最佳选择,因为TCP的连接和终止有交换7个分组的额外开销。如果使用UDP,那么只有两个分组需要交换:请求和应答。这样在发送的数据块不是很大的情况下,有些应用程序可以使用UDP协议。例如:可以将传输的数据放在一个物理UDP数据报中,并允许一些传输数据丢失。只要丢失的数据不损坏所传输的信息的完整性即可,因此这部分丢失的数据就不需要重发。,6.6UDP套接字编成的基本方法,DNS域名系统、NFS网络文件系统和SNMP(简单网络管理协议)等都使用的是UDP协议。通常,在局域网上UDP数据包很少丢失。在选择UDP还是是TCP传输数据时,还要考虑另外一个因素,即通信应用程序是否需要广播或组播通信。面向连接的TCP协议之提供点到点通信,而不能提供广播或组播通信;所以当然需要广播或组播通信服务时,需要使用UDP的无连接服务。下图给出了使用UDP编写客户服务器程序所需要的基本函数。在客户端不需要调用函数connect()与服务器建立连接,它直接调用sendto()函数向服务器发送数据就可以了。同样在服务器端也不需要监听套接口等待用户的连接请求,它直接调用recvfrom()函数等待客户数据的到达。,socket(),sendto(),recvfrom(),close(),sendto(),处理客户请求,recvfrom(),socket()bind(),阻塞,等待收到客户的数据,应答,请求,UDP客户,UDP服务器,UDP客户/服务器的程序流程示意图,1UDP程序使用的套接口函数,从上图中可以观察到一般UDP套接口无论是客户端还是服务器端都不能使用connect()函数,那样只能与一个特定的远程计算机和端点通信,服务器就不能使用一个套接口接收来自任意客户机的数据报。所以UDP服务器使用的套接口只能是一个非连接的,客户和服务器双方直接使用sendto()向对方发送请求和数据。它的函数原形如下:#includessize_tsendto(intsockfd,constvoid*buf,size_tlen,intflags,conststructsockaddr*to,socklen_taddrlen);第一个参数sockfd是由socket()函数返回的套接口描述符,第二个参数buf是指向存放发送数据的缓冲区指针;第三个参数len指明缓冲区中的字节数;第四个参数flags表明排错或者控制选项;第五个参数to是一个指向sockaddr_in结构的指针,结构中含有将报文要发往的IP地址和端口号;最后一个参数addrlen指明这个地址结构的大小。,1UDP程序使用的套接口函数,在UDP套接口编程中使用的另一个函数是recvfrom(),它可以使客户服务器双方接收对方的请求和数据。它的函数原形如下:#includessize_trecvfrom(intsockfd,void*buf,size_tlen,intflags,structsockaddr*from,socklen_t*addrlen

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论