一、网络的发展背景
网络的使用:数据传输(不同主机上的进程间通信)
协议:网络中主机之间进行通信必须使用相同的协议(相同的一个光电信号格式)。协议就是一个相互都能识别的约定。
计算机如何传输数据——光电波信号。
在网络中因为主机设备的生产厂商很多,每个厂商如果都使用自己的协议进行通信,那么将导致网络上主机间无法正常通信,这时候一个大型组织设计一个标准的协议,各厂商都使用这个协议进行通信,这时候就能实现网络全覆盖通信了。
网络间的数据传输必须使用同一个标准协议(网络通信协议)。
网络通信环境特别复杂。在网络通信中,因为应对场景复杂,各有不同,因此协议也相对复杂且繁多。为了使用更加方便,因此就有了协议的封装。
协议的封装/分层----按照各个协议的不同功能以及不同的应对场景对不同的协议进行了分层:在某种特定环境下如果协议的分层清晰明了,那么使用/替换起来就更加方便了。
局域网、广域网、以太网
局域网通信原理:所有主机收到数据,进行分析、提取、对比、采集或丢弃。
局域网信息碰撞:多个主机都发送消息,互相干扰,进行碰撞检测(碰撞避免->让)
数据没有跨网络传输,mac?地址->网卡地址(全球唯一)
在同一个局域网内任意两台电脑可以直接进行通信。举例:cs;
二、OSI网络七层参考模型
7.应用层:针对特定应用的协议
6.表示层:设备固有数据格式和网络标准数据格式的转换
5.会话层:通信管理。负责建立和断开通信连接(数据流动的逻辑通路)。管理传输层以下的分层
4.传输层:管理两个节点之间的数据传输
3.网络层:地址管理与路由选择
2.数字链接层:互联设备之间传送和识别数据帧
1.物理层:以“0”、“1”代表电压的高低、灯光的闪灭。界定连接器和网线的规格
三、TCP/IP五层模型
TCP/IP指的是一个协议簇(很多协议),因为TCP协议和IP协议是最典型的两个协议,因此这个协议簇就用这两个协议的名字命名了。
1.应用层:(应用层、会话层、表示层)负责应用程序间的数据沟通;http/https(比 http更安全) ftp ssh SMTP(卖家、封装)
2.传输层:负责端与端之间的数据传输;tcp udp(快递员、单子:协议包头->对物流最有效与卖家买家基本没什么关系)
3.网络层:负责地址管理和路由选择;ip 路由器(快递员的交通方式)
4.数据链路层:负责相邻设备节点之间的数据帧传输;以太网协议(eth)交换机(买家、解包)
报头+有效载荷
分用?:向上交付:每一层协议只要上一层还有协议,那么需要上一层的。。。()
5.物理层:负责光电信号的传输;以太网协议 集线器
四、理解源IP地址和目的IP地址
1.IP地址(无符号四字节的整数 uint 32_t):唯一定位网络上的一台主机。
2.端口(无符号两个字节的数据 uint16_t 0-65535):用来标识一台主机上的进程(标识一个进行网络通信的进程)。
进程的标识为什么不用pid?因为pid是会随着进程的创建改变,端口号不会变。
sip + sport + dip + dport + proto-----五元组,用于标识一条网络上的通信。
3.网络字节序(cpu操作顺序):大小端:2个字节
0x 01 02 03 04 00000001 00000010 00000011 00000011 00000100
大端字节序:低地址存高位
uchar buf【4】01 02 03 04
小端字节序:低地址存低位
uchar buf【4】04 03 02 01
不同字节序主机之间进行数据传输,将造成数据的逆序,也就是说得不到真正发送的数据。为了解决这个问题,指定一个标准:在网络进行通信的时候必须使用大端字节序(为了让不同的主机进行正常的网络通信),也称为网络字节序。这也就意味着如果我们的主机是小端字节序,那么通信的时候就需要对数据逆序。但也不是所有数据都需要逆序,数据逆序主要针对在内存中存储时占据内存大于一个字节的数据。
int short long float double 针对这些类型的数据,因为在内存中存储的跟展示的顺序刚好相反,而发送的时候,按字节发送,对方按字节接受,那么对方如果是大端,因为低地址存高位,因为存储顺序和使用顺序完全相同,导致数据跟实际想发送的数据顺序刚好相反,因此需要进行字节逆序转换。
主机字节序:当前主机的字节序——大小端不一定,取决于cpu的架构
X86「小端」 RISC-V MIPS「大端」
五、网络套接字编程:主要是传输层
因为传输层有两个典型协议(tcp/udp),根据使用场景选择其一进行传输。
两个协议的优缺点:
tcp:面向连接(可靠传输的前提),可靠传输,字节流服务
优点:可靠传输,数据传输灵活
缺点:传输速率低,数据粘包(数据堆积造成数据粘连)
udp:无连接,不可靠,面向数据报
优点:传输速度快,无粘包情况(一整条接受、传送)
缺点:不可靠
针对数据安全性要求高的场景(如文件传输),使用tcp保证数据的可靠。
针对数据安全性要求不是很高,但是实时性要求高的场景(如视频传输),保证传输的速度。
六、socket套接字编程:
网络编程涉及到对网卡的操作,因此操作系统就提供了一套接口来供我们操作——socket接口,供用户进行网络通信编程。
网络编程中分为客户端程序和服务端程序。客户端就是主动的一方,且客户端必须知道服务端的地址信息(ip+port),且服务端必须在指定的地址上等待客户端。
1.udp编程
A、服务端:
(1)创建套接字 socket
int socket(int domain, int type, int protocol)
//domain :地址域 AF_INET IPV4
//type :套接字类型
//SOCK_STREAM :流式套接字 tcp
//SOCK_DGRAM:数据报套接字。udp
//protocol:协议类型
//IPPROTO_TCP 6:tcp协议
//IPPROTO_UDP 17:udp协议
//返回值:非负整数(套接字描述符)
建立网卡与进程的关系
(2)为套接字绑定地址信息 band
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//sockfd :套接字描述符
//addr:地址信息
//addrlen:地址信息长度
//返回值:成功-0,失败-(-1)
(3)发送/接收文件 sendto/recvfrom
接收:
ssize_t recvfrom (int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
//sockfd:套接字描述符
//buf:要发送的数据
//len:要发送的数据的长度
//flags:发送标志, 0-默认阻塞
//MSG_PEEK 接收数据后数据并不会从缓冲区删除
场景:探测型获取数据
//src_addr:发送端的地址信息
//addrlen:地址信息长度
//返回值:实际读取到的数据字节长度
发送:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flag, struct sockaddr*dest_addr, sickle_t addrlen);
//sockfd:套接字描述符
//buf:要发送的数据
//要发送的数据的长度
默认0 ,阻塞发送
数据要发送到的对端地址信息
地址信息长度
返回值:实际发送的数据长度。失败-1
(4)关闭套接字 close
ip地址动态分配----DHCP
NAT服务——地址替换
IPV6——不向下兼容
为套接字绑定地址端口,就是为了声明,去网卡接受数据的时候,接收的是哪一部分的数据。
客户端:
(1)创建套接字
(2)绑定(不主动绑定,由操作系统去绑定)
(3)发送/接收
(4)关闭套接字
//udp服务端程序——实现简单的聊天功能
#include <stdio.h>
#include<stdilb.h>
#include<sys/socket.h>
#include<unistd.h>
int main()
{
//创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(socket < 0){
Perror(“socket error”);
return -1;
}
//为套接字绑定地址
struct sockadder_in addr;
add.sin_family = AF_INET;
//uint16_t htons(uint16_t hostshort);
//将一个短整形(16位)数据从主机字节序转换为网络字节序
add.sin_port = htons(9000);
//in_addr_t inet_addr(const char *cp);
//将点分十进制的字符串IP地址转换为网络字节序的地址
//INADDR_ANY INADDR_BROADCAST
add.sin_addr.s.addr = inet_addr(“192.168.122.133”);
//s_addr = 0.0.0.0 匹配任意一个地址(只能用于服务端,客户端不能使用)
int ret = bind(sockfd,(struct sockaddr*)&addr, len);
if(ret < 0){
perror(“bind error”);
return -1;
}
while(1){
//接收
char buff[1024] = {0};
Struct sockaddr_in cli_addr;
Recvfrom(sockfd, buff, 1023, 0, (struct sockaddr *)src_addr, &addrlen);
printf("client say:%s\n”,buff);
//发送
memset(buff, 0x00, 1024);
printf(“server say:”);
fflush(stdout);
scanf(“%s”,buff);
}
//关闭套接字
close(sockfd);
}
B、客户端:
客户端最好///
class UdpSocket
{
private:
int_sockfd;
public:
bool Socket()
{
_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(_sockfd < 0){
perror(“scoket error”);
return false;
}
return true;
}
//绑定地址
bool Bind(std::string &ip, uint16_t port)
//接收数据
ssize_t Recv(std::string *buf, struct sockaddr_in *addr)
{
ssize_t rlen;
char tmp[1024];
rlen = recvfrom(_sockfd, tmp, 1023, 0, (sockaddr*)addr, &len);
if(ret < 0){
return -1;
}
//assign:从一个字符串中拷贝指定长度的数据到string中
buf->assign(tmp, rlen);
return rlen;
}
//发送数据
ssize_t Send(std::string &buf, struct sockaddr_in *addr)
//关闭套接字
bool Close()
{
close(_sockfd);
}
};
先运行服务端。
2.tcp网络通信编程:
A、服务端:
(1)创建套接字socket
(2)绑定地址
(3)开始监听(告诉操作系统开始从端口接收数据。若接口为新,则先去完成三次握手过程)。
//监听—int listen(int soctfd, int backlog);
//sockfd套接字描述符
//backlog定义已经连接队列的最大节点数
//每一个用户都活创建新的socket,一个新连接建立建立额有一个过程,如果这个连接已经完成三次握手过程,就将这个新的 socket放到这个队列中。
//这个backlog决定了同一时间最大并发连接数。
//整型数字,用于定义一个挂起的连接队列的最大节点数,代表同一时间的并发连接数(能够接收的客户端连接数)
TCP建立连接的三次握手过程🤝(由操作系统内核完成):客户端向服务端发送SYN请求,服务端回复SYN+ACK,客户端再次发送ACK(确保安全)。
(4)获取新建socket:若只有一个socket,若端口太多会引起混乱,那么操作系统建立一个新的socket,单独与新客户端进行通信。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
//sockfd:套接字描述符
//addr:新连接的客户端地址信息
//addrlen:用于确定要获取地址信息的长度,接收实际长度
//返回值:新建的socket连接的套接字描述符,失败返回-1
//每一个客户端向服务端发起连接请求,在服务端都会新建一个socket结构,当这个新的连接走完三次握手过程,完成建立连接,这个新的socket结构会放到已经完成连接的队列中。
//accept获取新连接的客户端,实际上是从已完成连接队列获取一个已完成连接的socket,并且返回这个新的socket的套接字描述符。
(5)收发数据
发送数据:
ssize_t send(int sockfd, const void *buf, size_t len, int flags)
//sockfd:套接字描述符
//buf:要发送的数据
//len:数据长度
//flags:0-默认阻塞
//返回值:实际发送的数据长度,失败返回-1
//接收数据:
ssize_t recv(int sockfd, void *buf, size_t len, int flags)
//
//返回值:1.大于0:实际获取数据长度 2.等于0:连接断开 3.等于-1:出错