int socket(int domain, int type, int protocol);
type这个参数,可以加上一个 SOCK_NONBLOCK,把套接字设置为非阻塞,保证这一设置的原子性。
int listen(int sockfd, int backlog);
把sockfd设置成listen状态,之后就可以完成三次握手了,其实三次握手是在内核态完成的,accpet仅仅只是移除已经做好三次握手的链表中的套接字。而backlog有最大值限制,一般为128。这就是做好三次握手,却没有被accept移除的数据链表大小。
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
绑定sockfd和端口,以及IP,注意:sockaddr的另一个异构的结构体:sockaddr_in中的sin_port和sin_address必须是网络字节序。
为什么sin_family不用?
因为它不用发送到网络上去,而port和address分别放在传输层和网络层。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
用于从已经完成三次握手的链表中将移除队头,并且返回句柄(fd)。并且利用sockaddr数据结构返回连接的socket的port,family,IP等数据。
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
connect的一些错误返回分析:
(1)原因:连接没有打开的port用户态:errno=111(connection refused)
(内核态)传输层:TCP收到了对端机器发送的RST包。
(2)原因:连接一个不存在的IP(ping不到的IP)
用户态:errno=110(connection time out)
(内核态)传输层:TCP不断进行超时重传,总共127s,
时间间隔:1s,2s,4s,8s,16s,32s,64s,指数退避。
重传的TCP报文中一般只有时间戳选项会有变化,时间戳选项一般用于计算RTT,防止回绕(时间要大于2mst),syn cookie是把一些第一次SYN协商的值(如:wscale,sack)编进32bit时间戳中。
syn cookie:收到TCP SYN包,并返回SYN|ACK不分配数据区,而是用对方SYN包,算法出cookie值,在发SYN|ACK时,把cookie作为序列号!在SYN_RECVD状态下收到对方的ack号,比对,如果合法,则分配内核数据区(分配数据区的其他选项,协商的其他选项,会放在32bit时间戳中)。
(3)如果设置SOCK_NONBLOCK,connect一般会立即返回-1,errno = EINPROGRESS,当然也可能直接返回0表示成功,这个情况很特殊,就不做讨论。
一般会利用epoll来进行阻塞判断。
具体是先向epoll_create创建的epfd(句柄)注册connect获得的connfd,然后利EPOLLERR中利用getsockopt对套接字的错误errno进行提取。
注意:利用epoll多路复用,一般使用以上这个方法获取套接字的errno。而且取完以后,errno会被重置为0,就是说,任何错误,只有一次得知的机会;但是除此之外,一般函数如果成功返回都不会对errno进行修改。
当出现以上1、2的错误时,按以上方式注册的套接字,会触发无数次EPOLLIN/EPOLLOUT和一次EPOLLERR。
ET:
均只触发一次。
epoll_create(int size);
这个size是没有什么意义的,但是不能小于等于0,不然会出错。
epoll的ET和LT的比较:
LT:在EPOLLOUT方面,只要缓冲区有空闲位置,就会触发。而EPOLLIN事件,则只要数据没读完就会不断触发EPOLLIN。
在LT被发满的缓冲区以后(此时win=0),OUT就停止了,但是通过tcpdump可以发现有时还有0字节的数据包发送到对端,这是为了避免糊涂窗口。
ET:在OUT方面,第一次添加套接字 和 发送缓冲区从满变成非满会触发一次,但是只触发一次。而IN事件则有数据来触发一次,而且会赠送一次EPOLLOUT事件。
listen的套接字是不会触发EPOLLOUT事件,如果向它写入,会发生SIGPIPE信号。
利用getsockopt(conn_sock, SOL_SOCKET, SO_SNDBUF, &snd_size, &optlen);可以获得sock的缓冲区大小,但是这个大小和传输层的win的大小不一致,win是不可控制的,有内核完成大小控制。而win却刚好和 setsockopt(conn_sock, SOL_SOCKET, SO_RCVBUF, &rcv_size, optlen);的rcv_size一样大。
默认的sendbuffer在/proc/sys/net/ipv4/tcp_wmem
默认的recvbuffer在/proc/sys/net/ipv4/tcp_rmem
默认的sendbuffer在/proc/sys/net/core/wmem_max
默认的recvbuffer在/proc/sys/net/core/rmem_max
而最小值通过测试发现和网上说的不太一样,所以没有得出结论!!!mark一下,以后解决。
最小的sendbuffer=4608,recvbuffer=2304.
注意:不同的机器可能不同。我的机器版本:ubuntu14.04
write和read的阻塞和非阻塞行为:
read:
非阻塞:read读到数据,那么返回值为数据大小。读不到,则返回EAGAIN,表示数据还未就绪。
阻塞:read不管读不读够buffersize指定的数据,只要读到数据,都可能返回,没有数据则阻塞。
如果read套接字返回值为0,那么说明read到文件未,要close套接字。
write:
非阻塞:只要写入了数据就会返回写入数据的大小,当缓冲区满时,返回EAGAIN,表示还不能写入。
阻塞:这个和read不太一样,必须要写入buffersize的数据才能返回,否则阻塞。
write和sigpipe
阻塞和非阻塞的情况相同:
1.socket write中,对方socket断开(进入CLOSE_WAIT),收到RST包,第一次写,返回errno=104,不会产生SIGPIPE信号。但第二次写,就会产生sigpipe信号,errno=32(这个要对sigpipe做处理才能看到,因为不做处理,程序直接崩溃,根本看不到)。
可以使用shutdown进入半关闭状态。
2.如果read一个已经close的套接字,会产生errno=9(无效套接字)的错误。