TCP 安全可靠,可检查数据是否丢失,重传,重复等等 - 三路握手开启,四次连接终止
UDP 不安全,无连接,不可靠,但步骤较少,即时连接
SCTP 安全可靠,关联连接,持有一组IP,有类似路由的功能,寻路 - 四路握手开启,三次关联终止
TCP的TIME_WAIT状态是为了实现TCP的全双工连接终止(处理最终那个ACK丢失的情形),并允许老的重复分节从网络中消逝。
SCTP不像TCP那样需要TIME_WAIT状态,因为它使用了验证标记。
所有后续块都在捆绑它们的SCTP分组的公共首部标记了初始的INIT块和INIT ACK块中作为起始标记交换的验证标记;由来自旧连接的块通过所在SCTP分组的公共首部间接携带的验证标记对于新连接来说是不正确的。因此,SCTP通过放置验证标记值就避免了TCP在TIME_WAIT状态保持整个连接的做法。
一个TCP链接的套接字对(socket pair)是一个定义该链接的两个端点的四元组:本地IP地址、本地TCP端口号、外地IP地址、外地TCP端口号。套接字对唯一标识一个网络上的每个TCP链接。
就SCTP而言,一个关联由一组本地IP地址、一个本地端口、一组外地IP地址、一个外地端口标识。
在两个端点均非多宿这一最简单的情形下,SCTP与TCP所用的四元组套接字一致。然而在某个关联的任何一个端点为多宿的情形下,同一个关联可能需要多个四元组表示(这些四元组的IP地址各不相同,但端口号是一样的)。
IPv4主机对其产生的数据报执行分片,IPv4路由器则对其转发的数据报执行分片。然而IPv6只有主机对其产生的数据报执行分片,IPv6路由器不对其转发的数据报执行分片。
数据报大小、MTU、分片
IPv4最大 65536B ;IPv6最大 65575B
DF位 不分片
TCP MSS 一般设置为MTU减去IP和TCP首部的固定长度
TCP 发送缓冲区 使用SO_SNDBUF套接字选项来更改该缓冲区大小
只有收到来自对端的ACK,才会从套接字发送缓冲区中丢弃已确认的数据。TCP必须为已发送的数据保留一个副本,直到它被对端确认为止。
从写一个TCP套接字的write调用成功返回仅仅表示我们可以重新使用原来的应用进程缓冲区,并不表明对端的TCP或应用进程已接收到数据。
既然UDP是不可靠的,它不必保存应用进程数据的一个副本,因此无需一个真正的发送缓冲区。
从写一个UDP套接字的write调用成功返回表示所写的数据报或其所有片段已被加入数据链路层的输出队列。
SCTP必须等待SACK,在累积确认点超过已发送的数据后,才可以从套接字缓冲区中删除该数据。
IPv4地址和TCP或UDP端口号在套接字地址结构中总是以网络字节序来存储。
sin_zero字段未曾使用,不过在填写这种套接字地址结构时,我们总是把该字段置为0。
尽管多数使用该结构的情况不要求这一字段为0,但是当捆绑一个非通配的IPv4地址时,该字段必须为0。
IPv6的地址族是AF_INET6,而IPv4的地址族是AF_INET。
从内核到进程传递套接字地址结构时,把套接字地址结构大小这个参数从一个整数改为指向某个整数变量的指针,其原因在于:当函数被调用时,结构大小是一个值,它告诉内核该结构的大小,这样内核在写该结构时不至于越界;当函数返回时,结构大小又是一个结果,它告诉进程内核在该结构中究竟存储了多少信息。这种类型的参数称为值-结果(value-result)参数。
如果套接字地址结构是固定长度的,那么从内核返回的值总是那个固定长度。然而对于可变长度的套接字地址结构,返回值可能小于该结构的最大长度。
值-结果参数:
select函数中间的3个参数
getsockopt函数的长度参数
使用recvmsg函数时,msghdr结构中的msg_namelen和msg_controllen字段
ifconf结构中的ifc_len字段
sysctl函数两个长度参数中的第一个
当源字节串与目标字节串重叠时,bcopy能够正确处理,但是memcpy的操作结果确不可知。
ascii码(点分十进制)与网络字节序的二进制值之间的转换:
inet_aton
inet_addr
inet_ntoa
inet_pton
inet_ntop
主机字节序与网络字节序转换:
htons/htonl,ntohs/ntohl
connect收到RST的三个条件是:
目的地为某端口的SYN到达,然而该端口上没有正在监听的服务器;TCP想取消一个已有连接;TCP接收到一个根本不存在的连接上的分节。
当循环调用函数connect为给定主机尝试各个IP地址直到有一个成功时,在每次connect失败后,都必须close当前的套接字描述符并重新调用socket。
让内核来选择临时端口对于TCP客户来说是正常的,除非应用需要一个预留端口;然而对于TCP服务器来说却极为罕见,因为服务器是通过它们的众所周知端口被大家认识的。
例外:RPC-Remote Procedure Call
无论是网络字节序还是主机字节序,INADDR_ANY的值(为0)都一样,因此使用htonl并非必需,但一般还是按照主机字节序定义使用。
监听套接字/已连接套接字
一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字(也就是说对于它的TCP三路握手过程已经完成)。当服务器完成对某个给定客户的服务时,相应的已连接套接字就被关闭。
fork返回两次,它在调用进程(称为父进程)中返回一次,返回值是新派生进程(称为子进程)的进程ID号;
在子进程又返回一次,返回值为0。
因此,返回值本身告知当前进程是子进程还是父进程。
fork在子进程返回0而不是父进程的进程ID的原因在于:
任何子进程只有一个父进程,而且子进程总是可以通过getppid取得父进程的进程ID。
相反,父进程可以有许多子进程,而且无法获取多个子进程的进程ID。如果父进程想要跟踪所有子进程的进程ID,那么它必须记录每次调用fork的返回值。
fork有两个典型用法:
1.一个进程创建一个自身的副本,这样每个副本都可以在另一个副本执行其他任务的同时处理各自的某个操作。这是网络服务器的典型用法。
2.一个进程想要执行另一个程序。既然创建新进程的唯一方法是调用fork,该进程于是首先调用fork创建一个自身的副本,然后其中一个副本(通常为子进程)调用exec把自身替换成新的程序。这是诸如shell之类程序的典型用法。
exec把当前进程映像替换成新的程序文件,而且该新程序通常从main函数开始执行。进程ID并不改变。我们称调用exec的进程为调用进程,称新执行的程序为新程序。
进程在调用exec之前打开着的描述符通常跨exec继续保持打开。例外:使用fcntl设置FD_CLOEXEC描述符标志禁止掉。
如果我们确实想在某个TCP连接上发送一个FIN,那么可以改用shutdown函数以代替close。
SO_LINGER套接字选项
此选项指定函数close对面向连接的协议如何操作(如TCP)。注:内核缺省close操作是立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方。
返回某个套接字关联的本地协议地址,或者返回与某个套接字关联的外地协议地址
getsockname/getpeername
当一个服务器是由调用过accept的某个进程通过调用exec执行程序时,它能够获取客户的唯一途径便是调用getpeername。
临时分配的端口号,应该比1023大(我们不需要一个保留端口),比5000大(以免与许多源自Berkeley的实现分配临时端口的范围冲突),比49152小(以免与临时端口号的“正确”范围冲突),而且不应改与任何已注册的端口冲突。
环回地址
主要作用有两个:
一是测试本机的网络配置,能PING通127.0.0.1说明本机的网卡和IP协议安装都没有问题;
另一个作用是某些SERVER/CLIENT的应用程序在运行时需调用服务器上的资源,一般要指定SERVER的IP地址,但当该程序要在同一台机器上运行而没有别的SERVER时就可以把SERVER的资源装在本机,SERVER的IP地址设为127.0.0.1同样也可以运行。
本地回环地址指的是以127开头的地址(127.0.0.1 - 127.255.255.254),通常用127.0.0.1来表示。
ps中 WCHAN列指出进程处于睡眠状态时相应的条件
如:ps -t pts/6 -o pid,ppid,tty,stat,args,wchan -a
PID PPID TT STAT COMMAND WCHAN
3462 3204 pts/2 S+ ./tcp_server inet_csk_accept
3640 3557 pts/11 S+ ./tcp_client 127.0.0.1 n_tty_read
3641 3462 pts/2 S+ ./tcp_server sk_wait_data
3868 3463 pts/5 R+ ps -t pts/6 -o pid,ppid,tty -
信号 - 软中断
Sigfunc *signal(int signo, Sigfunc *func)
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if(signo == SIGALRM)
{
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
#endif
}else
{
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 4.4BSD */
#endif
}
if(sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}
一旦安装了信号处理函数,它便一直存在着。
在一个信号处理函数运行期间,正被递交的信号是阻塞的。而且,安装处理函数时在传递给sigaction函数的sa_mask信号集中指定的任何额外信号也被阻塞。一旦我们将sa_mask置为空集,意味着除了被捕获的信号外,没有额外信号被阻塞。
如果一个信号在阻塞期间产生了一次或多次,那么该信号被解阻塞之后通常只递交一次,也就是说Unix信号默认是不排队的。
其实,POSIX实时标准1003.1b定义了一些排队的可靠信号,不过一般我们不使用。
利用sigprocmask函数选择性地阻塞或解阻塞一组信号是可能的。这使得我们可以做到在一段临界区代码执行期间,防止捕获某些信号,以此保护这段代码。
慢系统调用:accept,read,对管道和终端设备的读和写。
适用于慢系统调用的基本规则是:当阻塞于某个慢系统调用的一个进程捕获某个信号且相应信号处理函数返回时,该系统调用可能返回一个EINTR错误。有些内核自动重启某些被中断的系统调用。不过为了便于移植,当我们编写捕获信号的程序时,我们必须对慢系统调用返回EINTR有所准备。
for(;;){
chilen = sizeof(cliaddr);
if((connfd = accept(listenfd, (SA *)&cliaddr, &clilen)) < 0)
{
if(errno == EINTR)
continue;
else
err_sys("accept error");
}
}
一般做法是发生错误后自己重启被中断的系统调用。对于accept以及诸如read、write、select和open之类函数来说,这是合适的。不过有一个函数我们不能重启:connect。如果该函数返回EINTR,我们就不能再次调用它,否则将立即返回一个错误。当connect被一个捕获的信号中断而且不自动重启时,我们必须调用select来等待连接完成(待续16.3)。