网络编程
网络基础
TCP/UDP通信
IO模型
服务器搭建
广播、组播
网络超时
Unix通信
数据库
项目
点菜宝
医院排队叫号
售票
超市收银
今天的内容
网络基础
网络的历史:战争的产物
分层结构 *****
OSI七层 : 应用层、表示层、会话层、传输层、网络层、数据链路层、物理层
TCP/IP四层:应用层、传输层、网络层、网络接口层
TCP/IP五层:应用层、传输层、网络层、数据链路层、物理层
协议:
应用层:http, DNS, FTP, TFTP, SMTP, telnet
传输层:TCP、UDP
网络层:IP、ICMP、IGMP
网络接口层:ARP
地址:
mac/HWaddr: 硬件地址,一般不可以改。
IP地址:IPV4是32位的数,点分十进制,可改。由网段+主机号构成。
A类:1.0.0.1 ~ 126.255.255.255(政府)
B类:128.0.0.1 ~ 191.255.255.255 (公司)
C类:192.0.0.1 ~ 223.255.255.255 (公用)
D类:224.0.0.1 ~ 239.255.255.255 (多播)
E类:保留地址
广播地址:某个网段内主机号最大的就是广播地址。
子网掩码:IP & mask = 网段 ;IP &(~mask) = 主机号
域名:
www.baidu.com
PORT:16bit,范围:0~65535
http:80, SMTP:23, FTP:20和21, tomcat:8080, TFTP:69, VNC:5800和5900 ... ...
自己的程序推荐端口号>50000
封包和解包
rawData: 表示的是用户想发的消息。
以太网头(协议+目的和源mac地址 ) | 网络层头(协议+目的和源ip地址) | 传输层头(协议+目的和源port) | 应用层头 | rawData | CRC(校验)
小结:
网络是一个分层结构,每一层使用下一层提供内容为上一层进行服务。
网络上的进程进行通信的三要素
IP地址 协议 PORT
TCP协议特点 *****
类比:打电话
建立连接,可靠性高,速度慢。
UDP协议特点 *****
类比:发短信
不需要建立连接,可靠性不高,速度快。
应用:
银行的业务,网络游戏(购买装备,打怪兽),在线看视频,网课
TCP三次握手 *****
建立连接的过程
1. 客户端发送连接请求(拨号)
2. 服务器回应(接电话,说:你好!)
3. 客户端确认连接(说:你好,我是张三)
TCP四次挥手 *****
断开连接的过程
1. 服务器发送断开请求(说:好,今天就聊天这)
2. 客户端回应收到断开请求(说:好的!)
3. 客户端发送断开请求(说:拜拜~)
4. 服务器回应收到断开请求(说:拜拜~)
预备知识
socket
套接字 一种特殊的文件描述符 整型 >=0
分类
流式套接字 (SOCK_STREAM面向连接TCP)
数据报套接字 (SOCK_DGRAM面向无连接UDP)
原始套接字(SOCK_RAW)
字节序
小端序:
低字节存放在低位,比如:intel/AMD
大端序:
高字节存放在低位,比如:ARM/网络
一个整型由四个字节构成,0x0a0b0c0d
tcp通信
掌握通信流程 ***
注意:
bind:一个套接字描述符,只能用一个端口号
UDP通信
服务器 客户端
创建用户数据报套接字 创建用户数据报套接字
绑定自己的ip/port //绑定自己的ip/port INADDR_ANY
通信 通信
关闭套接字 关闭套接字
IO模型
阻塞
例:tcp服务器accept接受连接是阻塞、tcp接收消息是阻塞、UDP接收消息、标准输入
非阻塞
例:标准输出
多路复用
理解select原理
1. 应用程序准备一张保存描述符的表 fd_set readFds;
2. 应用程序把所有想要监听的描述符都放在表里 FD_SET(fd, &readFds);
3. 应用程序调用select(),//此时,会将readFds拷贝一份给内核
4. //内核轮询描述符的表中的每一个描述符,查看哪一个描述符可以进行IO操作。
5. //直到某一个或者某几个描述符可以进行IO操作时,内核将不能操作的描述符从表中删除
6. select()返回//内核将新表返回给应用程序
7. 应用程序轮询描述符的表,如果某个描述符在表中,也就是说它可以进行IO操作,这时操作不会阻塞
问题:
1. 表有多大,最多能放多少个描述符
sizeof( readFds ) = sizeof( fd_set.__fds_bits )
= (1024 / __NFDBITS) * sizeof(long)
= 1024 / 8
= 128(byte)
= 1024(bit)
答:表的大小是128byte,也就是1024bit。最多可以放1024个描述符,0~1023。
//将描述符值对应的bit置1表示在表里,清0表示不在表里。
2. 有两次表的拷贝,多次轮询,如何提交效率?
答:记录当前表中描述符最大值maxFd,只需要轮询0~maxFd这些描述符,总共轮询maxFd+1。
/* fd_set for select */
typedef struct
{
long int __fds_bits[1024 / __NFDBITS];
} fd_set;
__NFDBITS = 8 * sizeof(unsigned long)
信号驱动
周末中午饿了才去吃饭。---饿就是一个信号,吃饭就是一个行为。
//将newID改为非阻塞
int flag = 0;
flag = fcntl( newID, F_GETFL, 0 ); //获取操作方式
flag |= O_NONBLOCK;
fcntl( newID, F_SETFL, flag );//设置新的操作方式
循环服务器
socket + bind + listen
while(1)
{
accept(...);
while(1)
{
recv(...);
send(...);
}
close();
}
close();
总结:
前一个客户端不结束,服务器无法连接下一个客户端。用户体验差。
多路复用
select/epoll/poll
多路复用在哪里用
当一个进程中有多个阻塞时,可以考虑使用多路复用。
例:10086
select原理
1. 首先创建一张保存阻塞描述符的表 fd_set readFds;
2. 把所有准备监听的描述符放在表中 FD_SET( fd, &readFds );
3. 调用select函数,此时,应用程序会将表拷贝到内核
4. 内核轮询表中描述符对应的文件,查看哪个文件可以进行IO操作,
如果有某个或者某几个文件可以进行IO操作时,就会修改表,只保留可以进行IO操作的描述符,
然后,通过select将表返回,保存在readFds
5. 应用程序轮询readFds,判断哪个文件可以进行IO操作,然后进行操作。
select特点
1. 表在用户空间和内核空间来回拷贝
2. 表在用户空间和内核空间进行轮询
3. 表是1024bit的大小,能保存的描述符范围0~1023
4. 为了提高效率,select第一个参数是表中最大描述符加1,轮询的范围0~最大描述符
服务器模型
客户端1可以和服务器通信,客户端2可以和服务器通信,在此中间不需要重启服务器。
1. 循环服务器
TCP循环服务器
创建流式套接字
绑定自己的ip/port
监听 --> 等连接套接字
while (1)
{
接受连接,返回已连接的套接字
while(1)
{
用已连接套接字接收消息
用已连接套接字回复消息
//某些情况下,跳出循环
}
}
关闭套接字(等连接套接字和已连接套接字)
TCP循环服务器
socket + bind + listen
while(1)
{
accept(...);
while(1)
{
recv(...);
send(...);
}
close();
}
close();
小结:
TCP循环服务器一次只能和一个客户端通信,只有前一个客户端通信结束后,才能和下一个客户端通信。 TCP服务器一般很少采用循环服务器模型。
UDP循环服务器
创建数据报套接字
绑定自己的ip/port
while(1)
{
接收消息
回复消息
}
关闭套接字
小结:
UDP循环服务器可以同时和多个客户端通信。
2. 并发服务器
UDP并发服务器
创建数据报套接字
绑定自己的ip/port
创建管道
创建子进程
while (1)//子进程
{
接收消息 recvfrom(来源地址)
把地址放在管道中
把处理结果放在管道中
}
父进程
while(1)
{
从管道中拿出目的地址
从管道中得到处理结果
回复消息 sendto(处理结果,目的地址)
}
关闭套接字
小结:
UDP的并发服务器复杂,效率低,一般不使用。
TCP并发服务器
创建流式套接字
绑定自己的ip/port
监听 --> 等连接套接字
while(1)
{
接受连接,返回已连接的套接字
创建进程/线程来实现通信
while(1) //子进程/子线程
{
用已连接套接字接收消息
用已连接套接字回复消息
}
关闭套接字(已连接套接字)
}
关闭套接字(等连接套接字)
小结:tcp的并发服务器可以实现一次来处理多个客户端通信。
客户端越多,线程/进程会越多,cpu可能会比较卡顿。
3. 多路复用服务器
网络超时
在tcp通信中的accept/recv以及udp通信中的recvfrom是阻塞操作。
如果不设置超时时间,那么,会一直阻塞下去。
如果设置超时时间,那么,在给定时间内等待消息,超时直接返回。
三种办法设置
struct timeval tv = {5, 0} ;//5秒0微秒
1. setsockopt( socketID, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
2. select( maxFd + 1, &readFds, NULL, NULL, &tv );
3. 启动定时器信号,在信号处理结束以后,重启阻塞/结束阻塞
单播:
tcp是一对一的,只能实现单播。
UDP可以实现单播,也可以实现多播。
广播
广播地址:当前网段内,主机号最大的ip地址。
默认,用户数据报套接字不能发广播消息,如果想发,需要进行设置setsockopt
广播通信流程
udp服务器(接收端) udp客户端(发送端)
创建用户数据报套接字 创建用户数据报套接字
绑定自己的ip/port 设置当前套接字可以发送广播消息
等待接收消息 设置接收地址(ip地址是广播地址,port是服务器的port),发送消息
回复消息 接收消息
关闭套接字 关闭套接字
小结:
在同一个局域网内的任意主机都能收到广播消息,这样,有时会造成广播风暴。
组播
只有加入某个多播组的主机才能收到数据。
D类地址(组播地址)
不分网络地址和主机地址,第1字节的前4位固定为1110
224.0.0.1 – 239.255.255.255
组播通信流程
udp服务器(接收消息) udp客户端(发送消息)
创建用户数据报套接字 创建用户数据报套接字
绑定自己的ip/port 设置接收地址(ip地址是组播地址,port是服务器的port),
加入到某个组播组中 发送消息
等待接收消息 等待接收消息
回复消息 关闭套接字
关闭套接字
小结:
组播是加入到组播组的主机都能收到组播消息。
操作数据库的流程
创建并打开数据库
操作数据库
关闭数据库
相关函数
创建并打开数据库
int sqlite3_open(char *path, sqlite3 **db); //FILE * fopen(...);
参数:path: 数据库文件路径
db: [返回]指向sqlite句柄的指针
返回值:成功返回0,失败返回错误码(非零值)
关闭数据库
int sqlite3_close(sqlite3 *db);
返回值:成功返回0,失败返回错误码
执行SQL
int sqlite3_exec(sqlite3 *db, const char *sql, sqlite3_callback callback, void *data, char **errmsg);
参数:
db:数据库句柄
sql:要执行的SQL语句
callback:回调函数,只有执行select查询语句的时候,它才会被调用
data :回调函数的第一个参数
errmsg:错误信息指针的地址 如果执行sql出错,会通过errmsg返回错误信息
返回值:成功返回0,失败返回错误码
释放出错信息
int sqlite3_free(char *errmsg);
返回值:成功返回0,失败返回错误码
不使用回调函数执行SQL语句
int sqlite3_get_table(sqlite3 *db, const char *sql, char ***resultp, int*nrow, int *ncolumn, char **errmsg);
功能:执行SQL操作,查询表
参数:db:数据库句柄
sql:SQL语句
resultp:用来指向sql执行结果的指针
nrow:满足条件的记录的数目
ncolumn:每条记录包含的字段数目
errmsg:错误信息指针的地址
返回值:成功返回0,失败返回错误码
释放查询结果
sqlite3_free_table(char ** ppTable);