TCP/IP & UDP
TCP/IP(Transmission Control Protocol/Internet Protocol):
- 输控制协议/网间协议,是一个工业标准的协议集(一系列网络协议的总和),它是为广域网设计的
UDP(User Data Protocol):
- 用户数据报协议,是与 TCP 相对应的协议,它是属于 TCP/IP 协议族中的一种
Socket简述
Socket 就是应用层与 TCP/IP 协议族通信的中间软件抽象层,它是一组接口:
- socket 可以大大简化“网络通信编程”,我们不需要完全掌握这种编程的各个细节,只需要使用 socket 的接口就可以了
网络通信
本地的进程间通信(IPC)有很多种方式,但可以总结为下面4类:
- 消息传递(管道、FIFO、消息队列)
- 同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
- 共享内存(匿名的和具名的)
- 远程过程调用(Solaris 门和 Sun RPC)
在进行网络通信之前,系统需要先“识别”程序,TCP/IP 协议族完成了这个功能:
- 网络层的 ip地址 可以唯一标识网络中的主机
- 传输层的 协议+端口 可以唯一标识主机中的应用程序(进程)
这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了
Socket函数
1 | int socket(int domain, int type, int protocol) |
用于创建一个 socket 描述符,它唯一标识一个 socket
- domain:即协议域,又称为协议族(family),协议族决定了 socket 的地址类型,在通信中必须采用对应的地址
- type:指定socket类型
- protocol:故名思意,就是指定协议
- return:返回一个文件描述符 sockfd(描述字它存在于协议族空间中,但没有一个具体的地址)
- 如果想要给它赋值一个地址,就必须调用 bind() 函数
- 否则就当调用 connect() 时系统会自动随机分配一个端口
1 | int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) |
把一个地址族中的特定地址赋给 socket
- sockfd:即socket描述字,它是通过 socket() 函数创建了,唯一标识一个socket,bind()函数就是将给这个描述字绑定一个名字
- addr:一个 const struct sockaddr * 指针,指向要绑定给 sockfd 的协议地址,这个地址结构根据地址创建 socket 时的地址协议族的不同而不同
- addrlen:地址的长度
通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器
而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合就行
- 这就是为什么通常服务器端在 listen 之前会调用 bind,而客户端就不会调用,而是在 connect 时由系统随机生成一个
socket 函数创建的 socket 默认是一个主动类型的,listen 函数将 socket 变为被动类型的,等待客户的连接请求:
1 | int listen(int sockfd, int backlog) |
- sockfd:即 socket 描述字
- backlog:相应 socket 可以排队的最大连接个数
服务端通过调用 accept 函数来接受客户端的 connect 请求:
1 | int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) |
- sockfd:即 socket 描述字
- addr:服务器的 socket 地址
- addrlen:地址的长度
客户端通过调用 connect 函数来建立与 TCP 服务器的连接(服务器必须先调用 listen 开启监听):
1 | int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) |
- sockfd:即 socket 描述字
- addr:服务器的 socket 地址
- addrlen:地址的长度
TCP 服务器端依次调用 socket、bind、listen 之后,就会监听指定的 socket 地址了
TCP 客户端依次调用 socket、connect 之后就想 TCP 服务器发送了一个连接请求
TCP 服务器监听到这个请求之后,就会调用 accept 函数取接收请求,这样连接就建立好了
- 如果 accpet 成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的 TCP 连接
三次握手 四次释放
一,TCP建立连接要进行“三次握手”,即交换三个分组,大致流程如下:
- 客户端向服务器发送一个 SYN J
- 服务器向客户端响应一个 SYN K,并对 SYN J 进行确认 ACK J+1
- 客户端再想服务器发一个确认 ACK K+1
- 当客户端调用 connect 时,触发了连接请求,向服务器发送了 SYN J 包,这时 connect 进入阻塞状态
- 服务器监听到连接请求,即收到 SYN J 包,调用 accept 函数接收请求向客户端发送 SYN K ,ACK J+1,这时 accept 进入阻塞状态
- 客户端收到服务器的 SYN K ,ACK J+1 之后,这时 connect 返回,并对 SYN K 进行确认,服务器收到 ACK K+1 时,accept 返回,至此三次握手完毕,连接建立
二,socket中有四次握手释放连接的过程,流程如下:
- 某个应用进程首先调用 close 主动关闭连接,这时 TCP 发送一个 FIN M
- 另一端接收到 FIN M 之后,执行被动关闭,对这个 FIN 进行确认(它的接收也作为文件结束符 EOF 传递给应用进程,因为 FIN 的接收意味着应用进程在相应的连接上再也接收不到额外数据)
- 一段时间之后,接收到文件结束符 EOF 的应用进程调用 close 关闭它的 socket,这导致它的 TCP 也发送一个 FIN N
- 接收到这个 FIN 的源发送端 TCP 对它进行确认
- 这样每个方向上都有一个 FIN 和 ACK
本地进程间通信案例
服务端
1 |
|
客户端
1 |
|
- 当 bind 执行以后,当前目录会出现一个名为 “B”(CAN_SERVICE) 的文件
- 当 unlink(CAN_SERVICE) 执行以后,文件 “B” 消失
- 如果不及时调用 unlink 的话,会出现
Address already in use
报错