hello~ 很高兴见到大家! 这次带来的是Linux网络中关于网络基础这部分的一些知识点,如果对你有所帮助的话,可否留下你宝贵的三连呢?
个 人 主 页 : 默|笙

文章目录
- [一、Echo sever (v1代码)](#一、Echo sever (v1代码))
-
- [1.1 给程序占好本机的 IP + 端口](#1.1 给程序占好本机的 IP + 端口)
-
- [1. socket (创建套接字对象)](#1. socket (创建套接字对象))
- [2. 填充 sockaddr_in (准备地址信息)](#2. 填充 sockaddr_in (准备地址信息))
- [3. bind socket信息](#3. bind socket信息)
- [1.2 发送和接收信息](#1.2 发送和接收信息)
-
- [1. 接收信息](#1. 接收信息)
- [2. 发送信息](#2. 发送信息)
- [1.3 ip地址介绍](#1.3 ip地址介绍)
-
- [1. 本地回环地址 127.0.0.1](#1. 本地回环地址 127.0.0.1)
- [2. 局域网 IP (内网 IP,私有 IP)](#2. 局域网 IP (内网 IP,私有 IP))
- [3. 公网 IP](#3. 公网 IP)
- [4. 特殊监听地址 `0.0.0.0`](#4. 特殊监听地址
0.0.0.0)
- [1.4 服务端与客户端差异](#1.4 服务端与客户端差异)
- [二、Chat server(v3代码)](#二、Chat server(v3代码))
代码:gitee代码
一、Echo sever (v1代码)
1.1 给程序占好本机的 IP + 端口
1. socket (创建套接字对象)

- socket 系统调用的作用其实就是制造一个 socket 对象。由于 Linux 里面一切皆文件,如果创建成功的话会返回套接字对应的文件描述符,失败则返回 -1。
这个套接字后续可以间接和网卡关联。现在刚创建好的 socket 对象相当于是一个空房间,socket 本身已经是内核里特殊的套接字文件类型,不是 bind 之后才变成网络文件;只有调用 bind 之后,相当于给这个空房间加上门牌号(IP + 端口),外部主机才能根据这个门牌号找到这个房间。
- 第一个参数是地址族(协议域),最常用的就是IPv4,给它传递AF_INET。
- 第二个参数用来决定通信模型,常用的有 TCP 和 UDP 两种:TCP 使用面向连接的字节流模型,写
SOCK_STREAM;UDP 使用无连接的数据报模型,写SOCK_DGRAM。 - 第三个参数用来指定传输层协议,选定 domain+type 的组合后通常只有一种默认协议,大部分情况下直接写 0,由系统自动选择对应协议。
cpp
int _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
2. 填充 sockaddr_in (准备地址信息)
cpp
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = ntohs(_port);//手动将端口号存储方式转为大端
local.sin_addr.s_addr = inet_addr(_ip.c_str());

- 我们要将 ip 和端口号信息塞进专门存储地址信息的对象,也就是
sockaddr_in结构体,需要指定协议域、端口号和 IP 地址。
这一步通俗来讲其实就是准备好空房间的门牌号信息,暂时还没有贴到房间(socket)上。
- 网络传输统一规定使用大端(网络字节序) 。主机可能是小端或者大端,所以填充结构体时,端口号和 ip 地址必须转成大端。用 htons 函数可以进行转换。
- 我们日常看到的
192.168.1.12是点分十进制字符串 ,方便人类阅读;但内核需要的是 4 字节整数,所以 IP 相比端口,多这一层「字符串→4 字节整数」的转换。使用inet_pton/inet_addr函数可以一步到位,不用再使用 htons 函数。
3. bind socket信息

- bind 的返回值就比较普通了,失败返回 -1,并设置 errno,成功返回 0。
- 第一个参数是 socket 对象对应的文件描述符;第二个参数传递填充好的地址结构体指针(我们代码里一般传
sockaddr_in,需要强制转为sockaddr*);第三个参数是这个地址结构体所占字节大小。这几个参数的作用,就是把 socket 对象和填充好的地址信息绑定在一起。-----解决问题:如何将 socket 对象填充好的地址信息绑定在一起。
这一步就是将门牌贴到对应的空房间上面。
cpp
int num = bind(_sockfd, (sockaddr*)&local, sizeof(local));
1.2 发送和接收信息
1. 接收信息

- UDP 发送数据使用
recvfrom函数,失败返回-1,成功返回实际接收的字节数。 - 第 1 个参数
sockfd:通过什么接收? socket 文件描述符,也就是我们前面创建的 socket 对象。 第 2 个参数buf:**把接收到的数据放到哪里?**存放到待接收数据的缓冲区。第 3 个参数len:这个缓冲区的大小。第 4 个参数flag:怎么接收 ?接收选项,填0就是默认阻塞接收。第 5 个参数src_addr:是谁发送的 ?用来接收发送方的地址信息(sockaddr 结构体,包含对方 IP + 端口)。第 6 个参数addrlen:是指针,传入时放 src_addr 结构体的大小;函数调用结束后,会被修改为实际地址结构体长度。
2. 发送信息

- UDP 发送数据使用
sendto函数;失败返回-1,成功返回实际发送的字节数。 - 第 1 个参数
sockfd:通过什么发送? socket 文件描述符,就是我们前面创建的 socket 对象。第 2 个参数buf:发送什么? 存放待发送数据的缓冲区。第 3 个参数len:待发送数据的字节长度。第 4 个参数flags:怎么发送? 发送选项,填 0 就是默认阻塞发送。 第 5 个参数dest_addr:发送给谁? 目标主机的地址信息(sockaddr 结构体,包含对方 IP + 端口)。第 6 个参数addrlen:dest_addr 结构体的大小。
1.3 ip地址介绍
1. 本地回环地址 127.0.0.1
- 范围:
127.0.0.0 ~ 127.255.255.255,最常用127.0.0.1。 - 特点:只在当前这台主机内部通信,数据包不会走出本机网卡,不会发到网线 / 局域网。
- 用途:本地测试服务端、客户端。比如你的 UDP 服务端绑定
127.0.0.1,只有本机上的程序能连接访问,别的电脑连不上。
2. 局域网 IP (内网 IP,私有 IP)
-
专门给同一个局域网内部设备使用,路由器不会把这类数据包转发到互联网。
私有 IP 三段:
10.0.0.0 ~ 10.255.255.255172.16.0.0 ~ 172.31.255.255192.168.0.0 ~ 192.168.255.255(家里路由器最常见,如192.168.1.12) -
特点:
- 同一个路由器下手机、电脑互相通信
- 不能直接在公网路由,出外网需要路由器做 NAT 地址转换
- 不同局域网之间,可以重复使用同一套内网 IP(A 家电脑
192.168.1.2,B 家电脑也可以是192.168.1.2,互不冲突)bind 绑定内网 IP,同局域网别的电脑可以连你的服务;外网不能直接访问。
3. 公网 IP
- 由运营商分配,互联网上唯一,全球不能重复。
- 数据包可以在整个互联网路由,外网的其他主机可以直接访问你。
- 现在家庭宽带大多不是独立公网 IP,是运营商 NAT 后的共享公网 IP。
服务端绑定公网 IP,互联网上任意主机都可以访问你的程序。
家庭电脑没有独立公网 IP 时,外网客户端无法直接连接本机服务。
4. 特殊监听地址 0.0.0.0
- 监听上面所提到过的所有类型地址。
- 如果服务端 bind 绑定具体 IP 地址 ,那么它就只能收到客户端发给那个具体地址的信息,而无法收到目的 IP 是其他地址的信息。而一台电脑它是拥有多个 ip 地址的,我们的目标是:只要是发给这台电脑该端口的数据包,就都应该接收,而不是限定到某个特定 IP 地址。这时就需要绑定
0.0.0.0,代表监听本机全部网卡与所有 IP。
1.4 服务端与客户端差异
- 服务端需要显性绑定 IP 地址和端口号,其中 IP 地址绑定
0.0.0.0;客户端一般不手动 bind 显性绑定。如果客户端手动绑定固定端口号,一旦多个客户端程序绑定同一个端口号,就会发生端口冲突,无法同时运行。所以客户端端口交给操作系统自动分配空闲临时端口;本机 IP 地址内核本身可以自动获取。客户端在第一次调用 sendto 发送消息的时候,内核会完成隐式绑定(本机 IP + 随机临时端口)。
二、Chat server(v3代码)

- 我们需要在 v1 版本代码的基础上,对现有功能进行扩展与完善。当客户端数量增多时,服务端需要对所有接入的客户端进行统一管理。我们可以通过先描述客户端地址信息、再对客户端集合进行组织管理的思路实现该需求,为此新增两个类:InetAddr(信息描述类)与 UserManager(用户管理类)。
- InetAddr 类提供两个构造函数:一个基于 sockaddr_in 结构体构造,另一个通过传入 IP 地址与端口号构造。创建该类对象时,自动完成两种地址表示形式的相互转换。
UserManager 负责管理所有接入服务端的客户端,存储并维护客户端对应的 InetAddr 对象。 - Route 类作为服务端与 UserManager 之间的中间调度模块,一方面保证对UserManager 的所有访问操作具备线程安全,另一方面实现消息广播逻辑。
今天的分享就到此结束啦,如果对读者朋友们有所帮助的话,可否留下宝贵的三连呢~~
让我们共同努力, 一起走下去!