C++学习之LINUX网络编程-套接字通信基础
目录
1.知识点概述
2.两种网络模型
3.服务器开发是什么
4.IP分类
5.IP协议
6.查看IP地址和测试主机之间是否能够通信
7.端口
8.IP和端口的使用以及域名作用
9.OSI 七层和四层模型
10.网络协议是什么
11.数据在网络环境中发送和接受过程
12.套接字通信涉及知识点
13.字节序
14.大小端存储举例
15.IP和端口大小端转换函数
16.字符串类型的主机字节序IP转网络字节序
17.网络字节序转字符串类型的主机字节序IP地址
1.知识点概述
- 网络设计模式
- B/S
- broswer - 浏览器 -> 客户端
- html
- css
- js
- server -> 服务器
- 优势:
- 跨平台
- 开发成本低
- 缺点:
- 网络通信的时候, 必须要使用http协议
- http / https -> 应用层协议
- 不能在磁盘缓存或者从磁盘加载大量数据
2.两种网络模型
- C/S
- client -> 桌面应用程序 -> Qt
- QQ
- 迅雷
- 微信
- server -> 服务器
- 特点:
- 优点:
- 使用的协议可以随意选择
- 可以在本地缓存或者加载大量数据
- 缺点:
- 研发成本高
- 在不同的平台开发不同的客户端版本
3.服务器开发是什么
服务器:
- 硬件: 配置比较高的主机
- 买阿里云, 百度云服务器
- 软件:
- 有一台主机, 主机上运行了一个进程, 这个进程可以处理网络协议, 称这台主机是一个服务器
- nginx
- 服务器开发:
- 工作不是去开发web服务器
- 我们做的工作:
- 在一台装有服务器的主机上开发应用程序
- 应用程序:
- 斗地主
- 文件服务器
4.IP分类
IP和端口
- IP地址分类
- 公网IP
- 可以访问Interface, 公网IP是唯一的
- 局域网IP
- 小的网络, 比如一个路由器对应的家里的网段
- 192.168.1.xxxx
- 在这个小的网络中IP是唯一的
- 在这个网络中的主机可以互相通信
- 如果局域网和外网连接, 那么通过局域网IP也可以连接外网
5.IP协议
- IP协议
- IPV4 - `“Internet Protocol Version 4`
- 现在应用很广泛的协议
- IP地址的点分十进制字符串
- `192.168.1.100`
- 本质是一个整形数, 4字节, 32位
- 通过3个点分成4分, 每份一个字节
- 字节的取值范围 0-255
- 最大的IP地址: `255.255.255.255`
- 可用的IP地址:
- 2的32次方 -1
- 现在IPv4的IP地址非常不够用, 在2011年就分配完了
- IPV6 -> `“Internet Protocol Version 6`
- 现在应用不是很广泛, 将来主推的一种网络协议
- 本质也是整形数, 16字节, 128bit
- IP地址表示:
- 分为8份, 每份2字节, 使用16进制的数字表示
- `fe80::020c:29ff:fe40:473a`
- ip地址的个数:
- 2的128次方 -1
6.查看IP地址和测试主机之间是否能够通信
```shell
# 查看IP地址
# linux
$ ifconfig
# windows
$ ipconfig
# 测试两台主机之间是否可用通信
ping ip地址
$ ping 192.168.1.6
PING 192.168.1.6 (192.168.1.6) 56(84) bytes of data.
64 bytes from 192.168.1.6: icmp_seq=1 ttl=128 time=1.88 ms
64 bytes from 192.168.1.6: icmp_seq=2 ttl=128 time=0.678 ms
7.端口
- 端口
```shell
# 1. 端口的本质?
无符号短整型数 -> unsigned short
# 2. 端口取值范围?
1. 可以有多少个端口: 2的16次方
2. 取值范围: 0 - 65535
# 3. 端口的作用?
定位某台主机上运行的某个进程
# 在电脑上运行了微信和QQ, 小明给我的的微信发消息, 电脑上的微信就收到了消息, 为什么?
- 运行的qq和微信在启动的时候都绑定的了不同的端口
- 小明给我的微信发送数据(点对点模型)
- 需要知道我的IP地址 -> 定位我电脑
- 如果定位电脑中的某一个进程呢? ---> 通过端口
8.IP和端口的使用以及域名作用
- ip和端口的使用
```shell
# 通过IP定位网络环境中的主机, 通过端口定位主机上的进程
# 比如通过浏览器进行网络服务器访问
# 服务器有IP地址/域名, 服务器需要绑定端口
# b/s -> 协议http -> 使用的端口是80, https协议端口-> 443
# 如果服务器使用的就是协议的默认端口, 访问的时候端口是可以省略不写的
http://www.baidu.com:80
https://www.baidu.com:443
http://192.168.1.100:9999
9.OSI 七层和四层模型
OSI/ISO 网络分层模型
> OSI(Open System Interconnect),即开放式系统互联。 一般都叫OSI参考模型,是ISO(国际标准化组织组织)在1985年研究的网络互联模型。
```shell
上层
|
| 七层模型 四层模型
|
| 应用层
| 表示层 应用层 http/ftp/ssh/ftps
| 会话层
--------------------------------------------------
| 传输层 传输层 tcp / udp
--------------------------------------------------
| 网络层 网络层 ip -> ipv4/ipv6
--------------------------------------------------
| 数据链路层 网络接口层 以太网帧协议
底层 物理层
10.网络协议是什么
> - 物理层负责最后将信息编码成电流脉冲或其它信号用于网上传输
> - 数据链路层:
> - 数据链路层通过物理网络链路供数据传输。
> - 规定了0和1的分包形式,确定了网络数据包的形式;
> - 网络层
> - 网络层负责在源和终点之间建立连接;
> - 此处需要确定计算机的位置,怎么确定?IPv4,IPv6
> - 传输层
> - 传输层向高层提供可靠的端到端的网络数据流服务。
> - 每一个应用程序都会在网卡注册一个端口号,该层就是端口与端口的通信
> - 会话层
> - 会话层建立、管理和终止表示层与实体之间的通信会话;
> - 建立一个连接(自动的手机信息、自动的网络寻址);
> - 表示层:
> - 对应用层数据编码和转化, 确保以一个系统应用层发送的信息 可以被另一个系统应用层识别;
> - 可以理解为:解决不同系统之间的通信,eg:手机上的QQ和Windows上的QQ可以通信;
> - 应用层:
> - 规定数据的传输协议
11.数据在网络环境中发送和接受过程
> - 网络协议:
>
> 计算机双方共同遵守的一组约定, 通过这个约定就可以完成连接的建立, 相互识别, 按照某种特定的数据格式进行网络通信
>
> - 发送端: 按照约定的数据格式组织数据
> - 接收端: 按照约定的数据格式解析数据
- 常见协议
- TCP协议 -> 传输层协议

- UDP协议 -> 传输层协议

- IP协议 -> 网络层协议

- 以太网帧协议

12.套接字通信涉及知识点
- 数据的封装

```shell
# 两台主机A,B
1. A给B发送字符串 "hello, world" , 字符串不会以这种形态发送到Internet, 需要在发送之前需要封装
2. 封装是由操作系统或者调用的API自动完成的, 程序猿无需关心
3. 如果程序猿需要封装数据, 一般是在应用层做处理, 应用层数据可封装也可以不封装
数据 "hello, world" 在应用层 进行发送
1. 在应用层对这个字符串封装(可选, 程序猿做的)
2. 数据往下走-> 传输层 -> 根据协议格式打包数据 , tcp
3. 数据往下走-> 网络层 -> 根据协议格式打包数据 , ip
4. 数据往下走-> 网络接口层 -> 根据协议格式打包数据, 以太网帧协议
5. 数据通过网口发送给B
B接收数据, 拆包
1. 网络接口层 -> 根据协议格式拆包, 以太网帧协议拆开
2. 得到了网络层的包 -> 拆包
3. 传输层的包 -> 拆包
4. 应用层的包
5. 应用层如果使用协议封装了数据继续拆包, 如果没有封装得到了 "hello, world"
程序猿只需要处理应用层, 应用层以下, 不需要我们处理
13.字节序
3. socket编程
> Socket套接字由远景研究规划局(Advanced Research Projects Agency, ARPA)资助加里福尼亚大学伯克利分校的一个研究组研发。其目的是将TCP/IP协议相关软件移植到UNIX类系统中。设计者开发了一个接口,以便应用程序能简单地调用该接口通信。这个接口不断完善,最终形成了Socket套接字。Linux系统采用了Socket套接字,因此,Socket接口就被广泛使用,到现在已经成为事实上的标准。与套接字相关的函数被包含在头文件sys/socket.h中。

```c
套接字通信就是网络通信, 跟语言没有关系, 因为通信是基于协议的, 所有的编程都需要基于这些协议对数据进行处理
// 1. 套接字是什么?
1. 套接字是一套网络通信的接口, 这个接口中封装了传输层协议( tcp / udp )
2. 接口就是 api, 也就是一套函数
// 2. socket(插座), 套接字通信的组成部分?
1. 服务器端, 插座的作用
- 被动接受连接的角色, 不会主动发起连接的
- 绑定固定IP和端口, 这样客户端才能进行连接
2. 客户端
- 主动发起连接的角色, 连接服务器
- 连接服务器需要地址:
- IP + 端口
// 3. 怎么用? -> 所有编程语言的通信流程都是固定的
- 服务器有通信流程
- 客户端有通信流程
14.大小端存储举例
```c
// 支持ipv4 和ipv6 格式的ip地址
#include <arpa/inet.h> // 套接字通信只需要包含这个头文件就可以了
// p-> 主机字节序的 字符串类型的IP地址
// 将主机字节序的字符串类型的IP -> 大端的整形数
int inet_pton(int af, const char *src, void *dst);
参数:
- af: 地址族协议
- AF_INET: 使用ipv4的网络协议
- AF_INET6: 使用ipv6的网络协议
- src: 主机字节序的字符串类型的IP地址, 要被转换的数据
- dst: 传出参数, 存储了转换之后的大端的IP地址
返回值:
成功: 0
失败: -1
// 大端的整形数 --> 主机字节序的字符串类型的IP
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
参数:
- af: 地址族协议
- AF_INET: 使用ipv4的网络协议
- AF_INET6: 使用ipv6的网络协议
- src: 传入参数, 要被转换的数据, 指针指向的内存中存储了大端的IP地址(整形数)
- dst: 传出参数, 指针指向的内存中存储了主机字节序 字符串类型的IP地址
- size: 参数dst指向的内存的大小
返回值:
失败: NULL
成功: 指针指向函数第三个参数 dst 指向的内存
```
15.IP和端口大小端转换函数
```c
// 在写数据的时候不好用
struct sockaddr {
sa_family_t sa_family; // 地址族协议, ipv4, ipv6
char sa_data[14]; // 端口(2字节) + IP地址(4字节) + 填充(8字节)
}
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
typedef unsigned short int sa_family_t;
#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))
struct in_addr
{
in_addr_t s_addr;
};
// sizeof(struct sockaddr) == sizeof(struct sockaddr_in)
struct sockaddr_in
{
sa_family_t sin_family; /* 地址族协议: AF_INET */
in_port_t sin_port; /* 端口, 2字节-> 大端 */
struct in_addr sin_addr; /* IP地址, 4字节 -> 大端 */
/* 填充 8字节 */
unsigned char sin_zero[sizeof (struct sockaddr) - sizeof(sin_family) -
sizeof (in_port_t) - sizeof (struct in_addr)];
};
```
## 3.4 套接字函数
16.字符串类型的主机字节序IP转网络字节序
#include <arpa/inet.h>
// 创建一个套接字(文件描述符), 用于通信或者监听都是可以的
int socket(int domain, int type, int protocol);
参数:
- domain:
- AF_INET: 使用ipv4的网络协议
- AF_INET6: 使用ipv6的网络协议
- type:
- SOCK_STREAM: 使用流式传输协议
- SOCK_DGRAM: 使用报式传输协议
- protocol: 默认写0
- 流式协议默认使用tcp
- 报式协议默认使用udp
返回值:
成功: 返回一个文件描述符
失败: -1
// 将监听的套接字和本地IP和端口进行关联
struct sockaddr_in addr;
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
- sockfd: 用于监听的套接字, 通过调用socket函数创建的
- add: 将本地IP和端口信息初始化给该结构体, ip和端口使用大端
- 绑定的时候服务器端一般ip地址使用 INADDR_ANY == 0
- 0 == 0.0.0.0
- 表示绑定本机的所有的ip地址
- 一台主机有多个网卡 -> 多个IP地址
- 先识别实际IP让后绑定
- addrlen: 记录第二个参数指针指向的内存大小, sizeof(struct sockaddr)
返回值:
成功: 0
失败: -1
17.网络字节序转字符串类型的主机字节序IP地址
// 发送数据
// 写缓冲区中数据写满了, 写阻塞
ssize_t write(int fd, const void *buf, size_t len);
ssize_t send(int fd, const void *buf, size_t len, int flags);
参数:
- fd: 通信的文件描述符
- accept的返回值(服务器端)
- 通过socket函数创建得到的, 通过connect初始化连接(客户端)
- buf: 要发送的数据, 数据进入到了通信的文件描述符的写缓冲区
- 写缓冲区数据是由内核维护管理的, 这里边有数据, 内核就会进行发送
- len: 发送的数据的实际长度, strlen();
- flag: 使用默认属性, 指定为0即可
返回值:
>0: 发送的实际字节数
=0: 没有发送任何数据
-1: 发送失败, 异常
// 客户端用来连接服务器
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
- sockfd: 通信的文件描述符, 通过socket()得到
- addr: 连接的服务器的IP和端口信息, 初始化该变量中, 使用网络字节序(ip+端口)
- addrlen: 参数addr指向的内存大小
返回值:
成功: 0
失败: -1
```