0%

《趣谈linux操作系统》小结(三十四) - 网络通信

前言

前面学习的进程间的通信主要适用于同一台机器的两个进程间的通信,不同机器间的进程间通信就由网络通信来实现。通过定义好的网络协议,不同机器的进程传递交互的内容。当然,网络通信也可以适用于同一台机器的进程间通信,效率比其他方式差一些。进程间通信最快的属于共享内存。

网络分层

系统提供的网络通信功能主要由一个叫做Socket的接口簇来实现。网络协议通过分层的方式,把复杂的过程进一步简化了,不同层的协议提供不同的功能。

分层也有两种标准,分别是OSI 的标准七层模型 和 TCP/IP 模型:

avatar

由于在网络传输的过程中,会经过许多不一一的设备,比如路由器、交换机、分流器、防火墙、主机等,不同用途的设备关系的报文内容是不一样的,可以根据不同层的内容对其处理。

Socket报文交互

Socket这个其实不属于任何层的,是系统对网络接口的实现,但是socket主要侧重的还是网络层以上的部分,开发者对更底层的实现则不需要关心。通过socket我们可以设置双方通信的ip、端口、传输层协议,还有具体的传输内容。

TCP 和 UDP

通常面试的时候,如果涉及网络通信相关的,一般都会问一问tcp和udp的区别,这里简单列举一下:

  • TCP 是面向连接的,UDP 是面向无连接的。
  • TCP 提供可靠交付,无差错、不丢失、不重复、并且按序到达;UDP 不提供可靠交付,不保证不丢失,不保证按顺序到达。
  • TCP 是面向字节流的,发送时发的是一个流,没头没尾;UDP 是面向数据报的,一个一个地发送。
  • TCP 是可以提供流量控制和拥塞控制的,既防止对端被压垮,也防止网络被压垮。

所谓的连接实际就是双方维护了一个连接的状态,用于保证传递的可靠性。流量控制和拥塞控制其实就是根据收到的对端的网络包,调整两端数据结构的状态。

从中我们也能看出,TCP是比UDP复杂的多的协议,不过,具体内部的实现都有操作系统搞定了,我们自己不需要进行这些控制,当然系统也提供了一些接口让我们设置相关的参数,一般使用默认的就好,除非你有特殊的需求。那我们就需要了解这个协议内部的实现了,根据实际情况进行处理。

接口

使用Socket首先需要调用socket函数,创建一个socket,获取对应的fd:

1
2

int socket(int domain, int type, int protocol);
  • domain:表示使用什么 IP 层协议。AF_INET 表示 IPv4,AF_INET6 表示 IPv6。
  • type:表示 socket 类型。SOCK_STREAM,顾名思义就是 TCP 面向流的,SOCK_DGRAM 就是 UDP 面向数据报的,SOCK_RAW 可以直接操作 IP 层,或者非 TCP 和 UDP 的协议。例如 ICMP。
  • protocol 表示的协议,包括 IPPROTO_TCP、IPPTOTO_UDP。

TCP的接口调用流程:

avatar

TCP的实现是一个CS的模型,客户端和服务端的处理不大一样:

TCP 的服务端要先监听一个端口,一般是先调用 bind 函数,给这个 socket 赋予一个端口和 IP 地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */
__be16 sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */

/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};

struct in_addr {
__be32 s_addr;
};

sockfd 是上面我们创建的 socket 文件描述符。在 sockaddr_in 结构中,sin_family 设置为 AF_INET,表示 IPv4;sin_port 是端口号;sin_addr 是 IP 地址。 addrlen就是sockaddr_in的大小。

然后调用listen进行监听,调用accept的接口获取连接的fd,获取fd后就基于这个fd进行内容的接收和发送了。

1
2
3
4
5

int listen(int sockfd, int backlog);


int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

TCP建立连接的时候,有一个三步握手的流程:

avatar

TCP客户端通过 connect 函数发起连接.

1
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

连接建立完成后,系统会给客户端分配一个临时的端口,用于后续服务端返回信息使用。监听的 socket 和真正用来传送数据的 socket,是两个 socket,一个叫作监听 socket,一个叫作已连接 socket。成功连接建立之后,双方开始通过 read 和 write 函数来读写数据,就像往一个文件流里面写东西一样。

针对UDP的流程就比较简单一些,两端的端口都是需要指定的。

avatar

每次通信时,调用 sendto 和 recvfrom,都要传入 IP 地址和端口。

1
2
3
4
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

系统对socket的实现分层:

avatar

源码

https://gitee.com/fishmwei/blog_code/tree/master/socket

avatar

更多

之前对sysrepo的优化彻底结束了,解决了不相关模块的性能影响的问题,验证充分后上线测试了,转测试一周了暂时没有反馈相关的问题。

前面还有一个超时问题的解决,后面发现系统时间不是可以任意设置的,比如设置3000年的时间就不能设置成功,最后限定了可设置的时间范围 ‘1970-01-01 23:59:59’到’2200-01-01 00:00:00’,正常我们的设备可以用到那个时间就很不错了,哈哈,到时候的系统应该可以设置的时间应该更往后了,后继者再来修改吧。

行动,才不会被动!

欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。