SOCKET章节读书笔记
强烈推荐Linux/Unix系统编程手册,号称超越APUE的神书。
backlog含义
#include <sys/socket.h>
int listen(int socketfd, int backlog)
backlog参数限制未决连接(未accept)的数量,在这个数量之内,connect会立刻成功。
Linux上上限为128,定义在
udp已连接socket
udp socket也是可以调用connect()的,这种叫已连接socket,内核会记录这个socket的对等socket的地址。
当一个udp socket已连接之后:
1.数据包发送可使用write或send,会自动发送到对等socket上,与sendto一样,每个write会发送一个独立的数据包
2.在这个socket上只能读取对等socket发送的数据**
零时端口
未调用bind(),tcp/udp会分配一个零时端口(tcp服务器也可以不调用bind)
可通过/proc/sys/net/ipv4/ip_local_port_range来修改范围,一般做高并发测试时,测试机可能需要修改以支持多并发。
UDP避免IP分段
一般来说,UDP会采用保守的方法来避免IP分段,即确保传输的IP数据包的大小小于IPv4组缓冲区576字节。8自己udp头部,至少需要20字节存放IP头,剩下548字节存放UDP数据包。实践中,一般会选择512来存放数据包。
shutdown与close区别
SHUT_WR被称为半关闭套接字,通过文件结尾来通知对端本地的写端已经关闭了。
shoutdown与close一个重要区别:无论该套接字上是否还关联其它的文件描述符,shotdown都会关闭套接字通道。
如sockfd指向一个已连接的流式套接字,执行以下调用,连接依然保持打开,仍然可以通过文件描述符fd2在该连接上做i/o操作。
fd2=dup(sockfd);
close(sockfd);
但如果执行以下调用,那么连接的双向通道都会关闭,通过fd2无法执行i/o操作.
fd2=dup(sockfd);
shutdown(sockfd,SHUT_RDWR);
如果套接字文件描述符fork()被复制,如果fork()后,一个进程在描述符副本上执行SHUT_RDWR操作,那么其它进程都无法在这个文件描述符上执行i/o操作了。
需要注意的是,shutdown并不会关闭文件描述符,要关闭文件描述符,还需调用clsoe。
TCP_CORK套接字选项
HTTP中使用sendfile()发送文件,为提供带宽利用率,可使用TCP_CORK将HTTP首部给缓冲,与数据报文合并为一个报文。
int optval =1
setsockopt(sockfd, IPPROTO_TCP, TCP_CORK, &optval, sizeof(optval);
write(sockfd, ...); //write http headers
sendfile(sockfd,...); // send data
optval =0
setsockopt(sockfd, IPPROTO_TCP, TCP_CORK, &optval, sizeof(optval));
TIME_WAIT状态
TIME_WAIT状态值出现在执行主动关闭的一端,迁移到CLOSED状态需要2MSL(报文最大生存时间)。MSL是IP报文在吃过TTL限制前可在网络中生存的最大估计时间。
Linux中TIME_WAIT将持续60s。
目的:
1.实现可靠的连接终止
如果最后发送的ACK丢失了,等待2MSL可以重新发送最后ACK;而如果主动关闭的一方不存在了,TCP协议将发送RST报文(RST会被解释为错误)
2.让老的重复的报文段在网络中过期失效,这样建立新的连接时将不再接受它们
当有TCP节点处于TIME_WAIT状态时是无法通过同样的IP和端口号重新建立新的连接的。SO_REUSEADDR选项可用来避免会遇到EADDRINUSE错误,同时仍然允许TIME_WAIT状态提供可靠性保证。
带外数据
send()和recv()需要制定MSG_OOB标志,当套接字接受到带外数据通知时,内核为套接字属主(通常为使用该套接字的进程)生成SIGURG信号。
带外数据是TCP套接字的一种特性,允许发送端将传送的数据标记为高优先级。TCP通过URG标志位来标示有紧急(带外)数据,并将紧急指针指向紧急数据,但TCP没有执行紧急数据长度,因此认为紧急数据只由一个字节组成。telnet,ftp等,都利用该特性来终止之前传送的命令。
s.send("hello",socket.MSG_OOB)
c.recv(100,socket.MSG_OOB) // "o"
c.recv(100) // "hell"
分散聚合IO
readv和writev可以一次系统调用读写多个非连续缓冲区
#include <sys/uio.h>
ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt)
struct iovec{
void *iov_base; //buffer
size_t iov_len; //size of buffer
}
I/O多路复用,信号驱动,epoll
水平触发通知:
如果文件描述法上可以非阻塞的执行I/O系统调用,此时认为它已经就绪。所以每次不需要读取很多数据,多个描述符读取平衡。
边缘触发通知:
如果文件描述符自上次状态检查以来有了新的I/O活动,此时触发通知。由于只有新状态才通知,所以一次需要读取所有数据,不然会导致数据丢失,会导致其他描述符处于饥饿状态。
I/O模式 | 水平触发 | 边缘触发 |
---|---|---|
select,poll | yes | |
信号驱动I/O | yes | |
epoll | yes | yes |
select
#include <sys/time.h>
#incldue <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timevel *timeout)
返回就绪(3个集合)的描述符数量,0 超时,-1 错误
exceptfds一般只会在下面两种情况发生:
1.连接到信包模式下的伪终端主设备上的从设备状态发生改变
2.tcp套接字接收到了带外数据
nfds是集合中最大文件描述符+1,目的是让select变得更有效率,内核不用检查大于这个值的描述符是否属于这个集合。
eopll
max_user_watches:每个用户可以注册到epoll实例上的文件描述符总数(/proc/sys/fs/epoll/max_user_watches,默认的上学值是根据系统可用内存计算)
可以研究研究https://github.com/cloudwu/socket-server,以及tornado源码,这两个都是水平触发的,边缘触发的可以研究nginx或golang,当然nginx更适合。
典型异步I/O读写:
int sz = write(fd, buffer, sz);
if (sz < 0) {
switch(errno) {
case EINTR:
continue;
case EAGAIN:
return -1;
}
int n = read(fd, buffer, sz);
if (n<0) {
switch(errno) {
case EINTR:
break;
case EAGAIN:
fprintf(stderr, "socket-server: EAGAIN capture.\n");
break;
default:
// close when error
return SOCKET_ERROR;
}
return -1;
}
配置参数
- 系统限制 /etc/sysctl.conf
来自gopush群的分享
net.ipv4.ip_forward = 0
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.default.accept_source_route = 0
kernel.sysrq = 0
kernel.core_uses_pid = 1
kernel.msgmnb = 1048576
kernel.msgmax = 1048576
kernel.shmmax = 68719476736
kernek.shmall = 4294967296
fs.file-max = 1048576
kernel.pid_max = 1048576
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_timestsmps = 0
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 1200
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_max_tw_buckets = 5000
net.ipv4.tcp_wmem = 4096 4096 1677216
net.ipv4.tcp_rmen = 4096 4096 1677216
net.ipv4.tcp_mem = 94500000 91500000 92700000
net.ipv4.tcp_max_orphans = 3276800
net.core.netdev_max_backlog = 32768
net.core.somaxconn = 32768
net.core.wmem_default = 8388608
net.core.rmem_default = 8388608
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
vm.overcommit_memory = 1
- 登陆用户限制 /etc/security/limits.conf
soft是警告设置,hard是阀值(鸟哥私房菜)
* soft nofile 150000
* hard nofile 150000
如果不管可直接用ulimit -n 10000设置