3.2
socket
socket就是对tcp/udp连接的一种包装, 相当于socket提供了api来建立和使用tcp/udp连接. 因此应用程序在建立tcp/udp连接的时候, 都是通过socket套接字完成的.
文件描述符
在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。
socket套接字就是一个文件描述符
socket套接字建立连接的准备过程
服务端:
- 如果有一个进程期望建立TCP连接, 那么需要经历下面三个准备工作
- (1) 建立socket套接字对象 A ----> socket()方法
- (2) A绑定端口号, 通常该端口号是进程在启动的时候就已经申请好了的(如果进程不需要通讯, 是不需要端口号的, 端口号用于TCP/UDP的报文结构中). -----> bind()方法
- (3) 进程监听A, 等到具体的连接到来 -----> listen()方法
客户端:
- 如果一个客户端期望建立TCP连接, 需要经历下面两个准备工期
- (1) 建立socket套接字对象 B ----> socket()方法
- (2) 初始化B, 即为B填充 源ip + 源端口号 和 目标ip + 目标端口号 -----> connect()方法
socket套接字建立连接的连接过程(以建立TCP连接为例)
(1) B对象的connect()方法会向A发送TCP连接请求, A和B经历三次握手后, 服务端就会创建一个新的套接字C, C和B就建立了连接. 客户端用B, 进程用C就可以进行通讯.
(2) 套接字A进入阻塞状态, 等待下一次请求的到达, 进程也会继续监听A.
同一个端口号创建了多个TCP连接?
没错的, 同一个端口号只能由一个socket套接字A监听, 但是同一个端口号可以建立多个TCP连接. 具体过程就是服务端建立连接的套接字都是由A新生成的. 每个TCP连接由两个套接字(客户端一个, 服务端一个)组成, 会构成一个[client ip, client port, server ip, server port]的四元数组, 用来区分不同的连接, 用于唯一标识tcp连接.
通信都是通过同一端口号, 不同的连接如何接受自己的数据?
操作系统,接收到一个端口发来的数据时,会在该端口,产生的连接中,查找到符合这个唯一标识的并传递信息到对应缓冲区。 查找的方式就是通过[client ip, client port, server ip, server port]这个四元数组.
一个端口号最多能建立多少TCP连接?
如果是同一个端口号, 能建立多少socket套接字, 就能建立多少TCP连接, 一个进程会监听这个端口号, 也就是这个进程有多少能够使用的文件描述符, 就能建立多少TCP连接.
比如Linux用户进程默认最大只能有1024个文件描述符。内核进程默认最大有4096个文件描述符。
进程使用socket套接字进行通讯的过程
首先客户端和服务端已经得到建立好连接的套接字.
客户端:
利用send()方法, 发送数据
服务端:
通过端口接受数据, 解析得到四元组, 识别到是哪个tcp连接, 将数据给相应的套接字, 进程调用recv()方法获得数据.
当通讯完毕后, 两个套接字四次挥手后断开连接, 两个套接字失效.
同一端口的多个已经建立的TCP连接, 如何知道哪个TCP连接有数据传送过来?
进程会采用select、poll、epoll方法来得到tcp连接正在传输数据, 这三个方法都属于IO多路复用范畴
socket的读写队列
每个Socket都在内核空间中都有与之相关联的读写队列(存储空间),一个读队列,一个写队列。且读队列的大小一般要大于写队列。Socket要读数据就从对应的读队列中读,写数据就写到相应的写队列。
同一端口可以由两个进程监听吗?
正常情况下, 一个端口只能由一个进程监听, 但是linux内核支持端口复用了, 也就是允许两个套接字同时监听同一个端口号, 但是需要设置端口重用选项. 就可以同一时刻使用两个监听进程/线程分别去监听它们,客户端发来的连接也就可以通过round-robin的均衡算法轮流地被接待。
因此端口重用一般是两个一摸一样的进程来重用一个端口, 不然端口数据就混乱了.