一、网络基础
1、网络应用程序设计模式
C/S 架构(客户服务器模式)需要下载客户端
优点:1、协议选用灵活
2、可以缓存数据(常用到的图片、视频之内的存放在客户端)速度快
缺点:1、对用户安全构成威胁(窃取用户数据)2、开发工作量大、调试困难
B/S 架构(浏览器服务器模式)
优点:跨平台 安全性
缺点:只能使用HTTP HTTPS 加密过的
不能缓存大量数据 严格遵守http
2、网络分层模型 (只需要管应用层,其他层不需要管,其他有操作系统帮助去处理)
7层OSI模型(物联网输会使用)
四层TCP/IP 模型
3、网络传输流程
数据没有封装之前,是不能在网络中传输
数据-应用层-传输层-网络层-链路层
4、协议格式
以太网帧协议(利用)
IP协议 ipv4 ipv6 推是因为实现多边控制
ARP协议(根据IP地址获取MAC地址)
TCP协议
UDP协议
二、socket编程
1、网络套接(socket)插座 客户端的套接字 服务器端
该套接字内部是由内核借助两个缓冲区实现。
在通信过程中,套接字是成对出现的。
一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现。)
2、网络字节序
小端法:高位存高地址,低位存地址
大端法:高位存低地址,低位存高地址
计算机采用的是小端存储,而在网络中采用的大端存储,需要在两者之间进行转换。
地址从低地址到高地址
#include<arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htonl(uint16_t hostlong);
// 以前的话 long 32位 就是 int short就是短字节 16位 用于端口
uint32_t ntohl(uint32_t hostlong);
uint16_t ntohl(uint16_t hostlong);
h表示host,n表示network,l表示32位长整型,s表示16位短整数。
htonl—>本地-网络(IP)
htons—>本地-网络(port)
ntohl—>网络-本地(IP)
ntohs—>网络-本地(port)
192.168.1.1 —> string —>atoi---->int---->htonl—>网络
普通用的是点分十进制 需要先通过atoi函数调用转为int然后再把其转为大端存储
这些写麻烦,系统封装了两个函数
p理解为ip的意思 n理解为网络
本地的字节序(string Ip)字符串类型 —> 网络字节序
int inet_pton(int af, const char *src, void *dst);
af:
AF_INET 为 ipv4
AF_INET6为ipv6
src:传入 IP地址(点分十进制)
dst:传出 转换后的网络字节序的IP地址
返回值:
成功为 1
异常:0 说明src指向的不是一个有效的Ip地址
失败:-1
const char *inet_ntop(int af, const void *src,
char *dst, socklen_t size);
af:
AF_INET 为 ipv4
AF_INET6为ipv6
src:网络字节序的Ip地址
dst:转换后本地字节序的网络地址(string Ip)字符串类型的,用户能够看懂的
size:dst的大小
返回值:
成功:dst
失败:null
3、sockaddr数据结构
bind函数
sockaddr地址结构
现在用的是这个
传入的时候需要初始化,不然传进去也没有用
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /*internet address */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
struct sockaddr_in addr;
addr.sin_family = AF_INET / AF_INET6; // 初始化网络
addr.sin_port = htons(9527); // 端口 本地字节序---> 网络字节序
int dst;
inet_pton(AF_INET, "192.157.22.45", (void*)&dst); // 把主机IP地址转换为网络字节序 这块写void* 是因为传出的时候是void*类型的指针
addr.sin_addr.s_addr = dst; // 端口 网络字节序
addr.sin_addr.s_addr = htonl(INADDR_ANY); // 取出系统中有效的IP地址,二进制类型
bind(fd, (struct sockaddr*)&addr, size); // 传入网络类型,传入sockaddr(需要初始化),结构体的大小
接口那里是struct sockaddr 但是那个定义成为另一个,传的时候需要强转
现在的sockaddr和之前的不太一样,所以需要进行强转
bind函数里面的sockaddr
accpet()函数
客户端和服务器端每一个一个套接字 服务器还有一个用于进行监听
服务器端
1、socket() 创建一个套接字
fd=socket(domain,type,protocol)
成功之后,返回一个文件描述符有一个fd(句柄)指向这个套接字, -1错误
2、**绑定Ip地址和端口** 给上面那个socket的绑定ip地址和端口,这个bind里面,它传入了fd,就是进程文件描述表这个表的索引
bind(fd, (struct sockaddr*)&addr, size); // 传入网络类型,传入sockaddr(需要初始化),结构体的大小
3、设置监听上线 客户端和服务器 同时连接的数量
listen(fd,20)可以同时进行3次握手的客户端数量
4、accpet()阻塞 监听客户端的连接
然后连接上,accpet会返回一个新的socket(),之前的被解放了,进行监听等待其他客户端 两端的socket进行通信
read读到0的话,
会返回读到的字节数 里面写的套接字相当于是一片空间 然后客户端给里面写数据,然后自己读结束,用close()进行关闭
客户端
1、创建socket()
2、调用connect() 绑定服务器的ip地址和端口号 与服务器建立连接,使用现有的socket 服务器的地址结构
3、write()
4、read()
connnect() 函数 第二个参数和accept()函数 一个是传入、一个是传出
三、TCP三次握手
三次握手、四次挥手统一称为通信时序图
服务器端 和 客户端先建立连接 两端各一个套接字,搭这根线就是三次握手
滑动窗口
四、TCP四次挥手
第一次,只关闭一半,另一端可以给另一端还可以发送数据。
套接字里面有两个缓冲区,一个读、一个写
关闭缓冲区指的要不就关闭读,要不就关闭写。
图反着呢
因为半关闭,所以是四次握手
全双工
发ack不需要缓冲区
TCP流量控制
MSS:最大报文长度
滑动窗口
往缓冲区里面写,但是服务器端会指定缓冲区大小
TCP建立连接与代码对应:
出错处理封装函数(健壮性封装)
封装目的:在server.c编程过程中突出逻辑,将出错处理与逻辑分开,同时也可以跳转man手册
readn、readline 读n个字节,读一行。
五、并发
1、多进程
服务器是一个进程,建立连接之后会重新创建出来新的子进程,用于和客户端进行通信