网络编程实践笔记

1.简述

高并发服务:事件驱动+非阻塞IO
以太网层(帧,frame)、IP层(分组)、传输层(TCP分节)、应用层(消息)
TCP断开的时间和条件
TCP是字节流协议,不保留消息的边界
非并发网络编程实例:

  1. TTCP:classic TCP performance testing tool(TCP性能测试工具)
  2. Round-trip:measure clock error between two hosts(测试两台机器的时间差)
  3. Netcat:a Swiss hnife
  4. Slow sink/source

并发网络编程实例:

  1. SOCKS proxy server 代理服务器,中继两个TCP连接
  2. SudoKu solver 典型的请求响应模型
  3. simple memcached(非阻塞IO网络编程)
  4. Broadcasting to multiple TCP peers 应用层的TCP广播(如何处理慢的接收者)

多台机器进行数据处理:

  1. Parallel N-queues 并行N皇后
  2. Median of numbers across machines 分布在多台机器上的书的中位数
  3. Frequent queries 计算最频繁的查询有哪些
  4. Distrbuted sorting 分布式排序

高级主题:

  1. RCP- A basic building block for various servers 不和IO打交道,调用远程的method
  2. Load balancing 负载均衡,最好的是round-robin
  3. Capacity of serving system 服务系统的容量管理
    How many machines do I need to support X QPS? 需要多少个机器可以支持X个QPS?
    What will be the number of replicas of each components?关键是确定每个系统的replica的数目(每个组件有一个replica函数,类似于阻抗匹配的关系)
  4. Fight for (tail) latency 延迟,关系到用户体验,测百分位数的延时

2.实战

2.1 TTCP 测试两个机器之间的TCP吞吐量

性能指标:
Bandwidth 带宽MB/S
Throughput 吞吐量,消息messages/s,查询queries/s(QPS),事务transactions/s(TPC)
Latency 延迟 milliseconds,percentiles,平均延时、百分数延时
Utilization 资源使用率, percent
Overhead eg. CPU usage, for commpression and/or encryption
先压缩后加密 ,否则压缩不起作用

TTCP Protoclol:
在这里插入图片描述
请求响应式协议:

  1. 客户端向服务端发送SessionMessage的消息,8个字节,表示要发1024个消息,每个消息的长度是8192字节。
  2. 服务端收到8102+4个字节后回复4个字节的响应,表示收到了8192个字节
  3. 断开连接,客户端接收到最后一个Ack断开连接,服务端发送完最后一个Ack断开连接

代码类型
Straight forward with blocking IO阻塞IO型

  • muduo/examples/ace/ttcp_blocking.cc (C with soclets API)
  • recipes/tpc/ttcp.cc (C++ with a thin wrapper)
  • muduo-examples-in-go/examples/ace/ttcp/tcp.go (Go)

Non-blocking IO with muduo library 非阻塞IO

  • muduo /examples/ace/ttcp/ttcp.cc

扩展:

  • 计算消息吞吐量(messages/s)and 延时(latency)
  • 服务器支持并发
  • 客户端增加并发连接
  • 流水线
  • Nagle算法、TCP慢启动
  • 如何检测TCP错误:校验和。或者服务端发一个随机数种子过去,客户端和服务端用相同的随机数算法生成数字,客户端发给服务端,服务端去验证。

Pipelining 流水线
在这里插入图片描述

避免内核阻塞: struct payload{int length;char data[0];};先告诉接收者自己要发多大的数据,然后让接受者准备好一个这么大的缓冲区,接受完解析后,同理将数据发回去。

TCP自连接: 本机不同端口之间的通信,正常情况下如果服务端的端口在侦听,内核是不会选择这个端口号作为源端口号的。但是如果服务的端口没有在侦听的话,如果没有选择这个端口作为源端口,客户端就会返回RST,如果选择这个端口号去作为源端口号,这样就相当于服务器地址+端口号=客户端地址+端口号,相当于两者同时发送SYN,然后又同时响应SYN+ACK,最后连接就建立了。解决方案: 可以比较源端口号和目的端口号是否相同,或者选择源端口号范围之外的作为目的端口号。
注:内核选择源端口号的策略:类似于一个计数器,如果这次选择的x,下次就选择x+1。

TCP产生RST报文段的条件:

  1. 目的端口没有进程在侦听;
  2. 一个连接异常终止;
  3. 处于半打开的链接:一方关闭了连接,另一方由于网路故障没有收到结束报文。

2.2 Round-Trip (基于UDP)

  • Timekeeping ,how NTP works 计时,NTP的工作原理
  • Roundtrip 计算两个机器之间的时间差
  • UDP vs. TCP from programming prosperctive :UDP和TCP在网络编程上的不同

NTP:使计算机时间同步的一种协议,可以精确到0.25ns,测得网络的延迟,把延迟算进去。
在这里插入图片描述

  • 客户端在T1时间点上给服务器发送一个UDP报文,服务器在T2时间点收到,然后在T3时间点发送给客户端一个TCP报文,客户端在T4时间点收到,计算出客户端和服务端的时间差(T1+T4)/2-(T2+T3)/2,客户端减去这个时间差就是和服务器相同的时间。还有一个复杂的反馈控制系统,差值不能是0,需要差值作为反馈系统的输入。
  • 客户端不断地调整时间补偿和频率
  • 避免时间跳变和频率跳变 ·

NTP服务器的台数:一般是4台,两台GPS,两台原子钟

2.3 netcat,瑞士军刀

基本概念

  • Read/write two(three) file descriptors simultaneously (并发性)
  • Two simple concurrent modes are examined(两种并发模型)
    • Thread-per-connection with blocking IO(每个连接创建一个线程+阻塞IO)
    • IO-multiplexing with non-blocking IO(IO多路复用+非阻塞IO)
  • How/when to close TCP connection(如果/何时去关闭TCP连接)
    • 确定所有的数据都被发送和接收
  • Why IO-multiplexing must be used with non-blocking IO?(为什么多路复用要用非阻塞IO)

nc < /dev/zero == chargen
nc > /dev/null == discard
dd /dev/zero | nc == poor man’s ttcp
nc < file , nc > file == poor man’s scp

如何正确关闭一个TCP连接

  • close之后,如果协议栈接收缓冲区有数据,会导致TCP协议栈发送RST分节,强行断开连接;如果协议栈的发送缓冲区有数据,会直接丢失。正常的应该发送FIN分节。
    read返回0的时候表示ENDOF FILE,表示对方close了
    wrong:send()+close()
    correct sender:send()+shutdown(WR) +read()->0+close()
    correct receiver: read()->0+if nothing more to send +close()
    sender 调用shutdown()之后receiver read()才会返回0
    receiver 调用close()之后sender read()才会返回0
    安全:如果sender调用shutdown之后,超过一定的时间read还没有返回0,就调用close(),防止恶意客户端
    发送方告诉数据长度,接收方就可以主动判断数据是否收全了,就不用借助end of file了。如果末端的进程退出了,整个管道的进程会依次收到SIGPIPE,好处是避免CPU进行多余的计算。
  • SIGPIPE信号
    程序向已经关闭的管道写数据,会收到SIGPIPE信号, writer会返回errno=EPIPE,默认行为是终止进程。
    gunzip -c huge.log.gz | grep |head
    head是取前是10行,当grep输出10行之后,grep收到SIGPIPE,然后gunzip收到SIGPIPE,依次死掉。
    网络IO的时候服务端的进程有可能因此死掉。因此网络进程启动的时候忽略SIGPIPE信号。忽略SIGPIPE的副作用:当网络进程穿起来的时候,要检查写的返回值,比如说实现一个CURL,从网络上抓取一个界面,如果抓不到的话,printf会返回一个错误码,如果返回小于0,表明管道的程序已经关闭,应该主动的把程序终止掉。
  • Nagle’s algorithm,TCP_NODELAY
    要求TCP连接上最多只能有一个未被确认的小分组,在该分组的确认到达之前熊发送其他小组。对于MMS的片段直接发送。如果程序频繁的发送一些小包,会造成网络拥挤。但是会严重影响请求响应式协议的延迟。
    对于write-write-read,第二个wriet要等一个往返延迟。解决办法是做一个缓冲,成为write-read。但是,这样会影响request pipelining,如果一个连接上有并发请求,就很难将这些个请求放在一个buffer里面,因为很有可能在程序的不同地方发请求。
    建议默认关闭Nagle’s 算法
  • REUSEADDR
    服务端尽量使用reuseaddr,如果服务器crash后,可以不用等TIME_WAIT,就可以重启服务器。

netcat的实现

  • server:bind+listen+accept
  • client:resolve address+connect

实现方式:thread-per-connection、IO-multiplexing,通过cpu使用率查看,可以看谁拖慢了性能
thread-per-connection 适用于连接数量不太多,线程比较廉价(C++、JAVA不适用)的情况
IO复用复用的是线程,一个线程可以处理多个文件描述符。最好使用非阻塞IO。
非阻塞IO的是实现主要是网络库的责任,程序员的责任是编程逻辑。
非阻塞IO业务逻辑:

  • 非阻塞写数据,返回写了多少。
  • 如果没有写完,保存没有写的数据,将sock的写事件放入poll 中,可以写的时候写,保存未写数据,写完后将sock的写事件踢出poll。
  • 写完了就不用管了。

不要将网络库填的坑又挖开,比如说再非阻塞IO里做一些费事的操作,像数据库的查询。

IO复用情况下要使用非阻塞IO,不能去检查IO是否可以取读写再去读写。比如说accept函数,如果又用户连接上来,sever的socket会变得可读,但是变得可读和真的取accept之间,客户可鞥已经断开连接了,然后accept就会永远阻塞下去,如果想用IO多路复用去处理accept的话,sever socket必须是非阻塞的。

select()可能会把一个sock文件描述符说成可读,但是读的时候有可能会阻塞。因为可能会意外的报告可读,应该用非阻塞。

非阻塞读用到应用层的一个缓冲区,把消息先放到缓冲区里,读完了之后触发消息处理。

非阻塞写是网络库的工作,所要作的是告诉网路库要发多少数据,网络库怎么收发数据,怎么观察pollout是网络库处理的事情。
网络库处理的事情:

  • save remaining data in some buffer
    • Never call write() when buffer is not empty,it reorders data
    • Alternatiovely, always send from buffer
  • Satart watching POLLOUT event
    • Meanwhile, any write() shoule append the buffer instead
  • When POLLOUT is ready ,write from buffer
    • consume bfufer
  • If buffer becomes empty, stop watching POLLOUT event
    • Otherwise , it end up with a busy loop

What if sink is slow?(带宽不匹配,接收方慢)。产生的结果:溢出(丢失数据)、内存消耗过多。这是一个需要从设计上考虑的问题,既设计的决策点
解决办法: 设置两个水位高度,水位高于高的那个,关闭水龙头,水位低于低的那个,打开水龙头。

  • The common pitfall in non-blocking IO
    • Avoid memory exhaustion or dropping messages
  • Stop reading and stop watching readiness
    • Oherwise end up with a busy loop in level-triggered IO multiplexer
  • Sender will be throttled because TCP advertised window drop to zero
  • Amortized throughput will be the same
    • Due to buffering,instant throughput will be different

另一个决策点:LT or ET
seletct、poll是电平触发
epoll支持水平和边缘触发
没有数据支持哪个更快:从程序的角度,对发送数据write(),接受连接accept(),边缘触发最好,省去很多麻烦。如果是水平数据的话要注册POLLOUT,反注册POLLOUT。对于读数据read()来说,水平触发最好,不会造成所谓的饥饿,饥饿就是数据没读完,不会再告诉你数据可读,会等到下一次对方发的数据到达时通知。accept()用ET,因为如果文件描述符用完的话,会陷入一个死循环。
在这里插入图片描述
多线程下的IO多路复用:one loop per thread:每个线程一个循环

2.4 procmon 监控进程的进程,典型网络编程

进程信息的位置:/proc/pid/stat
网络编程和实现网络收发没什么关系,重要的实现网络逻辑。
实现进程监控的工具,可以看成是带有web界面的top。
原理:读/proc/pid,proc的文件系统的cpu时间精确到一个tick,通常是10ms,每秒读依次CPU时间,可以算出CPU使用率。

实现procmon要考虑的点:

  • Intrusive or non-intrusive
    • 侵入式:做成一个库,其他程序链接这个库,被监控的程序主动调用这个库来暴露内部状态,能暴露出内部数据。
    • 非侵入式:可以监控任何程序,看不到内部状态,只能看到用了多少内存,多少CPU,不能看出CPU用在正常处理逻辑上,还是用在死循环了。
    • Internal data,eg,queries per second
    • Run as same user or root?(/proc/pid/{cwd,exe,fd} are private)
    • Implement once and monitor all processes in any language
  • How to draw a graph?
    • Standalone chart server(<img src=“http://chart/?data=1,0.9,0.8,0.2”>)
    • Render with JavaScript:jpPlot,flot
    • Dynamically generate PNG image with libgd(<img src=“cpu.png”>)
    • 前面两种对服务器的负载比较低,本例中第三种

从大的方面看这个工具:
专注业务逻辑,不在乎网络数据的具体收发

  • Why not use a standard httpd and write a CGI scitpt?
    • If it simply read /proc/pid ?
  • CPU usage graph needs historical data
  • We are implementing HttpeSerlet.doGet(), basically

计算过程

  • /proc/stat中读取CPU总体使用情况,计算CPU总的使用时间totalCpuTime = user + nice + system + idle + iowait + irq + softirq + stealstolen + guest。
  • /proc/pid/stat中读取某一进程的CPU使用情况,计算进程的CPU使用时间processCpuTime = utime + stime + cutime + cstime
  • 得到两个时刻的CPU使用总时间和进程的CPU使用时间,分别记作totalCpuTime1、totalCpuTime2、processCpuTime1、processCpuTime2:CPU使用率=100*( processCpuTime2 – processCpuTime1) / (totalCpuTime2 – totalCpuTime1) (按100%计算,如果是多核情况下还需乘以cpu的个数);

扩展

  • Reduce overhead by caching file descrptor to /proc/pid/stat

  • show CPU usage per thread

  • Show memory usage over time

  • collect machine wide data

    • muduo-protorpc/examples/collect
  • Monitoring processes running on multiple machines

    • A simple HTML with IFREAM could work

A project idea:nohup on web

  • A customized HTTP server,fork and run a command line tool.turns its STDOUT/STDERR into web pages
  • Watching “apt-get-upgrade” or “make boost” progress in real time
  • Security concerns
    在这里插入图片描述

2.6 memcached

缓存数据库查询的结果,失效模型:开机重启就没了。
负载的类型:IO密集型、CPU密集型、内存密集型。隔离度递增
隔离度:一个服务器上起两个进程,一个分配10G内存,一个分配20G内存,只要物理内存足够大,这两个进程是完全独立的。
缓存数据库查询的结果
基本功能:key-value,键值存储,远程的hash表,服务器已重启就没了,因为存在内存里
需要解决的问题:

  • memcahed的并发请求会访问共享的数据,减少锁的争用
  • 内存密集型

实现

可以被当作一个远程的hash表

  • Storage储存(set/add/replace/append/prepend)

    set <key> <flags> <exptime> <bytes> [noreply] \r\n \r\n
    <data block> \r\n
    STORED \r\n

  • Retrieval(get/gets)取回数据

    get <key>* \r\n
    VALUE <key> / <bytes> [<cas unique>]\r\n
    <data block> \r\n
    END\r\n

  • Deletion删除

    delete <key> [noreply]\r\n
    DELETED\r\n

  • Compare-and-Swap

减少锁的争用的办法

  • 将数据拷贝一份,避免在临界区进行系统调用
  • 将数据的引用计数+1,如果数据能做到const,别的线程就不会改变这个数据,也不会被删掉
  • 将hash增加一些分片,这样就避免全局锁的争用

Final decision
unordered_map<shared_ptr,Hash,Equal>

perf record 可以跟踪内存、CPU等使用情况

3.扩展

3.1 UDP vs. TCP

  • UDP是一个无连接的,不可靠的数据报协议。多个客户端的时候用一个sockfd就够了,revfrom的时候自然就有了客户端的地址,进程内部可以维护一个表,客户端有哪些,一段时间内不活动了就把他踢掉。可以多个线程同时操作一个sockfd,因为UDP是一个数据报文协议,自动维护了消息的边界,两个进程写不会产生串化的情况。
  • TCP是一个有连接的,可靠的字节流协议。多个客户端的时候为每一个客户端维护一个sockfd。线程不安全,两个线程最好不要去读或写同一个sockfd,因为TCP是字节流协议,假设线程A读了前500个字节,线程B读了后500个字节,但是不知道哪个在前面读,而且不能依靠这个时间戳,因为这个时间戳是不准的,所以一般一个线程操作一个sockfd的读或者写。

问题: 为什么不基于UDP设计TCP:因为TCP是先被设计出来的,然后再设计的UDP,其实基于UDP也是可以实现可靠连接的。
什么时候用UDP:受限的情况下会使用UDP,例如NAT穿透、不在乎可靠性NTP
默认用TCP,UDP用不完千兆网的带宽,如果用UDP的话,就拼命发,丢包,如果不拼命发,需要控制发包的速度用完带宽。

3.2 UTC,TAI,Leap Seconds,Unix Time

  1. UTC:原子钟+修正(年月日时分秒),GMT:通过地球自转来的。
  2. TAI:原子时
  3. Unix Time是一个整数,从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数。
    t%86400表示一天之内的秒数,如果结果是负数的话,加上86400,日期减1
  4. Leap seconds的缺陷:①没有考虑工作日或时区,对金融市场,股票交易可能会有影响。②Unix Time没有考虑Leap Time

3.2 go语言的特点

go语言中线程的代价很低,叫做goroutine,在函数面前直接加go就可以了。
go语言三大特点;goroutine、channel、select

3.3拒绝服务攻击

针对服务器做出特定的动作,使服务器不能服务其他客户。解决这个问题的办法:非阻塞IO;为每个客户开一个线程或者进程;设置一个超时时间。

3.4 网络编程

  • 网络编程
    不跟网络打交道,只管消息的模型
    前四层:跟网络设备相关,不是网络编程
    第七层:关心payload,关心收发数据本身的内容
    解析、理解、使用、消费和产生payload
  • focus on bussiness logic、
    更socket API关系不大,集中于业务逻辑
    将业务瑞吉放到业务逻辑的框架,让网络库关心细节

3.5 Channel 和 EventLoop

Channel是连接网络socket 和 具有read、write、connect、bind能力的组件 的一条通道
Channel通常有用下面几个属性:

  1. Channel本身组件的状态state,比如说是不是处于打开open状态,是否处于连接connected状态
  2. Channel本身将会跟一个ChannelConig对象相关联,用于Channel进行配置,比如接收缓冲区的大小
  3. Channel本身将会跟一个ChnnelPipeline对象相关联,用于处理IO事件events

3.6 linux可以维护多少并发连接

可以维护多少个连接,取决于服务器的配置。服务器处于安全的考虑,会在多个位置限制文件描述符打开的数量,包括系统级、用户级、进程级。内核维护TCP连接,需要消耗最低3.3k左右的连接,如果内存为4G的话,最多可以维护100万左右的连接,但是这些连接都是空链接。如果不是空链接,需要配置接收缓冲区和发送缓冲区的大小,可维护连接的的数量就会降低。如果业务处理比较复杂,可维护的连接还会进一步降低。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值