《网络应用程序设计》课件第2章 基于TCP套接字的编程_第1页
《网络应用程序设计》课件第2章 基于TCP套接字的编程_第2页
《网络应用程序设计》课件第2章 基于TCP套接字的编程_第3页
《网络应用程序设计》课件第2章 基于TCP套接字的编程_第4页
《网络应用程序设计》课件第2章 基于TCP套接字的编程_第5页
已阅读5页,还剩159页未读 继续免费阅读

下载本文档

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

文档简介

第2章基于TCP套接字的编程

2.1概述2.2套接字和套接字地址2.3基本套接字函数2.4高级套接字函数2.5多路复用2.6网络字节传输顺序及主机字节顺序2.7DNS与域名访问2.8基于IP和域名的通信编程2.9基于TCP套接字编程示例习题2.1概述 20世纪80年代早期,美国国防高级研究计划局(ARPA,AdvancedResearchProjectsAgency)资助了加利福尼亚大学伯克利分校一个研究组,将TCP/IP软件移植到UNIX操作系统中,并将结果提供给其他网点。作为项目的一部分,设计者们希望像访问文件一样去访问网络,因此,创建了一个接口,应用进程使用这个接口可以方便的进行通信。为了支持TCP/IP功能增加的新系统调用接口,形成了BerkeleySocket,这个系统被称为BerkeleyUNIX或BSDUNIX(TCP/IP首次出现在BSD4.1版本(release4.1ofBerkeleySoftwareDistribution))。由于许多计算机厂商都采用了BerkeleyUNIX,Socket得到了迅速普及并被广泛使用。

在UNIX系统中,网络应用编程界面有两类:UNIX

BSD的套接字(socket)和UNIX

System

V的TLI。由于Sun公司采用了支持TCP/IP的UNIX

BSD操作系统,使TCP/IP的应用有更大的发展,其网络应用编程接口──套接字在网络软件中也被广泛应用,至今已引进到Linux和Windows系统中,成为开发网络应用软件的强有力工具,本章将详细讨论套接字的使用。

Linux产生时,UNIX系统的网络功能已经相当成熟了,Linux网络的开发者选择了重新开发网络功能。在Linux网络代码开发的过程中,很多程序员做出了贡献。它提供的套接字有三种类型:流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM)及原始套接字(SOCK_RAW)。下面将详细介绍这些套接字的定义与应用。2.2套接字和套接字地址

2.2.1套接字 套接字是两个通信通道上的端节点。套接字函数可以用来产生通信信道,通过信道两个应用程序间可以传送数据。图2-1显示了利用套接字进行通信的示例。

图2-1套接字(a)套接字作为网络传输端点;(b)套接字对多种协议的支持

套接字是信道的末端,当应用程序产生一个套接字后,套接字函数就返回所用文件的描述符。在这里,可以把支持虚电路服务的信道看做电话线,套接字就像一个电话。同样,可以把提供数据报服务的信道看做邮局系统,套接字看做信箱,人们可以向邮箱投递信件,信件通过邮局系统到达另一个信箱。应用程序利用套接字发数据报,数据报通过信道传向另一个套接字。在产生信道时,用户可以指定所用的传输提供者。例如,可以用TCP、UDP、XNS作传输提供者。 Linux支持多种类型的套接字,也叫做套接字寻址簇,这是因为每种类型的套接字都有自己的寻址方法。Linux支持以下的套接字类型:

UNIX UNIX域套接字

INET Internet地址簇CP/IP协议支持通信

IPX NovellIPX APPLETALK AppletalkDDP X25 X25

这些类型的套接字代表各种不同的连接服务。

Linux的BSD套接字支持下面的几种套接字类型:

(1)流式(stream)。这种套接字提供了可靠的双向顺序数据流连接。它可以保证数据传输中的完整性、正确性和单一性。INET寻址簇中的TCP协议支持这种类型的套接字。

(2)数据报(datagram)。这种类型的套接字也可以像流式套接字一样提供双向的数据传输,但它们不能保证传输的数据一定能够到达目的节点,也无法保证到达数据以正确的顺序到达以及数据的单一性、正确性。UDP协议支持这种类型的套接字。

(3)原始(raw)。这种类型的套接字允许进程直接存取下层的协议。

(4)可靠递送消息(reliabledeliveredmessages)。这种套接字和数据报套接字一样,只能保证数据的到达。 (5)顺序数据包(sequencedpackets)。这种套接字和流式套接字相同,但是它的数据包的大小是固定的。

(6)数据包(packet)。这不是标准的BSD套接字类型,而是Linux中的一种扩展。它允许进程直接存取设备层的数据包。 套接字的特点:

(1)套接字没有与它相连的设备文件。应用程序可以用scoket()产生套接字,指定所用的信道类型。scoket()返回与所用信道末端相适应的文件描述符。

(2)只要进程保存文件描述符,套接字就一直存在,直到没有进程打开文件描述符为止,套接字才被撤消。 (3)可以产生一个套接字,也可以同时产生一对套接字。如果产生一对套接字,则操作系统会自动在它们之间建立信道。如果只产生一个套接字,则用户程序就需要用套接字函数在该套接字与其他套接字间建立信道。 因此,socket是一个工具,或者说是一种不可见控件,应用程序可以通过socket函数,来访问底层网络协议。 2.2.2套接字地址

套接字接口利用传送提供者进行工作,不同的传送提供者有不同的地址,套接字接口允许指定任意类型的地址。

Linux系统的套接字是一个通用的网络编程接口,它支持多种协议,每一种协议使用不同的套接字地址结构。Linux系统定义了一种通用的套接字地址结构,可以保持套接字函数调用参数的一致性。如下所示: structsockaddr { unsignedshortsa_family; /*

地址类型,AF_xxx*/ charsa_data[14]; /*

协议地址

*/ };

其中:

sa_family:保存协议标识符。

AF_INET:代表TCP/IP协议簇。

sa_data:保存具体的协议地址。

TCP/IP协议簇的套接字地址也可以采用如下结构:

#include<netinet/in.h>

#include<sys/socket.h>

structin_addr { _u32s_addr; /* UINT类型

*/ } structsockaddr_in { shortintsin_family; /*

地址类型:AF_XXX*/ unsignedshortintsin_port; /*

端口号

*/ structin_addrsin_addr; /* Internet地址

*/ unsignedcharsin_zero[8];}; (3) sin_port和sin_addr必须保证以网络字节顺序传输。

(4) IP地址作为参数传送时,注意×××addr.sin_addr与×××addr.sin_addr.s_addr之间的差别。×××addr.sin_addr形式引用的是structsin_addr结构类型的数据,×××addr.sin_addr.s_addr形式的引用是整数类型的数据。

(5)由于<linux/in.h>和<linux/socket.h>是Linux特有的头文件,为了能够保持代码的可移植性,在程序中不要直接包含它们,而与平台无关的<netinet/in.h>、<sys/socket.h>包含了它们。因此,程序中应该包含这两个头文件。 2.2.3IP地址的使用 在设置sockaddr_in类型的地址时,需要进行字符串形式的IP地址和二进制形式的地址间的转换,有如下一系列的函数可以处理IP地址的转换:

#include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> intinet_aton(constchar*cp,structin_addr*inp); unsignedlongintinet_addr(constchar*cp); char*inet_ntoa(structin_addrin);

这几个函数将点分十进制数字形式表示的IP地址与32位的网络字节顺序的二进制形式的IP地址进行转换。例如,可以使用 inet_addr()程序把诸如“0”形式的IP地址转化为无符号的整型数C0A8000A。函数inet_addr和inet_aton功能相同,函数inet_addr已过时,编程时应使用函数inet_aton。也可以调用inet_ntoa()把地址转换成数字和句点的形式:printf("%s",

inet_ntoa(ina.sin_addr));

这将会打印出IP地址。它返回的是一个指向字符串的指针。例如: char*a1,

*a2; … a1=inet_ntoa(ina1.sin_addr); /*

这是的二进制形式

*/ a2=inet_ntoa(ina2.sin_addr); /*

这是0的二进制形式*/ printf("address1:%s\n",a1); printf("address2:%s\n",a2);

输出如下:

address1: address2:02.3基本套接字函数

在各种网络编程接口中,socket脱颖而出,越来越得到大家的重视,这是因为socket规范是一套开放的、支持多种协议的网络编程接口。经过不断发展和完善,并在Intel、Microsoft、Sun、SGI、Informix、Novell等公司的全力支持下,已成为网络编程的事实上的标准。下面给出套接字函数及其使用方法。

1.socket()

在利用套接字进行网络通信时,进程要做的第一件事就是调用socket(),产生一个套接字,并指明将要使用的通信协议,如TCP、UDP、XNS、SPP等。 #include<sys/types.h>

#include<sys/socket.h>

intsocket(intfamily,inttype,intprotocol);

socket()返回一文件描述符,从应用的角度讲,该文件描述符是指通信信道的末端。如果调用失败,则返回-1。其中参数定义为:

●family:表示所用的协议是协议簇中的哪一个。协议簇是有相同地址格式的一组传送提供者。例如,TCP和UDP有同样的地址格式,因此它们属于同一协议簇。family的值可以为:

AF_INET:TCP/IP协议集合。

AF_UNIX:UNIX域协议簇,在本机的进程间通信时使用。

AF_ISO:ISO协议簇。 ●type:表示套接字类型: sock_STREAM:提供虚电路服务的流套接字。

sock_DGRAM:提供数据报服务的套接字。

sock_RAW:原始套接字,只对Internet协议有效,可以用来直接访问IP协议。

sock_SEQPACKET:有序分组套接字。

sock_RDM:能可靠交付信息的数据报套接字。 ●protocol:表示指定所用协议。对于大多数应用protocol都被设置为0,表示使用默认协议。但是,如果对给定的family及服务类型有多种协议可选,就须指定所用协议。例如,套接字要用TCP协议。因为TCP是TCP/IP协议集合中的一员,要用这个协议簇,应将family设为AF_INET。TCP/IP支持虚电路服务,故应将第二个参数types设为SOCK_STREAM。因为TCP是TCP/IP协议集合中惟一提供虚电路服务的传送提供者,所以protocol可以设为0。socket()调用为: intfd; fd=socket(AF_INET,SOCK_STREAM,0);

又例如,用UDP协议,支持数据报服务。因为UDP属于TCP/IP协议簇,是TCP/IP协议集合中仅有的提供数据报服务的传输提供者,所以,protocol设置为0。该socket()调用为:

intfd; fd=socket(AF_INET,SOCK_DGRAM,0);

例如,产生一个套接字,访问TCP/IP协议集合的低层协议,可将套接字类型设为SOCK_RAW,这样允许访问低层协议,包括IP和ICMP。要让产生的套接字直接访问IP,protcol应设为IPPROTO_RAW。socket()调用为: intfd fd=socket(AF_INET,SOCK_RAW,IPPROTO_RAW);

调用socket函数时,socket执行体将建立一个socket,并返回一个指向描述符表入口的socket句柄。实际上创建一个socket意味着为一个socket数据结构分配存储空间,如图2-2所示。图2-2socket数据结构

表2-1给出了适应AF_INET及AF_UNIX协议簇的type及protocol的属性取值。表2-1AF_INET及

AF_UNIX协议簇的type及protocol的属性取值 2.socketpair()

socketpair()产生两个套接字,连接这两个套接字,然后返回相应的文件描述符,它也称为UNIX域套接字。其调用格式为:

#include<sgs/types.h>

#include<sys/socket.h>

intsocketpair(intfamily,inttype,intprotocol,intfd_array[2]);

函数socketpair()返回两个套接字描述符:socket[0]和socket[1],与管道pipe相似,只是socketpair()返回一对套接字描述符,而不是文件描述符。socketpair()返回的两个套接字描述符是双向的,而管道是单向的。 Family、type、protocol参数含义与socket()函数一样,但是

socket()返回一个无连接套接字的文件描述符,socketpair()返回两个连接好的套接字。调用成功返回2,否则返回-1。

family只能取值AF_UNIX,由于此系统调用仅用于UNIX支配协议,因此只有两种可用形式。一种是:intrc,fd_array[2]; rc=socketpair(AF_UNIX,SOCK_STREAM,0,fd_array);

socketpair()用SVR4面向连接的传输提供者之一产生一个通信信道。另一种是: intrc,fd_array[2]; rc=socketpair(AF_UNIX,SOCK_DGRAM,0,fd_array);

这里,函数socketpair()用无连接的传输提供者产生一个通信信道。 下面的函数可以用来生成一个域套接字间的通信信道。

#include<sys/types.h> #include<sys/socket.h> /*

如果成功,则返回0,否则返回-1*/ /*

用fd返回两个文件描述符

*/

intfd[2]; { return(socketpair(AF_UNIX,SOCK_STREAM,0,fd)); } 3.bind()

调用socket函数创建套接字后,存在一个名字空间,但它没有被命名。bind()将套接字地址(包括本地主机地址和本地端口地址)与所创建的套接字句柄联系起来,即将名字赋予套接字,以指定本地址中的{协议,本地地址,本地端口}。其调用格式如下:

#include<sys/types.h> #include<sys/socket.h> int=bind(intfd,structsockaddr*addressp,

intaddrlen);

其中:

fd:由socket()函数返回的套接字描述符。

addressp:向协议传送地址的指针,包含有关的地址信息:名称、端口和IP地址。

addrlen:地址结构的字节数。

bind()把传送地址与套接字连接在一起,调用成功返回

0,否则返回-1。地址的格式取决于与套接字相连的信道。例如,如果套接字的family设置为AF_UNIX,那么地址取sockaddr_un结构地址。 服务器和客户机均可以调用函数bind绑定套接字地址,服务器往往绑定公认端口号,提供公共服务。

处理地址绑定的程序片段如下:

bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family=AF_INET; /*

地址簇

*/ servaddr.sin_port=htons(serverport); /*

端口号

*/ servaddr.sin_addr.s_addr=htonl(INADDR_ANY); /*

使用本机IP地址

*/

if(bind(sockfd,(structsockaddr*)&servaddr,

sizeof(structsockaddr))<0) { perror("bindtoserverporterror"); exit(1) }

注意,在给网络地址赋值时,程序使用了INADDR_ANY,而不是确定的IP地址。这里INADDR_ANY的含义是任何网络设备接口。对于一台只有一个IP地址的主机,它就对应于它的IP地址。但是有的主机可能有多个网络接口,即多个网卡,并拥有多个IP地址,INADDR_ANY表示来自所有网络接口的相应传输层端口的连接请求,都由这个服务器进行处理。路由器就是典型的例子。 一般来说,绑定操作具有以下几种组合方式,见表2-2。表2-2绑定地址及端口号的设置方式 (1)服务器的套接字设定公认端口号和INADDR_ANY地址。服务器的套接字设定INADDR_ANY地址表明它要接收来自任何网络设备接口的客户连接请求,这是服务器常用的地址绑定方式。

(2)服务器的套接字设定公认端口号和IP地址。如果服务器的套接字绑定公认端口号和具体IP地址,则表明服务器只接收来自这个IP地址的网络接口卡的客户机的连接请求。当服务器只有一个网络接口设备时,这种设置和(1)没有区别。当服务器有多个网络接口设备时,这种方法可以用来限制服务器的接收范围。 (3)客户机指定套接字地址中的端口号。当客户机调用connect函数请求TCP连接时,系统会自动为它选择一个未用端口号,并且用本地的IP地址设置套接字地址的相应项。因此,一般来说,客户机不用指定自己套接字地址的端口号。当客户机需要使用特定端口号时,如Linux系统中的rlogin命令,应当调用bind函数绑定一个未用的保留端口号。

(4)客户机设置指定IP地址和连接端口号。当客户机绑定指定IP地址和连接端口号时,表示要用指定的网络设备接口和端口号进行通信。

(5)指定客户机的IP地址。客户机使用指定的网络设备接口进行通信,由系统为其选择一个未用的端口号。这种情况在系统具有多个网络设备接口时使用。

4.connect()

客户进程在用socket()产生套接字后,用connect()将该套接字与服务器套接字相连接。

#include<sys/types.h>

#include<sys/socket.h>

intconnect(intfd,structsockaddr*addressp,intaddrlen);

其中:

fa:是套接字描述符。

addressp:套接字地址指针。

addrlen:地址结构的字节数。 地址的格式取决于所用的信道。例如,如果信道的domain=AF_INET,那么用sockaddr_in结构保存地址;如果domain=AF_UNIX,那么用sockaddr_un结构保存地址。

如果用数据报服务,则connect()在套接字与目的地址间建立简单的联系。系统在所有通过信道发送的数据报上均放上该地址。

通常,在调用connect()前,客户应用程序应绑定套接字地址,提出连接请求的程序片段如下:

bzero(&servaddr,

sizeof(servaddr));

servaddr.sin_family=AF_INET;

servaddr.sin_port=htons(serverport);

if(inet_aton("",&servaddr.sin_addr)<0)

{

perror("inet_atonerror");

exit(1);

} …

if(connect(sockfd,

(structsockaddr*)&servaddr,

sizeof(servaddr))<0)

{

perror("connecterror");

exit(1);

}

...

客户机一般不指定自己的端口号,而由系统为其选一个端口号,即自由端口。 5.listen()

当服务器完成当前请求以前又发生多个服务请求时,例如,假设重复服务器处理完最近服务请求以前,客户程序试图与它相连,或者假设并行服务器在为最近服务请求建立好一个单独进程以前,客户程序请求和服务器建立连接。在这种情况下,服务器可以拒绝到来的服务请求。为了更好地处理这些问题,服务器可以使用listen()函数将所有的服务请求放在一个请求队列中排队,并尽快地处理这些请求。 socket执行体建立一个输入请求队列,将到达的服务请求保存在此队列上,直到处理完它们为止,即listen()函数不仅使socket处于被动的侦听状态,同时告诉socket执行体对此socket处理多个同时的服务请求。面向连接的服务器用该函数指明它愿意接收连接。其调用格式为:

#include<sys/socket.h> intlisten(intfd,intqlen);

其中:

fd:套接字的文件描述符,套接字只能是SOCK_STREAM,SOCK_SEQPACKET类型。

qlen:连接请求队列长度。

当一个连接请求到达时,被插入请求队列。服务器用accept()函数从队列中移走一个请求并响应这个请求。listen()函数不进入睡眠状态等待请求的到达,它只建立一等待队列,控制便返回应用程序。执行成功便返回0值,否则返回-1。

在后面的例程中,将看到listen()函数的使用。 6.accept()

面向连接的服务器执行了listen()函数后,执行accept()函数等待来自某一客户进程的实际连接请求。然后进入睡眠状态,等待客户机的连接请求。当服务请求到达accept()函数监视的socket时,socket执行体将自动建立一个新的socket,并将此socket和客户进程连接起来。收到服务请求的初始socket仍具有设定的地址,因此,它可以继续接收到达的服务请求。 下面是accept()函数的声明:

#include<sys/types.h> #include<sys/socket.h> intaccept(intfd,sockaddr*addressp,int*addrlen);

参数addressp用于返回所连接的对等进程的地址,里面存储着远程连接的计算机信息(比如远程计算机的IP地址和端口)。参数addrlen是一个本地的整型数值,由协议执行体设置它的值。一般情况下,调用者把该值设置为缓冲区的大小,该调用也可设置成sockaddr的结构大小,在系统调用返回时将此值改变成为存放在缓冲区中的实际字节数。

accept()从队列上取出第一个连接请求,建立一个与参数fd相同特性的套接字。参数fd所指套接字类型可以是SOCK_STREAM或SOCK_SEQPACKET。如果采用同步通信模式,则accept()将等待连接请求的到达。在连接请求到达时,accept()产生一个新套接字,并对其行连接,返回新套接字的文件描述符。此刻用所产生的新套接字与客户应用进行通信,而用原来的套接字接收其他连接请求。

如果调用accept()时队列上无连接请求,那么此调用在一个请求到达前阻塞调用者的运行,accept()返回-1,errno置为EWOULDBLOCK,指出无连接请求到达。

7.read()及write() read()函数和write()函数是用来接收和发送数据的两个函数。read()函数用于从套接字缓冲区读取数据,write()函数用于往套接字缓冲区写数据,它们的定义分别如下:

intread(intfd,char*buf,intlen); intwrite(intfd,char*buf,intlen);

参数fd是我们要进行读写操作的连接套接字描述符(由connect()函数或accept()函数返回),参数buf是用于存放欲接收或待发送数据的应用缓冲区,参数len指定了欲发送或者接收的数据字节数。

对于read()函数,调用后将从套接字接收缓冲区中读取len字节的数据,如果函数成功返回,返回值将是实际读取数据的字节数,见表2-3。表2-3read()函数返回值及其意义

对于write()函数,返回值意义与read()函数基本相同,只不过当函数成功返回时,返回值表示的是实际写入数据的字节数。 对于read()和write()函数参数中的参数“buf”,指的是接收或发送进程的应用缓冲区。实际上,进程创建的每个TCP套接字还有一个套接字接收缓冲区和发送缓冲区,分别用于存放即将从网络读或写向网络的数据,真正从套接字缓冲区到网络读写数据的过程由内核TCP协议来完成,不需要应用进程干涉。

即read()函数并不是从网络读取数据,而只是完成从套接字接收缓冲区到接收进程应用缓冲区复制数据的任务,此时,有几种可能情况发生:如果套接字接收缓冲区的可读数据量小于read()函数的参数len指定的数据量时,则read()函数返回套接字缓冲区的所有数据,此时,返回值会小于len的值;如果套接字接收缓冲区的可读数据大于参数len指定的数据量时,则read()函数将返回参数len指定的数据量;如果套接字接收缓冲区暂时无可读数据,则read()函数将阻塞,一直等到有数据返回。

write()函数执行的操作以及返回情况类似于read()函数。特别需要注意的是,write()函数成功返回之后,并不意味着数据已经通过网络发送到对方主机,而只是说明已经存放在套接字缓冲区中,等待TCP协议来发送。 图2-3表示了进程应用缓冲区和套接字缓冲区的关系。图2-3进程应用缓冲区与套接字缓冲区的关系

下面给出两个程序段来帮助理解函数read()和write()的使用。

1)函数read_all intread_all(intfd,char*buf,intnBytes); { for(;;) { rc=read(fd,buf,nBytes); if(rc>0) /*

读出rc个字节

*/ {returnrc;} elseif(rc==0) /*

读通道已关闭

*/{close(fd);return0;}elseif(errno==EINTR){/*

由读中断引起错误

*/} else { printf(stderr,"Readerror"); close(fd); return-1; } } }

当read用于套接字读取时,应分析read()函数的返回值,特别是返回值小于等于零的情况,当返回值小于零时并非表示真正出错。 (1)返回值大于零,套接字接收缓冲区接收到数据,返回值表示读取的数据字节数。

(2)返回值等于零,并且以后这个套接字读操作返回值均为0,表示对方已发送完所有的数据。这和读文件时遇到文件结束符的情况相似。

(3)如果read错误返回,且错误号为EINTR,则是指进程阻塞过程中接收到了信号,并非真正出错,以后可以继续利用该套接字读数据。

(4)当read错误返回,且错误号为ECONNRESET时,表示TCP协议接收到RST数据段,连接出现了某种错误,并且以后所有在这个套接字上的读操作均返回错误。 2)函数write_all

intwrite_all(intfd,char*buf,intnBytes);

{

for(;;)

{

wc=write(sockfd,buf,

nBytes);

if(wc>0)/*

写入成功

*/

returnwc;

elseif(errno==EINTR) { /*

由中断引起错误

*/ } else { printf(stderr,"Writeerror"); close(sockfd); return-1; } } }

同读操作类似,write操作也要按返回值分情况处理。

(1) TCP协议接收到RST数据段。当接收方已关闭连接之后,如果继续向这个套接字上发送数据将导致对方的TCP协议返回RST数据段,函数wirte返回错误,错误类型为EPIPE,并且以后所有这个套接字上的写操作均以错误返回。

(2)进程阻塞期间接收到信号。如果进程接收到信号,则函数wirte()返回错误,错误类型为EINTR。这并不是真正出现了错误,可以继续利用该套接字发送数据。 以上两程序段分别实现对缓存内数据的读(read)、写(write)操作,较为详细的说明了read()和write()两个函数的调用方法和出错类型。2.4高级套接字函数 1.send()及sendto() send()与sendto()均可用来通过信道发送数据。send()可用于流式或数据报通信信道。在采用数据报传送方式时,应利用connect()函数给出所连接的套接字地址。sendto()函数用于无连接的数据报发送,发送数据报时须指出数据要发送到的目的地址。由于这个地址是与协议相关的,因此不同协议对应的地址也不同,其长度由addrlen指定。sendto()基本上用于无连接套接字。它们的功能与write系统调用相似。其调用方式如下: #include<sys/types.h>

#include<sys/socket.h>

intsend(intfd,char*buf,intlen,intflags);

intsendto(intfd,char*buf,intlen,intflags,structsockaddr*toaddr,intaddrlen);

其中,参数定义为: fd:套接字的文件描述符。

buf:数据缓冲区。

len:数据缓冲区字节数。

toaddr:存放数据要发送的目的套接字地址。

addrlen:地址的字节数。

flags:取值为0时,send()的功能与wrirte()相同;为MSG_OOB时,发送连接带外数据(OOB,Out_Of_Band);为MSG_DONTWAIT时,如果套接字缓冲区中没有足够空间,则进程不阻塞等待;MSG_DONTROULE:旁路路由选择。

2.recv()及recvfrom()

recv()及recvfrom()功能类似于read()系统调用。它们都是用来从连接的套接字上读数据,可以用在client或server上的应用中。它们的调用格式为:

#include<sys/types.h>

#include<sys/socket.h>

intrecv(intfd,char*buf,intlen,intflags);

intrecvfrom(intfd,char*buf,intlen,intflags,structsockaddr*fromaddr,intaddrlen);

其中,参数定义如下:

fd:套接字的文件描述符。

buf:数据缓冲区。

len:数据缓冲区字节数。

fromaddr:发送数据的套接字地址。

addrlen:地址的字节数。

flags:取值为:0:功能与read()相同;MSG_OOB:接收连接外数据;MSG_WAITALL:如果套接字缓冲区中没有足够空间,则进程不阻塞等待;MSG_PEEK:将数据放置于缓冲区中,该数据使用后并不马上被取消,因此,下一个读操作将会读到同样的数据;MSG_DONTROUTE:发送数据时,不需要查看路由表。

返回值:按同步方式工作时,等待数据的到达;按异步方式工作时,如果套接字中无数据,则设置errno为EWOULDBLOCK,并返回-1,表示数据未准备好。只返回-1时,表示出错。

3.sendmsg()及recvmsg() sendmsg()用来发送数据,包含sendto()的功能。除此之外,它还可以规格化数据以及发送被中断了的数据,与sendto()一样,可以用于虚电路和数据报方式。recvmsg()用于接收sendmsg()发送的数据。它们的使用方式为:

#include<sys/types.h> #include<sys/socket.h> #include<sys/uio.h> structiovec {caddr_t iov_base; /*

缓冲区的起始地址

*/ int iov_len; /*

缓冲区的字节数

*/ };

structmsghdr {caddr_t msg_name; /*

目的套接字地址

*/

int msg_namelen; /*

地址的字节数

*/ structiovec*msg_iov; /*

包含信息的数组

*/ intmsg_iovlen; /*

结构数组的成员量

*/caddr_tmsg_accrights; /*

访问权限

*/intmsg_accrightslen;};

函数定义:

intsendmsg(intfd,structmsghdr*msgp,intflags); intrecvmsg(intfd,structmsghdr*msgp,intflags);

其中,结构iovec保存信息数据。

msg_name和msg_namelen是发送信息的套接字地址。

msg_iov由sendmsg()函数填写结构

iovec的内容。 4.readv和writev函数

readv、writev函数同read、wirte功能类似,readv和writev函数用于一次读、写多个非连续缓存。有时也将这两个函数称为散布读(scatterread)和聚集写(gatherwrite)。调用格式为:

#include<sys/types.h>

#include<sys/uio.h>

ssize_treadv(intfiledes,conststructioveciov[],intiovcnt);ssize_twritev(intfiledes,conststructioveciov[],intiovcnt);

两个函数返回:已读、写的字节数;若出错,则为-1; 两个函数中的第二个参数是指向iovec结构数组的一个指针:

structiovec{ void*iov_base; /*

缓冲区起始地址

*/ size_tiov_len; /*

缓冲区大小

*/ };

成员变量iov_base指向缓冲区的开始地址,iov_len标明该缓冲区的大小。

readv和writev函数的定义以及它们使用的iovec结构在各种有关文献资料中略有差别。如果比较它们在

SVR4程序员手册〔AT&T1990e〕、SVR4的SVID〔AT&T1989〕,以及SVR4和

4.3+BSD<sys/uio.h>头文件中的定义,可以看出它们之间都有些差别。上面给出的结构定义为POSIX.1的定义。 图2-4显示了readv和writev的参数和iovec结构之间的关系。writev以顺序iov[0],iov[1]至iov[iovcnt-1]从缓存中聚集输出数据。writev返回输出的字节总数,它应等于所有缓存长度之和,readv则将读入的数据按上述同样顺序散布到缓存中。readv总是先填满一个缓存,然后再填写下一个。readv返回读得的总字节数。如果遇到文件结尾,已无数据可读,则返回0。图2-4readv和writev中的iovec结构

#include<sys/socket.h>#include<stdio.h>#include<netinet/in.h>#defineSERV_PORT8080voidrequ_client(intsockfd){intn;charbuf1[512],hdrbuf[512];structioveciov[2];while(1){if(gets(buf1)==NULL)return;iov[0].iov_base=hdrbuf;iov[0].iov_len=strlen(hdrbuf);iov[1].iov_base=buf1;iov[1].iov_len=strlen(buf1);writev(sockfd,iov,2);n=read_all(sockfd,buf1,n);if(n==0)return;elseif(n<0){fprintf(stderr,"readerror.\n");return;}write(1,buf1,n);}}

缓冲区hdrbuf存放的是请求首部,buf1存放的是请求内容,该函数将hdrbuf和buf1组成一个iovc结构,调用函数writev将它们一次发送出去,可见这种方法对有关联的多缓冲区中的数据传送比较方便。 因此,对于多缓冲区中的数据传送,一般采用readv和writev,而不采用多次read和write。

5.close()

close()可以用来关闭套接字。其调用格式为:

#include<unistd.h>

intclose(intfd);

其中,fd为套接字描述符。

通常close在关闭一个TCP连接时,close将立即返回。进程将不能再使用这个套接字描述符来访问套接字,但是TCP可能并没有删除套接字结构,因为可能在发送数据缓冲区还有数据没有发送完。TCP将继续发送剩余的数据,并在最后的数据段附加FIN控制信息。

6.shutdown()

终止网络连接除了可以利用close()系统调用外,还可以利用shoutdown()。shoutdown()终止网络连接并停止所有信息的发送和接收工作。client和server可以用该系统调用关闭虚电路或数据报的传送。其调用格式为:

#include<sys/socket.h>

intshoutdown(intfd,intaction);

参数说明:

fd:是套接字的文件描述符。

action:可取以下值。其含义为:0表示停止所有套接字上的读操作;1表示停止所有套接字上的写操作;2表示停止所有套接字上的读、写操作。 返回值:

0:成功。

-1:失败。 一般套接字是全双工通信信道,两个方向的信息传送相对是独立的,所以shoutdown()可以关闭连接的任一方向,不会影响另一方向的传输。 shutdown和close的比较:

(1)引用计数的比较:和文件的操作类似,函数close将会把套接字描述符指向的套接字结构的引用计数减1,当引用计数为0时,系统将关闭套接字,所有的进程都不能再访问这个套接字。而shutdown并不考虑套接字的引用计数,无论引用计数为多少,它都将发送FIN数据段给对端,表示本端的某个信息已经关闭。所以,shutdown实际上直接对TCP连接进行操作,而不是对套接字描述符进行操作。

(2)关闭信道的比较:如果引用计数为0,close调用将关闭该写信道。它不能单独关闭某个单向的信道。在close后,即使发送方还有数据没有发送完,接收方也不再接收。而shutdown可以直接控制某个单向信道的关闭。 7.getpeername() client和server双方均可用getpeername()获得与指定套接字连接的对等进程名。其调用格式为:

#include<sys/types.h> #include<sys/socket.h> intgetpeername(intfd,structsockaddr*proaddr,int*addrlen);

参数说明:

fd:套接字的文件描述符。

proaddr:缓冲区指针。

addrlen:缓冲区字节数指针。

getpeername()根据调用结果填充proaddr所指的缓冲区,并以缓冲区中实际填入的字节数更新addrlen。 返回值:

0:成功。

-1:失败。 8.getstockname()

getstockname()返回与套接字相连的本地进程的地址和进程元素。它的调用格式与getpeername()相似:

#include<sys/types.h>

#include<sys/socket.h>

intgetsockname(intfd,structsockaddr*proaddr,int*addrlen); 9.getsockopt()及setsockopt() getsockopt()用于检测信道上的各选择项。setsockopt()用于设置信道上的各选择项。这两个系统调用仅用于套接字的选择项,通过设置选择项来影响套接字,其调用格式为:

#include<sys/types.h>

#include<sys/socket.h>

intgetsockopt(intfd,intlevel,intoptname,char*optval,int*optlen);

intsetsockopt(intfd,intlevel,intoptname,char*optval,int*optlen);

参数说明:

fd:套接字描述符。

level:指明系统中由哪个程序解释选择项。例如,是普通套接字程序,TCP/IP程序还是XNS程序。图2-5用TCP/IP协议的信道,可以通过getsockopt()和setsockopt()获取和设置套接字、TCP、IP级别上的参数项。

optname:指定设置或查询的选择项。

optval:指向缓冲区的一个指针。setsockopt()从该缓冲区中取出值设置选择项,getsockopt()把某选择项的值返回到该缓冲区中。

optlen:指缓冲区的长度。对于setsockopt(),它指缓冲区的长度;对于getsockopt(),由系统返回值填充,该值是指在optval缓冲区中存取的数据量。图2-5套接字的选择项

表2-4列出了getsockopt()与setsockopt()可以使用的所有选择项,这些选择项大致可以分为两类:一类为允许或禁止某种特性的选择项,称为标志项;另一类为提取和返回允许设置和检测的选择项的值,称为值项。标志栏目表明是否是标志项。如果不含有“.”,则表示选择项用于用户进程和系统之间传递指定的参数。当调用getsockopt()获取标志项时,optval返回一个整数,返回值为0表示该选择项被禁止,非0表示允许使用该选择项。当调用setsockopt()时,optval为0禁止该选择项,非0允许该选择项。表2-4setsockopt和getsockopt的选项表

下面的程序可以加深我们对getsockopt()及setsockopt()的理解:

#include<sys/types.h>

#include<sys/socket.h>

#include<netinet/in.h>

#include<netinet/tcp.h>

/*

设置套接字级的参数

*/ /*

设置TCP级的参数,它只允许信道上使用

TCP/IP协议集合

*/ intmain() {intfd;

intmaxseg,buff,optlen;

/*

产生套接字

*/

if((fd=socket(AF_INET,SOCK_STREAM,0))<0)perror("Can'tcreatsocket."); /*

TCP最大段长度

*/ optlen=sizeof(maxseg); if(getsockopt(fd,IPPROTO_TCP, TCP_MAXSEG,(char*)&maxseg,&optlen)<0) perror("getsockoptTCP_MAXSEGiserror"); printf("TCPmaxseg=%d\n",maxseg); /*

设置发送缓冲区长度.*/buff=1024*16;if(setsockopt(fd,SOL_SOCKET,SO_SNDBUF,(char*)&buff,sizeof(buff))<0)perror("setsockoptSO_SNDBUFiserror.");optlen=sizeof(buff);if(getsockopt(fd,SOL_SOCKET,SO_SNDBUF,(char*)&buff,&optlen)<0)perror("getsockoptSOL_SNDBUFiserror.");printf("thesizeofsendbuffer=%d\n",buff);}

运行结果为:

TCPmaxseg=512

thesizeofsendbuffer=16384

10.fcntl()

fcntl()和ioctl()是两个能改变套接字属性的系统调用。fcntl()影响由套接字描述符fd参数所引用的打开文件,其调用格式为:

#include<fcntl.h>

#include<sys/types.h>

intfcntl(intfd,intcmd,intarg);

参数说明:

fd:套接字文件描述符。 cmd:指定要执行的操作。对套接字而言,它指F_GETOWN、F_SETOWN、F_GETFL及F_SETFL。其中,F_GETOWN:返回与套接字相关的进程标识(返值大于0)或进程标识(返回值小于0,但除-1以外)。前者指只要求一个进程接收信号,后者要求进程组中每个进程都接收信号。F_SETOWN:命令F_SETOWN用来设置进程ID和进程组ID,它们将接收与描述符fd相关的信号SIGIO或SIGURG。每个套接字均有一个相关的进程组号,套接字进程组号初始化为0,可以通过F_SETOWN命令的系统调用fcntl设置。对于F_SETOWN命令,其arg值可正可负,正是指进程标识数,负是指定进程组标识。F_SETFL:该命令设置文件的标志位。F_GETFL:该命令检查文件的标志位。

用F_SETFL和F_GETFL命令时,下面给出两种arg设置。

FNDELAY:指定套接字为非阻塞模型。对在非阻塞套接字上不能马上执行的I/O请求会立即返回到调用者,且将errno置为EWOULDBLOCK。该选择项对以下的系统调用有影响:accept、connect、read、readv、recv、recvfrom、recvmsg、send、sendto、sendmsg、write及writev。

对于无连接套接字上的connect不会阻塞,因为此时系统只记录以后输出时要用到的对方同等层地址。对于面向连接的套接字,connect可能需要一段时间,因为它一般要与对方交换实际信息。这样非阻塞套接字立即从connect系统调用返回,且errno被置为EINPROGRESS。send、sendto、sendmsg、write及writev在某些条件下可以在非阻塞套接字上进行部分写入。

FASYNC:允许接收异步

I/O信号,在数据准备好可读时,向该套接字相应的进程组发SIGIO信号。 11.ioctl() ioctl()处理各种设备的选择项,主要用来控制I/O操作,其调用格式为: #include<sys/ioctl.h> intioctl(intfd,intrequest,...);

参数说明:

fd:套接字文件描述符。

request:指定操作类型。一般request的设置可以为:

SIOCATMARK /*

带int参数,检测是否达到带外标记

*/ FIOASYNC /*

带int参数,异步输入输出标志

*/ FIONREAD /*

带int参数,缓冲区有多少数据可读

*/

当参数request的值为FIONREAD时,第三个参数所指的整数值返回当前套接字接收缓冲区中可读数据的字节数。下面给出ioctl的适用类型。

ioctl的适用类型

常量名称

要包含的头文件 磁盘卷标

DIOxxx <disklabel.h>

文件

I/O FIOxxx <ioctl.h>

磁带

I/O MTIOxxx <mtio.h>

套接字

I/O SIOxxx <ioctl.h>

终端

I/O TIOxxx <termios.h>

表2-5指出了函数ioctl用于网络的各选择项以及参数必须指向的数据类型。表2-5ioctl用于网络的控制选项

2.5多路复用

多路复用通过设置文件描述符集,将多个套接字组成一个集合,然后使用select()函数对集合进行监控,集合中任何一个(或几个)描述符就绪时,进程就可以作相应的I/O处理。文件描述符集分为读、写和异常三个类型,其中异常描述符集主要应用于带外数据的处理。select()和文件描述符操作函数的定义如下:

intselect(intmaxfd,structfd_set*rdset,structfd_set*wrset,structfd_set*exset,structtimeval*timeout); voidFD_SET(intfd,fd_set*fdset) /*

将fd加入到fdset*/ voidFD_CLR(intfd,fd_set*fdset) /*

将fd从fdset里面清除

*/ voidFD_ZERO(fd_set*fdset) /*

从fdset中清除所有的文件描述符

*/ intFD_ISSET(intfd,fd_set*fdset) /*

判断fd是否在fdset集合中

*/ select()可以设置超时,使长期没有文件描述符就绪时,进程可以跳出阻塞状态。select()的第一个参数maxfd是集合中最大的文件描述符加1,例如,一个包含3个套接字描述符的集合{12,23,30},那么

maxfd就应该是30+1=31。

在调用select()时,进程会一直阻塞到以下的一种情况发生: (1)有文件可以读,包括出现错误。

(2)有文件可以写,包括出现错误。

(3)超时所设置的时间到。

(4)被信号中断。 下面例子中,客户机与多个服务器建立连接,然后设置读描述符集合,调用函数将进程阻塞。当任一个服务器的数据到达时,进程就被唤醒,检查集合中哪个描述符就绪,然后作相应处理。代码片断如下: intmain(intargc,char*argv[]) { intsockfd[NUMBER]; /*NUMBER为需要建立的套接字数量

*/ structsockaddr_inservaddr[NUMBER]; fd_setrfds; charbuf[1024]; inti;

for(i=0;i<NUMBER;i++) { sockfd[i]=socket(AF_INET,SOCK_STREAM,0);} if(sockfd[i]<0) exit(1); } ... /*

填充NUMBER个地址结构

*/ ... /*

建立NUMBER个连接

*/ intnOK[NUMBER]; for(i=0;i<NUMBER;i++){nOK[i]=0;} intnEnd=NUMBER; while(nEnd!=0) { for(i=0;i<NUMBER;i++) if(nOK[i]==0) /*

只检查未被处理的套接字

*/FD_SET(sockfd[i],&rds); n=select(theMax(NUMBER,sockfd)+1,&rds,NULL,NULL,NULL); if(n<0&&errno==EINTR) continue; for(i=0;i<NUMBER;i++){nOK[i]=0;} {if(FD_ISSET(sockfd[i],&rds)){n=read(sockfd1,buf,1024);if(n<=0&&errno!=EINTR) { perror("AnError."); /*

发生错误,这个套接字不再工作

*/ nOK[i]=1;nEnd--; } elseif(n>0) { process(buf,...);

/*

数据处理

*/ nOK[i]=1;nEnd--; }}}}for(i=0;i<NUMBER;i++){close(sockfd[i]);}}2.6网络字节传输顺序及主机字节顺序 2.6.1网络字节顺序与主机字节顺序 每一台机器内部对变量的字节存储顺序不同,而网络传输的数据是一定要统一顺序的。所以对内部字节表示顺序与网络字节顺序不同的机器,一定要对数据进行转换,从程序的可移植性要求来讲,就算本机的内部字节表示顺序与网络字节顺序相同,也应该在传输数据以前先调用数据转换函数,以便程序移植到其他机器后能正确执行。真正转换还是不转换是由系统函数自己来决定的。

对于不同的主机:

Intel芯片:低字节在前,高字节在后,称little-endian;

RISC芯片:高字节在前,低字节在后,称big-endian,Internet采用此模式; 因此,不同机器表示数据的字节顺序是不同的。有关的转换函数为:

(1) unsignedshortinthtons(unsignedshortinthostshort)。htons()表示“HosttoNetworkShort”,将主机字节顺序转换成网络字节顺序,返回无符号短型处理结果(4B)。

(2) unsignedlonginthtonl(unsignedlonginthostlong)。htonl()表示“HosttoNetworkLong”,将主机字节顺序转换成网络字节顺序,返回无符号长型处理结果(8B)。 (3) unsignedshortintntohs(unsigned

short

int

netshort)。ntohs()表示“NetworktoHostShort”,将网络字节顺序转换成主机字节顺序,返回无符号

温馨提示

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

评论

0/150

提交评论