网络编程之端口与套接字
写给将来的自己看,对于其他人不敢保证可读性。
端口
概念与用途
端口是TCP/IP协议簇中,应用层进程与传输层协议实体间的通信接口。
应用层进程通过系统调用与某个端口进行绑定,然后就可以通过该端口接收或发送数据。
应用层进程与端口有一一对应的关系,所以可以用端口标识通信的网络应用进程。
端口号
每个端口拥有一个端口号,用于区别不同的端口。
端口与传输层的协议是密不可分的,必须区别是TCP的端口还是UDP的端口。
TCP和UDP各自的端口号是相互独立的,如TCP有一个端口号255,UDP也有一个端口号255,两者并不冲突,也没有任何联系。
实现与访问
端口是操作系统可分配的一种资源,是一种抽象的软件机制,包括一些数据结构和I/O缓冲区。
在TCP/IP的实现中,端口操作类似一般的I/O操作,进程获取一个端口,相当于获取本地唯一的I/O文件,可以用一般的读写原语访问它。
分配机制
全局(静态)分配与本地(动态)分配相结合。
将全部65535个端口号分为保留端口(熟知端口)号和自由端口号。
保留端口范围是0~1023,由一个公认的中央机构根据需要进行统一分配,静态地分配给Internet上著名的众所周知的服务器进程,由于一种服务使用一种应用层协议,也可以说把保留端口分配给了一些应用层协议。
其余端口号,1024~65535,称为自由端口号,采用本地动态的分配方法。
具体来说,应用层进程当需要访问传输层服务时,向操作系统提出申请,操作系统返回一个本地唯一的端口号,进程再通过调用合适的系统调用将自己与该端口绑定,然后通过它进行网络通信。
三元组
网络通信中通信的进程分别是在不同的计算机上,而两台主机还可能位于不同的网络中。
因此在Internet中定位一个应用进程,需要三级寻址:
- 指定主机所在的特定网络地址,即网络ID
- 主机应有唯一的地址,即主机ID
- 每一主机上每一应用进程应有在该主机上了的唯一标识符。
在TCP/IP中,主机IP地址就是由网络ID和主机ID组成的。
而应用进程用TCP或UDP的16位端口号标识。
综上所述,在Internet中,可以用一个三元组标识一个应用层进程:
应用层进程 = (传输层协议,主机IP地址,传输层端口号)
五元组
在Internet中,一个完整的网间进程通信需要由两个进程组成,两个进程是通信的两个端点,并且只能用同一种传输层协议,因此一个完整的网间通信需要一个五元组标识:
(传输层协议,本地机IP地址,本地机传输层端口,远地机IP地址,远地机传输层端口)
套接字
Socket,是网络中不同主机上应用进程之间进行双向通信的端点的抽象,从效果上来说,一个套接字就是网络上进程通信的一端。
套接字编程接口是应用程序与协议栈软件之间的接口,定义了应用程序与协议栈软件进行交互时可以使用的一组操作。
套接字编程接口给出了应用程序能够调用的一组过程,以及这些过程所需的产生,每个独立的过程完成一个与协议栈软件交互的基本操作。
例如,可能有一个过程用来建立通信连接,另一个过程用来接收数据。
两种实现方式
两种套接字在语义上是相同的,在使用上也具有相似性,所以程序具有可移植性。
一、在操作系统的内核中增加相应的软件
套接字函数时操作系统本身的功能调用,是操作系统内核的一部分。
二、开发操作系统之外的库函数
套接字库的过程是需要链接到应用程序中,并驻留于应用程序地址空间的,当应用程序从套接字库调用过程时,控制转向库程序,它接着调用一个或多个底层操作系统的功能调用,来完成套接字编程接口的功能。
因此,套接字库的库程序对应用程序隐蔽了本机操作系统,而只给出了一个套接字编程接口
UNIX的I/O模式
套接字编程接口最初是作为UNIX操作系统的一个部分发展而来的,被纳入了UNIX操作系统的传统的输入/输出(I/O)概念的范畴。
先来了解UNIX操作系统的I/O模式:
- open:用户进程调用open命令,获得对指定文件或设备的使用权,并返回一个描述符(标识文件或设备的短整型数)。
- read | write: 多次调用“读”或者“写”命令传输数据。
- close: 传输操作完成后,调用close命令,通知操作系统已经完成了对某对象的使用,释放所占用的资源。
套接字与传统I/O的相似之处
- 过程类似:沿用了打开——读——写——关闭模式:先创建套接字,然后使用它,最后将它删除。
- 方法类似:使用描述符标识套接字。
- 使用的过程的名字可以相同,如read,write
不同
- 需要建立不同的计算机上的进程间的联系
- 需要建立通用机制支持多种协议
- 一个使用套接字的应用程序必须说明许多细节,协议簇,远程计算机的地址,该应用进程是客户机还是服务器,服务类型是面向连接还是无连接。所以在创建套接字后,需要再调用其他函数说明套接字的细节。
套接字的三种类型
- 数据报套接字。提供无连接、不可靠、独立的数据报传输服务。UDP
- 流式套接字。提供双向、有序、无重复、无记录无边界、可靠的数据流传输服务。TCP
- 原始式套接字。允许直接访问较低层次的协议(如IP、ICMP),用于检验新的协议的实现。
套接字的使用
套接字由应用层的通信进程创建,并为其服务。操作该套接字的代码是该进程的组成部分。
在生成套接字描述符后,要将套接字与计算机上特定的IP地址和传输层端口号相关联,这个过程称为绑定。一个套接字要使用一个确定的三元组网络地址信息,使它在网络中被唯一地标识。
流式套接字的工作过程
- 双方创建并安装套接字
- 建立连接:三次握手
- 会话期:发送、接收数据。会话的内容是有一定格式的,一来一往的数据交换必须遵守一定顺序,这些都由应用层协议规定
- 释放连接
套接字编程接口的系统调用
- 创建套接字SOCKET()创建一个套接字并返回一个整型描述符。
int SOCKET( int Protofamily , int Type , int Protocol);
参数Protofamily:协议簇
参数Type:流式或数据报
参数Protocol: 传输层协议(Internet通信域中一般取0,因为Type就已经决定了传输层协议)
- 绑定
int BIND(int Sockfd, struct sockaddr * My_addr, int Addrlen);
Sockfd:套接字描述符
My_addr:保存特定的网络地址(IP地址+传输层端口号)
Addrlen:sockaddr的长度
在服务器端,用作监听客户机端连接请求的套接字一定要绑定,因为大多数服务器经常使用熟知端口,并且有时安装多块网卡,具有多个IP地址。
在客户机端一般不必绑定,除非要指定网络地址。
- 启动监听
int LISTEN( int Sokfd, int Queuesize);
Queuesize:等待连接队列的最大长度,最大可设为20,一般为5~10
这个调用仅用于服务器端,告诉套接字开始监听客户机的连接请求,并且规定了等待连接队列的最大长度。
操作系统为每个监听套接字各自建立一个用来等待连接的缓冲区队列。
- 接受连接请求
int ACCEPT<