TCP/IP socket

## TCP Socket
收发缓冲区:
每个socket在linux内核中都有一个发送缓冲区和一个接收缓冲区。
只要对端将数据发送过来,linux内核TCP/IP协议栈就会负责将数据缓存到socket对应的接收缓冲区中,无论是否调用recv。
recv()所做的工作,只是把内核缓冲区中的数据拷贝到应用层用户的buffer里面。
调用send()发送的数据的时候,只是将应用层buffer中数据拷贝进入socket的内核发送缓冲区之中,然后send便会在上层返回(注意:数据是否发送过去不一定)。
linux内核TCP/IP协议栈负责将数据发送到对端。

异常场景:
应用进程一直没有读取数据,导致接受缓冲区满了。此时接收端通知发送端,接收窗口关闭,保证接收缓冲区不会溢出。(TCP流控机制 滑动窗口法)


socket内核发送缓冲区的默认大小 (16KB)
cat /proc/sys/net/ipv4/tcp_wmem
4096 16384 4161536


## Errors:socket层标准错误
These are some standard errors generated by the socket layer.

EAGAIN or EWOULDBLOCK: socket被标记为非阻塞,当recv操作阻塞或者接收超时。
The socket is marked nonblocking and the receive operation would block, or a receive timeout had been set and the timeout expired before data was received.

EBADF: 无效socket
The argument sockfd is an invalid descriptor.

ECONNREFUSED: 远程主机拒绝网络连接
A remote host refused to allow the network connection (typically because it is not running the requested service).

EFAULT: 内存越界
The receive buffer pointer(s) point outside the process's address space.

EINTR: 被信号打断
The receive was interrupted by delivery of a signal before any data were available; see signal(7).

EINVAL: 参数无效
Invalid argument passed.

ENOTCONN: 没有建连的socket
The socket is associated with a connection-oriented protocol and has not been connected.

## 1: socket
#include
int socket(int domain, int type, int protocol);
domain : AF_UNIX--本地socket
         AF_INET--ipv4
         AF_INET--ipv6
type: SOCKT_STREAM--TCP
      SOCKT_DGRAM—UDP

## 2: socket reuse (ip复用 或者port复用)
一个socket的五元组:
{, , , , }
(1) non-reuse 
如果指定src addr为0.0.0.0,将不再表示某一个具体的地址,而是表示本地的所有的可用地址。
指定src addr为0.0.0.0,将不再表示某一个具体的地址,而是表示本地的所有的可用地址。

reuse有三个级别:
non-reuse: (src addr,src port)不能冲突。eg: ip1:port1和ip2:port1不冲突。0.0.0.0:port1和ip1:port1冲突。
             0.0.0.0和其他IP冲突。
reuse-addr: (src addr,src port)不能冲突。 eg: ip1:port1和ip2:port1不冲突。0.0.0.0:port1和ip1:port1不冲突。
             0.0.0.0和其他IP不冲突。
reuse-port: (src addr,src port)可以冲突。eg: 0.0.0.0:port1和0.0.0.0:port1不冲突。

## 使用setsockopt()设置复用。
设置reuse-addr:
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int));
设置reuse-port:
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof(int));


## 3: recv
定义:
The recv() function is normally used only on a connected socket

原理:
recv只是将socket对应的接收缓冲区的数据copy到buffer中,真正的是Linux内核的TCP/IP网络协议栈负责接收数据。

接口:
    #include
    ssize_t recv(int socket, void *buffer, size_t length, int flags);

参数:socket:
     buffer: 缓冲区。
     length: buffer的长度
     flags: 一般设置为0.

返回值:>0: 返回接收的字节数 -1:发生错误 0:对端关闭了连接 (无论阻塞还是非阻塞,返回值的都是这3钟)
    These calls return the number of bytes received, or -1 if an error occurred。
    For TCP sockets, the return value 0 means the peer has closed its half side of the connection.

阻塞与非阻塞:
阻塞: 默认为阻塞。没有数据时,recv函数将一直等待获取到数据,或者发生报错。
非阻塞: 没有数据时,不会阻塞着读数据,而是返回-1(需要判断errno),需要循环读取数据。

在返回值处理上:
阻塞: 缓冲区有数据,成功执行返回接收到的字节数(>0)。
      另一端关闭(=0)。
      缓冲区无数据,阻塞一直等。
      连接失败(-1), errno = ENOCONN。
非阻塞:缓冲区有数据,成功执行返回接收到的字节数 (>0)。
      另一端关闭(=0).
      缓冲区无数据,立即返回, 返回值为-1, 同时errno = EAGAIN || EWOULDBLOCK。
      连接失败(-1), errno = ENOCONN。

## 4: 设置阻塞方式
(1) sock = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
或者
(2) int flag = fcntl(sock, F_F_GETFL);fcntl(sock, F_SETFL, flag | O_NONBLOCK);


## 5: connect
#include
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

connect: 默认为阻塞式的。

非阻塞connect实现机制:
a: 创建socket,利用fcntl将其设置为非阻塞。
b: 调用connect函数,如果返回0,则连接建立;如果返回-1,检查errno ,如果值为 EINPROGRESS,则连接正在建立。如果值不是EINPROGRESS,建连错误。
c: 调用阻塞式接口select/pool检测是否连接建立。这里对于select/pool可以采用设置超时时间来等待。
d: 在select/pool的超时时间内成功建立,返回成功建连。否则调用getsockopt获取错误信息。

示例:
https://blog.csdn.net/qq_43684922/article/details/106444292?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0-106444292-blog-119000780.235^v27^pc_relevant_t0_download&spm=1001.2101.3001.4242.1&utm_relevant_index=3


## send

#include
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

返回值: > 0 发送的的数据字节数。 -1发送发生错误, 查看errno。=0 N/A
Return Value
On success, these calls return the number of characters sent. 
On error, -1 is returned, and errno is set appropriately.

阻塞与非阻塞:
在阻塞模式下,send函数的过程是将应用程序请求发送的数据拷贝到内核发送缓存中,待发送数据完全被拷贝到内核发送缓存区中才返回,当然如果内核发送缓存区一直没有空间能容纳待发送的数据,则一直阻塞。
When the message does not fit into the send buffer of the socket, send() normally blocks。
(1)如果内核发送缓冲区可用大小为0,send()直接堵塞
(2)如果内核发送缓冲区可用空间小于待发送的数据长度len,则send()函数会先把部分数据拷贝到内核发送缓冲区中,然后会阻塞等待有空间,再将剩余数据发送到内核缓冲区。
(3)如果待发送数据的长度大于s的内核发送缓冲区的长度时,会先将s(发送端)的内核发送缓冲区填满,然后发送端会将内核发送缓冲区的数据发送到接收端socket的内核接收缓冲区,所以s(发送端)的内核发送缓冲区又会慢慢腾出空间,send又会将待发送数据往s(发送端)的内核发送缓冲区中copy。

在非阻塞模式下,send函数的过程也是将应用程序请求发送的数据拷贝内核发送缓存中。
send函数不需要等到待发送数据完全被拷贝到内核发送区中才返回。
如果内核缓存区可用空间不够容纳所有待发送数据,则尽能力的拷贝,返回成功拷贝的大小;
如果缓存区可用空间为0,则返回-1,同时设置errno为EAGAIN.
In nonblocking mode it would fail with the error EAGAIN or EWOULDBLOCK in this case. The select(2) call may be used to determine when it is possible to send more data.

errno:
EBADF 参数s 非合法的socket处理代码。
EFAULT 参数中有一指针指向无法存取的内存空间
ENOTSOCK 参数s为一文件描述词,非socket。
EINTR 被信号所中断。
EAGAIN 此操作会令进程阻断,但参数s的socket为不可阻断。
ENOBUFS 系统的缓冲内存不足
ENOMEM 核心内存不足
EINVAL 传给系统调用的参数不正确
 

6: set_socketopt / get_scoketopt


7: TCP保活机制
应用场景:
(1) 如果对端程序是正常工作的。当TCP保活的探测报文发送给对端, 对端会正常响应,TCP保活时间会被重置,等待下一个TCP保活时间的到来。
(2) 由于某种原因导致报文不可达对端,当TCP保活的探测报文发送给对端后,没有响应,连续几次,达到保活探测次数后,会报告该TCP连接已经死亡,发送RST包给对端,并且本端关闭。

linux内核参数
sysctl -a | grep keepalive 
tcp_keepalive_time:
周期,每次发送探活心跳包的时间间隔,是保活机制的周期时间。
tcp_keepalive_probes:
次数,在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包次数。
tcp_keepalive_intvl
频率, 在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包的时间间隔。

意义:
(1) 保证连接有效性,keepalive机制会在空闲一段时间之后发送探测包进行检测,从而发现连接异常。
(2) 这是一种linux内核层的操作,默认不打开,需要应用set_socketopt设置生效。当然,应用层可以自己提供心跳机制。
(3) 客户端或者服务端意外断电、死机、进程挂掉重启等场景下的探活。
(4) 网络出现问题,连接双方无法知道一直等待, 探活。
(5) 大量CLOSE_WAIT, 使用keepalive进行探活。

8: RST报文段
 

你可能感兴趣的:(网络,linux,服务器)