网络编程其实就是基于编写能够使得两台连接网络的计算机之间进行数据交互的程序。
下面通过一个图示简单介绍一下著名的网络编程 OSI 七层模型,从中能够直观理解我们在网络编程时所处的交互层次,从而对具体应该做的数据处理有一定理解。
从上图中的模型能够看到,传输层、会话层、表示层、应用层这几部分都应该算是网络编程的主要部分,而其中最基础的也就是处于传输层的 TCP、UDP 协议实现。不论是 TCP 还是 UDP 协议在操作系统中实现都需要借助 socket 套接字作为软件接口来进行操作实现。
在 Linux 操作系统下,socket 套接字有一系列相关的系统 API 函数,这里分别简单介绍一下,在后面的网络编程应用中,这些都是最基本的操作接口,借助这些操作接口我们能够在操作系统上实现网络程序的数据传输和应用。
#include <sys/socket.h>
//成功返回文件描述符,失败返回-1
int socket(int domain, int type, int protocol);
socket()
函数的调用会生成套接字,并返回套接字的文件描述符,后面经过一系列操作最终实现 socket 套接字的成功连接后,就可以像实现本地文件 I/O 操作一样进行网络数据的写入和读取了。
#include <sys/socket.h>
//成功返回0,失败返回-1
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
调用 bind()
函数会为创建好的 socket 套接字分配网络 IP 地址和本地接听端口(当本地计算机存在多张网卡时尤其重要,因为本机会有多个 IP 地址),初始化好该 socket 套接字所属的 IP 地址和端口号后,这个 socket 套接字就相当于与这个固定的网络通信位置相绑定了。
#include <sys/socket.h>
//成功返回0,失败返回-1
int listen(int sockfd, int backlog);
前面完成对 socket 套接字的网络 IP 地址和端口绑定后,通过调用 listen()
函数就使得这个 socket 套接字进入等待接听状态,第二个参数设置同一时间运行等待接听的对方机器数量(在未成功建立连接前,对方处于这个 listen
等待队列中并不会回复失败,等待快速的建立连接操作)。
#include <sys/socket.h>
//成功返回文件描述符,失败返回-1
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
在进入等待接听后,调用 accept()
函数,正常情况下程序会进入阻塞状态,等待有连接请求到达这个 socket 绑定的网络地址端口上,一旦接收到对方请求会就会返回一个建立正常数据通信的文件描述符供双方使用,并且会将对方的 IP 地址及端口信息使用指针传参的方式传递回来。
简单概括以上的网络套接字创建过程如下( TCP 方式):
- 调用
socket()
函数创建 socket 套接字。 - 调用
bind()
函数初始化套接字的 IP 地址和端口号。 - 调用
listen()
函数将 socket 套接字转为可接收请求状态。 - 调用
accept()
函数接收连接请求,并返回能够正常进行数据通信的文件描述符。
以下是socket通信的流程
传输层中的主要协议分为 TCP 协议和 UDP 协议两种,这两种协议方式都是用来做数据交换的,在操作系统上分别是通过 TCP 套接字完成的 TCP 方式和通过 UDP 套接字完成的 UDP 方式。
UDP 传输方式其实就是指定了目标 IP 地址和端口后,将数据直接发送给对方而不需要通过调用 accept()
和 connect()
进行双方建立连接的操作,简单来说就是数据点对点传输,发送方只管发送,接收方只管接受数据,双方除了对同样的数据有接触以外并没有发生太多的关系。UDP 传输方式其实和现实生活中的信件邮寄方式很像。发送数据方将数据标好接收者的 IP 地址和端口号后就将信件(数据)塞到邮箱里(网络中)了,而接收信件(接收方)需要守在固定的邮箱中等待邮递员(网络)将数据送过来,一旦发现邮箱中(UDP 套接字)中有信件(数据)到达,那么就会取出信件(数据)进行读取。同时,在信件(数据)上也会存在发送者的相关信息( IP 地址和端口号)以方便接收者在收到数据后能够再次进行数据地回复(利用 UDP 方式再次发送数据)。
TCP 协议的全称为传输控制协议,是面向连接和流控制的。IP 层主要是解决数据传输过程中路径选择问题。在使用 TCP 传输协议时,虽然也是基于 IP 网际层之上实现了数据传输,但是为了保证数据传输的可靠性,TCP 协议在实际传输过程中添加了关于数据的接收反馈及数据校验等等操作来确保网络数据传输过程中的稳定可靠(当然,这样做必然会损失部分效率)。
与 UDP 传输方式最大的不同,TCP 传输方式需要建立连接,除此之外,还有对数据包传输的顺序要求(既对每次接受到的数据都有顺序要求,顺序不对会向对方发出正确序号数据包的接收请求,确保数据不丢失)。
TCP 三次握手的步骤:
第一次握手:
- 客户端将 SYN 标志位置为 1。
- 客户端生成一个随机的 32 位序号即 seq = J,序号后是可以携带数据的如数据的大小。
第二次握手:
- 服务器接收到客户端的连接将 ACK = 1。
- 服务器则会回发一个确认序号 ack = 客户端的序号 + 数据长度 + SYN/FIN(1 个字节)。
- 服务器向客户端发起连接请求 SYN = 1。
- 服务器生成一个随机的 32 位序号即 seq = K,序号后是可以携带数据的如数据的大小。
第三次握手:
- 客户端应答服务器的连接请求 ACK = 1。
- 客户端则会回发一个确认序号 ack = 服务端的序号 + 数据长度 + SYN/FIN(1 个字节)。
TCP 四次挥手的步骤:
第一次挥手:
- 客户端将 FIN 标志位置为 1。
第二次挥手:
- 服务器收到客户端的断开连接将 ACK = 1。
- 服务器则会回发一个确认序号 ack = 客户端的序号 + 数据长度 + SYN/FIN(1 个字节)。
第三次挥手:
- 服务器将 FIN 标志位置为 1。
第四次挥手:
- 客户端收到服务器的断开连接将 ACK = 1。
- 客户端则会回发一个确认序号 ack = 客户端的序号 + 数据长度 + SYN/FIN(1 个字节)。
思考:TCP 为什么是三次握手呢,两次握手可不可以,为什么?TCP 为什么是四次挥手呢,三次挥手可不可以,为什么?
UDP和TCP的对比如下
UDP | TCP | |
---|---|---|
是否连接 | 无连接 | 面向连接 |
是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 | 可靠传输,使用流量控制和拥塞控制 |
连接对象个数 | 支持一对一,一对多,多对一和多对多交互通信 | 仅支持一对一通信 |
传输方式 | 面向报文 | 面向字节流 |
首部开销 | 首部开销小,仅 8 字节 | 首部最小 20 字节,最大 60 字节 |
适用场景 | 适用于实时应用,视频、直播等 | 适用于要求可靠传输的应用,文件、数据等 |
虽然大部分的网络编程都是基于 TCP 实现的,这其实是出于数据可靠性和安全性的考虑,但是也有很多应用是基于 UDP 实现的。尤其是当应用场景对安全性、可靠性要求并不高,但是对传输效率和传输频率有很高要求时,UDP 传输方式就有很大的优势了。
比如网络传输文件、压缩包的时候都会采用 TCP 传输方式,因为要确保数据传输的不丢失。而在网络实时传输视频或音频的时候,数据丢失影响就不是很大,速度是非常重要的因素,这种情况下 UDP 传输方式就是非常好的选择。
参考链接