二十一章——套节字接口
1. 套节字(socket)API,定义了协议软件与应用程序之间的接口。
在套接字API中,网络I/O 的基础在于一种称为套接字(socket)的抽象。我们认为套接字是为通信提供端点的机制。应用程序在需要时请求操作系统创建一个套接字,而系统则返回一个短整数,由应用程序用来引用新创建的套接字。如果它对应的是TCP 连接,那么这条连接的两个端点都必须指明。如果是UDP 使用的套接字,那么对方端点可以不用指明,此时对方端点必须在每次发送报文时以参数的形式传递。
2. 创建一个套节字
result = socket(pf, type, protocol) —— (protocol family, protocol type, protocol)
1)参数pf (protocol family)指明套接字使用的协议族(即指明如何解释地址)。当前的协议族包括
IPv4(PF_INET),IPv6(PF_INET6),Xerox 公司的PUP 互联网(PF_PUP),Apple 计算机公司的
AppleTalk 网络(PF_APPLETALK)和UNIX 文件系统(PF_UNIX)。
2)参数type 指明了要求的通信类型。可能的类型包括可靠数据流交付服务(SOCK_STREAM)和无连接
数据报交付服务(SOCK_DGRAM),以及允许有特权的程序访问底层协议或网络接口的原始类型(SOCK_RAW)。
3)因为一个协议族可能有多个协议提供相同类型的通信,所以套接字就有了第三个参数,用于选择一个具体
的协议。如果在协议族中只存在一种能提供某种type通信的协议(例如IPv4 中只有TCP支持SOCK_STREAM
服务),这时可把第三个参数设置为0。
3. 绑定本地地址
在某个熟知端口上操作的服务器进程必须为系统指明端口。一旦创建了套接字,服务器就使用bind 功能为套接
字建立一个本地地址。bind具有如下形式:
bind(socket, localaddr, addrlen);
参数socket是要绑定的套接字描述符。参数localaddr 是—个指定套接字要绑定的本地地址的结构,参数addrlen是一个指定地址长度(字节数)的整数。(表示将套节字socket绑定到地址localaddr上)
这个结构通常称为sockaddr,以一个识别地址所属协议族的16 位地址族(ADDRESS FAMILY)字段开始,随后是不超过14 八位组的地址。注意,当某个特殊的地址结构作为参数传递到套接字函数时,这个结构必须伪装成通用结构sockaddr。
地址族字段的值决定其余地址八位组的格式。例如,地址族字段中值为2 则意味着其余八位组中包含的是一个TCP/IP 地址。每个协议族都规定了自己如何使用地址字段中的八位组。对于TCP/IP地址,套接字地址称为sockaddr_in。它包括一个IP地址和一个协议端口号(即,一个互联网套接字地址结构可以包含一个IP 地址以及该地址的一个协议端口)。下图所示为一个TCP/IP套接字地址的明确结构:
4. 将套节字连接到目的地址
初始创建的套接字处于一种未连接的状态(unconnected state),也就是说,套接字并未与任何远端目的地址关联。函数connect为套接字绑定一个永久目的地址,并将它置于连接状态(connected state)。
connect函数具有如下形式:
connect(socket, destaddr, addrlen);
参数socket是要连接的套接字的整数描述符。参数destaddr 是一个套接字地址结构,它指明要与套接字绑定的目的地址。参数addrlen指明按字节计算的目的地址长度。
5. 通过套节字发送数据
一旦应用程序建立起套接字,就可以使用套接字传输数据。
一共有五个系统调用可供选择:send,sendto,sendmsg,write和writev。send和write以及writev只用于已建立连接的套接字,因为它们不允许调用方指定目的地址。
1)write(socket, buffer, length);
参数socket 包含一个整数套接字描述符。参数buffer包含要发送的数据的地址。参数length 指明要发送的数据的长度。write 调用在数据传送出去之前是阻塞的(即,如果套接字使用的内部系统缓冲区已满,则发生阻塞)。与大多数系统调用类似,write给调用它的应用程序返回一个错误码,以便让程序员知道操作是否成功。
2)writev(socket, iovector, vectorlen);
writev系统调用与write类似,只不过writev使用“收集写”(gather write)的形式,使应用程序在写报文时可以不将报文复制到内存的连续字节区域。参数iovector 给出一个iovec 类型的数组地址,它含有一个指向构成报文的各字节块的指针序列。参数vectorlen指明iovector 中的指针项的数目。
3)send(socket, message, length, flags);
此处的socket参数指明所用套接字的描述符,参数message给出要发送数据的地址,参数length说明要发送的数据的字节数,参数flags 控制传输方式。有一个flags值允许发送方指明报文必须在支持带外传输的套接字上带外发送。flags的另一个值允许调用方请求不使用本地路由选择表发送报文。其意图是允许调用方来控制路由选择,这样就可以编写网络调试软件。
函数sendto和sendmsg允许调用方在无连接的套接字上发送报文,因为它们都需要调用方指定目的地址。
4)sendto(socket, message, length, flags, destaddr, addrlen)
前4 个参数与send 函数使用的完全相同。最后两个参数指定一个目的地址,并给出地址长度。
5)sendmsg(socket, messagestruct, flags);
程序员通常喜欢使用sendmsg函数,以免sendto所需的一长串参数使程序效率不高,而且难以读懂。
此处的参数messagestruct 是如下图所示的结构。该结构包含要发送报文的信息、长度、目的地址和地址长度。
对应的结构体为:
- struct msghdr {
- void *msg_name; /* optional address */
- socklen_t msg_namelen; /* size of address */
- struct iovec *msg_iov; /* scatter/gather array */
- size_t msg_iovlen; /* # elements in msg_iov */
- void *msg_control; /* ancillary data, see below */
- size_t msg_controllen; /* ancillary data buffer len */
- int msg_flags; /* flags on received message */
- };
6. 通过套接字接收数据
与5种不同的输出操作类似,套接字API提供了5个相应的输入函数read,readv,recv,recvfrom和recvmsg,进程可以使用这些函数通过套接字接收数据。
1)read(descriptor, buffer, length);
参数descriptor 给出了从中读取数据的套接字的整数描述符或文件描述符,buffer 指定了存储数据的存储区地址,参数length指定了要读取的最大字节数。
2)readv(descriptor, iovector, vectorlen);
使用一种“分散读取”(scatter read)类型的接口。参数iovector 给出了iovec类型结构的地址,它包含一组指向存储待读数据的内存块的指针。参数vectorlen指定iovector 中的数据项的数目。
3)recv(socket, buffer, length, flags);
参数socket指明从中接收数据的套接字的描述符。参数buffer 指定存放报文的内存缓冲区地址。参数length 指明缓冲区的长度。参数flags 允许调用方控制接收方式。
4)recvfrom(socket, buffer, length, flags, fromaddr, addrlen);
两个额外参数fromaddr和addrlen分别是一个套接字地址结构的指针和一个整数。操作系统使用fromaddr 来记录报文发送方的地址,并使用addrlen 记录发送方地址的长度。
5)recvmsg(socket, messagestruct, flags);
参数messagestruct给出一个结构的地址,该结构存放传入报文的地址以及发送方地址的位置。recvmsg生成的结构与sendmsg使用的结构完全一样,这样就使它们能够很好地成对操作。
7. 获取本地的和远程的套接字地址
1)getsockname(socket, localaddr, addrlen);
函数getsockname返回与套接字相关联的本地地址;
2)getpeername(socket, destaddr, addrlen);
函数getpeername返回套接字连接的对端(也就是远端)地址;
8. 获取和设置套接字选项
除了将套接字绑定到本地地址或连接到目的地址之外,还需要有一种允许应用程序控制套接字的机制。
函数getsockopt允许应用程序请求获取关于套接字的信息。调用方指明套接字、感兴趣的选项和存放请求信息的位置。操作系统检查套接字使用的内部数据结构,并将被请求的信息传给调用方。
调用格式如下:
getsockopt(socket, level, optionid, optionval, length)
用函数setsockopt,应用程序可以使用通过getsockopt 获取的一组值来设置套接字选项。调用方指明一个要设置选项的套接字、要改变的选项和选项的值。setsockopt 的调用格式如下:
setsockopt(socket, level, optionid, optionval, length)
参数socket 指明需要信息的套接字。参数level 标识出操作是用于套接字本身还是其底层协议。参数optionid指明申请的是哪一个选项。optionval和length这对参数定义两个指针。第一个指针给出系统存放请求值的缓冲区地址,第二个指针给出系统存放选项值长度的一个整数地址。