一、网络基础
1.1网络体系结构
OSI模型 7层 应用层 表示层 会话层 传输层 网络层 数据链路层 物理层
TCP/IP 4层 应用层 传输层 网络层 物理层--》在使用
1.2IP地址
iP地址--》网络地址
定义:在网络中唯一标识一台主机符号就是IP地址
(唯一标识一台主机的符号是MAC地址)
ipv4维护IP地址
4:1 IP协议的版本号是V4
2 IPV4协议维护的IP地址宽度是4byte=32bit
ipv4协议提供了2^32个IP地址,将这些地址分成了5类:
A 1.0.0.1~126.255.255.254
B 127.0.0.1~191.255.255.254
C 192.0.0.1~223.255.255.254 -->用户IP地址 24网络号+8主机号
D 224.0.0.1~239.255.255.254--》组播地址
E 240.0.0.1~255.255.255.254-->保留地址
unsigned int addr=inet_addr("127.0.0.1");

1.3 网络协议
定义:在网络通信中对某种通信规则的约定
分类:1 网络通用协议(TCP/IP协议族)
2 行业内部专有协议
3 自定义协议
1.4 端口号
作用:用作标识一个应用进程
是一个unsigned int 的整数,表示范围1-65535
1-1023已被著名的协议或程序占用,用户可用:1024~65535
1.5 字节序
主机字节序:是不同的CPU主机存贮多字节整数的方式,有大端主机字节序,
有小端主机字节序
大端主机字节序:将数据的高字节存放在内存的低地址上
小端主机字节序:将数据的高字节存放在内存的高地址上
注意:由于网络通信中两台主机的字节序有可能不同,会造成发送的数据和接收的
数据反序,所以在传送网络数据前要做字节适配处理,将多字节整数从主机
字节序转换成网络字节序
网络字节序:是大端字节序,用于兼容通信双方的字节序
htonl:将4字节的数据的主机字节序转换成网络字节序
htons:将2字节的数据的主机字节序转换成网络字节序
1.6 socket
1.socket概念
1.是一种特殊的文件描述符
2.需要一种通用的网络编程接口:Socket
2.socket种类
流式套接字(SOCK_STREAM) --->TCP
提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。
注意:TCP方式的unix域套接字类似于无名管道,用一个套接字文件可以实现双工
通信,通常用于两个不相关的进程或前后台进程的通信。
数据报套接字(SOCK_DGRAM) --->UDP
提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。
注意:UDP方式的unix域套接字类似于有名管道,用一个套接字文件只能单工
通信,如要实现双工通信,需要两个socket文件,通常用于两个不相关的
进程或前后台进程的通信。
原始套接字(SOCK_RAW)
二、网络客户端&服务端模型
2.1TCP和UDP协议
共同点:同为传输层协议
不同点:
TCP:通过TCP的三次握手,建立通信双方的全双工的通道。TCP对网络丢包、乱序等都会有相应的回复处理。
UDP:不需要建立连接。尽力传输,不保证可靠性而已。
TCP(即传输控制协议):是一种面向连接的传输层协议,它能提供高可靠性通信(即数据无误、数据无丢失、数据无失序、数据无重复到达的通信)
如何抉择使用TCP还是UDP
1.可靠性
2.实时性
可靠性 > 实时性: TCP
可靠性 < 实时性: UDP
他们的区别是:
tcp:是面向连接的可靠的传输协议
udp:是面向无连接的不可靠的传输协议
TCP:
三次握手:
第一次握手:客户端给服务器发送建链请求(客户端能发了)
第二次握手:服务器给客户端回复建链请求的响应,同时服务器向客户端发送建链请求(服务器能收了,服务器能发了)
第三次握手:客户端给服务器回复建链请求的相应(客户端能收了)
四次挥手:
第一次挥手:客户端向服务器发送断链请求(客户端不发了)
第二次挥手:服务器向客户端回复断链请求的响应(服务器不收了)
第三次挥手:服务器向客户端发送断链请求(服务器不发了)
第四次挥手:客户端向服务器回复断链请求的响应(客户端不收了)
2.2 基于tcp协议的网络客户端和服务端模型
服务端:socket-->bind-->listen-->accept-->IO函数(read/write recv/send)
客户端:socket-->connect-->IO函数
int socket(int domain, int type, int protocol);
作用:创建一个用于通信的网络套接字
参数1:协议域 AF_INET(ipv4协议)
参数2:套接字类型 SOCK_STREAM
参数3:需要的其他协议 0--》自动匹配其他必要协议
返回值:失败 -1
成功 标识socket通道的文件描述符 >3
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
作用:给socket通道绑定网络接收终端
参数1:文件描述符 socket的返回值
参数2:表示本机地址信息的结构体指针
参数3:地址结构体的长度
返回值:0 成功 -1 失败
int listen(int sockfd, int backlog);
作用:监听客户端的连接请求
参数1:文件描述符 socket的返回值
参数2:服务端一次最大监听的客户端个数
返回值:0 成功 -1 失败
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
作用:接收客户端的连接请求
参数1:文件描述符 socket的返回值
参数2:地址信息结构,用来存放接收到的客户端的地址信息
参数3:存放第二个参数大小的变量的地址
返回值:成功 返回新的文件描述符--》标识一个新的socket通道--》用于收发数据
失败 -1
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
作用:接收socket通道中的数据
参数1:新的文件描述符 是accept的返回值
参数2:本地接收buf
参数3:期望接收的字节数
参数4:是否阻塞的接收的标志 0表示阻塞接收
返回值:>0 成功接收到的子节数
-1 失败
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
作用:往socket通道中发送数据
参数1:新的文件描述符 是accept的返回值
参数2:本地发送buf
参数3:期望发送的字节数
参数4:是否阻塞的发送的标志 0表示阻塞发送
返回值:成功 返回发送的字节数
失败 -1
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
作用:连接服务端
参数1:文件描述符 socket的返回值
参数2:目标服务器的主机地址信息结构
参数3:第二个参数的长度
返回值:0 成功 -1 失败
TCP通信出现粘包问题原因
- 信道拥挤产生的
- 读写速率不匹配
解决方案:
1.使用延迟(用时间来匹配速率)
2.写多少,读多少(使用同等大小缓存,使得读写保持一致)
3.对粘合的包进行解析(在有严格的协议格式下,才能使用,数据本身和协议不能产生二义性)
附:
wireshark抓包工具
1.安装wireshark
2.运行wireshark
2.3 基于udp协议的网络客户端和服务端模型
服务端:socket-->bind-->IO函数(sendto/recvfrom)
客户端:socket-->IO函数(sendto/recvfrom)
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
参数1:文件描述符
参数2:接收buf
参数3:期望接收的字节数
参数4:阻塞或非阻塞标志 0--》阻塞
参数5:用来保存对方地址信息
参数6:参数5的长度的地址
返回值:成功 期望接收到的字节数 失败 -1
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
参数1:文件描述符
参数2:发送buf
参数3:期望发送的字节数
参数4:阻塞或非阻塞标志 0--》阻塞
参数5:用来存放对方地址信息
参数6:参数5的长度
返回值:成功 期望发送的字节数 失败 -1
三、IO模型
linux 系统提供了4种IO模型:
1.阻塞IO
若要读的数据没有准备好,或要写入的目标没有空间,将会发生读写阻塞
阻塞函数:优点:一直等待结果,直到等到结果
缺点:如果等待的接收数据的通道坏掉或文件损坏,那么当前进程
会一直被挂起
2.非阻塞IO
若要读的数据没有准备好,IO函数返回一个约定的错误值,不阻塞当前进程
,可以通过循环去多次尝试读取数据。
非阻塞函数:优点:没有读到数据不阻塞当前进程,而是直接返回,带回一个错误值
缺点:多次调用系统调用函数会有极大的系统资源开销。
3.IO多路复用
思想:分三步
1 构建一个文件描述符集合表,表的大小是1024bit位,每一个bit表示一个
文件描述符所对应的IO通道上是否有数据发生(或者IO通道是否活跃?)?
如果有数据发生,该bit位为1,没数据发生该bit为0。
2 使用select函数监控制定范围的文件描述符所对应的IO通道是否有数据发生,
如果有数据发生,该函数返回通道的个数,并且将文件描述符集合表的
相应bit置位1,同时将其他bit置位0,如果没有数据发生,那么select将
阻塞当前进程。
3 对文件描述符集合表中的置位结果做出相应的判断,使用FD_ISSET判断
该文件描述符在描述符集合表中是否为1,如果为1,响应这路IO数据,
否则不处理。
分析:1 文件描述符集合表--》文件描述符对应的IO通道是否有数据发生
fd_set stFdr;1024bit
2 select 监控 置位--》stFdr
3 对stFdr中的置位情况做判断 0-->fgets 3--》accept 4-->recv/send
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
作用:监控文件描述符对应的IO通道是否有数据发生
参数1:要监控的文件描述符的最大个数
参数2:读文件描述符集合表
参数3:写文件描述符集合表
参数4:异常文件描述符集合表
参数5:超时时间 ,若为NULL,表示将select变成阻塞函数
返回值:成功 有数据发生的通道的个数
失败 -1
为了设置文件描述符我们要使用几个宏:
-
- FD_SET 将fd加入到fdset
- FD_CLR 将fd从fdset里面清除
- FD_ZERO 从fdset中清除所有的文件描述符
- FD_ISSET 判断fd是否在fdset集合中
- 宏的形式:
- void FD_ZERO(fd_set *fdset) 将文件描述符对应位的值置为0
- void FD_SET(int fd,fd_set *fdset) 将文件描述符对应位的值置为1
- void FD_CLR(int fd,fd_set *fdset) 将文件描述符对应位的值置为0
- int FD_ISSET(int fd,fd_set *fdset) 判断文件描述符对应位是1还是0
fd_set stFdr;//创建描述符集和表
FD_ZERO(&stFdr);//清空指定的描述符集和表 (原描述符集和表)
FD_SET(iSever,&stFdr) FD_ISSET(i,&stFdrTmp)
4异步IO
四、服务器模型
1.循环服务器
定义:该服务可以服务于多个客户端,但在同一时刻只能服务于一个客户端
2.并发服务器
定义:该服务器可以同时服务于多个客户端
实现并发服务的方法:多进程 多线程 select poll epoll
2.1多进程并发服务器
为了响应客户机的请求,服务器要创建子进程来处理。 如果有多个客户端的话,服务器端需要创建多个子进程。过多的子进程会影响服务器端的运行效率
2.2多线程并发服务器
主进程负责专门连接多个客户端,若有客户端连接进来,主进程就创建一个子线程来负责该客户端的业务数据的收发。
多线程服务器是对多进程服务器的改进
2.3.IO多路复用服务器
select实现并发
1 文件描述符集合表
2 select 监控 置位
3 FD_ISSET(fd,&stFdr)-->判断置位的结果并响应
stFdr-->原文件描述符集合表--->S
stFdrTmp-->临时文件描述符集合表--->T
2.4 epoll函数
select,poll,epoll都是I/O多路复用机制,即能监视多个fd,一旦某fd就绪(读或写就绪),能够通知程序进行相应读写操作。 但select,poll,epoll本质都是同步I/O,因为他们都需在读写事件就绪后,自己负责进行读写,即该读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O实现会负责把数据从内核拷贝到用户空间。
五、网络服务器超时检测
1 select设置超时
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
参数1:监控文件描述符的最大个数
参数2:读文件描述符集合表
参数3:写文件描述符集合表
参数4:异常文件描述符集合表
参数5:timeout=NULL,一直阻塞,直到有文件描述符就绪或出错
timeout=0,仅仅检测文件描述符的状态,然后立即返回,不置位。
timeout=时间值,在指定的时间内如果没有描述符对应的IO通道
没有数据发生,则超时返回0。
返回值:成功 返回有IO数据发生的通道的个数
若=0,表示超时
若<0,表示出错
struct timeval {--》用来表示时间
long tv_sec;--》秒
long tv_usec;--》微秒
};
struct timeval t={5,0};
select(..,&t);
2 使用信号和alarm函数
udp_server.c-->recvform()
alarm-->闹钟函数--》用作定时
alarm(5);起一个定时器,定时时间5S,5秒时间到,它会给内核发送一个SIGALRM
信号,内核会执行该信号的默认处理动作(中止当前进程)
signal(SIGALRM,Func);-->set
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact); -->set/get
获取: sigaction(SIGALRM,NULL,&oldact)-->get
设置:sigaction(SIGALRM,act,NULL);-->set
代码:
struct sigaction act;
sigaction(SIGALRM, NULL, &act);--》获取信号对应的旧属性
act.sa_handler = handler;--》给信号设置新的处理函数
act.sa_flags &= ~SA_RESTART;--》给信号设置标志-》不重启
sigaction(SIGALRM, &act, NULL);
alarm(5);
注:act.sa_flags &= ~SA_RESTART ;不重启recvfrom函数,结束阻塞,超时退出
act.sa_flags = SA_RESTART;自动重启,定时时间到时重启recvfrom函数,
继续阻塞,不会超时退出
sa_flags表示信号的标志
SA_RESTART用在为某个信号设置信号处理函数时,给该信号设置一个标志。
一旦在进程中给信号设置了SA_RESTART标志,那么当执行某个阻塞系统调用函数时,
进程收到该信号时,进程不会返回,而是重新执行该系统调用函数。
struct sigaction {-->用来表示信号对应的属性
__sighandler_t sa_handler;--》信号对应的处理函数
unsigned long sa_flags; --》信号标志
__sigrestore_t sa_restorer;
sigset_t sa_mask; --》信号掩码
};
3 socket属性超时设置
udp_server.c-->recvfrom
通过设置socket通道的SOCKET层面上的接收超时属性去设置
服务器的超时检测功能。
struct timeval tv;
tv.tv_sec = 5; // 设置5秒时间
tv.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv,
sizeof(tv)); // 设置接收超时
六、广播、组播
在网络通信中,数据包的发送方式有几种?分别是什么?
三种,分别是单播 广播 组播
1广播--》一对多
发送方发送的数据,整个局域网的主机都能接收到
广播发送:socket-->setsockopt(允许广播发送) --》sendto(广播地址) udp_client.c
广播接收:socket-->bind-->recvfrom udp_server.c
2组播-->一对一组
发送方发送的数据,组播组的成员主机能接收到
组播发送:socket-->对方的地址(组播地址)--》sendto udp_client.c
组播接收:socket-->加入组播组--》bind-->recvfrom udp_server.c
加入组播组:
struct ip_mreq mreq;
bzero(&mreq, sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr(“224.10.10.1”);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
sizeof(mreq));
七、数据库
定义:是指以同一组织方式将相关数据组织在一起,并存放在PC或设备存储器上的供
多个用户共享使用的文件,与应用程序彼此独立,是一组相关数据的集合。
用户可以使用通用指令(sql语句)提取查看数据。
常见的嵌入式数据库:mysql sqlite ...
sqlite:是一种小型的嵌入式关系型数据库
sqlite特点:源码开发 代码精简 免安装 支持SQL语句。。
对应的源码sqlite3.c/sqlite3.h
7.1 操作数据库的通用指令--》SQL语句
数据库文件--》1 创建一张表 指令 表项 约束
2 给表插入内容 指令 表项 值
1 创建一张表
create table 表名 (字段列表 [各种约束])
create table student (id int primary key not null,
name text not null,
age int not null)
primary key--->主键约束
A 主键值必须唯一,用于标识一条记录,如学生的学号
B 主键同时也是一个索引,通过主键查找数据的速度最快
C 主键如果是整形类型,该类的值可以自动增长
2 插入表
insert into 表名 (字段列表) values (值列表)
insert into student (id,name,age) values (1,'zhangsan',21);
3 查询表项
select *from 表名--》查询整张表
select *from 表名 where id=n--->条件查询 查询id=n的这条数据记录
4 删除表里的内容
delete from 表名 条件
delete from student where id=1
5 修改表表项
update 表名 set 表项=? 条件
update student set age=19 where id=1
7 删除数据库中的一张表
drop table 表名
drop table student
注:一个数据库中可创建多张表,多张表的表名不能重复。
7.2 实现数据库操作的代码
完整例子
struct sqlite3-->描述一个数据库文件的结构
操作sqlite3数据库的接口函数:
sqlite3_open---》创建或打开数据库
sqlite3_exec-->执行SQL语句
sqlite3_close---》关闭数据库
sqlite3_get_table--》查询数据库表 select *from student
网络编程项目linux百度网盘实现局域网文件共享:
https://download.youkuaiyun.com/download/qq_59557128/87399958?spm=1001.2014.3001.5503