2023春季学期 网络编程技术 期末复习笔记
W3 3-1
UDP用途
- 只因你太美
- 简单的应答协议 DNS,DNS的16位ID用来请求应答匹配,太短了容易伪造
- 最新的QUIC协议
UDP socket套接字编程
五元组定义完整会话:2ports 2addr Protocol
字节序:尤其控制信息,统一使用网络字节序big endian
服务器常常需要bind地址,而客户端不必要。
tolen 对方地址长度
Bind和Socket首字母大写?表示对原有socket的封装,
MAXLINE长度后,recvline[n]=0来防止溢出
编程考虑问题:版本号、可靠、安全、性能、一致性…
UNP超时重传在编程实践中落后:1.粒度太粗,超时重传要几秒的检测,不可忍受 2.Risk Condition 依赖于事件发生次序?在不可靠网络中次序不定;andOS调度出去的时间超时等等
取而代之的是setsockopt建立读写超时即套接字选项
设计文件传输?可靠性 性能(并发性)
W5 3-1
代码分析角度
- recv接入char数组,
- 2-15 没有检查接受长度是否超过缓冲区(缓冲区应该是最大有效长度+1)
- 2-15 没有用0手动截断导致转string或后续的溢出错误
- 2-15 没有检查recv是否为有效长度(可能0)
- 2-32 recv直接读入结构体,要检查格式化的正确与否(对齐等)
TCP SOCK_STREAM
Coding
Client
socket
connect 服务器地址
read or write
close
Server
socket
bind
listen(sock, backlog未决队列即未完成三次握手的SYN)
accept
read or write
close
Danger
- 一个连接出错整个服务器退出syserr等
- 通过sigaction设置SIGIGN(SIGPIPE)来防止向已经关闭的socket写
- 重启会导致address already in use,在关闭连接后还要等待2MSL 约 4min——设置SO_REUSEADDR允许2MSL内被绑定,可以多个服务器绑定一个端口但是本地IP地址不同
- 设置SIGCHLD回收子僵尸进程 waitpid
- 设置setsockopt的RCVTIMEO和SNDTIMEO避免发带连接
- KEEPALIVE保活报文测试
- SOLINGER避免FIN阶段对方不回复的发呆连接,设置为1,0即可
- SO_REUSEPORT 负载均衡每一个进程有一个独立的监听socket,并且bind相同的ip:port,独立的listen()和accept();提高接收连接的能力。
一致性
安全性能
- FTP慢:
- 小数据包延迟大
Nagle算法延迟发送直到ACK才发或者超时才发,避免一堆小包
- 小数据包延迟大
延迟确认ack浪费,所以捎带确认,超时再ack
导致客户端先发一个包,然后下一个包太小不发送,服务器没收到请求不ack。直到服务器超时ack了,客户端才请求服务器才回应。
TCP_NODELAY来关闭NAGLE
TCP_CORK阻塞不足MSS的小包,手动开关来控制
SO_RCVBUF SNDBUF手动修改缓冲大小,在连接前设置,至少4个MSS
进程并发服务器
主进程loop accept+fork新进程处理连接,主进程要关闭acceptedsocket而子进程关闭监听socket。
prefork先fork一堆来监听,一个accept处理。。。其他接着听
主进程accept通过socket传输
子进程各个accept通过进程锁保证互斥(文件锁或者进程mutex),给fd上锁
避免accept的重复堵塞 BSD acceptfilter系统SO_ACCEPTFILTER SOL_SOCKET
Linux:TCP_DEFER_ACCEPT
accf_data: listen socket在收到client的数据后才可读
accf_dns: listen socket在收到一个完整的DNS请求后才
可读
accf_http: listen socket在收到一个完整的HTTP请求后才可读
UDP SOCK_DGRAM
Coding
地址中含地址+端口
int sock = socket(AF_INET,SOCK_DGRAM, 0)
申请套接字描述符
bind(mysock, (sockaddr *) myaddr,sizeof(myaddr));
绑定套接字到端口,服务端
int sendto(int sd, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
发送消息到指定地址
int recv(int sd, void *buf, size_t len, int flags);
int recvfrom(int sd, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);
Client
socket
sendto 自动选择己方端口,需指明对方地址
recvfrom ??从某个特定地址接受,如果不在乎对方地址设置地址和addrlen为null。也可能是放来源地址的
Server
1 | int mysock; |
INADDR_ANY 即0.0.0.0 监听任何来的地址
socket
bind
recvfrom
sendto
Danger
问题
可靠性
数据丢失,recv阻塞等待——设置超时重传时间
不要用alrm设置超时,被调度出运行且超时
使用选项setsockopt
int setsockopt( int sockfd, int level, int optname,constvoid *opval, socklen_t optlen);
SO_RCVTIMEO, SO_SNDTIMEO
指定时间没有可读就结束返回错误
指定时间数据没法出去结束返回错误
int select (int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *);限时读,直到有fd可读
FD_ISSET fd在fdset中的状态是否变化为可读
数据对应(可靠)
标识号
Final
lect1
互联网的经验:
问题驱动 成本低 核弹和分布式系统
遵循规律 18 吉尔德定律主干网带宽6 n^2
演化成长 接入终端丰富 协议栈演化 绝非偶然
OSI:权威机构ISO 严谨设计 区分服务协议接口
互联网侧重异构网络的互联
lect2 协议设计
角色:客户端、xx类服务器
体系结构:颗粒化:控制、数据分离) 传输统一(相同传输层) 功能选项化(考虑到但不实现)
状态:一问一答,有依赖的一问一答
PDU协议数据单元一次会话传递:边界!TCP数据流切割为各个协议原语、UDP报文和原语的对应——固定格式与分隔符、指定可变长度、传输层指定一次TCP连接和报文
编码、传输
协议设计准则
- 原语、消息类型(状态)编号:区分原语,指示错误
- 一定要有标准的协议规范否则任何都是合法
- 并发性能:区分多个用户的多次服务UDP,区分一个用户两次服务
- 会话ID
- 指定会话开始的位置
- 性能
- 分块ID
- 认证安全
- UDP定义协议规范,能够判定是否合规,丢弃不合规
- 认证信息
- 压缩加密
- 可扩展性
- 协议版本号
- 报文类型
- 扩展项
- 编码
- 区分控制信息和内容信息——规定文本格式
- 序列化(字节序!!)
- 可靠传输
- 校验和
- hash值
- 请求应答匹配
- ID
Protobuf
默认小端字节序
[rule] [type] [name] = [tag]
message rsp{
required bytes fuck = 1;
}
7bit varint 首bit为0表示下一个是新信息,否则是1表示下一个也用来表示此数字
udp性能好并发好,复杂
tcp简单,可靠但固化
http最简单 性能差
QUIC优化TCP+TLS??更快但等价
简易
可扩展和演化
互操作性
lect3 UDP编程
8B 包头 无连接 多路复用 可选差错控制
没有拥塞、流量控制
支持组播广播 DNS
不可靠:丢包、乱序、抖动、内容改变
信号系统:异步事件
INT 用户前台
KILL杀死
HUP 重启
QUIT建立core的退出
WINCH 忽略信号 窗口大小发生变化
SIGALARM 终止进程 计时器到时
hup disconnet时
lect4 UDP编程2
安全性
假冒服务器回复
拒绝服务攻击
一致性
切割协议的字符
长度限制:MTU MAXLINE UDP
长请求所有数据返回还是部分返回
recv中的MSG_TRUNC flag,返回截断前的长度
服务器尽早识别不合法用户,区分业务类型和时间
客户端分担服务器功能,尽量过滤异常请求
224.0.0.1子网所有系统 .2为所有路由器
IGMP协议报告组播组的加入与退出,路由器定期询问主机响应,不主动退出而是询问发现
lect5 TCP网络编程
不支持组播广播
lect7 多线程编程
int pthread_create( pthread_t tid, pthread_attr attr, void ( start_routine )(void), void* arg )
pthread_detach 从主控线程分离,自己回收资源,防止僵尸。否则要等待主控线程join
pthread_attr 线程属性
如PTHREAD_SCOPE_PROCESS进程级或系统级争cpu
gethostbyname线程不安全,使用全局变量指针,后面把前面覆盖,不要用!用两次或者多线程用都不安全——gethostbyname_r
共享数据的data race多个写,非原子的单个写(经常一条编译成多条),指令重排(线程内不冲突就重排但是线程间冲突)——使用锁和条件变量
使用mutex时的死循环甚至死锁 ??
pre threaded
- 主线程accept 通过工作队列(命名队列)传递
- 各个子进程accept通过互斥锁保证互斥
lect8 Event多路复用
select
select检测事件:可读、FIN(read返回0,endoffile)、RST的read-1、accept
可写或者写端关闭
用于TCP Server的select,一开始只有listen fd用于监听accept可接受新connfd。
- 套接字数目有限制 FDSETSIZE 1024无法更改——poll没有限制size,
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout); /return : count of ready descriptors, 0 on timeout, -1 on error/
pollfd包含fd,events和recents,指定验证的条件
- 阻塞非阻塞读写 阻塞IO
非阻塞io file control
val = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, val | O_NONBLOCK);
用这玩意取出老状态bit,与上新状态位再放回去。
- 进一步提高效率epoll不用来回copy fdset
- ET EPOLLET 只有监视句柄发生事件才报告,必须非阻塞读写防止一个阻塞饿死所有其他。纯正的事件触发
- 当read(2)或者write(2)返回EAGAIN时才需要挂起?
- LT 默认,阻不阻塞都支持,传统的select。检测缓冲区有没有没读完的东西
epoll create()创建空监控fd table
ctl(ADD)
epoll wait
工作方式:边缘触发ET或者等级触发LT
lect9 IPV6
任播取代广播
服务器纯v6 通过把v4地址映射到v6来服务v4
0:0:0:0:0:FFFF:210.25.132.100
全0作为srcaddr可以,不能目的,不能分配
IN6ADDR_ANY_INIT
loop地址:::1 给自己发,只能节点内部,类似loopback
inet_pton和aton
协议无关服务器编程
同时监听v4和v6
基于域名的切换 resolver查A和AAAA
getaddrinfo替代gethostbyname,域名或ip地址,返回一个链表多个地址
sockaddr_storage足够大的通用地址存储结构
16B的sockaddr
28B的sockaddr in6
128B的sockaddr storage
getnameinfo 解析sockaddr给出的域名或者服务名,返回host和service
n是规范化格式 aton ntop等等
IPV6_V6ONLY
lect10 安全编程1
协议缺陷
内存操作
缓冲区溢出
动态内存操作
读越界
缓冲区溢出,不要相信不可靠信息
整数安全
溢出、截断、符号
lect11 安全编程2
格式化字符串
格式化的字符串 不能交给用户,因为参数可变长可以偷栈信息
代码注入
command命令中含有用户的string
race condition
mysql_real_escape_string不能解决所有问题
使用prepare类型的调用接口,可以将数据和控制分开
使用’?’来作为占位符号,实际用时填入相应的数据
使用ORM 对象关系映射(Object Relational Mapping),将数据库中的数据映射为对象
ValgrindDRD 用于检测多线程编程中的常见错误
lect12
lect13
代码挑错
可能可以read但是无法readline