linux unix socket,Linux socket函数
windows套接字的类型和协议
在第一章中,我们看到了如何使用socketpair函数创建一对本地windows套接字。在本章中,我们将学习如何使用socket函数来创建一个windows sockets。通常,这两个函数有域、windows套接字类型和协议参数。
本章将建立在前几章的基础上,重点放在套接字函数调用上。这包括以下一些内容:
域参数
Windows套接字类型参数
协议参数
指定windows套接字的域。
在第一章我们可以看到,对于socketpair函数,域参数必须是AF_LOCAL或者AF_UNIX(这两个值是等价的)。然后在第二章我们可以注意到,我们使用了socket函数调用,并将其域参数指定为AF_INET。在这些和其他情况下,我们可以推断出域参数在某种程度上指示了要使用的协议。
从技术上讲,domain参数实际上表示要使用的协议族,而不是特定的协议。这需要一些历史的解释。
BSD windows sockets接口经历了一系列革命性的变化。在windows套接字的早期实现中,人们在指定windows套接字时会遇到以下问题:
协议族中的一个或多个协议。
一种或多种协议的一种或多种地址格式
基于这些可能的理解,最初的windows套接字接口提供了一些在创建windows套接字之前定义以下内容的方法:
要使用的1个协议簇。例如,C宏PF_INET表示将使用该协议的网络IP系列。
2要使用的系列中的特定协议。例如,宏IPPROTO_UDP将指示将使用UDP协议。
3要使用的地址系列。例如,宏AF_INET表示特定的协议将使用网络IP地址。
从我们后面的研究中,我们将知道对于一个给定的协议簇,永远不会有一个以上的地址格式定义。这是继承现代windows sockets接口的结果。Linux使用modern。这对我们意味着什么?这意味着windows sockets接口只接受PF_INET宏或AF_INET宏来指示要使用的域。
选择PF_INET或AF_INET。
建议使用PF_INET而不是AF_INET来指定域(即要使用的协议族)。然而,现有的大量C程序代码与旧版本保持一致,许多程序员拒绝做出这种改变。
上一章我们在socketpair函数和socket函数的域参数中使用了AF_UNIX、AF_LOCAL和AF_INET。这是因为AF_UNIX=PF_UNIX和AF_INET=PF_INET,等等。但是,以后可能就不是这样了。
为了使用新的标准和习惯,这里提供的例子和演示程序将使用新的标准。这意味着调用socketpair函数时,将指定PF_LOCAL而不是AF_LOCAL来指定域参数值。同样,socket函数也会使用同样的方式。
使用PF_LOCAL和AF_LOCAL宏
我们会注意到,windows套接字地址仍然需要用正确的地址族常量进行初始化,比如AF_INET。PF_INET在windows sockets中创建的函数中选择协议族,AF_INET宏在windows sockets的地址结构中选择地址族。以下代码显示了如何使用PF_LOCAL和AF_LOCAL:
int z;/*状态代码*/
int sp[2];/*插座对*/
struct sockaddr _ un adr _ unix/* AF_LOCAL */
z=socketpair (PF_LOCAL,SOCK_STREAM,0,sp);
adr _ unix.sun _ family=AF _ LOCAL
在socketpair函数中使用PF_LOCAL宏来指定要在域参数中使用的协议族。注意,我们在adr_unix结构中建立windows sockets地址时使用AF_LOCAL。
使用插座(2)功能
在进一步了解windows sockets类型参数和协议参数之前,我们先来了解一下socket的作用。与socketpair函数(其域参数只能指定为PF_LOCAL)不同,socket函数可用于创建任何协议族支持的windows套接字。该功能的概要如下:
#包含sys/types.h
#包含sys/socket.h
int socket(int域,int类型,int协议);
该函数接受三个输入参数:
1个windows套接字的域(要使用的协议族)
2 windows套接字所需的类型
3协议族使用的特定协议。
如果调用成功,windows套接字将作为函数的返回值返回。类似于文件描述符,如果值为零或正值,则返回windows套接字。如果出现错误,它将返回-1。当返回错误时,外部变量errno将记录错误号。
对于新程序员来说,套接字函数的一个非常困难的方面是,他们必须对三个输入参数做出许多选择。本章的目的是理解这些选择以及如何做出选择。
选择windows套接字类型。
我们已经知道,为socket或socketpair函数选择域值就是选择要使用的协议族。例如,我们已经知道:
PF_LOCAL表示指定了本地UNIX windows套接字协议族。
PF_INET表示使用网络协议族。
所以现在我们只需要知道另外两个输入参数。
socketpair函数调用中的windows套接字类型参数指示windows套接字如何与我们的程序交互。但这还不是全部,因为这个参数与选择的协议有关。
程序员通常选择以下之一作为windows套接字类型参数:
袜子流*
SOCK_DGRAM *
SOCK_SEQPACKET
袜子_RAW
标有星号的两个是我们平时用的两个。SOCK_SEQPACKET通常用于非网络协议,如X.25或广播协议AX.25。
了解SOCK_STREAM windows套接字类型
当我们想用远程windows套接字执行I/O操作时,我们可以使用SOCK_STREAM windows套接字类型。windows套接字中的流的概念与UNIX管道中的相同。写入管道(或套接字)一端的字节将在另一端作为连续的字节流接收。这里没有分界线或边界。在接收端没有长度、数据块大小或数据包概念的记录。接收方当前可用的任何数据都将返回到调用方的缓冲区。
让我们用一个例子来演示流I/O的概念。在这个例子中,我们的本地主机上有一个本地进程,它连接到一个远程主机的远程进程。本地主机通过两个独立的写调用向远程主机发送数据:
1本地进程将通过windows套接字向远程进程发送25字节的数据。Linux内核可能会也可能不会选择缓冲这些数据。缓冲有助于提高内核或网络的性能。
2本地进程写入另外30个字节,并将它们发送到远程进程。
3远程进程执行一个从windows套接字获取数据的函数。本例中的接收缓冲区最多可以读取256个字节。远程进程获取步骤1和2中发送的55个字节。
这里我们注意到发生了什么。本地进程对windows套接字执行两次单独的写入。这可以是两种不同的消息或两种不同的数据结构。但是,远程进程接收所有写入的数据,总共55个字节。
看待这个例子的另一种方式是,本地流程可能必须分成两部分来创建消息。接收者作为一个集体单位接收消息。
在其他情况下,根据可用的时间或缓冲区,远程进程可能会首先获得原始的25个单词(或更少)。然后,一旦接收函数的调用成功,就获得剩余的30个字节。简而言之,流式windows套接字不保留任何消息边界。他只是将数据返回给接收程序。
接收者不仅仅是原始信息的边界。在我们的例子中,他没有区分第一次写入的25个字节和第二次写入的30个字节。他只知道他收到的数据字节,总字节数是55。
流式windows套接字还有另一个重要属性。与UNIX管道类似,写入流windows套接字的字节被认为是按照写入顺序在另一端接收的。比如IP协议,包可以通过不同的路由到达目的地,经常会发生后面的包先到达的情况。SOCK_STREAM windows sockets保证我们的接收程序接收的数据是我们编写它的顺序。
让我们回顾一下SOCK_STREAM windows套接字:
不保留消息边界。接收器将不能确定是否使用了一个以上的写函数来发送数据。他也不确定写入在接收的字节流中的开始和结束位置。
认为接收数据的字节顺序就是我们写入的字节顺序。
相信所有写入的数据都将被远程进程无误地接收到。如果失败,将在所有合理的修复尝试后报告错误。所有的修复尝试都是自动的,不受我们程序的控制。
最后一点对我们的讨论来说是新的。一个流式windows套接字意味着将尽一切合理的努力将数据传输到另一个windows套接字。如果做不到这一点,就会向接收方和编写方报告错误。此时,SOCK_STREAM是一个可靠的数据传输。这个特性使他成为一个非常受欢迎的windows sockets类型。
SOCK_STREAM类型windows套接字的另一个属性是:
数据通过一对相连的windows套接字传输。
为了确保数据传输和强制字节顺序,底层协议使用一对相连的windows套接字。此时,我们只需要知道SOCK_STREAM类型意味着必须在操作之前建立连接。
了解SOCK_DGRAM windows套接字类型
有时,数据没有必要按顺序到达远端。此外,可能不要求数据传输可靠。以下是SOCK_DGRAM windows套接字类型的一些特征:
分组的传输在接收端可能是无序的。
也许包会丢。将不会尝试修复此错误。并且没有必要通知接收端分组丢失。
数据包大小是有限的。超过这些限制的数据包将不会通过某些路由或节点传输。
包以无连接的方式发送到远程进程。这允许程序将其消息寻址到不同的远程进程,这样每条消息都可以写入相同的windows套接字。
与连接的流式windows套接字不同,数据报windows套接字只是通过单个数据包传输数据。这里我们应该记住的是诸如IP之类的协议,在这种协议中,各个数据包可以以不同的方式进行路由。这通常会导致数据包到达目的地的顺序与发送的顺序不同。SOCK_DGRAM windows sockets类型意味着程序可以接受这种无序的信息传输。
发送数据报数据包是不可靠的。如果发送的数据包没有被中间路由或接收主机正确接收,该数据包就会被丢弃。不会保留他存在的记录,也不会试图修复这个传输错误。
如果一个包很大,它就会丢失。如果数据包很大或者没有足够的缓冲空间用于传输,发送主机和接收主机之间路径上的路由器也会丢弃该数据包。当这种情况发生时,SOCK_DGRAM不会修复错误。
SOCK_DGRAM类型的windows套接字的最后一个有趣的特性是,这个windows套接字并不意味着一个连接。每次我们使用windows套接字发送消息时,它可能会到达另一个接收者。
另一方面,面向连接的协议要求建立连接过程。这要求必须发送某些数据分组,以便建立连接。从这个角度来看,SOCK_DGRAM类型的windows套接字是非常高效的,因为它们不需要建立连接。
但是,在我们选择使用SOCK_DGRAM之前,我们必须仔细考虑以下几点:
可靠性的要求
顺序数据的要求
对数据的要求
了解SOCK_SEQPACKET windows套接字类型
这种类型的windows套接字对于X.25和AX.25协议非常重要。他和SOCK_STREAM类似,但是有明显的区别。区别在于SOCK_STREAM不提供信息边界,而SOCK_SEQPACKET提供。比如使用X.25协议时,会选择SOCK_SEQPACKET,每个包都会按照它原来写的单元大小来接收。
例如,假设在发送端有以下两个写操作:
1编写一个25字节的消息。
写一条30字节的消息
尽管接收过程表明在一次调用中接收了55个字节,但实际上会发生以下接收事件:
1接收一个25字节的消息。这对应于发送进程写入的第一条消息。
2接收长度为30字节的第二条消息。这对应于发送进程写入的第二条消息。
尽管接收缓冲区可以接收55字节的信息,但windows套接字上的第一个read调用只接收第一个25字节的信息。这将通知程序这个信息正好是25字节长。下一个read调用将接收接下来的30个字节的信息,而不管是否可以接收更多的数据。
因为这个特性,我们可以发现SOCK_SEQPACKET保持了原来的信息边界。以下是此windows套接字的摘要:
保持信息边界。这个特性将它与SOCK_STREAM类型区分开来。
接收的数据按照传输的顺序被准确地接收。
所有数据都无误地发送给了接收者。如果在合理的自动修复尝试后无法传输,将向发送方和接收方报告错误。
数据在一对相连的windows套接字上传输。
选择协议
也许我们会认为,协议族和windows sockets类型是为一个新的windows sockets指定的,所以不需要其他内容。一般来说,对于给定的协议族和windows套接字类型,只使用一种协议,但在某些情况下并非如此。当需要时,socketpair函数的协议参数可以使我们更加明确。
好消息是我们通常不需要为这个参数指定一个值。通常我们只需要将协议指定为零。这将允许Linux内核为我们指定的其他参数选择合适的协议。
然而,一些程序员更喜欢显式地指定协议参数的值。这对于需要特定协议且不允许替换的特定程序非常重要。这允许我们选择最终的协议,而不依赖于最新的内核。但这样做的一个缺点是,当网络和协议发生变化时,必须有人回去检查我们的代码,并做出相应的更改。
使用PF_LOCAL和SOCK_STREAM
在socket或socketpair函数中,对于PF_LOCAL windows套接字,我们将协议参数指定为零。这是该参数唯一支持的值。使用PF_LOCAL和SOCK_STREAM的正确套接字函数调用如下:
int s;
s=插座
(PF_LOCAL,SOCK_STREAM,0);
如果(s==-1)
perror( socket());
这将创建一个流windows套接字,允许一个进程与本地计算机上的另一个进程进行通信。
使用PF_LOCAL和SOCK_DGRAM
当我们想要保持信息边界时,我们可以在本地windows套接字上使用SOCK_DGRAM。目前,PF_LOCAL domain windows套接字不允许特定的协议。如下例所示:
int s;
s=socket(PF_LOCAL,SOCK_DGRAM,0);
如果(s==-1)
perror( socket());
据报道,windows sockets非常适合PF_LOCAL windows sockets,因为它们更可靠,并且它们可以保持信息边界。它们在网络传输中不会丢失错误,但是PF_INET数据报会,因为它们保存在本地主机中。但是我们必须明白,内核缓冲区不足会导致PF_LOCAL包丢失,虽然这种情况很少发生。
使用PF_INET和SOCK_STREAM
现在,当PF_INET域中socket函数的协议参数被指定为零时,内核会选择IPPROTO_TCP。这将使windows套接字使用TCP/IP协议。TCP/IP指令的TPC部分是基于IP层的传输层协议。这将提供数据包排序、错误控制和修复。简而言之,TCP使得使用网络协议提供流式windows套接字成为可能。
如下例所示,大多数程序会选择简单地将协议参数指定为零,从而允许内核选择适当的协议:
int s;
s=socket(PF_INET,SOCK_STREAM,0);
如果(s==-1)
perror( socket());
将套接字函数中的协议参数指定为零意味着使用TCP/IP协议。但是,如果我们希望完全控制,或者我们担心未来的内核默认协议不合适,我们可以显式选择协议:
int s;
s=socket(PF_INET,SOCK_STREAM,IP proto _ TCP);
如果(s==-1)
perror( socket());
使用PF_INET和SOCK_DGRAM
这里我们将描述我们将使用的最后一种组合。PF_INET和SOCK_DGRAM的结合使得内核选择UDP协议。UDP是用户数据报协议的缩写。数据报是一个独立的消息包。
这个协议允许程序将数据报从我们的windows套接字发送到我们识别的远程windows套接字。注意,这个服务是不可靠的,但是它适用于许多类型的需要高效率的服务。例如,网络时间协议(NTP)使用UDP协议,因为它是高效的和面向消息的,并且可以容忍消息的丢失。丢失消息的影响是,可能时间的同步需要更长的时间才能实现,或者当您想要从多个NTP服务器进行响应时,准确性会丢失。
为了创建UDP windows套接字,我们将协议参数指定为零。如下例所示:
int s;
s=socket(PF_INET,SOCK_DGRAM,0);
如果(s==-1)
perror( socket());
但是,如果我们更喜欢所示的指定UDP协议,我们可以使用以下示例:
int s;
s=socket(PF_INET,SOCK_DGRAM,IP proto _ UDP);
如果(s==-1)
perror( socket());