流套接字与数据报套接字

流套接字

流套接字(SOCK_STREAM)提供了一个可靠的双向的字节流通信信道。其特点为:

  • 可靠的:表示可以保证发送者传输的数据会完整无缺的到达接收应用程序或收到一个传输失败的通知。
  • 双向的:表示数据可以在两个socket之间的任意方向上传输。
  • 字节流:表示与管道一样不存在消息边界的概念。 简单的说,一个流套接字类似于使用一对允许在两个应用程序之间进行双向通信的管道,其差别在于socket可以在网络上进行通信。

流套接字的运作与电话系统类似:

  1. socket()系统调用将会创建一个socket,这等价于安装一个电话。为使两个应用程序能够通信,每个应用程序都必须要创建一个socket。
  2. 通过流套接字通信类似于一个电话呼叫。一个应用程序在进行通信之前必须要将其socket连接到另一个应用程序的socket上。连接过程为:
    a)一个应用程序调用bind以将socket绑定到一个众所周知的地址上,然后调用listen通知内核,它接受接入连接的意愿。这类似于硬件有一个为众人所知的电话号码,并确保打开了电话,这样人们就可以打进电话了。
    b)其他应用程序通过调用connect建立连接,同时指定需要连接的socket地址。这类似于拨某人的电话号码。
    c)调用listen的应用程序使用accept接受连接。这类似于在电话响时拿起电话,如果在对等应用程序调用connect之前执行了accept,那么accept就会阻塞

  3. 一旦建立了一个连接之后就可以在应用程序之间进行双向数据传输直到其中一个使用close关闭连接为止。通信是通过传统的read和write系统调用或send和recv来完成的。

流套接字分为主动和被动两种

  • 在默认情况下,使用socket()创建的socket是主动的。一个主动的socket可用在connect调用中来建立一个到一个被动socket的连接。这被称为执行一个主动地打开。
  • 一个被动的socket是一个通过调用listen以标记成允许接入连接的socket。接受一个接入连接通常称为执行一个被动的打开。

一个socket可以使用close系统调用来关闭或在应用程序终止之后关闭。之后当对等应用程序试图从连接的另一端读取数据时将会收到文件结束(当所有缓冲数据都被读取之后)。如果对等应用程序试图向其socket写入数据,那么它就会收到一个SIGPIPE信号,并且系统调用会返回EPIPE错误。

数据报套接字

数据报套接字(SOCK_DGRAM)允许数据以数据报的消息的形式进行交换。在数据报套接字中,消息边界得到了保留,但是数据传输是不可靠的,消息的到达可能是无序的、重复的、或者说根本无法到达。与流套接字不同,数据报套接字是无连接socket。即数据报socket在使用时无需与另一个socket建立连接。
数据报套接字类似于邮件系统:

  1. 所有要发送和接收数据报的应用程序都需要使用socket创建一个数据报套接字,socket系统调用等价于创建一个邮箱
  2. 为允许另一个应用程序发送其数据报,一个应用程序需要使用bind将其socket绑定到一个众所周知的地址上。一般服务器会这么做,客户端会通过向该地址发送一个数据报来发起通信。
  3. 要发送一个数据报,一个应用程序需要调用sendto(),它接收的其中一个参数是数据报发送到的socket地址。这类似于将收信人的写到信件上并投递这封信。
  4. 为接收一个数据报,一个应用程序需要调用recvfrom(),它在没有数据报到达时阻塞。由于recvfrom允许获取发送者的地址,因此可以在需要的时候发送一个响应。
  5. 当不再需要socket时,应用程序需要使用close关闭socket。
1
2
3
4
5
6
7
#include<sys/socket.h>
ssize_t recvfrom(int sockfd,void *buffer,size_t length,int flags,
struct sockaddr *src_addr,socklen_t *addrlen);
//成功返回接收到的字节,EOF返回0,出错返回-1。
ssize_t sendto(int sockfd,void *buffer,size_t length,int flags,
const struct sockaddr *dest_addr,socklen_t *addrlen);
//成功返回发送的字节数,出错返回-1。

这两个函数的返回值和前三个参数与read和write的返回值和响应参数类似。第四个参数flags是一个位掩码,它控制着socket特定的IO特性。src_addr和addrlen参数被用来获取或指定与之通信的对等socket的地址与地址长度。
不管length参数值是什么,recvfrom只会从一个数据报socket中读取一条消息。如果消息的大小超过length字节,那么消息会被截断为length字节。

尽管数据报套接字时无连接的,但也可以在数据报套接字上使用connect,这回导致内核记录这个socket的对等socket的地址。当一个数据报socket已连接后:

  • 数据报的发送可在socket上使用write或send来完成并且会自动被发送到同一的对等socket上。与sendto一样,每个write调用会发送一个独立的数据报。
  • 在这个socket上只能读取由对等socket发送的数据报。

注意:connect的作用对数据报socket是不对称的。以上所述的两点只适用于调用了connect数据报socket,并不适用于它连接的远程socket(除非对等应用程序在其socket上也调用了connect)。


参考资料:
《linux/UNIX系统编程手册 下》(tlpi)