第1章 简介
1.4 错误处理
Unix errno值
#include <sys/errno.h>
strerror(errno);
1.7
OSI模型 应用层--表示层--会化层--传输层--网络层--数据链路层--物理层
TCP/IP模型 应用层-------------------------传输层--网络层--网络接口层
网络层设备:路由、多层交换机
数据链路层:交换机、网卡
物理层:HUB、中继器
第二章 传输层
2.3 UDP
每个UDP数据报都有一个长度。如果一个数据报正确地到达其目的地,那么该数据报的长度将随数据一道传递给接收端应用进程。
UDP提供无连接服务,一个UDP客户可以创建一个套接字发送一个数据报给一个给定的服务器,然后立即用同一个套接字发送另一个数据报给另一个服务器。
2.4 TCP
TCP是一个字节流协议,没有任何记录边界。
TCP提供确认、序列号、RTT估算、超时重传等机制。
TCP提供流量控制。TCP总是告知对端在任何时刻它一次能够从对端接收多少字节的数据,这称为通告窗口。在任何时刻,该窗口指出接收缓冲区中当前可用的空间量,从而确保发送端发送的数据不会使缓冲区溢出。
2.7 TIME_WAIT状态
存在TIME_WAIT状态的两个理由:1.实现终止TCP全双工连接的可靠性(防止最后的ACK丢失);2.允许老的重复分节在网络中消逝(不给处于TIME_WAIT状态的连接启动新的化身)。
2.9 端口号
众所周知0~1023 经注册1024~49151 动态的或私用的49152~65535
2.11 缓冲区大小及限制
在两台主机间的路径上的最小MTU称为路径MTU。由于在因特网中路由是非对称的,所以不同方向的路径MTU可以不同。
TCP输出:当应用进程调用write时,内核从应用进程的缓冲区中拷贝所有数据到套接口的发送缓冲区。如果套接口的发送缓冲区容不下应用进程的所有数据(或是应用进程的缓冲区大于套接口的发送缓冲区,或是套接口发送缓冲区还有其他数据),应用进程将被挂起(睡眠)。这里假设套接口是阻塞的。内核将不从write系统调用返回,直到应用进程缓冲区中的所有数据都拷贝到套接口发送缓冲区。所以从写一个TCP套接口的write调用成功返回仅仅表示我们可以重新使用应用进程的缓冲区。TCP必须保留数据拷贝直到对方确认为止。TCP以MSS大小或更小的块发送数据给IP,其中MSS是由对方通告的,当对方未通告时就用536这个值。IP给每个TCP分节安上IP头部以构成数据报,查找其目的IP地址的路由表项以确定外出接口,然后把数据报传递给相应的数据链路。IP可能先将数据报分片,再传送给链路层。每一个链路有一个输出队列,如果输出队列满,则分组丢弃,并沿协议栈向上返回一个错误。TCP将注意到这个错误,并在以后某个时刻重传这个分节。应用进程并不知道这种暂时情况。
UDP输出:UDP套接口有发送缓冲区大小,但是它仅仅是写到套接口的UDP数据报的大小上限。如果应用进程写一个大于套接口发送缓冲区的数据报,则返回EMSGSIZE错误。应用进程的数据在沿协议栈向下传递时,以某种形式拷贝到内核的缓冲区,当链路层把数据传出后这个拷贝就丢弃。UDP安上它的8个字节的头部以构成数据报并把它传递个IP。IP给它安上相应的IP头部,执行路由操作确定外出接口,然后或者直接把数据报加入链路层输出队列(如果适合于MTU),或是分片后再把每个片段加入数据链路层的输出队列。从写UDP套接口的write调用成功地返回表示数据报或所有片段已被加入链路层的输出队列。如果输出队列没有足够的空间存放数据报或它的某个分节,UDP将返回应用进程ENOBUFS错误。
第3章 套接口编程简介
3.4 字节排序函数
小端字节序是将低序字节存储在起始地址,大端字节序是将高序字节存储在起始地址。
union {
short s;
char c[sizeof(short)];
}un;
un.s = 0x0102;
if (un.c[0] == 1 && un.c[1] == 2)
printf("big-endian\n");
else if (un.c[0] == 2 && un.c[1] == 1)
printf("little-endian\n");
第4章 基本TCP套接口编程
int socket(int family, int type, int protocol); //>0成功,-1出错
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen); //0成功,-1出错
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen); //0成功,-1出错
int listen(int sockfd, int backlog); //0成功,-1出错
对于给定的监听套接口,内核要维护两个队列:
1.未完成连接队列,为每个这样的SYN分节开设一个条目:已由客户发出并到达服务器,服务器正在等待完成相应的TCP三次我收过程;
2.已完成连接队列,为每个已完成TCP三次握手过程的客户开设一个条目,这些套接口都处于ESTABLISHED状态。
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); //0成功,-1出错
pid fork(void); //子进程返回0,父进程返回子进程ID,-1出错
exec族函数
int execl(const char *pathname, const char *arg0, ...);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ..., char *const envp[]);
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ...);
int execvp(const char *filename, char *const argv[]);
int close(int sockfd); //0成功,-1出错
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
第5章 TCP客户-服务器程序示例
5.8 Posix信号处理
Posix信号语义:一旦安装了信号处理程序,它便一直安装着;当一个信号处理程序正在执行时,所递交的信号是阻塞的,而且,安装处理程序时定义在信号集sa_mask中传递给函数sigaction的任何额外信号
也是阻塞的。如果一个信号在阻塞时生成一次或多次,在信号解阻塞后一般只递送一次。也就是说,缺省时Unix信号是不排队的;利用函数sigprocmask来有选择性地阻塞或不阻塞一组信号是可能的,这使得
我们可以再某段临界区代码执行时,不许捕获某些信号,以此来保护这段代码。
5.10 wait和waitpid函数
网络编程时可能遇到的三种情况:1.当派生子进程时,必须捕获信号SIGCHLD;2.当捕获信号时,必须处理被中断的系统调用(如accept被中断返回EINTR);3.SIGCHLD的信号处理程序应使用waitpid函数以免留下僵尸进程。
5.12 服务器进程终止
当服务器TCP接收到来自客户端的数据时,由于先前打开的那个套接口的进程已终止,所以以RST响应。
5.13 SIGPIPE信号
当一个进程向接收了RST的套接口进行写操作时,内核给该进程发一个SIGPIPE信号。此信号的缺省行为就是终止进程。
5.14 服务器主机崩溃
假设服务器主机已崩溃,对客户的数据分节根本没响应,则错误为ETIMEOUT;但如果某些中间路由器判定服务器主机不可达,且以一个目的地不可达的ICMP消息响应,则错误时EHOSTUNREACH或ENETUNREACH。
5.15 服务器主机崩溃后重启
服务器重启后,它的TCP丢失了崩溃前的所有连接信息,所以服务器TCP对接收的客户数据分节以RST响应。
5.16 服务器主机关机
当Unix系统关机时,一般是由init进程给所有进程发信号SIGTERM,等待一段固定时间,然后给还在运行的所有进程发信号SIGKILL。当进程终止时,所有打开的描述字都关闭,然后按5.12节中所述步骤进行。
第6章 I/O复用:select和poll函数
6.2 I/O模型
一个输入操作一般有两个不同的阶段:1.等待数据准备好;2.从内核到进程拷贝数据。
6.2.1 阻塞式I/O模型
进程调用recvfrom,其系统调用直到数据报到达且被复制到应用进程的缓冲区中或者发生错误(信号中断)才返回。
6.2.2 非阻塞式I/O模型
进程调用recvfrom没有数据可返回时,内核立即返回一个EWOULDBLOCK错误。进程循环调用recvfrom,直到有数据被复制到应用进程缓冲区,recvfrom成功返回。这种轮询技术,会耗费大量CPU时间。
6.2.3 I/O复用模型
进程调用select,等待数据报套接字变为可读。当select可读时,进程调用recvfrom把所读数据报复制到应用进程缓冲区。表面上select需要两次系统调用,但其优势在于可以等待多个描述符就绪。
6.2.4 信号驱动I/O
进程开启套接字的信号驱动式I/O功能,并通过sigaction系统调用安装一个信号处理函数。当数据报准备好读取时,内核就为该进程产生一个SIGIO信号。进程随后既可以在信号处理函数中调用recvfrom
读取数据报,并通知主循环数据已准备好待处理,也可以立即通知主循环,让它读取数据报。
该模型的优势在于等待数据报到达期间进程不被阻塞。
6.2.5 异步I/O
进程调用异步I/O函数,这些函数的工作机制是:告知内核启动某个操作,并让内核在整个操作完成后通知进程。信号驱动式I/O是由内核通知进程何时可以启动一个I/O操作,而异步I/O模型是由内核通知
进程I/O操作何时完成。
6.2.7 同步I/O和异步I/O对比
同步I/O操作导致请求进程阻塞,直到I/O操作完成;异步I/O操作不导致请求进程阻塞。
前四种I/O模型都是同步I/O模型,因为真正的I/O操作阻塞进程。
6.3 select函数
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
struct timeval {
long tv_sec;
long tv_usec;
};
参数timeout有三种可能:
1.设置为空时永远等待,仅在有一个描述符准备好I/O时才返回;
2.等待一段固定时间,该时间内所有一个描述符准备好I/O就返回;
3.设置为0根本不等待,检查描述符后立即返回,轮询技术。
void FD_ZERO(fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
void FD_CLR(int fd, fd_set *fdset);
int FD_ISSET(int fd, fd_set *fdset);
6.3.1 描述符就绪条件
1.满足下列四个条件中的任何一个时,一个套接字准备好读。
a.该套接字接收缓冲区中的数据字节数大于等于套接字接收缓冲区低水位标记的当前大小。对这样的套接字执行读操作不会阻塞并将返回一个大于0的值;
b.该连接的读半部关闭。对这样的套接字的读操作将不阻塞并返回0;
c.该套接字是一个监听套接字且已完成的连接数不为0。
d.其上有一个套接字错误待处理。对这样的套接字的读操作将不阻塞并返回-1,同时把errno设置成确切的错误条件。
2.满足下列四个条件中的任何一个时,一个套接字准备好写。
a.该套接字发送缓冲区中的可用字节数大于等于套接字发送缓冲区低水位标记的当前大小。如果我们把该套接字设置成非阻塞,写操作将不阻塞并返回一个正值。
b.该连接的写半部关闭。对这样的套接字的写操作将产生SIGPIPE信号;
c.使用非阻塞式connect的套接字已建立连接,或者connect已经以失败告终。
d.其上有一个套接字错误待处理。对这样的套接字的读操作将不阻塞并返回-1,同时把errno设置成确切的错误条件。
3.如果一个套接字存在带外数据或者仍处于带外标记,那么它有异常条件待处理。
6.4 select用法
{
int maxfdp = 0;
int sock;
FILE *fp;
struct fd_set fds;
struct timeval timeout={3,0}; //select等待3秒,3秒轮询,要非阻塞就置0
char buffer[256] = {0}; //256字节的接收缓冲区
int len = -1;
FD_ZERO(&fds);
while(1) {
maxfdp = sock > fileno(fp) ? sock+1 : fileno(fp)+1; //描述符最大值加1
select(maxfdp, &fds, &fds, NULL, &timeout);
1.4 错误处理
Unix errno值
#include <sys/errno.h>
strerror(errno);
1.7
OSI模型 应用层--表示层--会化层--传输层--网络层--数据链路层--物理层
TCP/IP模型 应用层-------------------------传输层--网络层--网络接口层
网络层设备:路由、多层交换机
数据链路层:交换机、网卡
物理层:HUB、中继器
第二章 传输层
2.3 UDP
每个UDP数据报都有一个长度。如果一个数据报正确地到达其目的地,那么该数据报的长度将随数据一道传递给接收端应用进程。
UDP提供无连接服务,一个UDP客户可以创建一个套接字发送一个数据报给一个给定的服务器,然后立即用同一个套接字发送另一个数据报给另一个服务器。
2.4 TCP
TCP是一个字节流协议,没有任何记录边界。
TCP提供确认、序列号、RTT估算、超时重传等机制。
TCP提供流量控制。TCP总是告知对端在任何时刻它一次能够从对端接收多少字节的数据,这称为通告窗口。在任何时刻,该窗口指出接收缓冲区中当前可用的空间量,从而确保发送端发送的数据不会使缓冲区溢出。
2.7 TIME_WAIT状态
存在TIME_WAIT状态的两个理由:1.实现终止TCP全双工连接的可靠性(防止最后的ACK丢失);2.允许老的重复分节在网络中消逝(不给处于TIME_WAIT状态的连接启动新的化身)。
2.9 端口号
众所周知0~1023 经注册1024~49151 动态的或私用的49152~65535
2.11 缓冲区大小及限制
在两台主机间的路径上的最小MTU称为路径MTU。由于在因特网中路由是非对称的,所以不同方向的路径MTU可以不同。
TCP输出:当应用进程调用write时,内核从应用进程的缓冲区中拷贝所有数据到套接口的发送缓冲区。如果套接口的发送缓冲区容不下应用进程的所有数据(或是应用进程的缓冲区大于套接口的发送缓冲区,或是套接口发送缓冲区还有其他数据),应用进程将被挂起(睡眠)。这里假设套接口是阻塞的。内核将不从write系统调用返回,直到应用进程缓冲区中的所有数据都拷贝到套接口发送缓冲区。所以从写一个TCP套接口的write调用成功返回仅仅表示我们可以重新使用应用进程的缓冲区。TCP必须保留数据拷贝直到对方确认为止。TCP以MSS大小或更小的块发送数据给IP,其中MSS是由对方通告的,当对方未通告时就用536这个值。IP给每个TCP分节安上IP头部以构成数据报,查找其目的IP地址的路由表项以确定外出接口,然后把数据报传递给相应的数据链路。IP可能先将数据报分片,再传送给链路层。每一个链路有一个输出队列,如果输出队列满,则分组丢弃,并沿协议栈向上返回一个错误。TCP将注意到这个错误,并在以后某个时刻重传这个分节。应用进程并不知道这种暂时情况。
UDP输出:UDP套接口有发送缓冲区大小,但是它仅仅是写到套接口的UDP数据报的大小上限。如果应用进程写一个大于套接口发送缓冲区的数据报,则返回EMSGSIZE错误。应用进程的数据在沿协议栈向下传递时,以某种形式拷贝到内核的缓冲区,当链路层把数据传出后这个拷贝就丢弃。UDP安上它的8个字节的头部以构成数据报并把它传递个IP。IP给它安上相应的IP头部,执行路由操作确定外出接口,然后或者直接把数据报加入链路层输出队列(如果适合于MTU),或是分片后再把每个片段加入数据链路层的输出队列。从写UDP套接口的write调用成功地返回表示数据报或所有片段已被加入链路层的输出队列。如果输出队列没有足够的空间存放数据报或它的某个分节,UDP将返回应用进程ENOBUFS错误。
第3章 套接口编程简介
3.4 字节排序函数
小端字节序是将低序字节存储在起始地址,大端字节序是将高序字节存储在起始地址。
union {
short s;
char c[sizeof(short)];
}un;
un.s = 0x0102;
if (un.c[0] == 1 && un.c[1] == 2)
printf("big-endian\n");
else if (un.c[0] == 2 && un.c[1] == 1)
printf("little-endian\n");
第4章 基本TCP套接口编程
int socket(int family, int type, int protocol); //>0成功,-1出错
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen); //0成功,-1出错
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen); //0成功,-1出错
int listen(int sockfd, int backlog); //0成功,-1出错
对于给定的监听套接口,内核要维护两个队列:
1.未完成连接队列,为每个这样的SYN分节开设一个条目:已由客户发出并到达服务器,服务器正在等待完成相应的TCP三次我收过程;
2.已完成连接队列,为每个已完成TCP三次握手过程的客户开设一个条目,这些套接口都处于ESTABLISHED状态。
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); //0成功,-1出错
pid fork(void); //子进程返回0,父进程返回子进程ID,-1出错
exec族函数
int execl(const char *pathname, const char *arg0, ...);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ..., char *const envp[]);
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ...);
int execvp(const char *filename, char *const argv[]);
int close(int sockfd); //0成功,-1出错
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
第5章 TCP客户-服务器程序示例
5.8 Posix信号处理
Posix信号语义:一旦安装了信号处理程序,它便一直安装着;当一个信号处理程序正在执行时,所递交的信号是阻塞的,而且,安装处理程序时定义在信号集sa_mask中传递给函数sigaction的任何额外信号
也是阻塞的。如果一个信号在阻塞时生成一次或多次,在信号解阻塞后一般只递送一次。也就是说,缺省时Unix信号是不排队的;利用函数sigprocmask来有选择性地阻塞或不阻塞一组信号是可能的,这使得
我们可以再某段临界区代码执行时,不许捕获某些信号,以此来保护这段代码。
5.10 wait和waitpid函数
网络编程时可能遇到的三种情况:1.当派生子进程时,必须捕获信号SIGCHLD;2.当捕获信号时,必须处理被中断的系统调用(如accept被中断返回EINTR);3.SIGCHLD的信号处理程序应使用waitpid函数以免留下僵尸进程。
5.12 服务器进程终止
当服务器TCP接收到来自客户端的数据时,由于先前打开的那个套接口的进程已终止,所以以RST响应。
5.13 SIGPIPE信号
当一个进程向接收了RST的套接口进行写操作时,内核给该进程发一个SIGPIPE信号。此信号的缺省行为就是终止进程。
5.14 服务器主机崩溃
假设服务器主机已崩溃,对客户的数据分节根本没响应,则错误为ETIMEOUT;但如果某些中间路由器判定服务器主机不可达,且以一个目的地不可达的ICMP消息响应,则错误时EHOSTUNREACH或ENETUNREACH。
5.15 服务器主机崩溃后重启
服务器重启后,它的TCP丢失了崩溃前的所有连接信息,所以服务器TCP对接收的客户数据分节以RST响应。
5.16 服务器主机关机
当Unix系统关机时,一般是由init进程给所有进程发信号SIGTERM,等待一段固定时间,然后给还在运行的所有进程发信号SIGKILL。当进程终止时,所有打开的描述字都关闭,然后按5.12节中所述步骤进行。
第6章 I/O复用:select和poll函数
6.2 I/O模型
一个输入操作一般有两个不同的阶段:1.等待数据准备好;2.从内核到进程拷贝数据。
6.2.1 阻塞式I/O模型
进程调用recvfrom,其系统调用直到数据报到达且被复制到应用进程的缓冲区中或者发生错误(信号中断)才返回。
6.2.2 非阻塞式I/O模型
进程调用recvfrom没有数据可返回时,内核立即返回一个EWOULDBLOCK错误。进程循环调用recvfrom,直到有数据被复制到应用进程缓冲区,recvfrom成功返回。这种轮询技术,会耗费大量CPU时间。
6.2.3 I/O复用模型
进程调用select,等待数据报套接字变为可读。当select可读时,进程调用recvfrom把所读数据报复制到应用进程缓冲区。表面上select需要两次系统调用,但其优势在于可以等待多个描述符就绪。
6.2.4 信号驱动I/O
进程开启套接字的信号驱动式I/O功能,并通过sigaction系统调用安装一个信号处理函数。当数据报准备好读取时,内核就为该进程产生一个SIGIO信号。进程随后既可以在信号处理函数中调用recvfrom
读取数据报,并通知主循环数据已准备好待处理,也可以立即通知主循环,让它读取数据报。
该模型的优势在于等待数据报到达期间进程不被阻塞。
6.2.5 异步I/O
进程调用异步I/O函数,这些函数的工作机制是:告知内核启动某个操作,并让内核在整个操作完成后通知进程。信号驱动式I/O是由内核通知进程何时可以启动一个I/O操作,而异步I/O模型是由内核通知
进程I/O操作何时完成。
6.2.7 同步I/O和异步I/O对比
同步I/O操作导致请求进程阻塞,直到I/O操作完成;异步I/O操作不导致请求进程阻塞。
前四种I/O模型都是同步I/O模型,因为真正的I/O操作阻塞进程。
6.3 select函数
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
struct timeval {
long tv_sec;
long tv_usec;
};
参数timeout有三种可能:
1.设置为空时永远等待,仅在有一个描述符准备好I/O时才返回;
2.等待一段固定时间,该时间内所有一个描述符准备好I/O就返回;
3.设置为0根本不等待,检查描述符后立即返回,轮询技术。
void FD_ZERO(fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
void FD_CLR(int fd, fd_set *fdset);
int FD_ISSET(int fd, fd_set *fdset);
6.3.1 描述符就绪条件
1.满足下列四个条件中的任何一个时,一个套接字准备好读。
a.该套接字接收缓冲区中的数据字节数大于等于套接字接收缓冲区低水位标记的当前大小。对这样的套接字执行读操作不会阻塞并将返回一个大于0的值;
b.该连接的读半部关闭。对这样的套接字的读操作将不阻塞并返回0;
c.该套接字是一个监听套接字且已完成的连接数不为0。
d.其上有一个套接字错误待处理。对这样的套接字的读操作将不阻塞并返回-1,同时把errno设置成确切的错误条件。
2.满足下列四个条件中的任何一个时,一个套接字准备好写。
a.该套接字发送缓冲区中的可用字节数大于等于套接字发送缓冲区低水位标记的当前大小。如果我们把该套接字设置成非阻塞,写操作将不阻塞并返回一个正值。
b.该连接的写半部关闭。对这样的套接字的写操作将产生SIGPIPE信号;
c.使用非阻塞式connect的套接字已建立连接,或者connect已经以失败告终。
d.其上有一个套接字错误待处理。对这样的套接字的读操作将不阻塞并返回-1,同时把errno设置成确切的错误条件。
3.如果一个套接字存在带外数据或者仍处于带外标记,那么它有异常条件待处理。
6.4 select用法
{
int maxfdp = 0;
int sock;
FILE *fp;
struct fd_set fds;
struct timeval timeout={3,0}; //select等待3秒,3秒轮询,要非阻塞就置0
char buffer[256] = {0}; //256字节的接收缓冲区
int len = -1;
FD_ZERO(&fds);
while(1) {
FD_SET(sock, &fds); //添加描述符
FD_SET(fileno(fp), &fds); //同上maxfdp = sock > fileno(fp) ? sock+1 : fileno(fp)+1; //描述符最大值加1
select(maxfdp, &fds, &fds, NULL, &timeout);
if (FD_ISSET(sock, &fds)) { //测试sock是否可读,即是否网络上有数据
//读取网络数据
if (有错误) {
FD_CLR(fp, &fds);
}
else {
if(FD_ISSET(fp, &fds)) //测试文件是否可写
//写入文件
}
}
}
}
本文介绍了网络编程的基本概念,包括TCP/IP模型、OSI模型、错误处理、传输层协议(TCP和UDP)、端口号分配、缓冲区大小限制等内容。此外,还详细讲解了套接字编程的基础知识、基本的TCP套接字编程、I/O复用技术select和poll函数的应用。
370

被折叠的 条评论
为什么被折叠?



