一、基本概念
1、编程接口
什么是伯克利套接字(Berkeley Socket)?
美国加利福尼亚大学比克利分校于 1983年发布 4.2 BSD Unix 系统。其中包含一套用 C 语言编写的应用程序开发库。该库既可用于在同一台计算机上实现进程间通信,也可用于在不同计算机上实现网络通信。当时的 Unix 还受 AT&T 的专利保护,因此直到 1989 年,伯克利大学才能自由发布他们的操作系统和网络库,而后者即被称为伯克利套接字应用编程接口(Berkeley Socker APIs)。
什么是套接字?
什么是套接字的异构性?
如前所述,套接字是对 ISO/OSI 网络协议模型中传输及其以下诸层的逻辑抽象,是对 TCP/IP 网络通信协议的高级封装,因此无论所依赖的是什么硬件,所运行的什么操作系统所使用的是什么编程语言,只要是基于套接字构建的应用程序,只要是在互联网环境中通信,就不会存在任何障碍。
2、通信模式
单播模式
每个数据包发送单个目的主机,目的地址指明单个接收者。
服务器可以及时响应客户机的请求。
服务器可以针对不同客户的不同请求提供个性化的服务。
广播模式
一台主机向网上的所有其它主机发送数据。
无需路径选择,设备简单,维护方便,成本低廉。
服务器不用向每个客户机单独发送数据,流量负载极低。
无法针对具体客户的具体要求,及时提供个性化的服务。
网络无条件地复制和转发每一台主机产生的信息,所有的主机可以收到所有的信息,而不管是否需要,网络资源利用率低,带宽浪费严重。
禁止广播包穿越路由器,防止在更大范围内泛滥。
多播模式
网络中的主机可以向路由器请求加入或退出某个组,路由器和交换机有选择地复制和转发数据,只将组内数据转发给那些加入组的主机。
需要相同信息的客户机只要加入同一个组即可共享同一份数据,降低了服务器和网络的流量负载。
既能一次将数据传输给多个有需要的主机,又能保证不影响其他不需要的主机。
多播包可以穿越路由器,并在穿越中逐渐衰减。
缺乏纠错机制,丢包错包在所难免。
3、绑定与连接
如前所述,套接字是一个提供给程序员使用的逻辑对象,它表示对 ISO/OSI 网络协议模型中传输层及其以下诸层的的抽象。但真正发送和接收数据的毕竟是那些实实在在的物理设备。这就需要在物理设备和逻辑对象之间建立一种关联,使后续所有针对这个逻辑对象的操作,最终都能反映到实际的物理设备上。建立这种关联关系的过程就叫做绑定。
绑定只是把套接字对象和一个代表自己的物理设备关联起来。但为了实现通信还需要把自己的物理设备与对方的物理设备关联起来。只有这样才能建立一种以物理设备为媒介的,跨越不同进程甚至机器的,多个套接字对象之间的联系。建立这种联系的过程就叫做连接。
二、常用函数
1、函数 socket:创建套接字
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int socket(int domain,int type,int protocol); 返回值:成功返回套接字描述符,失败返回 -1
(1)参数解析
domain:通信域,即地址族,可取以下值:
AF_LOCAL/AF_UNIX 本地通信,即进程间通信
AF_INET 基于 IPv4 的网络通信
AF_INET6 基于 IPv6 的网络通信
AF_PACKET 基于底层包接口的网络通信
tyoe:套接字类型,可取以下值:
SOCK_SEQPACKET 固定长度的、有序的、可靠的、面向连接的报文传递。
(2)函数解析
下图总结了到目前为止所讨论的大多数以文件描述符为参数的函数使用套接字描述符的行为。未指定和由实现定义的行为通常意味着该函数对套接字描述符无效。例如,lseek 不能以套接字描述符为参数,因为套接字不支持文件偏移量的概念。
2、函数 shutdown:禁止套接字
#include<sys/socket.h> int shutdown(int sockfd,int how); 返回值:若成功则返回0,出错则返回-1.如果 how 是 SHUT_RD (关闭读端),那么无法从套接字读取数据;如果 how 是 SHUT_WR (关闭写端),那么无法使用套接字发送数据。如果 how 是 SHUT_RDWR ,则既无法读取数据,又无法发送数据。
能够关闭(close)套接字,为什么还要使用 shutdown 呢?这里有若干个理由。
首先,只有最后一个活动引用关闭时,close 才释放网络端点。这意味着,如果复制一个套接字(如采用 dup),套接字直到关闭了最后一个引用它的文件描述符之后才会被释放。而 shutdown 允许使一个套接字处于不活动状态,和引用它的文件描述符数目无关。
其次,有时可以很方便地关闭套接字双向传输中的一个方向。
首先,只有最后一个活动引用关闭时,close 才释放网络端点。这意味着,如果复制一个套接字(如采用 dup),套接字直到关闭了最后一个引用它的文件描述符之后才会被释放。而 shutdown 允许使一个套接字处于不活动状态,和引用它的文件描述符数目无关。
其次,有时可以很方便地关闭套接字双向传输中的一个方向。
3、地址结构
struct sockaddr{ sa_family_t sa_family; //地址族 char sa_data[]; //地址值 ... };
本地地址结构,用于 AF_LOCAL/AF_UNIX 域的本地通信
#include <sys/un.h> struct sockaddr_un { sa_family_t sun_family; //地址族(AF)LOCAL) char sun_path[]; //踏破戒指文件路径 };
网络地址结构,用于 AF_INET 域的 IPv4 网络通信
#include <netinet/in.h> struct sockaddr_in { sa_family_t sin_family; //地址族(AF_INET) in_port_t sin_port; //端口号 struct in_addr sin_addr; //IP地址 }; struct in_addr { in_addr_t s_addr; }; typedef unit16_t in_port_t; //无符号短整型 typedef unit32_t in_addr_t; //无符号长整型
如前所述,通过 IP 地址可以定位网络上的一台主机,但一台主机上可能同时又多个网络应用在运行,究竟想跟哪个网络应用通信呢?这就需要靠所谓的端口号来区分,因为不同的网络应用会使用不同的端口号。用 IP 地址定位主机,再用端口号定位运行子啊这台主机上的一个具体的网络应用,这样一种对通信主体的描述才是唯一确定的。
套接字接口库中的端口号被定义为一个 16 位的无符号整数,其值介于 0 到 65535,其中 0 到 1024 已被系统和一些网络服务占据,比如 21 端口用于 ftp 服务、23端口用于 telnet服务。80端口用于 www 服务等,因此一般应用程序最好选择 1024 以上的端口号,以避免和这些服务冲突。
网络应用与单机应用不同,经常需要在具有不同硬件架构和操作系统的计算机之间交换数据,因此编程语言里一些多字节数据类型的字节序问题就是需要特别予以关注。
这就涉及到大小端问题,参看:C语言再学习-- 大端小端详解(转)
假设一台小端机器里有一个 32 位整数:0x1234 5678,它在内存中按照小端字节序低位低地址的规则存放:
假设一台小端机器里有一个 32 位整数:0x1234 5678,它在内存中按照小端字节序低位低地址的规则存放:
现在,这个整数通过网络被传送到一台大端机器里,内存中的形态不会有丝毫差别,但在大端机器看来地址越低的字节数位应该越高,因此它会把这 4 个字节解读为:0x7856 3412,而这显然有悖于发送端的初衷。
为了避免字节序带来的麻烦,套接字接口库规定凡是在网络中交换的多字节整数(short、int、long、long long 和它们的 unsigned 版本)一律采用网络字节序传输。
而所谓网络字节序,其实就是大端字节序。也就是说,发数据时,先从主机字节序转成网络字节序,然后发送;收数据时,先从网络字节序转成主机字节序,然后使用。
网络地址结构 sockaddr_in 中表示端口号的 sin_port 成员和表示 IP 地址的 sin_addr.s_addr 成员,分别为 2 字节和 4 字节的无符号整数,同样需要用网络字节序来表示。