【RL-TCPnet网络教程】第18章 BSD Sockets基础知识
第18章 BSD Sockets基础知识
本章节为大家讲解BSD Sockets,需要大家对BSD Sockets有个基础的认识,方便后面章节Socket实战操作。
(本章的知识点主要整理自网络)
18.1 初学者重要提示
18.2 Socket基础知识参考资料
18.3 Socket基础知识点
18.4 BSD Sockets简介
18.5 BSD Sockets的API说明
18.6 总结
18.1 初学者重要提示
初学者务必要对Socket的基础知识点有个认识,不是特别理解没有关系,随着后面逐渐的实战操作,会有比较全面的认识。
18.2 Socket基础知识参考资料
首次搞Socket,需要对Socket的一些基础知识有个了解。大家可以从以下地址获得Socket基础知识,下面是Socket参考资料:
下面是BSD Sockets参考资料:
对于初学者来说,学习上面六个参考资料就够了。如果大家有网络方面的书籍,比如《TCP/IP详解》,也可以直接看书籍。
18.3 Socket基础知识点
(这里的知识点整理自上面的参考资料地址)
教程这里也对Socket的基础知识做个介绍,方便大家快速上手操作。
18.3.1 网络套接字(Network Socket)
在计算机科学中,网络套接字,又译网络接口、网络插槽,是电脑网络中进程间数据流的端点。使用以网际协议IP为通信基础的网络套接字,称为网际套接字Internet Socket。因为网际协议的流行,现代绝大多数的网络套接字,都是属于网际套接字。
Socket是一种操作系统提供的进程间通信机制。Socket最初被翻译为 “媒介(字)”。不久,ARPANET的Socket就被翻译为“套接字”,其理由是:
由于每个主机系统都有各自命名进程的方法,而且常常是不兼容的,因此,要在全网范围内硬把进程名字统一起来是不现实的。所以,每个计算机网络中都要引入一种起媒介作用的、全网一致的标准名字空间。这种标准名字,在ARPA网中称作套接字,而在很多其他计算机网中称作信口。更确切地说,进程之间的连接是通过套接字或信口构成的。
在操作系统中,通常会为应用程序提供一组应用程序接口,称为套接字接口(Socket API)。应用程序可以通过套接字接口,来使用网络套接字,以进行数据交换。最早的套接字接口来自于4.2 BSD,因此现代常见的套接字接口大多源自Berkeley套接字(Berkeley Sockets)标准。
——————————————
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个Socket。建立网络通信连接至少要一对端口号(Socket)。Socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口。HTTP是轿车,提供了封装或者显示数据的具体形式,Socket是发动机,提供了网络通信的能力。
Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原意那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电,有的提供110伏交流电,有的则提供有线电视节目。客户软件将插头插到不同编号的插座,就可以得到不同的服务。
18.3.2 Socket形象解释
Socket非常类似于电话插座。以一个国家级电话网为例,电话的通话双方相当于相互通信的2个进程,区号是它的网络地址;区内一个单位的交换机相当于一台主机,主机分配给每个用户的局内号码相当于Socket号。任何用户在通话之前,首先要占有一部电话机,相当于申请一个Socket;同时要知道对方的号码,相当于对方有一个固定的Socket。然后向对方拨号呼叫,相当于发出连接请求(假如对方不在同一区内,还要拨对方区号,相当于给出网络地址)。假如对方在场并空闲(相当于通信的另一主机开机且可以接受连接请求),拿起电话话筒,双方就可以正式通话,相当于连接成功。双方通话的过程,是一方向电话机发出信号和对方从电话机接收信号的过程,相当于向Socket发送数据和从Socket接收数据。通话结束后,一方挂起电话机相当于关闭Socket,撤消连接。
在电话系统中,一般用户只能感受到本地电话机和对方电话号码的存在,建立通话的过程,话音传输的过程以及整个电话系统的技术细节对他都是透明的,这也与Socket机制非常相似。Socket利用网间通信设施实现进程通信,但它对通信设施的细节毫不关心,只要通信设施能提供足够的通信能力,它就满足了。
至此,我们对Socket进行了直观的描述。抽象出来,Socket实质上提供了进程通信的端点。进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。正如打电话之前,双方必须各自拥有一台电话机一样。
在网间内部,每一个Socket用一个半相关描述(协议,本地地址,本地端口)。一个完整的Socket有一个本地唯一的Socket号,由操作系统分配。最重要的是,Socket是面向客户/服务器模型而设计的,针对客户和服务器程序提供不同的Socket系统调用。客户随机申请一个Socket(相当于一个想打电话的人可以在任何一台入网电话上拨号呼叫),系统为之分配一个Socket号;服务器拥有全局公认的Socket,任何客户都可以向它发出连接请求和信息请求(相当于一个被呼叫的电话拥有一个呼叫方知道的电话号码)。
Socket利用客户/服务器模式巧妙地解决了进程之间建立通信连接的问题。服务器Socket半相关被全局所公认非常重要。大家不妨考虑一下,两个完全随机的用户进程之间如何建立通信?假如通信双方没有任何一方的Socket固定,就好比打电话的双方彼此不知道对方的电话号码,要通话是不可能的。
18.3.3 Sockets连接过程
根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
(1) 服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
(2) 客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
(3) 连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
大体流程图如下:
18.4 Windows Sockets简介
Windows Sockets是Windows下得到广泛应用的、开放的、支持多种协议的网络编程接口。从1991年的1.0版,经过不断完善并在Intel、Microsoft、Sun、SGI、Informix、Novell等公司的全力支持下,已成为Windows网络编程的事实上的标准。
Windows Sockets规范以U.C.Berkeley大学BSD UNIX中流行的Socket接口为范例定义了一套Microsoft Windows下网络编程接口。它不仅包含了人们所熟悉的BerkeleySocket风格的库函数;也包含了一组针对Windows的扩展库函数,以使程序员能充分地利用Windows消息驱动机制进行编程。WindowsSockets规范本意在于提供给应用程序开发者一套简单的API,并让各家网络软件供应商共同遵守。此外,在一个特定版本Windows的基础上,WindowsSockets也定义了一个二进制接口(ABI),以此来保证应用WindowsSocketsAPI的应用程序能够在任何网络软件供应商的符合WindowsSockets协议的实现上工作。因此这份规范定义了应用程序开发者能够使用,并且网络软件供应商能够实现的一套库函数调用和相关语义。遵守这套WindowsSockets规范的网络软件,我们称之为WindowsSockets兼容,而WindowsSockets兼容实现的提供者,我们称之为WindowsSockets提供者。一个网络软件供应商必须百分之百地实现WindowsSockets规范才能做到WindowsSockets兼容。任何能够与WindowsSockets兼容实现协同工作的应用程序就被认为是具有WindowsSockets接口。我们称这种应用程序为WindowsSockets应用程序。WindowsSockets规范定义并记录了如何使用API与Internet协议族(IPS,通常我们指的是TCP/IP)连接,尤其要指出的是所有的WindowsSockets实现都支持流套接字接口和数据报套接字接口,应用程序调用WindowsSockets的API实现相互之间的通讯。WindowsSockets又利用下层的网络通讯协议功能和操作系统调用实现实际的通讯工作。
18.5 BSD Sockets简介
Berkeley sockets,又称BSD sockets,是一种应用程序接口,用于网际套接字和Unix域套接字(Unix domain sockets),包括了一个用C语言写成的应用程序开发库,主要用于实现进程间通讯。BSD Sockets可以在很多不同的输入/输出设备和驱动之上运行,尽管这有赖于操作系统的具体实现。接口实现用于TCP/IP协议,因此它是维持Internet的基本技术之一。它是由加利福尼亚的伯克利大学开发,最初用于Unix系统。 如今,所有的现代操作系统都有一些源于Berkeley套接字接口的实现,它已成为连接Internet的标准接口。
BSD Sockets刚开始是4.2BSD Unix操作系统(于1983发布)的一套应用程序接口。然而,由于AT&T的专利保护着Unix,所以只有在1989年伯克利大学才能自由地发布自己的操作系统和网络库。
Berkeley套接字应用程序接口形成了事实上的网络套接字的标准精髓。大多数其他的编程语言使用与这套用C语言写成的应用程序接口类似的接口。这套应用程序接口也被用于Unix域套接字。
18.5.1 使用BSD Sockets的系统
由于Berkeley套接字是第一个socket,大多数程序员很熟悉它们,所以大量系统把伯克利套接字作为其主要的网络API,比如下面四个:
- Windows Sockets (Winsock) ,和Berkeley Sockets很相似,最初是为了便于移植Unix程序。
- Java Sockets
- Python sockets
- Perl sockets
18.5.2 BSD Sockets的头文件
Berkeley套接字接口的定义在几个头文件中。这些文件的名字和内容与具体的实现之间有些许的不同。 大体上包括:
<sys/socket.h>
BSD套接字核心函数和数据结构。
AF_INET、AF_INET6 地址集和它们相应的协议集PF_INET、PF_INET6。 广泛用于Internet,这些包括了IP地址和TCP、UDP端口号。
<netinet/in.h>
AF_INET 和AF_INET6 地址家族和他们对应的协议家族 PF_INET 和 PF_INET6。在互联网编程中广泛使用,包括IP地址以及TCP和UDP端口号。(有待查阅,跟socket.h的功能说明重复了)
<sys/un.h>
PF_UNIX/PF_LOCAL 地址集。用于运行在一台计算机上程序间的本地通信,不用于网络通讯。
<arpa/inet.h>
处理数值型IP地址的函数。
<netdb.h>
将协议名和主机名翻译为数值地址的函数,搜索本地数据以及DNS。
18.5.3 BSD Sockets的API函数
这个列表是BSD Sockets API库提供的函数概要(这里的介绍,有个了解即可,下一章节会专门讲解RL-TCPnet提供的Socket API):
socket()
创建一个新的确定类型的套接字,类型用一个整型数值标识(文件描述符),并为它分配系统资源。
bind()
一般用于服务器端,将一个套接字与一个套接字地址结构相关联,比如,一个指定的本地端口和IP地址。
listen()
用于服务器端,使一个绑定的TCP套接字进入监听状态。
connect()
用于客户端,为一个套接字分配一个自由的本地端口号。如果是TCP套接字的话,它会试图获得一个新的TCP连接。
accept()
用于服务器端。它接收一个从TCP客户端发出的连接请求并创建一个新的套接字,并与该连接相应的套接字地址相关联。
send()和recv(),或者write()和read(),或者recvfrom()和sendto(),
用于套接字的数据收发。
close()
用于释放套接字资源。如果是TCP,连接会被中断。
gethostbyname()和gethostbyaddr()
用于解析主机名和地址。
select()
用于修整有如下情况的套接字列表:准备读,准备写或者有错误。
poll()
用于检查套接字的状态。套接字可以被测试,看是否可以写入、读取或是有错误。
getsockopt()
用于查询指定的套接字中一个特定的套接字选项的当前值。
setsockopt()
用于为指定的套接字设定一个特定的套接字选项。
18.5.4 BSD Sockets支持的协议
套接字API是Unix网络的通用接口,允许使用各种网络协议和地址。下面列出了BSD Sockets支持的协议,现在的 Linux 和 BSD 中一般都已经实现了。
PF_LOCAL, PF_UNIX, PF_FILE Local to host (pipes and file-domain) PF_INET IP protocol family PF_AX25 Amateur Radio AX.25 PF_IPX Novell Internet Protocol PF_APPLETALK Appletalk DDP PF_NETROM Amateur radio NetROM PF_BRIDGE Multiprotocol bridge PF_ATMPVC ATM PVCs PF_X25 Reserved for X.25 project PF_INET6 IP version 6 PF_ROSE Amateur Radio X.25 PLP PF_DECnet Reserved for DECnet project PF_NETBEUI Reserved for 802.2LLC project PF_SECURITY Security callback pseudo AF PF_KEY PF_KEY key management API PF_NETLINK, PF_ROUTE routing API PF_PACKET Packet family PF_ASH Ash PF_ECONET Acorn Econet PF_ATMSVC ATM SVCs PF_SNA Linux SNA Project PF_IRDA IRDA sockets PF_PPPOX PPPoX sockets PF_WANPIPE Wanpipe API sockets PF_BLUETOOTH Bluetooth sockets
18.6 BSD Sockets的API说明
(说明:这些知识点整理自wiki中文)
这里主要将几个主要的BSD Sockets API做个介绍(这里的介绍,有个了解即可,下一章节会专门讲解RL-TCPnet提供的Socket API):
18.6.1 函数socket
函数原型:
int socket(int domain, int type, int protocol);
函数描述:
socket() 为通讯创建一个端点,为套接字返回一个文件描述符。socket() 有三个参数:
-
第1个参数domain 为创建的套接字指定协议集(或称做地址族 address family)。 例如:
AF_INET 表示IPv4网络协议。
AF_INET6 表示IPv6。
AF_UNIX 表示本地套接字(使用一个文件)。
-
第2个参数type(socket类型) 如下:
SOCK_STREAM (可靠的面向流服务或流套接字)。
SOCK_DGRAM (数据报文服务或者数据报文套接字)。
SOCK_SEQPACKET (可靠的连续数据包服务)。
SOCK_RAW (在网络层之上自行指定运输层协议头,即原始套接字)。
- 第3个参数protocol 指定实际使用的传输协议。 最常见的就是IPPROTO_TCP、IPPROTO_SCTP、IPPROTO_UDP、IPPROTO_DCCP。这些协议都在<netinet/in.h>中有详细说明。 如果该项为“0”的话,即根据选定的domain和type选择使用缺省协议。
- 返回值,如果发生错误,函数返回值为-1。否则,函数会返回一个代表新分配的描述符的整数。
18.6.2 函数bind
函数原型:
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
函数描述:
bind() 为一个套接字分配地址。当使用socket()创建套接字后,只赋予其所使用的协议,并未分配地址。在接受其它主机的连接前,必须先调用bind()为套接字分配一个地址。bind()有三个参数:
- 第1个参数sockfd, 表示使用bind函数的套接字描述符。
- 第2个参数my_addr, 指向sockaddr结构体(用于表示所分配地址)的指针变量。
- 第3个参数addrlen, 用socklen_t字段指定了sockaddr结构的长度。
- 返回值,如果发生错误,函数返回值为-1,否则为0。
18.6.3 函数listen()
函数原型:
int listen(int sockfd, int backlog);
函数描述:
当socket和一个地址绑定之后,listen()函数会开始监听可能的连接请求。然而,这只能在有可靠数据流保证的时候使用,例如:数据类型(SOCK_STREAM, SOCK_SEQPACKET)。
listen()函数需要两个参数:
- 第1个参数sockfd, 表示socket的描述符。
- 第2个参数backlog, 表示监听队列大小,当有一个连接请求到来,就会进入此监听队列;当一个连接请求被accept()接受,则从监听队列中移出;当队列满后,新的连接请求会返回错误。
- 返回值,一旦连接被接受,返回0表示成功,错误返回-1。
18.6.4 函数accept()
函数原型:
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
函数描述:
当应用程序监听来自其他主机的数据流连接时,通过事件(比如Unix select()系统调用)通知它。必须用 accept()函数初始化连接。Accept() 为每个连接创立新的套接字并从监听队列中移除这个连接。它使用如下参数:
- 第1个参数sockfd,监听的套接字描述符。
- 第2个参数cliaddr,指向sockaddr 结构体的指针,客户机地址信息。
- 第3个参数addrlen,指向 socklen_t的指针,确定客户机地址结构体的大小 。
- 返回值,返回新的套接字描述符,出错返回-1。进一步的通信必须通过这个套接字。
Datagram 套接字不要求用accept()处理,因为接收方可能用监听套接字立即处理这个请求。
18.6.5 函数connect()
函数原型:
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
函数描述:
函数connect用于配置要连接的远程IP地址和端口, 返回-1表示出错,0表示成功。
18.6.6 函数gethostbyname()
函数原型:
struct hostent *gethostbyname(const char *name);
函数描述:
gethostbyname()函数是用来解析主机名。可能会使用DNS或者本地主机上的其他解析机制。返回一个指向 struct hostent的指针,这个结构体描述一个IP主机。
函数gethostbyname使用如下参数:
- name 指定主机名,例如 www.armfly.com 。
- 出错返回NULL指针,可以通过检查 h_errno 来确定是临时错误还是未知主机。正确则返回一个有效的 struct hostent *。
注意:这个函数并不是BSD Sockets严格的组成部分。这个函数可能是过时了,新函数是getnameinfo(), 这个函数是基于addrinfo数据结构。
18.6.7 函数gethostbyaddr
函数原型:
struct hostent *gethostbyaddr(const void *addr, int len, int type);
函数描述:
gethostbyaddr()函数是用来解析主机名和地址的。可能会使用DNS或者本地主机上的其他解析机制。返回一个指向 struct hostent的指针,这个结构体描述一个IP主机。
函数gethostbyname使用如下参数:
- addr 指向 struct in_addr的指针,包含主机的地址。
- len 给出 addr的长度,以字节为单位。
- type 指定地址族类型 (比如 AF_INET)。
- 返回值,出错返回NULL指针,可以通过检查 h_errno 来确定是临时错误还是未知主机。正确则返回一个有效的 struct hostent *。
注意:这个函数并不是BSD Socket严格的组成部分。这个函数可能是过时了,新函数是 getaddrinfo(),这个新函数是基于addrinfo数据结构。
18.7 总结
本章节就为大家讲解这么多,更多BSD Sockets的相关知识需要大家查阅相关书籍进行学习,或者网上搜索相关资料进行学习。