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. SSH框架使用poi插件实现Excel的导入导出功能

    采用POI生成excel结构 直接贴出代码  excel表格导出功能 action代码: struts.xml配置: 前台jsp代码:

  2. 01_1_准备ibatis环境

    01_1_准备ibatis环境 1. 搭建环境:导入相关的jar包 mysql-connector-java-5.1.5-bin.jar(mysql)或者ojdbc6.jar(oracle).ibat ...

  3. 关于bc中小数点length,scale,(())以及进制转换

    这是我在codewar上遇到的一个题,我用我自己的方法做出了解答,如下: 1 #!/bin/bash 2 3 distance=`echo "$1*10000"|bc|cut -d ...

  4. ubuntu安装easygui模块

    使用pip安装easygui 如果未安装pip,则使用如下命令 sudo apt-get install python-pip 安装完pip后,使用如下命令安装easygui sudo pip ins ...

  5. ccf 201712-2 游戏(Python实现)

    一.原题 问题描述 试题编号: 201712-2 试题名称: 游戏 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 有n个小朋友围成一圈玩游戏,小朋友从1至n编号,2号小朋友坐 ...

  6. Django小总结

    初始Git git init 初始化本地仓库,会在根目录下创建一个.git文件夹 git log 查看提交日志 git status 查看日志 git add 文件名 添加到缓存区 git commi ...

  7. Django ORM (三) 查询,删除,更新操作

    ORM 查询操作 修改 views.py 文件 from django.shortcuts import render, HttpResponse from app01 import models f ...

  8. CodeForces - 948C Producing Snow(优先队列)

    题意: n天. 每天你会堆一堆雪,体积为 v[i].每天都有一个温度 t[i] 所有之前堆过的雪在第 i 天体积都会减少 t[i] . 输出每天融化了的雪的体积. 这个题的正解我怎么想都很难理解,但是 ...

  9. HDU 5044 Tree LCA

    题意: 给出一棵\(n(1 \leq n \leq 10^5)\)个节点的树,每条边和每个点都有一个权值,初始所有权值为0. 有两种操作: \(ADD1 \, u \, v \, k\):将路径\(u ...

  10. 利用委托实现自己的数据缓存仓库(附上Demo)

    Demo源码 写在前面的话 写完这篇博客后,总觉得少了些什么,后来想了下,感觉自己只是把结果给亮了出来,自己为什么想到这么做,这个类库出生的缘由未详述,因此,在本段作下说明,如有不足之处,希望能和大家 ...