打印

qnx高级套接字通信教程

[复制链接]
372|18
手机看帖
扫描二维码
随时随地手机跟帖
沙发
keer_zu|  楼主 | 2023-9-19 17:49 | 只看该作者
介绍

本章改编自高级4.3 BSD进程间通信教程。它提供了套接字设施的高级描述,旨在补充TCP/IP库一章中的参考资料。
在本章中,我们将看到:


  • socket的相关功能和基本通信模型
  • 您可能会发现一些支持库函数在构建分布式应用程序时很有用
  • 开发应用程序时使用的客户机/服务器模型,包括两种主要服务器类型的示例
  • 资深用户在使用套接字工具时可能遇到的问题。



使用特权

评论回复
板凳
keer_zu|  楼主 | 2023-9-19 17:50 | 只看该作者
基础知识
通信的基本构件是套接字。套接字是可以绑定名称的通信端点。使用中的每个套接字都有一个类型和一个或多个相关进程。套接字存在于通信域中。通信域是一种抽象,用于捆绑通过套接字通信的进程的公共属性。

使用特权

评论回复
地板
keer_zu|  楼主 | 2023-9-19 17:51 | 只看该作者
套接字类型
套接字是根据用户可见的通信属性来键入的。假定进程只在相同类型的套接字之间通信。
目前有几种类型的套接字可用:


流socket (SOCK_STREAM)

提供无记录边界的双向、可靠、有序和不可重复的数据流。除了数据流的双向特性之外,一对连接的流套接字提供了几乎与管道相同的接口。

数据报套接字(SOCK_DGRAM)

支持不能保证顺序、可靠或不可复制的数据的双向流。也就是说,在数据报套接字上接收消息的进程可能会发现消息是重复的,而且可能顺序与发送消息的顺序不同。数据报套接字的一个重要特征是保留数据中的记录边界。数据报套接字与许多现代分组交换网络(例如以太网)中的设施非常相似。

原始socket (SOCK_RAW)

为用户提供对支持套接字抽象的底层通信协议的访问。这些套接字通常是面向数据报的,尽管它们的确切特征取决于协议提供的接口。原始套接字不是为一般用户设计的;它们主要是为那些对开发新的通信协议或访问现有协议的一些更深奥的功能感兴趣的人提供的。使用原始套接字将在本章的高级主题部分进行讨论。

使用特权

评论回复
5
keer_zu|  楼主 | 2023-9-19 18:10 | 只看该作者
本帖最后由 keer_zu 于 2023-9-19 18:13 编辑

创建套接字

要创建一个套接字,可以使用socket()函数:
Sock_fd = socket(domain, type, protocol);

这个函数请求在指定的域中创建一个指定类型的套接字。也可以要求特定的协议。如果您不指定协议(值为0),系统将从组成通信域的协议中选择合适的协议,该协议可用于支持所请求的套接字类型。返回一个描述符,可以在以后操作套接字的函数中使用。

域被指定为 <sys/socket.h> 中定义的清单常量之一。对于Internet域,这个常数是AF_INET。套接字类型也在此文件中定义;必须指定SOCK_STREAM、SOCK_DGRAM或SOCK_RAW中的一个。

要在Internet域中创建流套接字,可以使用以下调用:
s = socket(AF_INET, SOCK_STREAM, 0);

这个调用产生一个流套接字,该套接字是用提供底层通信支持的TCP协议创建的。
默认协议是在套接字调用的protocol参数为0时选择的,对于大多数情况应该是正确的。尽管如此,仍然可以指定默认协议以外的协议(请参阅本章的高级主题部分)。

使用特权

评论回复
6
keer_zu|  楼主 | 2023-9-20 15:03 | 只看该作者
TCP是默认用于支持SOCK_STREAM抽象的协议,而UDP是默认用于支持SOCK_DGRAM抽象的协议。
套接字调用可能由于以下几种原因而失败,包括:
  • 内存不足(ENOBUFS)请求未知协议
  • (EPROTONOSUPPORT)请求没有支持协议
  • (EPROTOTYPE)的套接字类型。




使用特权

评论回复
7
keer_zu|  楼主 | 2023-9-20 15:07 | 只看该作者
8
keer_zu|  楼主 | 2023-9-20 15:36 | 只看该作者
绑定本地名称
套接字创建时没有名称。在将名称绑定到套接字之前,进程无法引用它,因此无法接收到有关它的消息。通信过程受“关联”的约束。
在Internet域中,一个关联由本端地址和远端地址、本端端口和远端端口组成。在大多数域中,关联必须是唯一的;在Internet域中,可能***不会有重复的<本地地址,本地端口,远程地址,远程端口>元组。
bind()函数允许进程指定关联的一半(<local_address, local_port>),而connect()和accept()函数用于完成套接字的关联。
将名称绑定到套接字可能相当复杂。幸运的是,您通常不必显式地将地址和端口号绑定到套接字,因为connect()和send()调用在与未绑定的套接字一起使用时,会自动绑定适当的地址。
bind()函数有这样的形式:
bind( s, name, namelen );
绑定的名称是由支持协议解释的可变长度字节字符串。它的解释可能因通信领域而异(这是构成域的属性之一)。如前所述,Internet域中的名称包含Internet地址和端口号。
在绑定Internet地址时,使用以下顺序:
#include <sys/types.h>
#include <netinet/in.h>
...
struct sockaddr_in sin;
...
bind(s, (struct sockaddr *) &sin, sizeof (sin));
决定你应该在地址中放置什么需要一些讨论。我们将在网络地址函数一节中回到制定Internet地址的问题,这一节将讨论名称解析。

使用特权

评论回复
9
keer_zu|  楼主 | 2023-9-20 15:42 | 只看该作者
建立连接
建立联系通常是不对称的;一个进程是客户端,另一个是服务器。当服务器愿意提供其所宣传的服务时,将套接字绑定到与该服务相关联的已知地址,然后被动地侦听其套接字。然后,不相关的进程可以与服务器会合。
客户端通过启动到服务器套接字的连接来向服务器请求服务。为了初始化连接,客户端使用connect()调用。这可能显示为:

struct sockaddr_in server;
...
connect( s, (struct sockaddr *)&server, sizeof (server));
其中服务器包含客户端**访问的Internet地址和端口号。如果客户端的套接字在connect调用时未绑定,系统将自动选择并在必要时将名称绑定到套接字(这通常是本地地址绑定到套接字的方式)。


建立连接时返回的错误

如果连接不成功,则返回错误(但操作系统自动绑定的任何名称都会保留)。如果成功,则套接字与服务器关联,并且可以开始数据传输。
以下是连接尝试失败时返回的一些常见错误:


ETIMEDOUT
在一段时间内无法建立连接后,系统认为没有必要重新尝试。这通常是因为目标主机关闭或网络问题导致传输丢失而发生的。
ECONNREFUSED
主机由于某种原因拒绝了服务(通常是因为没有向服务器进程提供请求的名称)。
ENETDOWN或EHOSTDOWN
这些操作错误是根据底层通信服务传递给客户机主机的状态信息返回的。

ENETUNREACH或EHOSTUNREACH
这些操作错误可能是因为网络或主机是未知的(不存在到网络或主机的路由),也可能是因为中间网关或交换节点返回的状态信息。通常,返回的状态不足以区分网络断开和主机断开,在这种情况下,系统表示整个网络不可达。



使用特权

评论回复
10
keer_zu|  楼主 | 2023-9-20 15:44 | 只看该作者
监听套接字

为了让服务器接收客户端的连接,它必须在绑定套接字之后:
表明愿意监听传入的连接请求。
实际上接受这个连接。
为了表示监听连接请求的意愿,服务器使用listen()调用:
listen(s, 5);
listen()调用的backlog参数指定可能排队等待服务器进程接受的未完成连接的最大数量。在任何一个队列上都有一个系统定义的最大连接数。通过将backlog值设置得非常大,然后忽略所有连接请求,可以防止进程占用系统资源。
如果在服务器的队列已满时请求连接,则不会拒绝该连接。相反,组成请求的单个消息将被忽略,从而迫使客户机重试。当客户端重新尝试连接请求时,服务器有时间在其挂起的连接队列中腾出空间。
如果返回的连接带有ECONNREFUSED错误,则客户机将无法判断服务器是否已启动。通过让服务器忽略连接请求,仍然有可能返回ETIMEDOUT错误。


使用特权

评论回复
11
keer_zu|  楼主 | 2023-9-20 15:46 | 只看该作者
接受连接
当套接字被标记为监听时,服务器可以接受连接:
struct sockaddr_in from;
...
fromlen = sizeof (from);
newsock = accept( s, (struct sockaddr *)&from, &fromlen);
当接受连接时,将返回一个新的描述符(以及一个新的套接字)。
如果服务器**找出它的客户端是谁,它可以为客户端套接字的名称提供一个缓冲区。value-result参数fromlen由服务器初始化,以指示与from相关联的空间大小,然后在返回时进行修改,以反映名称的真实大小。如果对客户端名称不感兴趣,则第二个参数可能是NULL指针。


accept()调用通常会阻塞。在连接可用或调用被进程的信号中断之前,它不会返回。
此外,进程不能指示它将只接受来自一个或多个特定个体的连接。用户进程负责考虑连接来自谁,并在不**与该进程对话时关闭该连接。如果服务器进程想要接受多个套接字上的连接,或者避免在accept调用上阻塞,它可以通过几种方式来实现(在高级主题部分中讨论)。


使用特权

评论回复
12
keer_zu|  楼主 | 2023-9-20 15:48 | 只看该作者
数据传输
随着连接的建立,数据可能开始流动。要发送和接收数据,您可以从几个呼叫中进行选择。
如果连接两端的对等体实体都是锚定的,则可以在不指定对等体的情况下发送或接收消息。在这种情况下,你可以使用普通的read()和write()函数:
write(s, buf, sizeof (buf));
read(s, buf, sizeof (buf));
除了read()和write(),你还可以使用新的recv()和send()调用:
send(s, buf, sizeof (buf), flags);
recv(s, buf, sizeof (buf), flags);
虽然recv()和send()实际上与read()和write()相同,但是额外的flags参数很重要(在<sys/socket.h>中定义了这些标志值)。可以指定以下一个或多个标志:
MSG_OOB
发送/接收带外数据。
MSG_PEEK
不看数据。
MSG_DONTROUTE
不发送路由报文发送数据。

带外数据是特定于流套接字的概念;我们在这里不会马上考虑它。发送数据而不对传出数据包应用路由的选项目前仅由路由表管理进程使用,一般用户不太可能感兴趣。
另一方面,预览数据的能力可能非常有用。当使用recv()调用指定MSG_PEEK时,将返回存在的任何数据,但仍将其视为未读数据。也就是说,应用于套接字的下一个read()或recv()调用将返回先前查看的数据。

使用特权

评论回复
13
keer_zu|  楼主 | 2023-9-20 15:50 | 只看该作者
丢弃的套接字

一旦对套接字不再感兴趣,可以通过对描述符应用close来丢弃它:
close(s);
如果数据与一个承诺可靠传输的套接字(例如流套接字)相关联,当close()发生时,系统将继续尝试传输数据。但是,如果在相当长的一段时间后数据仍未交付,则将丢弃该数据。如果你不需要任何挂起的数据,你可以在关闭套接字之前对它执行shutdown():
shutdown(s,how);
其中,如果对读取数据不再感兴趣,则为0;如果不再发送数据,则为1;如果不发送或接收数据,则为2。

使用特权

评论回复
14
keer_zu|  楼主 | 2023-9-20 15:52 | 只看该作者
无连接(数据报)套接字

到目前为止,我们主要研究了遵循面向连接模型的套接字。但是也支持无连接交互,这是当代分组交换网络中常见的数据报设施。数据报套接字为数据交换提供了一个对称接口。虽然进程仍然可能是客户机和服务器,但是不需要建立连接。相反,每条消息都包含目的地址。
数据报套接字与前面一样创建。如果需要特定的本地地址,则bind()操作必须在第一次数据传输之前进行。否则,系统将在首次发送数据时设置本地地址和/或端口。要发送数据,可以使用sendto()函数:
sendto(s, buf, buflen, flags, (struct sockaddr *)&to, tolen);
s、buf、buflen和flags参数与以前一样使用。to和tolen值指示消息的预期收件人的地址。
当使用不可靠的数据报接口时,不太可能向发送方报告任何错误。当本地存在信息以识别无法传递的消息(例如网络不可达)时,sendto()调用将返回-1,全局变量errno将包含错误编号。
要接收未连接的数据报套接字上的消息,可以使用recvfrom()函数:
recvfrom( s, buf, buflen, flags, 
          (struct sockaddr *)&from, &fromlen );
同样,fromlen是一个值-结果参数,最初包含from缓冲区的大小,并在返回时进行修改,以指示接收数据报的地址的实际大小。

使用特权

评论回复
15
keer_zu|  楼主 | 2023-9-20 15:53 | 只看该作者
对数据报使用connect()

除了上面提到的两个调用之外,数据报套接字还可以使用connect()调用将套接字与特定的目标地址关联起来。在这种情况下,在套接字上发送的任何数据都将自动发送到连接的对等体,只有从该对等体接收到的数据才会传递给用户。每个套接字在同一时间只允许一个连接地址;第二次连接将更改目标地址,连接到空地址(AF_UNSPEC族)将断开连接。
数据报套接字上的连接请求立即返回,因为对等体的地址被简单地记录下来。将此与流套接字连接进行比较,在流套接字连接中,connect()请求实际上会启动端到端连接的建立。accept()和listen()函数不用于数据报套接字。

使用特权

评论回复
16
keer_zu|  楼主 | 2023-9-20 15:54 | 只看该作者
检测错误
在连接数据报套接字时,可能会异步返回最近send()调用的错误。这些错误可能会在后续对套接字的操作中报告。或者,与getsockopt()一起使用的特殊套接字选项SO_ERROR可用于查询错误状态。
当接收到错误指示时,用于读取或写入的select()返回true。下一个操作返回错误,然后清除错误状态。

使用特权

评论回复
17
keer_zu|  楼主 | 2023-9-20 15:55 | 只看该作者
输入/输出多路复用
许多应用程序使用该功能在多个套接字和/或文件之间进行多路I/O请求。这是通过select()函数完成的(请参阅C库参考)。

要确定套接字上是否有等待accept()调用的连接,可以使用select()和FD_ISSET(fd, &mask)宏来检查相应套接字上的读准备情况。如果FD_ISSET()返回一个非零值,表示准备好读取,则连接在套接字上挂起。

select()函数提供了一个同步多路复用方案。输出完成、输入可用性和异常条件的异步通知可以通过使用SIGIO和SIGURG信号来实现,这些信号将在高级主题部分中描述。

使用特权

评论回复
18
keer_zu|  楼主 | 2023-9-20 16:00 | 只看该作者
网络地址功能

在前一节中,我们研究了在分布式环境中使用进程间通信设施时定位和构造网络地址的可能需求。在本节中,我们将研究用于操作网络地址的C函数。


映射的水平

在客户端和服务器通信之前,在远程主机上定位服务需要多达三个级别的映射:
  • 该服务被分配一个人类可读的名称(例如:“telnet”)。
  • 此名称和对端主机的名称(例如。“sun”),每个都必须转换成IP地址。
  • 这些地址用于选择到所需服务的接口和路由。



这三种映射的细节往往因网络体系结构而异。例如,网络不应该要求以客户端主机知道其物理位置的方式来命名主机。相反,当客户端主机**通信时,网络中的底层服务应该发现主机的实际位置。

尽管以与位置无关的方式命名主机可能会导致建立连接的开销(因为必须进行发现过程),但它允许主机在物理上移动,而不需要通知其潜在客户机其当前位置。我们提供了映射函数:
  • 主机名到网络地址,
  • 网络名到网络号,
  • 协议名到协议号,
  • 服务名到端口号,以及服务器进程的适当协议。


在使用这些函数时,必须包含<netdb.h>文件。


使用特权

评论回复
19
keer_zu|  楼主 | 2023-9-20 16:02 | 只看该作者
主机名

Internet主机名到地址的映射由主机结构表示:
struct hostent {
     char *h_name;      
     char **h_aliases;  
     int  h_addrtype;   
     int  h_length;     
     char **h_addr_list;
};

#define h_addr h_addr_list[0]
gethostbyname()函数接受一个Internet主机名并返回一个主机结构,而gethostbyaddr()函数将Internet主机地址映射到一个主机结构。
这些函数返回主机的正式名称和公共别名,以及地址类型(族)和以空结尾的可变长度地址列表。这个列表是必需的,因为一个主机可能有许多地址,所有地址都具有相同的名称。h_addr定义是为了向后兼容而提供的,它被定义为宿主结构中地址列表中的第一个地址。
这些调用的数据库要么由/etc/hosts文件提供,要么通过使用名称服务器(如/etc/resolv.conf中指定的)提供。由于这些数据库及其访问协议之间的差异,返回的信息可能不同。当使用主机表版本的gethostbyname()时,将只返回一个地址,但将包括所有列出的别名。name-server版本可以返回备用地址,但不会提供任何别名,除了作为参数给出的别名。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:qq群:49734243 Email:zukeqiang@gmail.com

1314

主题

12271

帖子

53

粉丝