1. TCP协议的状态机

TCP一共定义了11种状态,这些状态可以使用 netstat 命令查看

@左耳朵耗子 tcp系列教程: 上篇 下篇

2. TCP建立连接3次握手、释放连接4次握手

TCP包头有4个非常重要的东西:

(1) Sequence Number:包的序列号,用来解决 网路包乱序的问题

(2) Acknowledge Number:ACK确认号,用来实现 超时重传机制(不丢包)

(3) Window:滑动窗口,用来解决 拥塞控制的

(4) TCP flag:包的类型,主要是用来 操控 TCP状态机的

TCP建立连接:三次握手

主要是 初始化 Sequence Number的初始值

通信的双方要互相通知对方自己的初始化的Sequence Number,这个号作为以后的数据通信的序号,以保证应用层接收到的数据不会因为网络上的传输的问题而乱序

SYN超时:server端接收到client发的SYN后 发送了 SYN-ACK之后,此时 client掉线,server端没有收到client回送的ACK,这时连接处于中间状态,既没成功,也没失败,这时,server端每隔一段时间会重发SYN-ACK,Linux下默认重发次数为5,时间间隔依次位1s、2s、4s、8s、16s,第5次发送之后需要等 32s才知道 第5次也超时了,所以,总共需要 1 + 2 + 4 + 8 + 16 + 32 = 2 ^ 6 - 1 = 63s,这时TCP才会彻底断开连接

SYN Flood攻击:人为发起多个连接,在client 收到 server发送的 SYN-ACK之后 恶意掉线,这时服务器默认需要等待63s才断开连接,攻击者就可以把服务器的syn连接的队列耗尽,让正常的连接请求得不到处理。 解决办法:调整TCP参数,减少默认的重试次数、增大SYN队列连接数

TCP释放连接:四次握手

TCP是全双工的,发送发和接收方都需要FIN和ACK

如果server和client同时断开连接,就会进入CLOSING状态,然后到达TIME_WAIT状态

TIME_WAIT状态

为什么有TIME_WAIT状态?

这个状态是主动执行关闭的话会经历的状态,在这个状态停留时间 是最长分节生命期(maximum segment liftime,MSL)的两倍,我们称为2MSL.MSL意思是任何一个IP数据报可能停留在网络中存活 的最长时间,这个时间是一个有限值,不同系统设置不同。RFC建议值是2min,而BSD的传统实现是30s.

TIME_WAIT状态存在有两个理由:

(1) 可靠地实现TCP全双工连接终止

TIME_WAIT确保有足够的时间让 对端接收到了ACK,如果被动关闭的一方 没有收到ACK,就会触发 被动端重发FIN,一来一去正好2个MSL

(2) 允许老的重复分组在网络中消失

假设A->B发送一个分节,中途由于路由器出现故障而缓存在路由器中,A超时重发之后,连接关闭。现在AB又同时使用相同的IP和端口并且 分节序列号也正好匹配的话,那么以前连接丢失的分组就会出现在新的连接而被处理,TIME_WAIT状态 不允许2MSL之内使用相同的端口连接,就不会出现老分组出现在新连接上了

关于TIME_WAIT数量太多?

如果服务器是HTTP服务器,那么设置一个HTTP的 KeepAlive(浏览器会重用一个TCP连接来处理多个HTTP请求)

3. 套接字socket编程

/* rio_readn -robustly read n bytes (unbuffered) */
int rio_readn(int fd, void* usrbuf, size_t n)
{
size_t nleft = n;
int nread = ;
char* bufp = (char*)usrbuf; while(nleft > ) {
nread = read(fd, bufp, nleft);
if (nread < ) {
if(errno == EINTR) { /* interrupted by sig handler return */
nread = ; /* and call read() again */
} else {
return -; /* errno set by read() */
}
} else if (nread == ) {
break; /* EOF */
}
nleft -= nread;
bufp += nread;
}
return (n - nleft); /* return >= 0 */
}
/* rio_writen -robustly write n bytes (unbuffered) */
int rio_writen(int fd, void* usrbuf, size_t n)
{
size_t nleft = n;
int nwrite = ;
char* bufp = (char*)usrbuf;
while(nleft > ) {
nwrite = write(fd, bufp, nleft);
if (nwrite <= ) {
if(errno == EINTR) { /* interrupted by sig handler return */
nwrite = ; /* and call write() again */
} else {
return -; /* errno set by write() */
}
}
nleft -= nwrite;
bufp += nwrite;
}
return (n - nleft);
}

4. 浏览器输入网址的背后

当你输入一个网址的时候,实际会发生什么?

5. Unix I/O模型 阻塞I/O和非阻塞I/O

Unix一共有5中I/O模型

(1) 阻塞式I/O:进程read系统调用,一直阻塞到 内核将数据准备好并成功返回。默认情况下,所有套接字调用都是阻塞的(read、write、connect、accept)

(2) 非阻塞式I/O:进程反复调用read(轮询),如果没有数据准备好,立即返回一个EWOULDBLOCK错误。fcntl函数将默认套接字转换为 non-blocking

(3) I/O多路复用:进程阻塞于select调用,等待可能多个套接字中的任一个变为可读

(4) 信号驱动式I/O:SIGIO

(5) 异步I/O:(POSIX aio_系列函数)

知乎关于 阻塞/非阻塞、异步同步的讨论

Linux 网络I/O模型

6. 非阻塞I/O

套接字默认状态是阻塞的,可能阻塞的套接字调用可分为以下4类:

(1) 读操作:read、readv、recv、recvfrom、recvmsg

这些函数如果对 阻塞的套接字进行调用,如果该套接字的接收缓冲区中没有数据可读,该进程将投入睡眠(即阻塞),直到有数据可读时才唤醒

这些函数如果对 非阻塞套接字进行调用,如果该套接字的接收缓冲区中没有数据可读,相应调用立即返回一个 EWOULDBLOCK错误

(2) 写操作:write、writev、send、sendto、sendmsg

这些函数如果对 阻塞的套接字进行调用,如果该套接字的发送缓冲区中没有剩余空间可写,该进程将投入睡眠(即阻塞),直到有剩余空间可写时才唤醒

这些函数如果对 非阻塞套接字进行调用,如果该套接字的发送缓冲区中没有剩余空间可写,相应调用立即返回一个 EWOULDBLOCK错误

(3) accept函数

如果对 阻塞套接字进行调用,并且尚无新的连接到达,调用进程将投入睡眠

如果对 非阻塞套接字进行调用,并且尚无新的连接到达,accept调用会立即返回一个 EWOULDBLOCK错误

(4) connect函数

如果对 非阻塞套接字调用 connect函数,并且连接不能立即建立,那么连接的建立能照常发起,不过会返回一个 EINPROGRESS错误

int connect_nonb(int sockfd, const struct sockaddr* addr, socklen_t addrlen, int nsec)
{
int flags = fcntl(sockfd, F_GETFL, );
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); // fcntl设置非阻塞 int error = ;
int ret = connect(sockfd, addr, addrlen);
if (ret < ) {
if (errno != EINPROGRESS) { // 期望的错误是EINPROGRESS,表示连接建立已经启动但是 尚未完成,其他错误一律直接返回-1
return -;
}
} /* Do whatever we want while the connect is taking place. */
if (ret == ) { // 非阻塞connect返回0,表示连接建立完成
fcntl(sockfd, F_SETFL, flags); // 恢复套接字的文件标志并返回
if (error != ) { // 如果getsockopt返回的error变量非0,表示连接建立发生错误
close(sockfd);
errno = error;
return -;
}
return ;
} else { // 非阻塞connect,连接建立已经启动但是尚未完成,调用select等待套接字变为可读或可写
fd_set read_set;
fd_set write_set;
FD_ZERO(&read_set);
FD_SET(sockfd, &read_set);
write_set = read_set; struct timeval tval; // 设置select超时时间
tval.tv_sec = nsec;
tval.tv_usec = ;
ret = select(sockfd + , &read_set, &write_set, NULL, nsec ? &tval : NULL);
if (ret == ) { // select返回0,超时,关闭套接字
close(sockfd);
errno = ETIMEDOUT;
return -;
} // 如果描述符变为可读或可写,调用getsockopt获取套接字的待处理错误(SO_ERROR选项),如果连接成功建立,error值为0,如果连接建立发生错误,error = errno
if (FD_ISSET(sockfd, &read_set) || FD_ISSET(sockfd, &write_set)) {
len = sizeof(error);
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < ) {
return -;
}
} else {
err_quit("select error:sockfd not set");
}
}
}

7. I/O多路复用 select vs. epoll

select vs. epoll

当我们执行epoll_ctl时,除了把socket放到epoll文件系统里file对象对应的红黑树上之外,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里

(1) 进程打开的最大描述符数目

select中 一个进程打开的最大描述符数目由FD_SETSIZE设置,32位默认为1024个(硬编码)

epoll没有FD_SETSIZE的限制,它所支持的FD上限是最大可以打开文件的数目,1GB内存大约10W

(2) FD集合扫描

select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降

epoll每次调用只扫描 "活跃"的socket(一般情况下,任一时间只有部分的socket是"活跃"的),这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的

(3) 内核与用户空间的消息传递

无论是select,poll还是epoll都需要内核把FD消息通知给用户空间

select和poll直接采用 内存拷贝

epoll使用mmap内存共享,避免内存拷贝

 for( ; ; )
{
nfds = epoll_wait(epfd, events, , );
for(i = ;i < nfds; ++i)
{
if(events[i].data.fd == listenfd) //有新的连接
{
connfd = accept(listenfd, (sockaddr *)&clientaddr, &clilen); //accept这个连接
ev.data.fd = connfd;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD, connfd, &ev); //将新的fd添加到epoll的监听队列中
} else if( events[i].events & EPOLLIN ) //接收到数据,读socket
{
n = read(sockfd, line, MAXLINE)) < //读
ev.data.ptr = md; //md为自定义类型,添加数据
ev.events = EPOLLOUT | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓
}
else if(events[i].events & EPOLLOUT) //有数据待发送,写socket
{
struct myepoll_data* md = (myepoll_data*)events[i].data.ptr; //取数据
sockfd = md->fd;
send(sockfd, md->ptr, strlen((char*)md->ptr), ); //发送数据
ev.data.fd = sockfd;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev); //修改标识符,等待下一个循环时接收数据
}
else
{
//其他的处理
}
}
}

我要好offer之 网络大总结的更多相关文章

  1. 我要好offer之 二叉树大总结

    一. 二叉树定义 二叉树具有天然的递归特性,凡是二叉树相关题,首先应该联想到递归 struct BinTreeNode { BinTreeNode* left; BinTreeNode* right; ...

  2. 我要好offer之 搜索算法大总结

    1. 二分搜索 详见笔者博文:二分搜索的那些事儿,非常全面 2. 矩阵二分搜索 (1) 矩阵每行递增,且下一行第一个元素大于上一个最后一个元素 (2) 矩阵每行递增,且每列也递增 3. DFS 深度优 ...

  3. 我要好offer之 C++大总结

    0. Google C++编程规范 英文版:http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml 中文版:http://zh-g ...

  4. 我要好offer之 链表大总结

    单链表是一种递归结构,可以将单链表看作特殊的二叉树(我把它叫做一叉树) 单链表的定义: /** * Definition for singly-linked list. * struct ListNo ...

  5. 我要好offer之 系统基础大总结

    1. APUE Unix环境高级编程 (1) Unix基础知识: 内核->系统调用->shell和库函数->应用软件 (2) 文件I/O:read函数返回值.进程的文件描述符表.文件 ...

  6. 我要好offer之 概率题大总结

    1. 利用等概率Rand5生成等概率Rand3 Rand5生成等概率Rand3 这个题目可以扩展为:利用等概率RandM生成等概率RandN (M > N) 这里,我们首先明白一个简单的知识点: ...

  7. 我要好offer之 排序算法大总结

    1. 插入排序 (1) 直接插入排序 void StraightInsertionSort(std::vector<int>& num) { || num.size() == ) ...

  8. 我要好offer之 字符串相关大总结

    1. str*系列手写代码 a. 一定要注意末尾'\0'的处理,切记切记 b. 一定要对输入做有效性判断,多用断言就是了 int Strlen(const char* str) { assert(st ...

  9. Virtual Private Cloud 专有网络 软件定义网络的方式 私有网络 大流量视频、直播类业务

    私有网络 VPC_云上网络空间_自定义网络 - 腾讯云 https://cloud.tencent.com/product/vpc 私有网络 VPC 简介 私有网络(Virtual Private C ...

随机推荐

  1. 天坑之mysql乱码问题以及mysql重启出现1067的错误解决

    相信很多小伙伴都遇到过数据库中文乱码问题,很头疼,明明Navicat上的编码格式都是utf-8是一样的啊? 为什么还是乱码? 原因是Navicat上的数据库编码格式并不是真正的编码格式 ,所以明白了吗 ...

  2. 01_3_创建一个Action

    01_3_创建一个Action 1. 定义一个action 具体视图的返回可以由用户自己定义的Action来决定 具体的手段是根据返回的字符串找到相应的配置项,来决定视图的内容 具体Action的实现 ...

  3. 我的offer之路(一)

    目录 1.职业规划. 2.刷题. 3.看书. <剑指offer> <数据结构算法与应用:C++语言描述 > <Effective C++> <C与指针> ...

  4. Python学习记录4(语句)

    赋值语句 序列解包 条件语句 语句块 布尔变量 条件执行和if语句 条件运算符 循环 while语句 for循环 迭代工具 跳出循环 break continue while truebreak语句 ...

  5. Your Ride Is Here

    纯粹的水题= = /* ID:yk652321 LANG:C++ TASK:ride */ #include<iostream> #include<cstring> #incl ...

  6. matplotlib绘图(四)

    控制文字属性的方法: 所有的方法都会返回一个matplotlib.text.Text对象  文本注释: annnotate() xy参数设置箭头指示的位置,xytext参数设置注释文字的位置 arro ...

  7. PHP获取文件夹内所有文件包括子目录文件的名称或路径

    /* * new getFile($_dir[,$_emptyDir,$_fileType]); * @parma $_dir 是目录名称 * @parma $_emptyDir 是否获取空文件夹,选 ...

  8. python-numpy-pandas

    目录 numpy 模块 创建矩阵方法: 获取矩阵的行列数 切割矩阵 矩阵元素替换 矩阵的合并 通过函数创建矩阵 矩阵的运算 pandas模块 series (一维列表) DataFrame DataF ...

  9. CUB reduce errorinvalid configuration argument

    解决CUB reduce errorinvalid configuration argument问题 在写TensorFlow代码时遇到报错 CUB reduce errorinvalid confi ...

  10. 如何解决js跨域问题

    Js跨域问题是web开发人员最常碰到的一个问题之一.所谓js跨域问题,是指在一个域下的页面中通过js访问另一个不同域下的数据对象,出于安全性考 虑,几乎所有浏览器都不允许这种跨域访问,这就导致在一些a ...