还剩39页未读,继续阅读
本资源只提供10页预览,全部文档请下载后查看!喜欢就下载吧,查找使用更方便
文本内容:
socket编程原理socket编程原理1问题的引入UNIX系统的I/O命令集,是从Maltics和早期系统中的命令演变出来的,其模式为打开一读/写一关闭open-write-read-close在一个用户进程进行I/O操作时,它首先调用打开获得对指定文件或设备的使用权,并返回称为文件描述符的整型数,以描述用户在打开的文件或设备上进行I/O操作的进程然后这个用户进程多次调用读/写以传输数据当所有的传输操作完成后,用户进程关闭调用,通知操作系统已经完成了对某对象的使用TCP/IP协议被集成到UNIX内核中时,相当于在UNIX系统引入了一种新型的I/O操作UNIX用户进程与网络协议的交互作用比用户进程与传统的I/O设备相互作用复杂得多首先,进行网络操作的两个进程钥纪纪同机器上,如何建立它们之间的联系其次,网络协议存在多种,如何建立一种通用机制以支持多种协议这些都是网络应用编程界面所要解决的问题在UNIX系统中,网络应用编程界面有两类UNIX BSD的套接字socket和UNIX SystemV的TLI由于Sun公司采用了支持TCP/IP的UNIX BSD操作系统,使TCP/IP的应用有更大的发展,其网络应用编程界面──套接字socket在网络软件中被广泛应用,至今已引进微机操作系统DOS和Windows系统中,成为开发网络应用软件的强有力工具,本章将要详细讨论这个问题2套接字编程基本概念钥纪纪始使用套接字编程之前,首先必须建立以下概念
2.1网间进程通信进程通信的概念最初来源于单机系统由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进程之间既互不干扰又协调一致工作,操作系统为进程通信提供了相应设施,如UNIX BSD中的管道pipe、命名管道named pipe和软中断信号signal,UNIX systemV的消息message、共享存储区shared memory和信号量semaphore等,但都仅限于用在本机进程之间通信网间进程通信要解决的是不同主机进程间的相互通信问题可把同机进程通信看作是其中的特例为此,首先要解决的是网间进程标识问题同一主机上,不同进程可用进程号process ID唯一标识但在网络环境下,各主机独立分配的进程号不能唯一标识该进程例如,主机A赋于某进程号5,在B机中也可以存在5号进程,因此,5号进程这句话就没有意义了其次,操作系统支持的网络协议众多,不同协议的工作方式不同,地址格式也不同因此,网间进程通信还要解决多重协议的识别问题为了解决上述问题,TCP/IP协议引入了下列几个概念端口网络中可以被命名和寻址的通信端口,是操作系统可分配的一种资源按照OSI七层协议的描述,传输层与网络层在功能上的最大区别是传输层提供进程通信能力从这个意义上讲,网络通信的最终地址就不仅仅是主机地址了,还包括可以描述进程的某种标识符为此,TCP/IP协议提出了协议端口protocol port,简称端口的概念,用于标识通信的进程端口是一种抽象的软件结构包括一些数据结构和I/O缓冲区应用程序即进程通过系统调用与某端口建立连接binding后,传输层传给该端口的数据都被相应进程所接收,相应进程发给传输层的数据都通过该端口输出在TCP/IP协议的实现中,端靠纪纪作类似于一般的I/O操作,进程获取一个端口,相当于获取本地唯一的I/O文件,可以用一般的读写原语访问之类似于文件描述符,每个端口都拥有一个叫端口号port number的整数型标识符,用于区别不同端口由于TCP/IP传输层的两个协议TCP和UDP是完全独立的两个软件模块,因此各自的端口号也相互独立,如TCP有一个255号端口,UDP也可以有一个255号端口,二者并不冲突端口号的分配是一个重要问题有两种基本分配方式第一种叫全局分配,这是一种集中控制方式,由一个公认的中央机构根据用户需要进行统一分配,并将结果公布于众第二种是本地分配,又称动态连接,即进程需要访问传输层服务时,向本地操作系统提出申请,操作系统返回一个本地唯一的端口号,进程再通过合适的系统调用将自己与该端口号联系起来绑扎TCP/IP端口号的分配中综合了上述两种方式TCP/IP将端口号分为两部分,少量的作为保留端口,以全局方式分配给服务进程因此,每一个标准服务器都拥有一个全局公认的端口即周知口,well-known port,即使钥纪纪同机器上,其端口号也相同剩余的为自由端口,以本地方式进行分配TCP和UDP均规定,小于256的端口号才能作保留端口地址网络通信中通信的两个进程分别钥纪纪同的机器上在互连网络中,两台机器可能位涌纪纪同的网络,这些网络通过网络互连设备网关,网桥,路由器等连接因此需要三级寻址
1.某一主机可与多个网络相连,必须指定一特定网络地址;
2.网络上每一台主机应有其唯一的地址;
3.每一主机上的每一进程应有在该主机上的唯一标识符通常主机地址由网络ID和主机ID组成,在TCP/IP协议中用32位整数值表示;TCP和UDP均使用16位端口号标识用户进程网络字节顺序不同的计算机存放多字节值的顺序不同,有的机器在起始地址存放低位字节低价先存,有的存高位字节高价先存为保证数据的正确性,在网络协议中须指定网络字节顺序TCP/IP协议使用16位整数和32位整数的高价先存格式,它们均含在协议头文件中连接两个进程间的通信链路称为连接连接在目纪纪表现为一些缓冲区和一组协议机制,在外部表现出比无连接高的可靠性半相关综上所述,网络中用一个三元组可以在全局唯一标志一个进程协议,本地地址,本地端口号这样一个三元组,叫做一个半相关half-association,它指定连接的每半部分全相关一个完整的网间进程通信需要由两个进程组成,并且只能使用同一种高层协议也就是说,不可能通信的一端用TCP协议,而另一端用UDP协议因此一个完整的网间通信需要一个五元组来标识协议,本地地址,本地端口号,远地地址,远地端口号这样一个五元组,叫做一个相关association,即两个协议相同的半相关才能组合成一个合适的相关,或完全指定组成一连接
2.2服务方式在网络分层结构中,各层之间是严格单向依赖的,各层次的分工和协作集中体现在相量纪纪之间的界面上服务是描述相量纪纪之间关系的抽象概念,即网络中各层向紧邻上层提供的一组操作下层是服务提供者,上层是请求服务的用户服务的表现形式是原语primitive,如系统调用或库函数系统调用是操作系统内核向网络应用程序或高层协议提供的服务原语网络中的n层总要向n+1层提供比n-1层更完备的服务,否则n层就没有存在的价值在OSI的术语中,网络层及其以下各层又称为通信子网,只提供点到点通信,没有程序或进程的概念而传输层实现的是端到端通信,引进网间进程通信概念,同时也要解决差错控制,流量控制,数据排序报文排序,连接管理等问题,为此提供不同的服务方式面向连接虚电路或无连接面向连接服务是电话系统服务模式的抽象,即每一次完整的数据传输都要经过建立连接,使用连接,终止连接的过程在数据传输过程中,各数据分组不携带目的地址,而使用连接号connect ID本质上,连接是一个管道,收发数据不但顺序一致,而且内容相同TCP协议提供面向连接的虚电路无连接服务是邮政系统服务的抽象,每个分组都携带完整的目的地址,各分组在系统中独立传送无连接服务不能保证分组的先后顺序,不进行分组出错的恢复与重传,不保证传输的可靠性UDP协议提供无连接的数据报服务下面给出这两种服务的类型及应用中的例子服务类型服务例子面向连接可靠的报文流可靠的字节流不可靠的连接文件传输FTP远程登录Telnet数字话音无连接不可靠的数据报有确认的数据报请求-应答电子邮件E-mail电子邮件中的挂号信网络数据库查询顺序在网络传输中,两个连续报文在端-端通信中可能经过不同路径,这样到达目的地时的顺序可能会与发送时不同顺序是指接收数据顺序与发送数据顺序相同TCP协议提供这项服务差错控制保证应用程序接收的数据无差错的一种机制检查差错的方法一般是采用检验检查和Checksum的方法而保证传送无差错的方法是双方采用确认应答技术TCP协议提供这项服务流控制在数据传输过程中控制数据传输速率的一种机制,以保证数据不被丢失TCP协议提供这项服务字节流字节流方式指的是仅把传输中的报文看作是一个字节序列,不提供数据流的任何边界TCP协议提供字节流服务报文接收方要保存发送方的报文边界UDP协议提供报文服务全双工/半双工端-端间数据同时以两个方向/一个方向传送缓存/带外数据在字节流服务中,由于没有报文边界,用户进程在某一时刻可以读或写任意数量的字节为保证传输正确或采用有流控制的协议时,都要进行缓存但对某些特殊的需求,如交互式应用程序,又会要求取消这种缓存在数据传送过程中,希望不通过常规传输方式传送给用户以便及时处理的某一类信息,如UNIX系统的中断键Delete或Control-c、终端流控制符Control-s和Control-q,称为带外数据逻辑上看,好象用户进程使用了一个独立的通道传输这些数据该通道与每对连接的流相联系由于Berkeley SoftwareDistribution中对带外数据的实现与RFC1122中规定的Host Agreement不一致,为了将互操作中的问题减到最小,应用程序编写者除非与现有服务互操作时要求带外数据外,最好不使用它
2.3客户/服务器模式在TCP/IP网络应用中,通信的两个进程间相互作用的主要模式是客户/服务器模式Client/Server model,即客户向服务器发出服务请求,服务器接收到请求后,提供相应的服务客户/服务器模式的建立基于以下两点首先,建立网络的起因是网络中软硬件资源、运算能力和信息不均等,需要共享,从而造就拥有众多资源的主机提供服务,资源较少的客户请求服务这一非对等作用其次,网间进程通信完全是异步的,相互通信的进程间既不存在父子关系,又不共享内存缓冲区,因此需要一种机制为希望通信的进程间建立联系,为二者的数据交换提供同步,这就是基涌纪纪户/服务器模式的TCP/IP客户/服务器模式钥纪纪作过程中采取的是主动请求方式首先服务器方要先启动,并根据请求提供相应服务
1.打开一通信通道并告知本地主机,它愿意在某一公认地址上周知口,如FTP为21接收客户请求;
2.等待客户请求到达该端口;
3.接收到重复服务请求,处理该请求并发送应答信号接收到并发服务请求,要激活一新进程来处理这个客户请求如UNIX系统中用fork、exec新进程处理此客户请求,并不需要对其它请求作出应答服务完成后,关闭此新进程与客户的通信链路,并终止
4.返回第二步,等待另一客户请求
5.关闭服务器客户方
1.打开一通信通道,并连接到服务器所在主机的特定端口;
2.向服务器发服务请求报文,等待并接收应答;继续提出请求.
3.请求结束后关闭通信通道并终止从上面所描述过程可知
1.客户与服务器进程的作用是非对称的,因此编码不同
2.服务进程一般是先涌纪纪户请求而启动的只要系统运行,该服务进程一直存在,直到正常或强迫终止
2.4套接字类型TCP/IP的socket提供下列三种类型套接字流式套接字SOCK_STREAM提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复地发送,且按发送顺序接收内设流量控制,避免数据流超限;数据被看作是字节流,无长度限制文件传送协议FTP即使用流式套接字数据报式套接字SOCK_DGRAM提供了一个无连接服务数据包以独立包形式被发送,不提供无错保证,数据可能丢失或重复,并且接收顺序混乱网络文件系统NFS使用数据报式套接字原始式套接字SOCK_RAW该接口允许对较低层协议,如IP、ICMP直接访问常用于检验新的协议实现或访问现有服务中配置的新设备3基本套接字系统调用为了更好地说明套接字编程原理,下面给出几个基本套接字系统调用说明
3.1创建套接字──socket应用程序在使用套接字前,首先必须拥有一个套接字,系统调用socket向应用程序提供创建套接字的手段,其调用格式如下SOCKET PASCALFAR socketintaf,int type,int protocol;该调用要接收三个参数af、type、protocol参数af指定通信发生的区域,UNIX系统支持的地址族有AF_UNIX、AF_INET、AF_NS等,而DOS、WINDOWS中仅支持AF_INET,它是网际网区域因此,地址族与协议族相同参数type描述要建立的套接字的类型参数protocol说明该套接字使用的特定协议,如果调用者不希望特别指定使用的协议,则置为0,使用默认的连接模式根据这三个参数建立一个套接字,并将相应的资源分配给它,同时返回一个整型套接字号因此,socket系统调用实际上指定了相关五元组中的协议这一元有关socket的详细描述参看
5.
2.
233.2指定本地地址──bind当一个套接字用socket创建后,存在一个名字空间地址族,但它没有被命名bind将套接字地址包括本地主机地址和本地端口地址与所创建的套接字号联系起来,即将名字赋予套接字,以指定本地半相关其调用格式如下int PASCALFAR bindSOCKETs,const struct sockaddr FAR*name,int namelen;参数s是由socket调用返回的并且未作连接的套接字描述符套接字号参数name是赋给套接字s的本地地址名字,其长度可变,结构随通信域的不同而不同namelen表明了name的长度如果没有错误发生,bind返回0否则返回值SOCKET_ERROR地址在建立套接字通信过程中起着重要作用,作为一个网络应用程序设计者对套接字地址结构必须有明确认识例如,UNIX BSD有一组描述套接字地址的数据结构,其中使用TCP/IP协议的地址结构为struct sockaddr_in{short sin_family;/*AF_INET*/u_short sin_port;/*16位端口号,网络字节顺序*/struct in_addr sin_addr;/*32位IP地址,网络字节顺序*/char sin_zero
[8];/*保留*/}有关bind的详细描述参看
5.
2.
23.3建立套接字连接──connect与accept这两个系统调用用于完成一个完整相关的建立,其中connect用于建立连接无连接的套接字进程也可以调用connect,但这时在进程之间没有实际的报文交换,调用将从本地操作系统直接返回这样做的优点是程序员不必为每一数据指定目的地址,而且如果收到的一个数据报,其目的端口未与任何套接字建立连接,便能判断该端靠纪纪可操作而accept用于使服务器等待来自某客户进程的实际连接connect的调用格式如下int PASCALFAR connectSOCKETs,const struct sockaddr FAR*name,int namelen;参数s是欲建立连接的本地套接字描述符参数name指出说明对方套接字地址结构的指针对方套接字地址长度由namelen说明如果没有错误发生,connect返回0否则返回值SOCKET_ERROR在面向连接的协议中,该调用导致本地系统和外部系统之间连接实际建立由于地址族总被包含在套接字地址结构的前两个字节中,并通过socket调用与某个协议族相关因此bind和connect无须协议作为参数有关connect的详细描述参看
5.
2.4accept的调用格式如下SOCKET PASCALFAR acceptSOCKETs,struct sockaddrFAR*addr,int FAR*addrlen;参数s为本地套接字描述符,在用做accept调用的参数前应该先调用过listenaddr指向客户方套接字地址结构的指针,用来接收连接实体的地址addr的确切格式由套接字创建时建立的地址族决定addrlen为客户方套接字地址的长度字节数如果没有错误发生,accept返回一个SOCKET类型的值,表示接收到的套接字的描述符否则返回值INVALID_SOCKET accept用于面向连接服务器参数addr和addrlen存放客户方的地址信息调用前,参数addr指向一个初始值为空的地址结构,而addrlen的初始值为0;调用accept后,服务器等待从编号为s的套接字上接受客户连接请求,而连接请求是由客户方的connect调用发出的当有连接请求到达时,accept调用将请求连接队列上的第一个客户方套接字地址及长度放入addr和addrlen,并创建一个与s有相同特性的新套接字号新的套接字可用于处理服务器并发请求有关accept的详细描述参看
5.
2.1四个套接字系统调用,socket、bind、connect、accept,可以完成一个完全五元相关的建立socket指定五元组中的协议元,它的用法与是否为客户或服务器、是否面向连接无关bind指定五元组中的本地二元,即本地主机地址和端口号,其用法与是否面向连接有关在服务器方,无论是否面向连接,均要调用bind;钥纪纪户方,若采用面向连接,则可以不调用bind,而通过connect自动完成若采用无连接,客户方必须使用bind以获得一个唯一的地址以上讨论仅对客户/服务器模式而言,实际上套接字的使用是非常灵活的,唯一需遵循的原则是进程通信之前,必须建立完整的相关
3.4监听连接──listen此调用用于面向连接服务器,表明它愿意接收连接listen需在accept之前调用,其调用格式如下int PASCALFAR listenSOCKETs,int backlog;参数s标识一个本地已建立、尚未连接的套接字号,服务器愿意从它上面接收请求backlog表示请求连接队列的最大长度,用于限制排队请求的个数,目前允许的最大值为5如果没有错误发生,listen返回0否则它返回SOCKET_ERROR listen在执行调用过程中可为没有调用过bind的套接字s完成所必须的连接,并建立长度为backlog的请求连接队列调用listen是服务器接收一个连接请求的四个步骤中的第三步它在调用socket分配一个流套接字,且调用bind给s赋于一个名字之后调用,而且一定要在accept之前调用有关listen的详细描述参看
5.
2.
132.3节中提到钥纪纪户/服务器模式中,有两种类型的服务重复服务和并发服务accept调用为实现并发服务提供了极大方便,因为它要返回一个新的套接字号,其典型结构为int initsockid,newsockid;ifinitsockid=socket.0errorcant createsocket;ifbindinitsockid,.0errorbind error;iflisteninitsockid,50errorlisten error;for;{newsockid=acceptinitsockid,./*阻塞*/ifnewsockid0erroraccept error;iffork==0{/*子进程*/closesocketinitsockid;donewsockid;/*处理请求*/exit0;}closesocketnewsockid;/*父进程*/}这段程序执行的结果是newsockid与客户的套接字建立相关,子进程启动后,关闭继承下来的主服务器的initsockid,并利用新的newsockid与客户通信主服务器的initsockid可继续等待新的客户连接请求由于在Unix等抢先多任务系统中,在系统调度下,多个进程可以同时进行因此,使用并发服务器可以使服务器进程在同一时间可以有多个子进程和不同的客户程序连接、通信钥纪纪户程序看来,服务器可以同时并发地处理多个客户的请求,这就是并发服务器名称的来由面向连接服务器也可以是重复服务器,其结构如下int initsockid,newsockid;ifinitsockid=socket.0errorcant createsocket;ifbindinitsockid,.0errorbind error;iflisteninitsockid,50errorlisten error;for;{newsockid=acceptinitsockid,./*阻塞*/ifnewsockid0erroraccept error;donewsockid;/*处理请求*/closesocketnewsockid;}重复服务器在一个时间只能和一个客户程序建立连接,它对多个客户程序的处理是采用循环的方式重复进行,因此叫重复服务器并发服务器和重复服务器各有利弊并发服务器可以改善客户程序的响应速度,但它增加了系统调度的开销;重复服务器正好与其相反,因此用户在决定是使用并发服务器还是重复服务器时,要根据应用的实际情考网考网来定
3.5数据传输──send与recv当一个连接建立以后,就可以传输数据了常用的系统调用有send和recv send调用用于钥纪纪数s指定的已连接的数据报或流套接字上发送输出数据,格式如下int PASCALFAR sendSOCKETs,const char FAR*buf,int len,int flags;参数s为已连接的本地套接字描述符buf指向存有发送数据的缓冲区的指针,其长度由len指定flags指定传输控制方式,如是否发送带外数据等如果没有错误发生,send返回总共发送的字节数否则它返回SOCKET_ERROR有关send的详细描述参看
5.
2.19recv调用用于钥纪纪数s指定的已连接的数据报或流套接字上接收输入数据,格式如下int PASCALFAR recvSOCKETs,charFAR*buf,int len,int flags;参数s为已连接的套接字描述符buf指向接收输入数据缓冲区的指针,其长度由len指定flags指定传输控制方式,如是否接收带外数据等如果没有错误发生,recv返回总共接收的字节数如果连接被关闭,返回0否则它返回SOCKET_ERROR有关recv的详细描述参看
5.
2.
163.6输入/输出多路复用──select select调用用来检测一个或多个套接字的状态对每一个套接字来说,这个调用可以请求读、写或错误状态方面的信息请求给定状态的套接字集合由一个fd_set结构指示在返回时,此结构被更新,以反映那些满足特定条件的套接字的子集,同时,select调用返回满足条件的套接字的数目,其调用格式如下int PASCALFAR selectintnfds,fd_set FAR*readfds,fd_set FAR*writefds,fd_set FAR*exceptfds,const struct timeval FAR*timeout;参数nfds指明被检查的套接字描述符的值域,此变量一般被忽略参数readfds指向要做读检测的套接字描述符集合的指针,调用者希望从中读取数据参数writefds指向要做写检测的套接字描述符集合的指针exceptfds指向要检测是否出错的套接字描述符集合的指针timeout指向select函数等待的最大时间,如果设为NULL则为阻塞操作select返回包含在fd_set结构中已准备好的套接字描述符的总数目,或者是发生错误则返回SOCKET_ERROR有关select的详细描述参看
5.
2.
183.7关闭套接字──closesocket closesocket关闭套接字s,并释放分配给该套接字的资源;如果s涉及一个打开的TCP连接,则该连接被释放closesocket的调用格式如下BOOL PASCALFAR closesocketSOCKETs;参数s待关闭的套接字描述符如果没有错误发生,closesocket返回0否则返回值SOCKET_ERROR有关closesocket的详细描述参看
5.
2.
32.4典型套接字调用过程举例如前所述,TCP/IP协议的应用一般采用客户/服务器模式,因此在实际应用中,必须有客户和服务器两个进程,并且首先启动服务器,其系统调用时序图如下面向连接的协议如TCP的套接字系统调用如图
2.1所示服务器必须首先启动,直到它执行完accept调用,进入等待状态后,方能接收客户请求假如客户在此前启动,则connect将返回出错代码,连接不成功图
2.1面向连接的套接字系统调用时序图无连接协议的套接字调用如图
2.2所示图
2.2无连接协议的套接字调用时序图无连接服务器也必须先启动,否则客户请求传不到服务进程无连接客户不调用connect因此在数据发送之前,客户与服务器之间尚未建立完全相关,但各自通过socket和bind建立了半相关发送数据时,发送方除指定本地套接字号外,还需指定接收方套接字号,从而在数据收发过程中动态地建立了全相关实例本实例使用面向连接协议的客户/服务器模式,其流程如图
2.3所示图
2.3面向连接的应用程序流程图服务器方程序/*File Namestreams.c*/#include#include#define TRUE1/*这个程序建立一个套接字,然后开始无限循环;每当它通过循环接收到一个连接,则打印出一个信息当连接断开,或接收到终止信息,则此连接结束,程序再接收一个新的连接命令行的格式是streams*/main{int sock,length;struct sockaddr_in server;struct sockaddrtcpaddr;int msgsock;char buf
[1024];int rval,len;/*建立套接字*/sock=socketAF_INET,SOCK_STREAM,0;ifsock0{perroropening stream socket;exit1;}/*使用任意端口命名套接字*/server.sin_family=AF_INET;server.sin_port=INADDR_ANY;ifbindsock,struct sockaddr*server,sizeofserver0{perrorbinding streamsocket;exit1;}/*找出指定的端口号并打印出来*/length=sizeofserver;ifgetsocknamesock,struct sockaddr*server,length0{perrorgetting socketname;exit1;}printfsocket port#%d\n,ntohsserver.sin_port;/*开始接收连接*/listensock,5;len=sizeofstruct sockaddr;do{msgsock=acceptsock,struct sockaddr*tcpaddr,int*len;ifmsgsock==-1perroraccept;else do{memsetbuf,0,sizeofbuf;ifrval=recvmsgsock,buf,10240perrorreading streammessage;ifrval==0printfending connection\n;else printf--;%s\n,buf;}whilerval!=0;closesocketmsgsock;}whileTRUE;/*因为这个程序已经有了一个无限循环,所以套接字sock从来不显式关闭然而,当进程被杀死或正常终止时,所有套接字都将自动地被关闭*/exit0;}客户方程序/*File Namestreamc.c*/#include#include#define DATAhalf aleague,halfaleague./*这个程序建立套接字,然后与命令行给出的套接字连接;连接结束时,在连接上发送一个消息,然后关闭套接字命令行的格式是streamc主机名端口号端口号要与服务器程序的端口号相同*/mainargc,argv intargc;char*argv[];{int sock;struct sockaddr_in server;struct hostent*hp,*gethostbyname;char buf
[1024];/*建立套接字*/sock=socketAF_INET,SOCK_STREAM,0;ifsock0{perroropening streamsocket;exit1;}/*使用命令行中指定的名字连接套接字*/server.sin_family=AF_INET;hp=gethostbynameargv
[1];ifhp==0{fprintfstderr,%s unknownhost\n,argv
[1];exit2;}memcpychar*server.sin_addr,char*hp-;h_addr,hp-;h_length;sever.sin_port=htonsatoiargv
[2];ifconnectsock,struct sockaddr*server,sizeofserver0{perrorconnecting streamsocket;exit3;}ifsendsock,DATA,sizeofDATA0perrorsending onstreamsocket;closesocketsock;exit0;}
2.5一个通用的实例程序在上一节中,我们介绍了一个简单的socket程序实例从这个例子我们可以看出,使用socket编程几乎有一个模式,即所有的程序几乎毫无例外地按相同的顺序调用相同的函数因此我们可以设想,设计一个中间层,它向上提供几个简单的函数,程序只要调用这几个函数就可以实现普通情考网考网下的数据传输,程序设计者不必太多地关心socket程序设计的细节本节我们将介绍一个通用的网络程序接口,它向上层提供几个简单的函数,程序设计者只要使用这几个函数就可以完成绝大多数情考网考网下的网络数据传输这些函数将socket编程和上层隔离开来,它使用面向连接的流式套接字,采用非阻塞的工作机制,程序只要调用这些函数查询网络消息并作出相应的响应即可这些函数包括l InitSocketsStruct初始化socket结构,获取服务端口号客户程序使用l InitPassiveSock初始化socket结构,获取服务端口号,建立主套接字服务器程序使用l CloseMainSock关闭主套接字服务器程序使用l CreateConnection建立连接客户程序使用l AcceptConnection接收连接服务器程序使用l CloseConnection关闭连接l QuerySocketsMsg查询套接字消息l SendPacket发送数据l RecvPacket接收数据
2.
5.1头文件/*File Nametcpsock.h*//*头文件包括socket程序经常用到的系统头文件本例中给出的是SCO Unix下的头文件,其它版本的Unix的头文件可能略有不同,并定义了我们自己的两个数据结构及其实例变量,以及我们提供的函数说明*/#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include typedef struct SocketsMsg{/*套接字消息结构*/int AcceptNum;/*指示是否有外来连接等待接收*/int ReadNum;/*有外来数据等待读取的连接数*/int ReadQueue
[32];/*有外来数据等待读取的连接队列*/int WriteNum;/*可以发送数据的连接数*/int WriteQueue
[32];/*可以发送数据的连接队列*/int ExceptNum;/*有例外的连接数*/int ExceptQueue
[32];/*有例外的连接队列*/}SocketsMsg;typedefstructSockets{/*套接字结构*/int DaemonSock;/*主套接字*/int SockNum;/*数据套接字数目*/int Sockets
[64];/*数据套接字数组*/fd_set readfds,writefds,exceptfds;/*要被检测的可读、可写、例外的套接字集合*/int Port;/*端口号*/}Sockets;Sockets Mysock;/*全局变量*/SocketsMsg SockMsg;int InitSocketsStructchar*servicename;int InitPassiveSockchar*servicename;void CloseMainSock;int CreateConnectionstruct in_addr*sin_addr;int AcceptConnectionstruct in_addr*IPaddr;int CloseConnectionintSockno;int QuerySocketsMsg;int SendPacketintSockno,void*buf,int len;int RecvPacketintSockno,void*buf,int size;
2.
5.2函数源文件/*File Nametcpsock.c*//*本文件给出九个函数的源代码,其中部分地方给出中文注释*/#includetcpsock.h intInitSocketsStructchar*servicename/*Initialize Socketsstructure.If succeedthen return1,else returnerror code0*//*此函数用于只需要主动套接字的客户程序,它用来获取服务信息服务的定义在/etc/services文件中*/{struct servent*servrec;struct sockaddr_in serv_addr;ifservrec=getservbynameservicename,tcp==NULL{return-1;}bzerochar*Mysock,sizeofSockets;Mysock.Port=servrec-;s_port;/*Service Portin Network Byte Order*/return1;}int InitPassiveSockchar*servicename/*Initialize PassiveSocket.If succeedthen return1,else returnerror code0*//*此函数用于需要被动套接字的服务器程序,它除了获取服务信息外,还建立一个被动套接字*/{int mainsock,flag=1;struct servent*servrec;struct sockaddr_in serv_addr;ifservrec=getservbynameservicename,tcp==NULL{return-1;}bzerochar*Mysock,sizeofSockets;Mysock.Port=servrec-;s_port;/*Service Portin NetworkByte Order*/ifmainsock=socketAF_INET,SOCK_STREAM,00{return-2;}bzerochar*serv_addr,sizeofserv_addr;serv_addr.sin_family=AF_INET;serv_addr.sin_addr.s_addr=htonlINADDR_ANY;/*任意网络接口*/serv_addr.sin_port=servrec-;s_port;ifbindmainsock,struct sockaddr*serv_addr,sizeofserv_addr0{closemainsock;return-3;}iflistenmainsock,5==-1{/*将主动套接字变为被动套接字,准备好接收连接*/closemainsock;return-4;}/*Set thissocket asaNon-blocking socket.*/ifioctlmainsock,FIONBIO,flag==-1{closemainsock;return-5;}Mysock.DaemonSock=mainsock;FD_SETmainsock,Mysock.readfds;/*申明对主套接字可读感兴趣*/FD_SETmainsock,Mysock.exceptfds;/*申明对主套接字上例外事件感兴趣*/return1;}void CloseMainSock/*关闭主套接字,并清除对它上面事件的申明在程序结束前关闭主套接字是一个好习惯*/{closeMysock.DaemonSock;FD_CLRMysock.DaemonSock,Mysock.readfds;FD_CLRMysock.DaemonSock,Mysock.exceptfds;}int CreateConnectionstructin_addr*sin_addr/*Create aConnectionto remotehost whichIP addressis insin_addr.Param sin_addr indicatesthe IPaddress inNetworkByteOrder.if succeedreturn thesocket numberwhich indicatesthis connection,else returnerror code0*/{struct sockaddr_in server;/*server address*/int tmpsock,flag=1,i;iftmpsock=socketAF_INET,SOCK_STREAM,00return-1;server.sin_family=AF_INET;server.sin_port=Mysock.Port;server.sin_addr.s_addr=sin_addr-;s_addr;/*Set thissocket asaNon-blocking socket.*/ifioctltmpsock,FIONBIO,flag==-1{closetmpsock;return-2;}/*Connect tothe server.*/ifconnecttmpsock,structsockaddr*server,sizeofserver0{iferrno!=EWOULDBLOCKerrno!=EINPROGRESS{/*如果错误代码是EWOULDBLOCK和EINPROGRESS,则不用关闭套接字,因为系统将在之后继续为套接字建立连接,连接是否建立成功可用select函数来检测套接字是否可写来确定*/closetmpsock;return-3;/*Connect error.*/}}FD_SETtmpsock,Mysock.readfds;FD_SETtmpsock,Mysock.writefds;FD_SETtmpsock,Mysock.exceptfds;i=0;whileMysock.Sockets!=0i++;/*look forablank socketsposition*/ifi;=64{closetmpsock;return-4;/*too manyconnections*/}Mysock.Sockets=tmpsock;Mysock.SockNum++;returni;}int AcceptConnectionstructin_addr*IPaddr/*Accept aconnection.If succeed,return thedata socketsnumber,else return-
1.*/{int newsock,len,flag=1,i;structsockaddr_in addr;len=sizeofaddr;bzerochar*addr,len;ifnewsock=acceptMysock.DaemonSock,addr,len==-1return-1;/*Accept error.*//*Set thissocket asaNon-blocking socket.*/ioctlnewsock,FIONBIO,flag;FD_SETnewsock,Mysock.readfds;FD_SETnewsock,Mysock.writefds;FD_SETnewsock,Mysock.exceptfds;/*Return IPaddress inthe Parameter.*/IPaddr-;s_addr=addr.sin_addr.s_addr;i=0;whileMysock.Sockets!=0i++;/*look forablank socketsposition*/ifi;=64{closenewsock;return-4;/*too manyconnections*/}Mysock.Sockets=newsock;Mysock.SockNum++;returni;}int CloseConnectionintSockno/*Close aconnectionindicated bySockno.*/{int retcode;ifSockno;=64||Sockno0||Mysock.Sockets[Sockno]==0return0;retcode=closeMysock.Sockets[Sockno];FD_CLRMysock.Sockets[Sockno],Mysock.readfds;FD_CLRMysock.Sockets[Sockno],Mysock.writefds;FD_CLRMysock.Sockets[Sockno],Mysock.exceptfds;Mysock.Sockets[Sockno]=0;Mysock.SockNum--;returnretcode;}int QuerySocketsMsg/*Query SocketsMessage.If succeedreturn messagenumber,else return-
1.The messageinformation storedin structSockMsg.*/{fd_set rfds,wfds,efds;int retcode,i;structtimevalTimeOut;rfds=Mysock.readfds;wfds=Mysock.writefds;efds=Mysock.exceptfds;TimeOut.tv_sec=0;/*立即返回,不阻塞*/TimeOut.tv_usec=0;bzerochar*SockMsg,sizeofSockMsg;ifretcode=select64,rfds,wfds,efds,TimeOut==0return0;ifFD_ISSETMysock.DaemonSock,rfds SockMsg.AcceptNum=1;/*some clientcall server.*/fori=0;i64;i++/*Data inmessage*/{ifMysock.Sockets;0FD_ISSETMysock.Sockets,rfds SockMsg.ReadQueue[SockMsg.ReadNum++]=i;}fori=0;i64;i++/*Data outready message*/{ifMysock.Sockets;0FD_ISSETMysock.Sockets,wfds SockMsg.WriteQueue[SockMsg.WriteNum++]=i;}ifFD_ISSETMysock.DaemonSock,efds SockMsg.AcceptNum=-1;/*server socketerror.*/fori=0;i64;i++/*Error message*/{ifMysock.Sockets;0FD_ISSETMysock.Sockets,efds SockMsg.ExceptQueue[SockMsg.ExceptNum++]=i;}returnretcode;}int SendPacketintSockno,void*buf,int len/*Send apacket.If succeedreturn thenumber ofsend data,else return-1*/{int actlen;ifSockno;=64||Sockno0||Mysock.Sockets[Sockno]==0return0;ifactlen=sendMysock.Sockets[Sockno],buf,len,00return-1;returnactlen;}int RecvPacketintSockno,void*buf,int size/*Receive apacket.If succeedreturn thenumber ofreceive data,else ifthe connectionis shutdownby peerthen return0,otherwise return0-errno*/{int actlen;ifSockno;=64||Sockno0||Mysock.Sockets[Sockno]==0return0;ifactlen=recvMysock.Sockets[Sockno],buf,size,00return0-errno;returnactlen;/*actlen是接收的数据长度,如果为零,指示连接被对方关闭*/}
2.
5.3简单服务器程序示例/*File Nameserver.c*//*这是一个很简单的重复服务器程序,它初始化好被动套接字后,循环等待接收连接如果接收到连接,它显示数据套接字序号和客户端的IP地址;如果数据套接字上有数据到来,它接收数据并显示该连接的数据套接字序号和接收到的字符串*/#includetcpsock.h mainargc,argv intargc;char*argv;{structin_addr sin_addr;int retcode,i;char buf
[32];/*对于服务器程序,它经常是处于无限循环状态,只有在用户主动kill该进程或系统关机时,它才结束对于使用kill强行终止的服务器程序,由于主套接字没有关闭,资源没有主动释放,可能会给随后的服务器程序重新启动产生影响因此,主动关闭主套接字是一个良好的变成习惯下面的语句使程序在接收到SIGINT、SIGQUIT和SIGTERM等信号时先执行CloseMainSock函数关闭主套接字,然后再结束程序因此,在使用kill强行终止服务器进程时,应该先使用kill-2PID给服务器程序一个消息使其关闭主套接字,然后在用kill-9PID强行结束该进程*/voidsignalSIGINT,CloseMainSock;voidsignalSIGQUIT,CloseMainSock;voidsignalSIGTERM,CloseMainSock;ifretcode=InitPassiveSockTestService0{printfInitPassiveSock errorcode=%d\n,retcode;exit-1;}while1{retcode=QuerySocketsMsg;/*查询网络消息*/ifSockMsg.AcceptNum==1{/*有外来连接等待接收*/retcode=AcceptConnectionsin_addr;printfretcode=%d,IP=%s\n,retcode,inet_ntoasin_addr.s_addr;}else ifSockMsg.AcceptNum==-1/*主套接字错误*/printfDaemon Socketserror.\n;fori=0;i ifretcode=RecvPacketSockMsg.ReadQueue,buf,32;0printfsockno%d Recvstring=%s\n,SockMsg.ReadQueue,buf;else/*返回数据长度为零,指示连接中断,关闭套接字*/CloseConnectionSockMsg.ReadQueue;}}/*end while*/}
2.
5.4简单客户程序示例/*File Nameclient.c*//*客户程序在执行时,先初始化数据结构,然后等待用户输入命令它识别四个命令connect和服务器建立连接;send给指定连接发送数据;close关闭指定连接;quit退出客户程序*/#includetcpsock.h mainargc,argv intargc;char*argv;{char cmd_buf
[16];structin_addr sin_addr;int sockno1,retcode;char*buf=This isastring fortest.;sin_addr.s_addr=inet_addr
166.
111.
5.249;/*运行服务器程序的主机的IP地址*/ifretcode=InitSocketsStructTestService0{/*初始化数据结构*/printfInitSocketsStruct errorcode=%d\n,retcode;exit1;}while1{printf;;getscmd_buf;if!strncmpcmd_buf,conn,4{retcode=CreateConnectionsin_addr;/*建立连接*/printfreturn code%d\n,retcode;}else if!strncmpcmd_buf,send,4{printfSockets Number;scanf%d,sockno1;retcode=SendPacketsockno1,buf,26;/*发送数据*/printfreturn code%d\n,retcode,sizeofbuf;}else if!strncmpcmd_buf,close,4{printfSockets Number;scanf%d,sockno1;retcode=CloseConnectionsockno1;/*关闭连接*/printfreturn code%d\n,retcode;}else if!strncmpcmd_buf,quit,4exit0;else putchar[message]7;}/*end while*/}接着直接上图说明socket编程的连接过程,图文结合更易于理解,图片是网上找的第一幅STREAM方式TCP连接流程第二幅DGRAMUDP连接流程特别声明1资料来源于互联网,版权归属原作者2资料内容属于网络意见,与本账号立场无关3如有侵权,请告知,立即删除。