Linux: 网络
- 一、前言
- 二、端口号 (port)
- 1)port、套接字概念
- 2)端口号 vs 进程id
- 3)端口号和进程关系
- 三、认识TCP/Udp协议
- 1)连接性解释
- 2)可靠性解释
- 3)面向数据报/字节流解释
- 四、网络字节序
- 五、struct sockaddr类型介绍
- 六、TCP 通信API
- 1)socket()解释
- 2) bind()解释
- 2.1 setsockopt()函数
- 2.2 服务端`struct sockaddr`结构体填充样式
- 3)listen()解释
- 4)accept()解释
- 5)connect()解释
- 6)tcp读写
- 7)Udp通信API
一、前言
二、端口号 (port)
1)port、套接字概念
网络通信的本质不同主机上的两个进程进行间通信,并且两进程会看到同一块资源——网络。并且我们用ip是用于表示互联网中不同主机的地址;端口号(port)这是用于标识一台主机上的不同进程。所以ip + port
既可以用于标识互联网中的唯一进程,我们也将ip + port
称为套接字!!
2)端口号 vs 进程id
端口号用于标识同一台主机上进程的唯一性,但进程id作用同样如此。那为啥在网络中不复用进程id知识,而是引入端口号呢?
3)端口号和进程关系
- 一个进程可以绑定多个端口号,但一个端口号不能被多个进程绑定!
三、认识TCP/Udp协议
我们先对TCP和UDP有一个大概的认识,在后面再详细讲解它的协议内容。
TCP协议:
- 传输层协议
- 有连接
- 可靠传输
- 面向字节流
UDP协议:
- 传输层协议
- 没有连接
- 不可靠传输
- 面向数据报
1)连接性解释
tcp在通信前需要先建立连接,也就是大名鼎鼎的3次握手,2次挥手;而UDP通信什么都不用提前做。我们可以将tcp通信比喻成打电话时,我们在通信前需要先获得对方同意,对方同意接你电话;将tcp想象为寄快递,我只需要知道对方地址即可!
2)可靠性解释
tcp是可靠,Udp不可靠。那生活中我们采用tcp就可以了,为啥需要Udp?
原因在于tcp的可靠性是有代价的(数据丢包,超时重传、数据粘包等问题需要处理),而udp认为只要将即数据从传输层发给网络层就认为数据发生成功了,所以tcp通信比udp慢。并且可靠、不可靠描述的是两种协议的特点,并没有谁好谁坏。在当前网络环境中,数据丢包的概率极小。
针对不同的场景,可以选择不同的通信方式。如果对数据可靠性要求高的场景,我们可以采用tcp,比如支付过程。反之采用udp协议,在满足需求的前提下,大大提高通信速度。比如直播,偶尔出现数据丢包,对我们影响不大就可以采用udp协议!
3)面向数据报/字节流解释
面向数据报是指报文之间是有边界的,应用层交给UDP多长的报文,UDP原样发送,既不拆分也不合并。读端每次只读取一个完整报文!
面向字节流是指数据向流水一样,是没有明显边界的。对于读端,读端并不关系写端写了多少次,读到的数据是否完整,而是尽可能将数据全部读取上来交给上层!
四、网络字节序
内存中的多字节数据相对于内存地址有大端和小端之分。这也意味着数据从A主机经网络发给B主机,B主机收到的数据可能是反的。所以网络规定,所有发送到网络中的数据必须是大端!!先发出的数据是低地址,后发出的数据是高地址。
但人为大小端数据转换非常复杂,所以系统也提高了一系列转换接口:
五、struct sockaddr类型介绍
网络通信时,socket有很多类型,主要有如下3种:
- 域间套接字
Unix socket
:主要用于本主机内部通信。域间套接字通过套接字文件来进行通信,进程可以通过打开这个文件来进行读写操作,从而实现通信。 - 网络
socket
:通过ip + port
来进行网络通信。 - 原始
socket
:原始套接字绕过传输层,直接通过网络层进行通信。主要用于编写一些网络工具。
网络中套接字种类很多,理论而言,我们要为每一种套接字设计对应的一套接口。但这无疑加重程序员的负担,为了统一所有套接字API,网络通用地址类型struct sockaddr类型
诞生!!
六、TCP 通信API
1)socket()解释
socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符。应用程序可以像读写文件一样用read/write在网络上收发数据。如果socket()调用出错则返回-1。
domain(协议家族):即socket()函数采用的网络协议种类,在网络通信中一般选择AF_INET
type(套接字类型):对于tcp而言,面向字节流,选择SOCK_STREAM
protocol(协议):通常前面两个参数就明确的通信协议种类,所以一般填0
2) bind()解释
bind 函数用于将一个套接字(socket)与特定的IP地址和端口号关联起来。(将参数sockfd和myaddr绑定在一起, 使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号)
- 对于服务端ip和port是固定的,但我们一般只绑定port,而ip选择任意地址。原因在于一台机器上可能存在多张网卡,即一台主机从多个ip(网卡)读取数据都应该交给上层指定端口(port) 但对于客户端而言,虽然也需要绑定ip+port,但不需要显示绑定。原因在于客户端程序很多,如果直接由用户显示绑定,可能会导致某些时刻端口号冲突,导致一些程序无法正常启动!所以客户端在首次发送信息时,OS会自动绑定。
- bind调用时,如果短时间内重复调用会失败,这和tcp断开连接时会进入
TIME_WAIT
状态有关。我们只需要通过地址复用即可,具体做法如下:
int opt = 1; setsockopt(_listensockfd, SOL_SOCKET, SO_REUSEADDR| SO_REUSEPORT, &opt, sizeof(opt)); // 地址复用
2.1 setsockopt()函数
int setsockopt(int sockfd, int level, int option_name, const void *option_value, socklen_t option_len);
- socockfd:要设置选项的套接字描述符。
- level:指定了选项所在的协议层级。套接字层选
SOL_SOCKET
。 - option_name:设置的选项的名称。主要有两选项:
SO_REUSEADDR
允许地址和端口的复用;SO_REUSEPORT
与 SO_REUSEADDR 类似,但提供了更细粒度的控制。 option_value
需要设置的选项值。option_len
:option_value指针指向的变量长度。
2.2 服务端struct sockaddr
结构体填充样式
3)listen()解释
tcp在通信前需要先建立连接,但服务端如何得知客户端发起了请求开始建立连接呢?所以我们需要通过listen()函数将套接字设置为监听状态!!
listen()声明sockfd处于监听状态, 并且最多允许有backlog个客户端处于连接等待状态, 如果接收到更多的连接请求就忽略。listen()成功返回0,失败返回-1;
4)accept()解释
三次握手完成后, 服务器调用accept()接受连接,并返回一个新的文件描述符! 如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。 addr是一个传出参数,accept()返回时传出客户端的地址和端口号;如果给addr 参数传NULL,表示不关心客户端的地址。
如何理解返回一个新的文件描述符:
通过listen()将套接字设置为监听状态,我们可以帮监听套接字理解为饭店外进行拉客的工作人员!他的工作只是将客人拉到字节饭店即可,并不真正提高服务;而是重新叫一名服务员进行招待后,出去重新拉客。
所以监听套接字就好比拉客的工作人员,他只是用于监听等待客户端发起连接。 而返回的新的文件描述符才是真正和客户端进行交互的。后续可客户端和服务端进行网络通信,都是通过新的文件描述符来进行!!
5)connect()解释
客户端需要调用connect()连接服务器;connect和bind的参数形式一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址;!!
connect()成功返回0,出错返回-1。
6)tcp读写
tcp客户端和服务端通信和文件一样,通过read、write
进行!
7)Udp通信API
UDp只需要将建socket文件文件描述符(本质是创建文件细节,将网卡文件打开),然后绑定ip+port即可通信。
UDP通信消息收发通过recvfrom、sendto
完成!