目录
一、网络编程
1、协议
一组规则。
2、分层模型结构
OSI七层模型:物、数、网、传、会、表、应
物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
TCP/IP 4层模型:
网络接口层(链路层)、网络层、传输层、应用层(网网传应)
应用层:http、ftp、nfs、ssh、telnet。。。
传输层:tcp、UDP。。
网络层:IP、ICMP、IGMP
链路层:以太网帧协议、ARP
3、c/s模型: client-server
优点:缓存大量数据,协议选择灵活,速度快,
缺点:安全性,跨平台,开发工作量
4、b/s模型:browser-server
优点:安全性,跨平台,开发工作量较小
缺点:不能缓存大量数据、严格遵守http协议
5、网络传输流程:
数据没有封装之前,是不能再网络中传递。
6、以太网帧协议:
ARP协议:根据Ip地址获取mac地址
以太网帧协议:根据mac地址,完成数据包传输
7、IP协议
版本:IPv4、IPv6----4位
TTL:time to live。设置数据包在路由节点中的跳转上限。每经过一个路由节点,该值-1。当减为0的路由,有义务将该数据包丢弃
源IP:32位-----4字节 192.168.1.108 ----点分十进制 IP地址(string)---二进制
目的IP:32位-----4字节
· IP地址: 可以在网络环境中,唯一标识一台主机
端口号:可以在网络的一台主机上,唯一标识一台进程
IP地址+端口号:可以在网络环境中,唯一标识一台进程
8、UDP
16位:源端口号, 2^16=65536
16位:目的端口号
9、IP协议:
16位:源端口号, 2^16=65536
16位:目的端口号
32序号:
32确认序号:
6个标志位
16位窗口大小 2^16=65536
10、网络套接字 socket
一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现)
在通信过程中,套接字一定是成对出现的
11、网络字节序
小端法:(pc本地存储) 高位高地址,低位存低地址。 int a=0x12345678
大端法:(网络存储) 高位低地址,低位存高地址
htonl --》本地 --》网络(IP)
htons --》本地 --》网络(port)
ntohl --》网络 --》本地(IP)
ntohs --》网络 --》本地(port)
12、IP地址转换函数
int inet_pton(int af, const char *src, void *dst) 本地字节序(string Ip)--》网络字节序
af:AF_INET、AF_INER6(指代选择什么协议)
src:传入参数,IP地址(点分十进制)
dst:传输参数,转换后的网络字节序的IP地址
返回值:
成功:返回1
异常:0,说明src指向的不是一个有效的IP地址
const char *inet_ntop(int af, const void *src, char *dst,socklen_t size)网络字节序--》本地(string Ip)
af:AF_INET、AF_INER6(指代选择什么协议)
src:网络字节序IP地址(点分十进制)
dst:转换后的本地字节序的IP地址
size:dst的大小
返回值:
成功:返回dst
失败:NULL
13、sockaddr地址结构
struct sockaddr_in addr;
addr.sin_family=AF_INET/AF_INET6
addr.sin_port=htons(9527)
1、 int dst;
inet_pton(AF_INET, "192.157.22.45", &dst);
addr.sin_addr.s_addr=dst;
2、addr.sin_addr.s_addr=htonl(INADDR_ANY); //自动取出系统中有效的任意IP地址。二进制类型
bind(fd,(struct sockaddr *)addr,size)
14、网络套接字函数
socket模型创建流程图
bind():绑定IP+port
listen():设置监听上限
accept():阻塞直到客户端连接
connect():绑定IP+port
(1)socket函数
#include <sys/socket.h>
int socket(int domain, int type, int protocol) 创建一个套接字
domain:所选用的IP地址协议是什么AF_INET,AF_INET6,AF_UNIX
type:创建套接字所选用的数据传输协议默认是什么:SOCK_STREAM、SOCK_DGRAM
protocol:0
返回值:
成功:新套接字所对应的文件描述符
失败:-1 errno
(2)int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)给socket绑定一个地址结构(IP+port)
sockfd:socket返回值
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(8888);
addr.sin_addr.s_addr=htonl(INADDR_ANY);
addr:(struct sockaddr *)&addr
addrlen:sizoef(addr) 地址结构的大小
返回值:
成功:0
失败:-1,errno
(3)int listen(int sockfd, int backlog); 设置同时与服务器建立连接的上限数(可以同时进行3次握手的客户端数量)
sockfd:socket返回值
backlog:上限数值.最大值为128
返回值:
成功:0
失败:-1,errno
(4)int accept(int sockfd, struct sockaddr *addr, socklen_t addrlen)阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的socket的文件描述符
sockfd:socket返回值
addr:传出参数。成功与服务器建立连接的那个客户端的地址结构(IP+port)
addrlen:传入传出参数 入:addr的大小 出:客户端addr的实际大小
返回值:能与服务器进行数据通信的socket对应的文件描述
(5)int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen)使用现有的socket 与服务器建立连接
sockfd:socket返回值
addr:传出参数。服务器的地址结构
socklen_t addrlen:服务器的地址结构大小
返回值:
成功:0
失败:-1,errno
如果不适用bind()绑定客户端地址结构,采用“隐式绑定”
15、TCP通信流程分析
server:
(1)socket():创建socket
(2)bind():绑定服务器地址结构
(3)listen():设置监听上限
(4)accept():阻塞监听客户端连接
(5)read():读socket获取客户端数据
(6)小写--》大写:toupper()
(7)write(fd)
(8)close();
client:
(1)socket():创建socket
(2)connect():与服务器建立连接
(3)write():写数据到socket
(4)read():读转换后的数据
(5)显示读取结果
(6)close();
16、三次握手协议
主动发起连接请求端,发送SYN标志位,请求建立连接。携带序号、数据字节数(0)、滑动窗口大小
被动接受连接请求端,发送ACK标志位,同时携带SYN请求标志位。携带序号、确认序号、数据字节数(0)、滑动窗口大小
主动发起连接请求端,发送ACK标志位,应答服务器请求连接。携带确认序号。
17、四次挥手
主动关闭连接请求端,发送FIN标志位
被动关闭连接请求端,应答ACK标志位 ------半关闭完成
被动关闭连接请求端,发送FIN标志位
主动关闭连接请求端,应答ACK标志位 ------连接全部关闭
18、滑动窗口
发送给连接对端,本端的缓存区大小(实时),保证数据不会丢失
19、错误处理函数:
封装目的:在server.c编程过程中突出逻辑,将出错处理与逻辑分开,可以直接跳转man手册。
(1)wrap.c
存放网络通信相关常用 自定义函数
命名方式:系统调用函数首字符大写,方便查看man手册。如:listen(),accept()
函数功能:调用系统调用函数,处理出错场景
在server.c和client.c中调用自定义函数
联合编译 server.c和wrap.c生成server
client.c和wrap.c生成client
(2)wrap.h
存放 网络通信相关应用 自定义函数原型(声明)
20、readn、readline:读N个字节,读一行
read函数的返回值:
(1)>0 实际读到的字节数
(2)=0 已经读到结尾(对端已经关闭)
(3)-1 应进一步判断errno的值
errno = EAGAIN or EWOULDBLOCK :设置了非阻塞方式 读:没有数据到达
errno = EINTR 慢速系统调用被中断
errno = “其他情况” 异常
21、多进程并发服务器
(1)socket(); 创建 监听套接字lfd
(2)bind(); 绑定地址结构struct socketaddr_in addr;
(3)listen();
(4)while(1){
cfd=Accept();接收客户端连接请求
pid=fork();
if(pid==0){ 子进程 read(cfd)-----小写--》大写----write(cfd)
close(lfd) 关闭用于建立连接的套接字lfd
read()
小写--》大写
write()
}
else if(pid>0){
close(cfd) 关闭用于与客户端通信的套接字cfd
while(1){
waitpid(0,NULL,WNOHANG);
}
continue;
}}
(5)子进程:
close(lfd)
read()
小写--》大写
write()
父进程:
close(cfd)
注册信号捕捉函数 SIGCHLD
在回调函数中,完成子进程回收
while(1){
waitpid(0,NULL,WNOHANG);
}
22、多线程并发服务器
(1)socket(); 创建 监听套接字lfd
(2)bind(); 绑定地址结构struct socketaddr_in addr;
(3)listen();
(4)while(1){
cfd=Accept(lfd,)
pthread_create(&tid, NULL, tfn, NULL);
pthread_detach();
}
(5)子线程:
void *tfn(void *arg){
close(lfd)
read()
小写--》大写
write(cfd)
pthread_exit((void *)10);
}
23、TCP状态时序图:
1、主动发起连接请求端:
CLOSE--发送SYN --SEND_SYN--接受ACK、SYN--发送ACK-- ESTABLISHED(数据通信态)
2、主动关闭连接请求端
ESTABLISHED(数据通信态)--发送FIN--FIN_WAIE1---接受ACK --FIN_WAIE2(半关闭) --接收到对端发送的FIN-- FIN_WAIE2(半关闭)--回发ACK--TIME_WAIT(只有主动关闭连接方,会经历这种状态)--等2MSL时长--CLOSE
3、被动接收连接请求端
CLOSE -- LIDTEN -- 接收SYN -- LIDTEN -- 发送ACK、SYN -- SYN_RCVD -- 接受ACK -- ESTABLISHED(数据通信态)
4、被动关闭连接请求端:
ESTABLISHED(数据通信态)-- 接受FIN-- ESTABLISHED(数据通信态)-- 发送ACK -- CLOSE_WAIT(说明对端【主动关闭连接端】处于半关闭状态)-- 发送FIN -- LAST_ACK
24、2MSL时长
一定出现在【主动关闭连接请求端】 -- TIME WAIT
保证,最后一个ACK能成功被对端接收。(等待期间,对端没收到我发的ACK,对端会再次发送FIN请求)
25、 端口复用
int opt = 0/1; // 设置端口复用
setsockopt(lfd,SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt))
26、半关闭
通信双方中,只有一端关闭通信 ---FIN_WAIT_2
close(cfd);
shutdown(int fd, int how)
how: shut_RD:关读端
shut_WR:关写端
shut_RDWR:关读写
27、select多路IO转接:
原理:借助内核,select来监听 ,客户端连接、数据通信事件
(1) void FD_ZERD (fd_set *set):--------清空一个文件描述符集合
fd_set rset;
FD_ZERD(&rset);
(2)void FD_SET(int fd, fd_set *set); 将待监听的文件描述符,添加到监听集合中
FD_SET(3,&rset); FD_SET(5,&rset);
(3)void FD_CLR(int fd, fd_set *set); 讲一个文件描述符从监听集合中移除
FD_SET(3,&rset);
(4)void FD_ISSET(int fd, fd_set *set); 判断一个文件描述符是否在监听集合中
FD_SET(3,&rset);
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *, struct timeval *timeout)
nfds:监听的所有文件描述符中,最大的文件描述符+1
readfds:读 文件描述符监听集合 · 传入、传出参数
writefds:写 文件描述符监听集合 传入、传出参数 NULL
exceptfds: 异常 文件描述符监听集合 传入、传出参数 NULL
timeout: >0:设置监听超时时长
NULL:阻塞监听
0:非阻塞监听,轮询
返回值:
>0: 所有监听集合(3个)中,满足对应事件的总数
0: 没有满足监听条件的文件描述符
-1: errno
思路分析:
lfd=socket();创建套接字
bind();绑定地址结构
listen();设置监听上限
fd_set rset,allset;创建r监听集合,所有集合
void FD_ZERD (&allset);将集合清空
FD_SET(lfd,&allset)将lfd添加至读集合中
while(1){
rset=allset; 保存监听集合
set=select(lfd+1,&rset,NULL,NULL,NULL)监听文件描述符集合对应事件
if(ret>0){ 有监听的描述符满足对应事件
if(FD_ISSET(lfd,&rset)) //1在,0不在
{
cfd=accept(); 建立连接,返回用于通信的文件描述符
FD_SET(cfd,&allset) 添加到监听通信描述符集合中
}
for(i=0;i<=最大文件描述符;i++){
FD_ISSET(i,&rset) 有read、write事件
read();
小--大
write();
}
}
}
28、select优缺点
缺点:监听上限受文件描述符限制。最大1024
检测满足条件的id,自己添加业务逻辑提高小。提高了编码难度
优点:扩平台。win、linux、macOS、Unix、类Unix
29、多路IO转换
select:
30、poll:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds:监听的文件描述符(数组)
struct pollfd{
int fd; 待监听的文件描述符
short events; 待监听的文件描述符对应的监听事件
取值:POLLIN、POLLOUT、POLLERR
short revents:传入时,给0。如果满足对应事件的话,返回非0--》POLLIN、POLLOUT、POLLERR
}
nfds:监听数组的,实际有效监听个数
timeout:>0 超时时长,单位ms
-1: 阻塞等待
0:不阻塞
返回值:返回满足对应文件监听事件的文件描述符的总个数
优点:
自带数组结构。可以将监听事件和返回事件集合分离
拓展监听上限。超出1024限制
缺点:
不能跨平台。只能在linux、类linux下使用
无法直接定位满足监听事件的文件描述符。监听难度大
31、read函数返回值:
>0:实际读到的字节数
=0:socket中,表示对端关闭。close()
-1:如果errno=EINIR 被异常终端。需要重启
如果errno=EAGIN或EWOULDBLOCK 以非阻塞方式读数据,但是没有数据。需要再次读
如果errno==ECONNRSET说明连接被重置。需要close(),移除监听队列
错误
32、突破1024文件描述符限制:
cat /proc/sys/fs/file-max -->当前计算机所能打开的最大的文件个数。受硬件影响。
ulimit -a:-->当前用户下的进程。默认打开文件描述符个数。缺省为1024
修改:
打开sudo vi /etc/security/limits.conf. 写入
*soft nofile 65535 -->设置默认值,可以直接借助命令修改【注销用户,使其生效】
*hard nofile 10000 -->命令修改上限
33、epoll
int epoll_create(int size) 创建一颗监听红黑树
size:创建的红黑树的监听节点数量(仅供内核参考)
返回值:指向新创建的红黑树的根节点的fd
失败:-1,errno
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);操作监听红黑树
epfd:epoll_create函数的返回值。epfd
op:对该监听红黑树所做的操作
EPOLL_CTL_ADD:添加fd到监听红黑树
EPOLL_CTL_MOD:修改fd在监听红黑树上的监听事件
EPOLL_CTL_DEL 将一个fd从监听红黑树上摘下(取消监听)
fd: 待监听的fd
event:本质struct epoll_event结构体 地址
events:EPOLLIN/EPOLLOUT/EPOLLERR
data: 联合体
int fd:对应监听事件的fd
void *ptr;
uint32_t u32;
uint64_t u64;
返回值:成功0;失败:-1,errno
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout); 阻塞监听
epfd:epoll_create函数的返回值。
events:传出参数。【数组】。满足监听条件的fd的结构体。
maxevents:数组元素的总个数。1024 struct epoll_event events[1024]
timeout:>0 超时时长,单位ms
-1: 阻塞等待
0:不阻塞
返回值:>0:满足监听的总个数,可以用作循环上限。
0:没有fd满足监听事件
-1:失败,errno
34、epoll实现多路IO转接思路:
lfd=socket(); 监听连接事件
bind();
listen();
int epfd= epoll_create(); epfd监听红黑树的树根
struct epoll_event tep, ep[1024]; tep:用来设置单个fd属性,ep是epoll_wait()传出的满足监听事件的数组
tep.events=EPOLLIN;
tep.data.fd=lfd;
epoll_ctl( epfd,EPOLL_CTL_ADD,lfd,&tep); 将lfd添加到监听红黑树上
while(1){
ret= epoll_wait(epfd,ep,1024,-1); 实施监听
for(i=0;i<ret;i++){
if(ep[i].data.fd==lfd) //lfd满足读事件,有新的客户端发起连接请求
{
cfd=Accept();
tep.events=EPOLLIN; 初始化cfd的监听属性
tep.data.fd=cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD,cfd,&tep)
}else{ cfd们满足读事件,有客户端写数据来
n=read();
if(n==0){
close();
epoll_ctl(epfd, EPOLL_CTL_ADD,cfd,&tep);将关闭的fd,从监听树上摘下
}
}else if{
小-->大
}
}
}
35、epoll事件模型
ET模式:边缘触发
缓冲区剩余未读尽的数据不会导致epoll_wait 返回。新的事件满足,才会出发
struct epoll_event event;
event.events=EPOLLIN | EPOLLET
LT模式: 水平触发-->默认采用模式
缓冲区剩余未读尽的数据会导致 epoll_wait返回
结论:epoll的ET模式,高效模式,但是只支持非阻塞模式
struct epoll_event event;
event.events =EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event);
int flg=fcntl(cfd, F_GETFL);
flg |= O_NONBLOCK;
fcntl(cfd, F_SETFL,flg);
优点:高效。突破1024文件描述符
缺点:不能跨平台。Linux
36、反应堆模型
epoll ET模式+非阻塞+void *ptr
原来:socket、bind、liisten -- epoll_creat 创建监听红黑树 --返回 epfd--epoll_ctl()向树上添加一个监听fd -- while(1) -- epoll_wait监听 -- 对应监听fd有事件产生 -- 返回监听满足数组。 -- 判断返回数组个数 -- lfd满足 -- Accept -- cfd满足 -- read()-- 小-》大 -- write回去
反应堆:不但要监听cfd的读事件,还要监听cfd的写事件
socket、bind、liisten -- epoll_creat 创建监听红黑树 --返回 epfd--epoll_ctl()向树上添加一个监听fd -- while(1) -- epoll_wait监听 -- 对应监听fd有事件产生 -- 返回监听满足数组。 -- 判断返回数组个数 -- lfd满足 -- Accept -- cfd满足 -- read()-- 小-》大 --cfd从监听红黑树上摘下-- EPOLLOUT -- 回调函数 -- epoll_ctl()-- EPOLL_CTL_ADD 重新放到红黑树上监听写事件 -- 等待epoll_wait()返回 -- 说明cfd可写 -- write回去 -- cfd从监听红黑树上摘下-- EPOLLIN -- epoll_ctl()-- EPOLL_CTL_ADD 重新放到红黑树上监听读事件 -- epoll_wait监听
37、eventset函数:
设置回调函数。 lfd-->acceptconn()
cfd--》recvdata()
cfd--》senddata()
38、 eventadd函数:
讲一个fd,添加到监听红黑树。设置监听read函数,还是监听写事件
网络编程中:read -- recv()
write -- send()
39、 struct threadpool_t{
pthread_mutex_t lock; 用于锁住本结构体 、 、
pthread_mutex_t thread_counter 记录忙状态线程个数的锁
pthread_cond_t queue_not_full; 当任务队列满时,添加任务的线程阻塞,等待此条件变量
pthread_cond_t queue_not_empty; 当任务队列不为空时,通知等待任务的线程
pthread_t *threads; 存放线程池中每个线程的tid
pthread_t adjust_tid; 存管理线程tid;
threadpool_task_t *task_queue 任务队列(数组首地址)
int min_thr_num; 线程池最小线程数
int max_thr_num; 线程池最大线程数
int live_thr_num; 当前存活线程个数
int busy_thr_num; 忙状态线程个数
int wait_exit_thr_num; 要销毁的线程个数
int queue_front; task_queue队头下标
int queue_rear; task_queue队尾下标
int queue_size; task_queue队中实际任务数
int queue_max_size; task_queue队列可容纳任务数上限
int shutdown; 标志位,线程池使用状态,true或false
}
typed struct{
void *(*function)(void *) 函数指针,回调函数
void *arg; 上面函数的参数
}threadpool task_t 各子线程任务结构体
40、线程池模块分析
(1)main()
创建线程池,向线程池中添加任务。借助回调处理任务。
销毁线程池
(2)pthreadpool_create()
创建线程池结构体指针
初始化线程池结构体(N个成员变量)
创建N个任务线程
创建1个管理者线程
失败时,销毁开辟的所有空间(释放)
(3)threadpool_thread()
进入子线程回调函数
接收参数void *arg --》pool结构体
加锁--》lock--》整个结构体锁
端条件变量--》wait------170
(4)adjust_thread()
循环10s执行一次
进入管理者线程回调函数
接收参数void *arg --》pool结构体
加锁--》lock--》整个结构体锁
获取管理线程池要用到的变量。 task_num. live_num, busy_num
根据既定算法,使用上述3变量,判断是否应该被创建,销毁线程池中指定步长的线程
(5)threadpool_add()
总功能:
模拟产生任务。 num{20]
设置回调函数,处理任务。sleep(1)代表处理完成
内部实现:
加锁;
初始化任务结构体。 回调函数function,arg
利用环形队列机制,实现添加任务。借助队尾指针挪移%实现
唤醒阻塞在条件变量上的进程
解锁
(6)从(3)中的wait之后继续执行,处理任务。
加锁;
获取任务处理回调函数及参数
利用环形队列机制,实现添加任务。借助队尾指针挪移%实现
唤醒阻塞在条件变量上的server
解锁
加锁
改忙线程数++
解锁
执行处理任务的线程
加锁
改忙线程数--
解锁
(7)创建销毁进程
管理者根据task_num. live_num, busy_num
根据既定算法,使用上述3变量,判断是否应该被创建,销毁线程池中指定步长的线程
如果满足创建条件
pthreadpool_create();回调任务线程函数 live_num++
如果满足销毁条件
wait_exit_thr_num =10;
signal给阻塞在条件变量上的进程发送假条件满足信号
跳转至 --170 wait阻塞进程会被假信号唤醒。
判断:wait_exit_thr_num >0 pthread_exit()
41、TCP和UDP通信各自的优缺点
TCP:面向连接的 ,可靠数据包传输。对于不稳定的网络层,采用完全你补的通信方式。丢包重传。
优点: 稳定。数据流量稳定、速度稳定、顺序
缺点:传输速度慢。相率低,开销大
使用场景:数据的完整型要求较高,不追求效率
大数据传输、文件传输
UDP:无连接的,不可靠的数据报传递。对于不稳定的网络层,采取完全不弥补的通信方式,默认还原网络状况。
优点:传输速度快。相率高。开销小。
缺点:不稳定。 数据流量、速度、顺序
使用场景:对时效性要求高的场合。稳定性其次
游戏、视频会议、视频电话
腾讯、华为、阿里--应用层数据校验协议,弥补udp的不足
42、UDP实现的C/S模型
recv()/send()只能用于TCP通信。替代 read、write
accept();-----Connect()---被舍弃
server:
lfd=socket(AF_INET,STREAM,0) ; SOCK_DGRAM---报式协议
bind();
listen();---可有可无
while(1){
read(cfd,buf,sizeof)--被替换--recvfrom()--涵盖accept传出地址结构
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *scr_addr, socklen_t *addrlen)
sockfd:套接字
buf:缓冲区地址
len:缓冲区大小
flags:0
scr_addr:( struct sockaddr *)&addr传出。对端地址结构
addrlen:传入传出
返回值:成功接收数据字节数。失败:-1.errno。 0:对端关闭
小--》大
write()---被替换 --sendto()--connect
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *scr_addr, socklen_t *addrlen)
sockfd:套接字
buf:存储数据的缓冲区
len:数据长度
flags:0
scr_addr:( struct sockaddr *)&addr传入。目标地址结构
addrlen:地址结构长度
返回值:成功写出数据字节数。失败:-1.errno。 0:对端关闭
}
close();
client:
connfd=socket(AF_INET,SOCK_DGRAM,0);
sendto(“服务器地址结构”,地址数据大小)
recvfrom()
写到屏幕
close()
43、本地套接字
IPC:pipe、fifo、mmap、信号、本地套(domain)-- CS模型
对比网络编程TCP C/S模型,注意以下几点:
1、int socket(int domain,int type,int protocol);
参数domain:AF_INET --》AF_UNIX/AF_LOCAL
type:SOCK_STREAM/SOCK_DGRAM 都可以
2、地址结构 sockaddr_in --> sockaddr_un
struct sockaddr_in srv_addr;-->struct sockaddr_un srv_addr;
srv_addr.sin_family=AT_INET --> srv_addr.sin_family= AF_UNIX;
srv_addr.sin_port=htons(8888);
strcpy(srv_addr.sun_path,"srv.socket");
srv_addr.sin_addr.s_addr=htons(INADDR_ANY);
len=offsetof(struct sockaddr_un, sun_path)+ strlen("srv.socket");
bind(fd,(struct sockaddr *)&srv_addr, sizeof(srv_addr));
--> bind(fd,(struct sockaddr *)&srv_addr, len);
3、bind()函数调用成功,会创建一个socket,因此为保证bind成功,通常我们在bind之前,可以使用unlink("srv.socket")
4、客户端不能依赖隐式绑定。并且应该在通信建立过程中,创建且初始化两个地址结构
(1)client_addr -->bind()
(2)server_addr -->connect()
44、对比本地套接字和网络套接字
45、libevent库
开源、精简、跨平台(所有平台)、专注于网络通信
46、源码包安装:
参考readme
47、特性:
基于“事件”异步通信模型---回调
48、libevent框架:
1、创建event_base (乐高底座)
struct event_base *event_base_new(void)
struct event_base *base =event_base_new();
2、创建事件event
常规事件event --> event_new();
bufferevent --> bufferevent_socket_new();
3、将事件添加到base上
int event_add(struct event *ev,const struct timeval *v)
4、循环监听事件满足
int event_base_dispatch(struct event_base *base);
5、释放event_base
event_base_free(base)
49、创建事件event
struct event *ev;
struct event *event_new(struct event_base *base,evutil_socket_t fd,short what, event_callback_fn cb,void *arg);
base:event_base_new()的返回值
fd:绑定到event上的文件描述符
what:对应事件(r、w、e)
EV_READ 一次读事件
EV_WRITE 一次写事件
EV_PERSIST 持续触发。结合event_base_dispath函数使用。生效。
cb:一旦事件满足监听条件,回调的函数。
typedef void(*event_callback_fn )(evutil_socket_t fd, short, void *)
arg:回调的函数的参数
返回值:成功创建的event
50、添加事件到event_base
int event_add(struct event *ev,const struct timeval *tv)
ev:event_new()的返回值
tv:NULL
51、从event_base上摘下事件【了解】
int event_del(struct event *ev)
ev:event_new()的返回值
52、销毁事件
int event_base_free(struct event *ev)
ev:event_new()的返回值
53、未决和非未决
非未决:没有资格被处理
未决:有资格被处理,但尚未被处理
event_new---》event ---》非未决 --》event_add --》未决--》dispatch()&&监听事件呗触发--》激活态--》执行回调函数--》处理态 ---》非未决 event_add && EV_PERSIST --》未决--》event_del()--》非未决
54、 带缓冲区的事件bufferevent
#include<event2/bufferevent.h>
55、创建、销毁bufferevent
struct bufferevent *ev;
struct bufferevent *bufferevent_socket_new(struct event_base *base,evutil_socket_t fd, enum bufferevent_options options);
base: event_base
fd:封装到bufferevent内的fd
options:BEV_OPT_CLOSE_ON_FREE
返回:成功创建的bufferevent事件对象
void bufferevent_socket_free(struct bufferevent *ev)
56、给bufferevent设置回调
对比event: event_new(fd,callback);
event_add()--挂到event_base上
bufferevent_socket_new(fd) bufferevent_setcb(callback)
void bufferevent_setcb(struct bufferevent *bufev, bufferevent_data_cb readcb, bufferevent_data_cb writecb, void *cbarg)
bufev: bufferevent_socket_new()返回值
readcb:设置bufferevent读缓冲,对应回调readcb(bufferevent_read()读数据)
writecb:设置bufferevent写缓冲,对应回调writecb--给调用者,发送写成功通知。
eventcb:设置事件回调。也可以为NULL
typed void(*bufferevent_event_cb)(struct bufferevent *bev,short events, void *ctx)
void event_cb(struct bufferevent *bev,short events, void *ctx)
{
...
}
events:BEV_EVENT_CONNECTED
cbarg:上述回调函数使用的参数
read回调参数类型:
typed void(*bufferevent_data_cb)(struct bufferevent *bev, void *ctx)
void event_cb(struct bufferevent *bev,short events, void *ctx)
{
。。
bufferevent_read()---read();
}
bufferevent_read()函数的原型():
size_t bufferevent_read(struct bufferevent *bev, void *buf,size_t bufsize)
write回调函数类型:
int bufferevent_writestruct bufferevent *bev,const void *data,size_t size)
57、启动、关闭bufferevent的缓冲区
void bufferevent_enable(struct bufferevent *befev,short events) 启动
events:EV_READ、EV_WRITE、EV_READ|EV_WRITE
默认、write缓冲是enable、read缓冲是disable
bufferevent_enable(evev,EV_READ);---开启读缓冲
58、连接客户端
socket();connect();
int bufferevent_socket_connect(struct bufferevent *bev,struct sockaddr *address, int addrlen)
bev:bufferevent事件对象(封装了fd)
address、len:等同于connect()
59、创建监听服务器
---socket();bind();listen();accept()
struct evconnlistener *listener
struct evconnlistener *evconnlistener_new_bind(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, const struct sockaddr *sa, int socklen);
base:event_base
cb:回调函数。一旦被回调,说明在其内部应该与客户端完成,数据读写操作,进行通信。
ptr:回调函数的参数
flags:LEV_OPT_CLOSE_ON_FREE |LEV_OPT_REUSEABLE
backlog:listen()
sa:服务器自己的地址结构体
socklen:服务器自己的地址结构体大小
返回值:成功创建的监听器
60、释放监听服务器
void evconnlistener_free(struct evconnlistener *lev);
61、服务器端libevent创建TCP连接
1、创建event_base
2、创建bufferevent事件对象。bufferevent_socket_new()
3、使用bufferevent_setcb()函数给bufferevent的read、write、event设置回调函数
4、当监听事件满足时,read_cb会被调用,在其内部bufferevent_read()读
5、使用evconnlistener_new_bind创建监听服务器。设置其回调函数,当有客户端成功连接时,这个回调函数会被调用。
6、封装listener_cb()在函数内部。完成与客户端通信
7、设置读缓冲、写缓冲的使能状态enable、disable
8、启动循环event_base_dispatch();
9、释放连接
62、请求协议 -- 浏览器组织,发送
63、应答协议:
1、getline()获取http协议的第一行
2、从首行拆分出GET、文件名、协议版本。获取用户请求的文件名
3、判断文件是否存在。stat()
4、 判断是文件还是目录。
5、是文件 --open -- read --写回给浏览器
6、先写http应答协议头:http/1.1 200 ok
Content-Type: text/plain;charset=iso-8859-1
7、写文件数据