IP数据报:最多就1460字节,因为除去了头部
这个图中的ACK 1001,意思是1001之前的数据都收到了,mss,最大的数据上限,8000随便起的
连接的过程:
三次握手:主动发起连接请求端,发送SYN标志位,请求建立连接.携带数据包包号,数据字节数(0),滑动窗口大小
被动接受连接请求端,发送ACK标志位,同时携带SYN请求标志位,携带序号,确认序号,数据字节数(0),滑动窗口大小
主动发起连接请求端,发送ACK标志位,应答服务器连接请求.携带确认序号
客户端发送SYN 500,服务器端接受连接,那么回复ACK501,确认收到,顺便携带ACK700,客户端回复是ACK701,则建立连接,这些都是在内核完成的,体现在用户空间的是accept 和 connect 的返回成功与否
如图,数据传输的第一个1001,一般是按照下图中的服务器端上一个回复的1001开始的,ACK的8001是保险起见,防止上一次的ACK8001没有送到服务器端,不然的话服务器就无法知道是否成功建立起三次握手
确认的过程就是发送一个标志位,然后服务器端回复一个ACK携带数据的终止节点,比如1025(1024),收到就回复ACK2049
断开连接,就是四次挥手:
客户端作为主动的一方,是主动关闭连接的,
半关闭:客户端,将写的缓冲区关闭,读保留,因为有半关闭,所以有四次
挥手是:客户端说,我准备挂电话,不说话了,服务器端说,行,别说话了,然后,服务器又说,我也没有啥想说的了,既然你没有可说的,就结束了吧,客户端说:结束吧,四次挥手就这样结束了,
客户端在收到服务器端说可以允许我不说话的时候,就把写的缓存关了,因为服务器可能还有话说,所以读缓存留着,等服务器也不想说话了,这时候确认不会再读到数据,才把写缓存关了,因为,这个过程就很安全.
四次挥手:主动关闭连接请求端,发送FIN标志位
被动关闭连接请求端,应答ACK标志位 ------半关闭完成
被动关闭连接请求端,发送FIN标志位
主动关闭连接请求端,应答ACK标志位
滑动窗口:
发送给连接请求端.本端的缓冲区大小(实时),保证数据不会丢失.
多进程服务器:
一个进程负责接收,然后创建进程进行连接
伪代码:
- socket
- bind
- listen
- while (1)
{
cfd = accept();
pid = fork();
if(0 == pid)
{
close(lfd) //不需要,关闭
read()
业务处理
write()
}else if(pid > 0)
{
close(cfd);
while(1)
{
waitpid(0, NULL, WNOHANG);
}
continue;
}
}
- (子进程,为了子进程被init接收,父进程只能死,但是为了能处理以后的请求,不能死
,如果使用while 一直waitpid,也是无法处理新的请求,不回收子进程死亡就会变僵尸,占用资源),因此用信号来进行处理
注册信号捕捉函数: SIGCHLD
在回调函数中,完成子进程的回收
while(waitpid())
多线程服务器:
伪代码:
1.socket
2.bind
3.listen
4.while (1)
{cfd = accept
pthread_create(&tid, NULL, tfn, NULL )}
5.也要做相应的回收操作,不回收就会有僵尸线程,
可以两个方式回收,一个是detach分离开来,一个是专门建立一个线程负责将这些线程回收;
(兄弟进程,跨代进程间不能互相回收,兄弟线程可以)
封装socket的接口,将错误处理都放在里面,这样增加主要业务代码的逻辑清晰度
TCP状态:主动发起连接
CLOSE -- 发送SYN -- SEND_SYN -- 接受ACK. SYN -- SEND_SYN -- 发送ACK -- ESTABLISHED
对应的状态图,使用命令netstat -apn 可以进行监控
TCP状态:主动关闭连接:
ESTABLISHED -- 发送FIN -- FIN_WAIT_1 -- 接收ACK -- FIN_WAIT_2 (半关闭) -- 接收端发送FIN -- FIN_WAIT_2(半关闭) -- 回发ACK -- TIME_WAIT(只有主动关闭的连接方才会经历这个状态) -- 等2MSL时长 -- CLOSE
如果服务端先关闭了,再关客户端,客户端就会等回复,就会出现问题,等到这个超时结束
TCP状态:被动接收连接:
CLOSE 很快变成LISTEN
CLOSE -- LISTEN -- 接收SYN -- 发送ACK .SYN -- SYN_RCVD -- 接收ACK (如果收不到,就一直发) -- ESTABLISHED
TCP状态:被动关闭连接:
接收到FIN 发送ACK -- CLOSE_WAIT
ESTABLISHED -- 接收对端发送的 -- 接收FIN -- ESTABLISHED -- 发送ACK -- CLOSE_WAIT(主动关闭连接端处于半关闭) -- 发送FIN -- LAST_ACK
2MSL时长的意义:
如果发送的ACK没到被动关闭端怎么办,所以等一下,如果被动关闭端没有收到这个ACK,会重复发送FIN,但是如果等太久就失去意义了,因此就是共40s
端口复用函数:
setsockopt
定义一个opt = 0/1 是否生效,启用端口复用
端口复用,不用再等待2MSL,用在bind之前,绑定端口之前进行复用,他没有改变那个等待的动作,只是让这个端口可以被复用而已
半关闭:通信双方中只有一端关闭通信,
shutdown,close只是减少描述符的一个引用,引用为0,的时候,就关闭,而shutdown则是直接关闭这个描述符
select:
select负责监听,操作accept仍然交给server
select参数:
原理:借助内核,select监听,客户端连接.数据通信服务
FD_ZERO 清空文件描述符集合
FD_SET 等待监听的文件描述符集合
FD_CLR 文件描述符从集合里面移除
FD_ISSET判断文件描述符是否在集合里面
select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval * timeout)
nfds ..
readfds..
exceptionfds ..
timeout > 0 :设置监听超时时长
NULL: 阻塞监听
0 : 非阻塞监听,轮询
返回值:
> 0 :所有监听集合中,满足对应事件的总数
0: 没有满足监听条件的文件描述符
-1 : error
传入传出参数的实例图片:
思路:
lfd = socket()
bind();
listen();
fd_set rset, allset;
FD_ZERO(&rset);
while(1)
{
rset = allset
ret = select(lfd+1, &rset, NULL,NULL,NULL);
if(ret > 0)
{
if(FD_ISSET(lfd, &rset))
{
cfd = accept();
FD_SET(cfd, &allset);
}
for(i=lfd+1;i<=最大文件描述符;i++)
{
FD_ISSET(i, &rset);
read()
//业务处理
write();
}
}
}
如果读不到sock fd的文件,read返回0,可以当成是网络已经关闭了
select缺点:如果一个client fd 就是1023,那么就得轮询判断
优缺点:
缺点:监听上限:监听上限受问间描述符上限,最大1024
检测满足条件的fd,自己添加业务逻辑提高小,提高编码难度
优点:跨平台,win.linux.macOS,Unix,类unix.mips,
添加自定义的数组,提高select的效率:
通过红色的数组存fd信息,减少循环遍历的时间
在处理读写的fd的时候,如果只有一个读写的fd返回,那么处理完一次之后进行break对fd数组的查询,也可以节省一定的时间
多路IO转换:
select:
poll:(时代的本成品,了解一下)
int poll(struct pollfd *fds, neds_t nfds, int timeout)
fds:监听的文件描述符[数组]
struct pollfd
{
int fd;待监听的文件描述符
short events:待监听的文件描述符对应的监听事件,取值POLLIN,POLLOUT,POLLERR
short revents 传入时是0,如果满足对应事件,返回非0
}
nfds:监听数组的,实际监听个数,
timeout:超时时长
>0:超时时长
-1:阻塞等待
0,不阻塞
返回值:返回满足对应监听事件的个数
poll的示意图:
使用的代码实例:
poll的优缺点:
优点:自带数组结构,可以将 监听事件集合 和 返回事件集合 分离.
拓展监听上限超出1024.
缺点,不能跨平台,只能在linux,类unix平台使用,
epoll
突破1024文件描述符限制:
cat /proc/sys/fs/file-max 查看当前的计算机能打开的最大文件个数,
可以修改 /etc/security/limits.conf 修改
epoll是一个红黑树
操作函数
int epoll_create(int size)
size:创建一个数量,但是这个数量是给内核进行参考的
返回值:成功,指向新创建的红黑树的根节点
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd 创建的返回值
op: 对该监听红黑树所做的操作,
EPOLL_CTL_ADD 添加fd到监听红黑树
EPOLL_CTL_MOD 修改监听事件
EPOLL_CTL_DEL 取消对fd的监听
fd 等待监听的fd
event : 本质struct epoll_event 结构体地址
events:
EPOLLIN/EPOLLOUT/EPOLLERR
data:联合体
int fd; 对应监听事件的fd
void *ptr; 指针
uint32 u32:
uint u64 u64;
返回值:
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 阻塞监听
epfd:..
events:数组,传出参数,满足监听条件的fd结构体,
maxevents:数组的大小,
timeout:
负 0 正
返回值:
>0 满足监听的总个数,可以用作循环上限
0: 没有fd满足监听事件
-1: 错误码
创建根节点,加上一个节点,行为,fd,结构体fd(包含动作,fd),wait行为,数组,还有这个数组的大小,还有阻塞时间
epoll对于多连接,但是只有少量进行发送的情况下,非常适合这个场景
EL和LT模:
缓存区有数据,读走一半,剩下的一半的能触发,叫做LT,水平触发(默认)
不能触发,叫做ET,边缘触发
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文件描述符
缺点: 不能跨平台
epoll反应堆模型
自动回调:反应堆
原来:socket.bind.listen -- epoll_create 创建监听红黑树 -- 返回epfd -- epoll_ctl()向树上添加一个监听fd -- epoll_wait 监听 -- 对应监听fd事件发生 -- 返回监听满足数组. -- 判断返回数组元素 -- lfd满足 --Accept -- cfd满足 -- read() -- 业务 -- write()
反应堆:不但监听cfd的读事件,还要监听cfd的写事件
........--业务处理 -- cfd从监听红黑树摘下 -- EPOLLOUT -- 回调函数 -- epoll_ctl() -- EPOLL_CTL_ADD重新放到红黑树上监听写事件 -- 等待epoll_wait返回 -- 说明可写 -- write
-- cfd从监听红黑树摘下 -- EPOLLIN -- epoll_ctl() -- EPOLL_CTLADD重新放到红黑上监听事件 -- epoll_wait监听
线程池模型:
减少线程创建的时间,提前进行设置
TCP通信和UDP通信各自的优缺点:
TCP:面向连接的,可靠数据报传输.对于不稳定的网络层,采取完全弥补的通信方式.丢包重传
优点:
稳定.数据流量稳定.速度稳定.传递的顺序,
缺点:传输速度慢,效率低,开销大
使用的场景:数据的完整性要求高,不追求效率.大数据传输,文件传输.
UDP:无连接,不可靠的数据报传输,对于不稳定的网络层,采取完全不弥补的方式,默认还原网络状况
优点:传输数据大,效率大,开销小
缺点:不稳定,数据包流量比较差.速度.顺序
使用场景:对时效性要求较高,稳定性其次,游戏.视频会议,视频电话
腾讯.华为.阿里 -- 应用层数据校验,弥补udp的不足
UDP实现的C/S模型:
由于省去了三次握手 那么accept和connect可以省略不计
recv()/send() 只能用于TCP通信
server:
lfd = socket(AF_INET, DGRAM,0);
bind(); ip地址,端口号
listen可有可无
while(1)
{
read被替换 -- recvfrom()
业务处理;
write( )-- 被替换成sento()
}
close()
client:
connfd = socket(AF_INET, SOCK_DGRAM, 0)
sendto(‘服务器地址结构’, 地址结构大小);
recvfrom
写到屏幕
close;
recvfrom:
sockfd:监听的fd
buf:缓冲区地址
len:缓冲区长度
flags:0
src_addr:传出参数,&addr传出,对端的地址结构
addrlen:传入传出参数
返回值:成功就是返回字节数,失败的话就是负1的实现
sendto:
sockfd:..
buf:..
len:..
flags:0
src_addr:传入,目标的地址结构
addrlen:目标地址结构的长度
udp的bind无用 ,端口都用的addr地址结构的