TLPI读书笔记第56章-SOCKET介绍1

socket 是一种 IPC 方法,它允许位于同一主机(计算机)或使用网络连接起来的不同主机上的应用程序之间交换数据。 第一个被广泛接受的 socket API 实现于 1983 年, 出现在了 4.2BSD中,实际上这组 API 已经被移植到了所有 UNIX 实现以及其他大多数操作系统上了。

本章以及后续章节将介绍 socket 的用法,具体如下。

1.本章将对 socket API 进行一个全面的介绍。下面的章节将假设读者已经理解了本章介绍的常规概念。本章不会介绍任何示例代码,后续章节将会介绍有关 UNIX 和 Internetdomain 的代码示例。

2.第 57 章将介绍 UNIX domain socket,它允许位于同一主机系统上的应用程序之间通信。

3.第 58 章将介绍各种计算机联网概念并描述 TCP/IP 联网协议的关键特性,它为后续章节提供了需要的背景知识。

4.第 59 章将描述 Internet domain socket,它允许位于不同主机上的应用程序之间通过一个 TCP/IP 网络进行通信。

5.第 60 章将讨论使用 socket 的服务设计。

6.第 61 章将介绍一些高级主题,包括 socket I/O 的其他特性、 TCP 协议的细节信息以及如何使用 socket 选项来获取和修改 socket 的各种特性。

这些章节的目标仅仅是让读者在使用 socket 方面建立良好的基础。 socket 程序设计,特别是网络通信,本身就是一个庞大的主题,它需要使用一整本书来介绍。 59.15 节列出了有关这一主题的更多信息源。

56.1 概述

在一个典型的客户端/服务器场景中,应用程序使用 socket 进行通信的方式如下。

1.各个应用程序创建一个 socket。 socket 是一个允许通信的“设备”,两个应用程序都需要用到它。 2.服务器将自己的 socket 绑定到一个众所周知的地址(名称)上使得客户端能够定位到它的位置。 使用 socket()系统调用能够创建一个 socket,它返回一个用来在后续系统调用中引用该socket 的文件描述符。

fd=socket(domain,type,protocol)

在后续章节中将会对 socket domain 和类型进行介绍。在本书介绍的所有应用程序中,protocol 参数总是被指定为 0。

通信 domain

socket 存在于一个通信 domain 中,它确定: 1.识别出一个 socket 的方法(即 socket“地址”的格式); 2.通信范围(即是在位于同一主机上的应用程序之间还是在位于使用一个网络连接起来的不同主机上的应用程序之间)。 现代操作系统至少支持下列 domain。 1.UNIX (AF_UNIX) domain 允许在同一主机上的应用程序之间进行通信。

2.IPv4 (AF_INET) domain 允许在使用因特网协议第 4 版( IPv4)网络连接起来的主机上的应用程序之间进行通信。 3.IPv6 (AF_INET6) domain 允许在使用因特网协议第 6 版( IPv6)网络连接起来的主机上的应用程序之间进行通信。尽管 IPv6 被设计成了 IPv4 接任者,但目前后一种协议仍然是使用最广的协议。 表 56-1 对这些 socket domain 的特点进行了总结。

在一些代码中读者可能会看到名称诸如 PF_UNIX 而不是 AF_UNIX 的常量。在这种上下文中, AF 表示“地址族( address family)”, PF 表示“协议族( protocol family)”。

在一开始的时候,设计人员相信单个协议族可以支持多个地址族。但在实践中,没有哪一个协议族能够支持多个已经被定义的地址族, 并且所有既有实现都将 PF常量定义成对应的 AF常量的同义词。 在本书中会一直使用 AF_常量。

socket 类型

每个 socket 实现都至少提供了两种 socket:流和数据报。这两种 socket 类型在 UNIX 和Internet domain 中都得到了支持。表 56-2 对这两种 socket 类型的属性进行了总结。

流 socket( SOCK_STREAM)提供了一个可靠的双向的字节流通信信道。在这段描述中的术语的含义如下。 1.可靠的:表示可以保证发送者传输的数据会完整无缺地到达接收应用程序或收到一个传输失败的通知。 2.双向的:表示数据可以在两个 socket 之间的任意方向上传输。 3.字节流:表示与管道一样不存在消息边界的概念。 一个流 socket 类似于使用一对允许在两个应用程序之间进行双向通信的管道,它们之间的差别在于( Internet domain) socket 允许在网络上进行通信。 流 socket 的正常工作需要一对相互连接的 socket,因此流 socket 通常被称为面向连接的。术语“对等 socket”是指连接另一端的 socket, “对等地址”表示该 socket 的地址, “对等应用程序”表示利用这个对等 socket 的应用程序。

有些时候,术语“远程”(或外部)是作为对等的同义词使用。类似地,有些时候术语“本地”被用来指连接的这一端上的应用程序、 socket或地址。

一个流 socket 只能与一个对等 socket 进行连接。 数据报 socket( SOCK_DGRAM)允许数据以被称为数据报的消息的形式进行交换。在数据报 socket 中,消息边界得到了保留,但数据传输是不可靠的。消息的到达可能是无序的、重复的或者根本就无法到达。 数据报 socket 是更一般的无连接 socket 概念的一个示例。与流 socket 不同,一个数据报socket 在使用时无需与另一个 socket 连接。 在 Internet domain 中,数据报 socket 使用了用户数据报协议(UDP),而流 socket 则(通常)使用了传输控制协议(TCP)。一般来讲,在称呼这两种 socket 时不会使用术语“Internet domain数据报 socket”和“Internet domain 流 socket”,而是分别使用术语“UDP socket”和“TCP socket”。

socket 系统调用

关键的 socket 系统调用包括以下几种。 1.socket()系统调用创建一个新 socket。

2.bind()系统调用将一个 socket 绑定到一个地址上。通常,服务器需要使用这个调用来将其 socket 绑定到一个众所周知的地址上使得客户端能够定位到该 socket 上。

3.listen()系统调用允许一个流 socket 接受来自其他 socket 的接入连接。

4.accept()系统调用在一个监听流 socket 上接受来自一个对等应用程序的连接, 并可选地返回对等 socket 的地址。

5.connect()系统调用建立与另一个 socket 之间的连接。

socket I/O 可以使用传统的 read()和 write()系统调用或使用一组 socket 特有的系统调用(如send()、 recv()、 sendto()以及 recvfrom())来完成。在默认情况下,这些系统调用在 I/O 操作无法被立即完成时会阻塞。通过使用 fcntl() F_SETFL 操作( 5.3 节)来启用 O_NONBLOCK 打开文件状态标记可以执行非阻塞 I/O。

56.2 创建一个 socket: socket()

socket()系统调用创建一个新 socket。

#include<sys/socket.h>
int socket(int domain,int type,int protocol)

domain 参数指定了 socket 的通信 domain。 type 参数指定了 socket 类型(流还是数据报)。这个参数通常在创建流 socket 时会被指定为 SOCK_STREAM,而在创建数据报 socket 时会被指定为SOCK_DGRAM。 protocol 参数在本书描述的 socket 类型中总会被指定为 0。在一些 socket 类型中会使用非零的 protocol 值,但本书并没有对这些 socket 类型进行描述。如在裸 socket( SOCK_RAW)中会将 protocol 指定为 IPPROTO_RAW。 socket()在成功时返回一个引用在后续系统调用中会用到的新创建的 socket 的文件描述符。

56.3 将 socket 绑定到地址: bind()

bind()系统调用将一个 socket 绑定到一个地址上。

#include<sys/socket.h>
int bind(int fd,const struct sockaddr *addr,socklen_t addrlen);

sockfd 参数是在上一个 socket()调用中获得的文件描述符。 addr 参数是一个指针,它指向了一个指定该 socket 绑定到的地址的结构。传入这个参数的结构的类型取决于 socket domain。

addrlen 参数指定了地址结构的大小。 addrlen 参数使用的 socklen_t 数据类型在 SUSv3 被规定为一个整数类型。 一般来讲,会将一个服务器的 socket 绑定到一个众所周知的地址——即一个固定的与服务器进行通信的客户端应用程序提前就知道的地址。

56.4 通用 socket 地址结构: struct sockaddr

传入 bind()的 addr 和 addrlen 参数比较复杂,有必要对其做进一步解释。从表 56-1 中可以看出每种 socket domain 都使用了不同的地址格式。

如 UNIX domain socket 使用路径名,而Internet domain socket 使用了 IP 地址和端口号。对于各种 socket domain 都需要定义一个不同的结构类型来存储 socket 地址。 然而由于诸如 bind()之类的系统调用适用于所有 socket domain,因此它们必须要能够接受任意类型的地址结构。

为支持这种行为, socket API 定义了一个通用的地址结构 struct sockaddr。 这个类型的唯一用途是将各种 domain 特定的地址结构转换成单个类型以供 socket 系统调用中的各个参数使用。 sockaddr 结构通常被定义成如下所示的结构。

struct sockaddr{
   sa_family_t sa_family;
   char sa_char[14];
};

这个结构是所有 domain 特定的地址结构的模板,其中每个地址结构均以与 sockaddr 结构中 sa_family 字段对应的 family 字段打头。 ( sa_family_t 数据类型在 SUSv3 中被规定成一个整数类型。 )通过 family 字段的值足以确定存储在这个结构的剩余部分中的地址的大小和格式了。

原文地址:https://www.cnblogs.com/wangbin2188/p/14692481.html