unplinux accept函数数在哪儿

博客访问: 27706
博文数量: 15
博客积分: 0
博客等级: 民兵
技术积分: 200
注册时间:
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: 服务器与存储
TCP分组交换图
TIME_WAIT状态的持续时间为2MSL(最长生命周期),原因:
& &1. &&可靠的实现TCP全双工连接的终止。如果最后客户发送的ACK N+1丢失,服务器将重新发送FIN N,因此客户端必须维护状态信息以重发最终的ACK。
& &&2. &&允许老的重复的分节在网络中消逝。假设在关闭这个连接之后在相同的IP地址和端口之间建立新的TCP连接,TCP必须防止上一个连接的老的分组发送到新的连接,2MSL将保证某个方向上的分组最多存活MSL即被丢弃。
& & & &不要用stdio读写socket,因为I/O标准库stdio中的函数(例如fgets,fputs)在内部维持一个静态的缓冲区,当从网络接收和发送数据时,数据首先会保存在缓冲区中,不会立即被应用程序接收或直接发送到网络上,这会导致一些问题。
& & & &对于网络IO,有三点注意的地方:
& & & &1. &&不要在同一个描述符上同时使用带缓冲和不带缓冲的IO函数。
& & & &2. &&不要在IO函数中使用静态缓冲区。
& & & &3. &&读写socket最好自己实现缓冲区,以减少系统调用的次数,提高效率。
& &connect函数:
& & & 客户在调用connect函数前不必非得调用bind函数,如果需要,内核会确定源IP地址,并选择一个临时端口作为源端口。
& & & connect函数将激发TCP三次握手,在连接成功或出错时才返回,出错原因:
& & & 1. TCP客户没有收到SYN分节的响应,则返回ETIMEDOUT错误。以4.4BSD为例,当客户发送第一个SYN之后么有收到响应,则等待6秒后再发送一个,若还无响应,则等待24秒后再发送一个,总共等待75秒后仍未收到响应则返回错误。
& & &&2. 若对客户的SYN分节响应是RST(复位),则表明该服务器在我们指定的端口上没有进程在等待与之连接(例如服务器进程没有运行)。
& & & 产生RST的三个条件:(1).目的地为某端口的SYN到达,然而该端口上没有正在监听的服务器;(2).TCP想取消一个已有的连接。(3). TCP接收到一个根本不存在的连接上的分节。
& & &&3. 若客户发出的SYN在中间的某个路由器引发一个"destination unreachable(目的地不可达)"ICMP错误,则认为是一种软错。客户主机将保存该消息,并按照第一种情况中所述的时间间隔继续发送SYN,若在某个规定的时间后仍未收到响应,则把保存的消息(ICMP错误)作为EHOSTUNREACH或ENETUNREACH错误返回给进程。
& &&&bind函数:
& & & 1. 如果一个TCP客户或者服务器没有调用bind绑定一个端口,当调用connect或listen是,内核就要为相应的套接口选择一个临时端口。
& & &&2. 进程把一个特定IP地址绑定到它的套接口上,对于TCP客户,这就为该套接口上发送的IP数据报指派了源IP地址,对于TCP服务器,这就限定了该套接口只接收那些目的地为这个IP地址的客户连接。
& & &3. 如果TCP服务器没有把IP地址绑定到它的套接口上,内核就把客户发送的SYN的宿IP地址作为服务器的源IP地址。(宿IP地址指的是接收端的IP地址,在这句话中就是SYN中指定的服务器的IP地址)
& &&&listen函数:
&& &&调用listen函数导致套接口由CLOSED状态转换到LISTEN状态。内核为每一个给定的监听套接口维护两个队列:
& & &1. 未完成队列。每个这样的SYN分节对应一项:已由某个客户发出并到达服务器,而服务器正在等待完成TCP三次握手的过程,这些套接口处于SYN_RCVD状态。
& & &2. 已完成连接队列。已完成TCP三次握手的客户对应其中一项。这些套接口处于ESTABLISHED状态。
& & & &当客户的SYN到达时,TCP在未完成的连接队列中创建一个新项,然后响应以三路握手的第二个分节:服务器的SYN响应,其中捎带对客户SYN的ACK。这一项一直保留在未完成连接的队列中,直到三路握手的第三个分节(客户最后的ACK)到达或超时为止。当进程调用accept时,已完成连接队列中的队头项返回给进程,或者如果该队列为空,进程进入睡眠,直到TCP在该队列中放入一项才唤醒它。
& & & &当一个客户的SYN到达时,若这些队列是满的,TCP就忽略该分节,也就是不发送RST。这是因为:这种情况是暂时的,客户TCP将重发SYN,期望不久就能在这些队列中找到可用空间。
&&close函数:
& & close一个TCP套接口的默认行为是把该套接口标记成已关闭,然后立即返回到进程。该套接口不能再被调用进程使用,然而TCP将尝试发送已排队等待发送到对端的任何数据,发送完毕后发生的是正常的TCP终止分节。并发服务器中父进程关闭已连接套接口只是导致描述符引用计数减1。如果需要某个TCP连接发送一个FIN,那么可以改用shutdown函数代替close。
& & &getsockname和getpeername分别返回与某个套接口关联的本地协议地址和对端协议地址,当我们不知道本地地址和端口(bind通配地址和端口,由内核指定)或对端地址时可用上述两个函数获取。
& & &当一个服务器是由调用过accept的某个进程通过调用exec更换了程序时,它能够获取客户身份的唯一途径就是调用getpeername。因为当子进程执行了exec之后,子进程的内存影像就会被替换(也就是说包含对端地址
的那个套接口地址结构丢失了),不过已连接的套接口描述字跨exec继续保持开放。
& & &本章例子为一个echo服务器,使用fork派生子进程处理客户的数据并返回收到的数据。
&正常终止:
& & &1. 客户端输入一个EOF(CTRL + D),客户端fgets返回一个空指针,客户端main函数调用exit终止。
& & &2. 进程终止会关闭打开的文件描述符,内核会关闭客户端的套接字,这导致客户TCP发送一个FIN给服务器,服务器TCP则以ACK响应,至此,服务器套接口处于CLOSE_WAIT状态,客户套接口处于FIN_WAIT_2状态。
& & &3. 当服务器收到FIN时,服务器阻塞在read调用,于是read返回0,这导致服务器子进程的main函数调用exit终止。
& & &4. 内核关闭服务器子进程的套接口,服务器TCP发送一个FIN到客户端,客户端返回一个ACK。至此,连接完全终止,客户套接口进入TIME_WAIT状态。
& & &5. 服务器子进程终止时,给父进程发送SIGCHLD信号。由于父进程未处理,子进程进入僵死状态。
accept返回前连接夭折:
& & & 这种情况下POSIX指出返回的errno值为ECONNABORTED(软件引起的连接夭折),这种情况下,服务端忽略该错误并再次调用accept就行。
& & &&&服务器进程终止:
& & & 这里模拟服务器进程崩溃的情形,发生的步骤如下:
& & & 1. 客户端和服务器连接建立之后找到服务器子进程,用kill命令杀死它,这导致在关闭套接口时向客户发送发送一个FIN,而客户TCP响应一个ACK。这是TCP连接终止的前一半工作。
& & & 2. SIGCHLD信号发送给服务器父进程。
& & & 3. 由于客服端阻塞在STDIN的fgets调用上,等待从终端接收一行文本,因此客户TCP仅仅是对FIN响应一个ACK。
& & & 4. 当我们在客户端STDIN上输入一行文本"another line"时,客户端调用writen,客户TCP接着把数据发送给服务器。TCP允许这么做,因为客户TCP收到FIN只是表示服务器进程关闭了连接的服务端,从而不再往其中发送任何数据而已(类似于服务器调用shutdown)。FIN的接收并没有告知客户TCP服务器进程已经终止。
& & & 5. 当服务器TCP接收到来自客户的数据时,既然打开那个套接口的进程已经终止,于是响应一个RST。
& & & 6. 然而客户进程看不到这个RST,因为它调用writen后立即调用readline,并且由于第一步中接收到的FIN,所调用的readline立即返回0(表示EOF),客户进程并未预料会收到EOF(因为发送的是"another line",不是EOF)。
& & &&7. 客户如果对收到EOF的处理是终止进程,将关闭套接字。
& & & 这个例子中的问题还取决于时序。客户调用readline既可能发生在服务器RST被客户收到之前,也可能发生在收到之后,如果是readline发生在收到RST之前,那么结果如上所示,否则结果是由readline返回一个ECONNECTIONSET(对方复位连接)错误。
& & & 这个例子问题在于:当FIN到达套接口时,客户正阻塞在fgets调用上,对此,应该使用select(poll/epoll)等待接收数据(从STDIN或套接口),这样一旦杀死服务器进程,客户就会立即被告知已收到FIN。
SIGPIPE信号
& & & &&如果客户端不理会readline函数返回的错误,反而写入更多的数据到服务端,那么将会引起错误。
& & &&规则: 当进程向一个已收到RST的套接口执行写操作时,内核向该进程发送一个SIGPIPE信号,该信号的默认行为是终止进程,因此进程必须捕获它以免被终止。
& & & Writen(sockfd, sendline, 1); &//向接收到FIN的套接口写,将收到对端发来的RST
& & & Writen(sockfd, sendline+1, strlen(sendline)-1); //向收到RST的套接口写,将引发SIGPIPE信号
& & & 第一个写操作引发RST,第二个写操作引发SIGPIPE信号,写一个收到FIN的套接口不成问题,但是写一个已收到RST的套接口则会出错。对SIGPIPE的处理取决于应用进程的意图,当使用多个套接口时,SIGPIPE信号将无法告诉我们是哪个套接口出了错,如果需要知道是哪个write出了错,那么我们只能忽略该信号,然后从信号处理函数返回后处理来自write的EPIPE。
服务器主机崩溃
& & 1. 在客户上键入一行文本,由writen写入内核,再由客户TCP作为一个数据分节发出。客户随后阻塞在readline调用,等待回射的应答。
& & 2. 由于客户收不到应答ACK,因此客户TCP会持续重传数据分节,试图从服务器收到应答ACK,当客户TCP最终放弃重传时,服务器仍然没有重新启动,由于客户阻塞在readline调用上,那么将返回一个错误。
& & 3. 服务器主机崩溃,从而对客户的数据分节没有响应,那么将返回错误ETIMEDOUT。如果某个中间路由器判定服务器不可达,将响应一个"destination unreachable(目的地不可达)"ICMP消息,那么返回的错误是EHOSTUNREACH或ENETUNREACH。
& & 尽管最终发现了对方崩溃或不可达,但是仍然消耗了大量的时间,为了更块的检测这种情况,可以给readline调用设置一个超时。
服务器崩溃后重启
&&&1. 启动服务器和客户,并在客户键入一行文本以确认已经连接。
& &2. 服务器主机崩溃并且重启。
& &3. 在客户上键入一行文本,发送到服务器端。
& &4. 服务器重启后,它的TCP丢失了崩溃前的所有连接信息,因此服务器TCP对所收到的客户的数据分节响应一个RST。
& &5. 客户TCP收到该RST后,客户正阻塞在readline调用,导致该调用返回ECONNRESET错误。
& &如果客户想检测服务器是否崩溃,可以采用SO_KEEPALIVE套接口选项或心跳机制。
服务器主机关机
& &如果连接建立之后服务器主机关机,那么服务器子进程终止的时候,将关闭所有打开的文件描述符,发生的步骤和服务器子进程关闭一样。
第六章& & &&
& & 1. 在读写socket时,不要使用stdio库函数,考虑有多个来自标准输入的文本行可读的情况,假设使用fgets读取输入,这将使得数据被读入到stdio缓冲区中,但fgets返回时仅仅返回其中第一行,其他行仍然留在stdio缓冲区中,将导致返回的数据不完整。
套接口描述符就绪条件:
& & & &在内核中,为套接口准备了一个发送缓冲区和接收缓冲区,缓冲区状态的变化(可读or不可读,可写or不可写)与缓冲区的low-water mark相关:
& & & &当下面四个条件中的任何一个满足时,套接口准备好读(LT模式):
& & & &a. &套接口的接收缓冲区中的数据字节数大于等于low-water mark。对这样的套接口的读操作将不阻塞并返回一个大于0的值。
& & & & b. &该连接的读这一半关闭(也就是接收了FIN的TCP连接)。对这样的套接口的读操作将不阻塞并返回0(也就是返回EOF)。
& & & & c. &该套接口是一个监听套接口且已完成的连接数不为0。对这样的套接口调用accept将马上返回。
& & & & d. &套接口上有一个错误待处理。对这样的套接口进行读操作将不阻塞并返回-1,同时把errno设置成确切的错误条件。这些待处理的套接口错误也可以通过指定SO_ERROR套接口选项调用getsockopt获取并清除。(如果对端TCP发送一个RST:对端主机崩溃并重新启动,那么该套接口变为可读,read返回-1,errno中含有确切的错误码)
& & & & 当下面四个条件中的任何一个满足时,套接口准备好写(LT模式):
& & & &a. &套接口的发送缓冲区中的可用空间字节数大于等于low-water mark,并且或者(1)该套接口已连接,或者(2)该套接口不需要连接(例如UDP套接口)。这意味着把这样的套接口设置为NONBLOCKING,写操作将不阻塞并返回一个正值。
& & & &b. &该连接的写这一半关闭。对这样的套接口的写操作将产生SIGPIPE信号。
& & & &c. &该套接口早先使用非阻塞connect以建立连接,并且连接已经异步建立或者connect已经失败告终。
& & & d. &接口上有一个错误待处理。对这样的套接口的写操作将不阻塞并返回-1,同时把errno设置成确切的错误条件。这些待处理的套接口错误也可以通过指定SO_ERROR套接口选项调用getsockopt获取并清除。
LT和ET模式
& & &select和poll都是LT模式,epoll有LT和ET模式。
从socket读数据
向socket写数据
& & & &ET和LT模式的区别在于level-trigger模式下只要某个socket处于readable/writable状态,无论什么时候进行epoll_wait都会返回该socket;而edge-trigger模式下只有某个socket从unreadable变为readable或从unwritable变为writable时,epoll_wait才会返回该socket。
& & & &所以,在epoll的ET模式下,正确的读写方式为:
& & & &读:只要可读,就一直读,直到返回0,或者 errno = EAGAIN
& & & &写: &只要可写,就一直写,直到数据发送完,或者 errno = EAGAIN
while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) {
&&&&n += nread;
if (nread == -1 && errno != EAGAIN)
&&&&perror("read error");
正确的写:
int nwrite, data_size = strlen(buf);
n = data_size;
while (n > 0) {
&&&&nwrite = write(fd, buf + data_size - n, n);
&&&&if (nwrite < n) {
&&&&&&&&if (nwrite == -1 && errno != EAGAIN) {
&&&&&&&&&&&&perror("write error");
&&&&&&&&break;
&&&&n -= nwrite;
while ((conn_sock = accept(listenfd,(struct sockaddr *) &remote, (size_t *)&addrlen)) > 0) {
&&&&handle_client(conn_sock);
if (conn_sock == -1) {
&&&&if (errno != EAGAIN && errno != ECONNABORTED && errno != EPROTO && errno != EINTR)
&&&&perror("accept");
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/epoll.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <errno.h>
#define MAX_EVENTS 10
#define PORT 8080
//设置socket连接为非阻塞模式
void setnonblocking(int sockfd) {
&&&&int opts;
&&&&opts = fcntl(sockfd, F_GETFL);
&&&&if(opts < 0) {
&&&&&&&&perror("fcntl(F_GETFL)\n");
&&&&&&&&exit(1);
&&&&opts = (opts | O_NONBLOCK);
&&&&if(fcntl(sockfd, F_SETFL, opts) < 0) {
&&&&&&&&perror("fcntl(F_SETFL)\n");
&&&&&&&&exit(1);
int main(){
&&&&struct epoll_event ev, events[MAX_EVENTS];
&&&&int addrlen, listenfd, conn_sock, nfds, epfd, fd, i, nread, n;
&&&&struct sockaddr_in local, remote;
&&&&char buf[BUFSIZ];
&&&&//创建listen socket
&&&&if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
&&&&&&&&perror("sockfd\n");
&&&&&&&&exit(1);
&&&&setnonblocking(listenfd);
&&&&bzero(&local, sizeof(local));
&&&&local.sin_family = AF_INET;
&&&&local.sin_addr.s_addr = htonl(INADDR_ANY);;
&&&&local.sin_port = htons(PORT);
&&&&if( bind(listenfd, (struct sockaddr *) &local, sizeof(local)) < 0) {
&&&&&&&&perror("bind\n");
&&&&&&&&exit(1);
&&&&listen(listenfd, 20);
&&&&epfd = epoll_create(MAX_EVENTS);
&&&&if (epfd == -1) {
&&&&&&&&perror("epoll_create");
&&&&&&&&exit(EXIT_FAILURE);
&&&&ev.events = EPOLLIN;
&&&&ev.data.fd = listenfd;
&&&&if (epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev) == -1) {
&&&&&&&&perror("epoll_ctl: listen_sock");
&&&&&&&&exit(EXIT_FAILURE);
&&&&for (;;) {
&&&&&&&&nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
&&&&&&&&if (nfds == -1) {
&&&&&&&&&&&&perror("epoll_pwait");
&&&&&&&&&&&&exit(EXIT_FAILURE);
&&&&&&&&for (i = 0; i < nfds; ++i) {
&&&&&&&&&&&&fd = events[i].data.fd;
&&&&&&&&&&&&if (fd == listenfd) {
&&&&&&&&&&&&&&&&while ((conn_sock = accept(listenfd,(struct sockaddr *) &remote,
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&(size_t *)&addrlen)) > 0) {
&&&&&&&&&&&&&&&&&&&&setnonblocking(conn_sock);
&&&&&&&&&&&&&&&&&&&&ev.events = EPOLLIN | EPOLLET;
&&&&&&&&&&&&&&&&&&&&ev.data.fd = conn_sock;
&&&&&&&&&&&&&&&&&&&&if (epoll_ctl(epfd, EPOLL_CTL_ADD, conn_sock,
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&ev) == -1) {
&&&&&&&&&&&&&&&&&&&&&&&&perror("epoll_ctl: add");
&&&&&&&&&&&&&&&&&&&&&&&&exit(EXIT_FAILURE);
&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&if (conn_sock == -1) {
&&&&&&&&&&&&&&&&&&&&if (errno != EAGAIN && errno != ECONNABORTED
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& errno != EPROTO && errno != EINTR)
&&&&&&&&&&&&&&&&&&&&&&&&perror("accept");
&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&continue;
&&&&&&&&&&&&}
&&&&&&&&&&&&if (events[i].events & EPOLLIN) {
&&&&&&&&&&&&&&&&n = 0;
&&&&&&&&&&&&&&&&while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) {
&&&&&&&&&&&&&&&&&&&&n += nread;
&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&if (nread == -1 && errno != EAGAIN) {
&&&&&&&&&&&&&&&&&&&&perror("read error");
&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&ev.data.fd = fd;
&&&&&&&&&&&&&&&&ev.events = events[i].events | EPOLLOUT;
&&&&&&&&&&&&&&&&if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev) == -1) {
&&&&&&&&&&&&&&&&&&&&perror("epoll_ctl: mod");
&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&}
&&&&&&&&&&&&if (events[i].events & EPOLLOUT) {
&&&&&&&&&&&&&&&&sprintf(buf, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\nHello World", 11);
&&&&&&&&&&&&&&&&int nwrite, data_size = strlen(buf);
&&&&&&&&&&&&&&&&n = data_size;
&&&&&&&&&&&&&&&&while (n > 0) {
&&&&&&&&&&&&&&&&&&&&nwrite = write(fd, buf + data_size - n, n);
&&&&&&&&&&&&&&&&&&&&if (nwrite < n) {
&&&&&&&&&&&&&&&&&&&&&&&&if (nwrite == -1 && errno != EAGAIN) {
&&&&&&&&&&&&&&&&&&&&&&&&&&&&perror("write error");
&&&&&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&&&&&&&&&break;
&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&&&&&n -= nwrite;
&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&close(fd);
&&&&&&&&&&&&}
&&&&return 0;
shutdown函数
& & & & close函数有两个限制,可以使用shutdown函数来避免:
& & & &1.&close把套接口描述符的引用计数减一,仅在该引用计数变为0时才关闭套接口,使用shutdown函数可以不管引用计数就激发TCP的正常连接终止分节序列。
& & & &2. close终止数据传输的两个方向:读和写。
& & & &shutdown函数的行为依赖于参数,SHUT_RD关闭连接的读这一半,套接口中不再有数据可接收,而且套接字接收缓冲区汇总的现有数据都被丢弃。对于一个TCP套接口这样调用shutdown函数后,由该套接口接收的来自对端的任何数据都被确认,然后悄悄丢弃。SHUT_WR关闭连接的写这一半,当前留在套接口发送缓冲区中的数据将被发送掉,后跟TCP的正常终止序列。SHUT_RDWR连接的读这一半和写这一半都关闭,这与调用两次shutdown等效。& &
& & & & & & & & & & &&
第七章 套接口选项
SO_BROADCAST
& & & 本选项开启或禁止进程发送广播消息的能力。只有数据报套接口支持广播,而且还必须是在支持广播消息的网络上。
& & &本选项仅由TCP支持。当给一个TCP套接口开启本选项时,内核将对TCP在该套接口发送和接收的所有分组保留详细的跟踪信息。
SO_DONTROUTE
& & &本选项规定外出的分组将旁路底层协议的正常路由机制。路由守护进程(routed和gated)经常使用本选项来旁路路由表,以强制分组从特定接口送出。
& & &当一个套接口上发生错误时,源自Berkeley的内核中的协议模块将该套接口的名为so_error的变量设为标准的Unix Exxx值中的一个,我们称为该套接口的待处理错误,内核能够以下面两种方式之一立即通知进程这个错误:
& & &1. &如果进程阻塞在select调用上,那么无论是检查可读条件还是可写条件,select均返回并设置其中一个或所有两个条件。
& & &2. &如果进程使用信号驱动I/O模型,那就给进程或进程组产生一个SIGIO信号。
& & &进程然后可以通过访问SO_ERROR套接口选项获取so_error的值。由getsockopt返回的整数值就是该套接口的待处理错误。
& & &当进程调用read且没有数据返回时,如果so_error为非0值,那么read返回-1且errno被置为so_error的值。so_error随后被复位为0。如果该套接口上有数据在排队等待读取,那么read返回那些数据而不是返回错误条件。如果进程调用write时so_error为非0值,那么write返回-1且errno被设为so_error的值,so_error随后被复位为0。
SO_KEEPALIVE
& & & 给一个TCP套接口设置保持存活(keep-alive)选项后,如果2小时内在该套接口的任一方向上都没有数据交换,TCP就自动给对端发送一个保持存活的探测分节(keep-alive-probe)。这是一个对端必须响应的TCP分节,它会导致以下三种情况之一发生:
& & &1. &对端以期望的ACK响应。应用程序得不到通知(因为一切正常)。又过2小时后,TCP将发出另一个探测分节。
& & &2. &对端以RST响应,它告知本端TCP:对端以崩溃且已重新启动。该套接口的待处理错误被置为ECONNRESET,套接口本身被关闭。
& & &3. &对端对保持存活的TCP探测分节没有任何响应,源自Berkeley的TCP将另外发送8个探测分节,相隔75秒一个,试图得到一个相应。TCP在发出第一个探测分节后11分15秒内没有得到任何响应则放弃。
& & &如果根本没有对TCP的探测分节的响应,该套接口的待处理错误就被置为ETIMEOUT,套接口本身则被关闭。
对端进程崩溃
对端主机崩溃
对端主机不可达
本端TCP正在
主动发送数据
对端TCP发送一个FIN,这通过select判断可读条件立即就能检测出来。如果本端TCP发送另外一个分节,对端TCP就以RST响应,如果在本端TCP收到RST后进程仍试图写套接口,我们的套接口就给该进程发送一个SIGPIPE信号
本端TCP将超时,套接口待处理错误设置为ETIMEDOUT
本端TCP将超时,套接口的待处理错误设置为EHOSTUNREACH
本端TCP正在
主动接收数据
对端TCP将发送一个FIN,我们将把它作为一个(可能是过早的)EOF输入
我们将停止收取数据
我们将停止收取数据
连接空闲,保持
存活选项已设置
对端TCP发送一个FIN,这通过select判断可读条件立即能检测出来。
在毫无动静2小时后,发送9个保持存活探测分节,然后套接口的待处理错误被设置为ETIMEDOUT
在毫无动静2小时后,发送9个保持存活探测分节,然后套接口的待处理错误被设置为EHOSTUNREACH
连接空闲,保持
存活选项未设置
对端TCP发送一个FIN,这通过使用select判断可读条件立即就能检测出来。
SO_LINGER套接口选项
& & &本选项指定close对面向连接的协议如何操作。默认动作是close立即返回,如果有数据残留在套接口发送缓冲区中,系统将试着把这些数据发送给对端。
& & SO_LINGER套接口选项使得我们可以改变这个缺省设置,本选项要求在用户进程和内核间传递以下结构:
struct linger
&&&int l_onoff; /* 0=off, nonzero=on */
&&&int l_linger; /* linger time, POSIX specifies units as seconds*/
& & &对setsockopt的调用将按照其中两个结构成员的值导致下面三种情形:
& & 1. 如果l_onoff为0,那么关闭选项。l_linger的值被忽略,先前讨论的TCP缺省设置生效,即close立即返回。
& & 2. 如果l_onoff为非0且l_linger为0,那么当close某个连接时TCP将夭折该连接,这就是说TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST到对方,而没有通常的四分组连接终止序列。这么一来避免了TCP的TIME_WAIT状态,然而会存在下面的可能性:在2MSL秒内创建该连接的另外一个化身,导致老的来自刚被终止的连接上的重复分节被不正确的传递到该新的化身上。(不要轻易避免TIME_WAIT状态)。
& & 3. 如果l_onoff为非0值且l_linger也为非0值,那么当套接口关闭时内核将拖延一段时间。这就是说如果在套接口发送缓冲区中仍然留有数据,那么进程将被投入睡眠,直到(a):所有数据都已发送完并被对方确认。(b):超时返回,返回值为-1,errno被设置为EWOULDBLOCK。如果套接口被设置为非阻塞,那么它将不等待close完成,即使延滞时间为非0。
close的默认方式:立即返回
& & &如图所示,客户可能在服务器进程读套接口接收缓冲区中的剩余数据之前就返回。对服务器进程来说,在服务器应用进程读这些剩余数据之前就崩溃时完全可能的,而客户应用进程永远不会知道。
& & &客户可以设置SO_LINGER套接口选项,指定一个正的延滞时间(在未超时的情况下收到确认)。这种情况下客户的close要直到它的数据和FIN已被服务器主机的TCP确认后才返回。(但收到确认仅仅代表服务器内核收到数据,不代表服务器应用进程收到)
设置SO_LINGER套接口选项且在超时时间内收到确认
& & &&设置SO_LINGER选项后在超时后才收到确认
& & & & 他们的共同问题是即使close成功返回也只是告诉我们发送的数据和FIN已经由对端TCP确认,而不能告诉我们对方应用进程是否收到数据(因为close返回之后才收到对端发来的FIN,但我们已无从知晓)。要想得知对方应用进程是否收到数据,可以采用的一个方法是改用shutdown函数,并等待对端调用close发来的FIN。
shutdown后跟一个read来获知对方已接收数据
& & & & 获知对端应用进程已读取我们的数据的另外一个方法是使用应用级确认。
SO_REUSEADDR和SO_REUSEPORT套接口选项(详细参见“SO_REUSEADR和SO_REUSEPORT异同")
& & & SO_REUSEADDR套接口选项为下面四个不同的目的提供服务:
& & & 1. &SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知的端口,即使以前建立的将该端口用作它们的本地端口的连接仍然存在。这个条件是这样碰到的:
& & &a. &启动一个监听服务器。
& & &b. &连接请求到达,派生一个子进程处理这个客户。
& & &c. &监听服务器终止,但子进程继续为现有连接上的客户服务。
& & &d. 重启监听服务器。
& & &默认情况下,当监听服务器在第4步通过调用socket,bind和listen重新启动时,由于它试图捆绑一个现有连接(即正由派生的子进程处理的连接)上的端口,从而bind调用失败。但是如果服务器在socket和bind之间设置了SO_REUSEADDR套接口选项,那么bind将成功。所有TCP服务器都应该指定该套接口选项,以允许服务器重新启动。
& & &2. &SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址。注意,允许某个给定服务存在多个服务器的情形在服务器总是设置SO_REUSEADDR套接口选项时是自动处理的。
& & &3. &SO_REUSEADDR允许单进程捆绑同一端口到多个套接口上,只要每次捆绑指定不同的IP地址即可。
& & &4. &SO_REUSEADDR允许完全重复的绑定,当一个IP地址和端口已经绑定到某个套接口上,如果传输协议支持,同样的IP地址和端口还可以捆绑到另一个套接口上,一般来说本特性仅支持UDP套接口。
& & SO_REUSEPORT的语义:
& & 1. 本选项允许完全重复的绑定,不过只有在想要捆绑同一IP地址和端口都指定了本套接口选项才行。
& & 2. 如果捆绑的IP地址是一个多播地址,那么SO_REUSEADDR和SO_REUSEPORT被认为等效。
& & 对于不支持本选项但是支持多播的系统上,可以改用SO_REUSEADDR以允许合理的完全重复的捆绑。
TCP套接口选项
TCP_MAXSEG套接口选项
& & 本选项允许我们获取或设置TCP连接的最大分节大小(MSS),返回值是我们的TCP可以发送给对端的最大数据量,它通常是由对端使用SYN分节通告的MSS,除非我们的TCP选择使用一个比对端通告的MSS小一些的值。
& &4.4BSD限制应用程序只能减小其值,而不能增加。
TCP_NODELAY套接口选项
& &开启本选项将禁止TCP的Nagle算法,默认情况下,该算法是启动的。
& &Nagle算法的目的在于减小广域网(WAN)上小分组的数目。该算法指出,如果某个给定连接上有待确认数据,那么原本应该作为小数据量用户写操作之响应而在该连接上立即发送相应小分组的行为却不会发生,直到已有数据被确认为止。Nagle算法的目的在于防止一个连接在任何时刻有多个小分组待确认。
& &Nagle算法通常与ACK延滞算法联合使用。该算法使用TCP在接收到数据后不立即发送ACK,而是等待一小段时间,然后才发送ACK。TCP期待在这一小段时间内自身有数据发送回对端,被延滞的ACK就可以由这些数据捎带,从而省掉一个TCP分节。
& &如果要禁用Nagle算法,可以使用TCP_NODELAY选项。
& &当客户是以若干小片数据向服务器发送单个逻辑请求,那么将不适合使用Nagle算法和TCP的ACK延滞。比如某个客户向它的服务器发送一个400字节的请求,该请求由一个4字节的请求类型和后跟396字节的请求数据构成。如果客户先执行一个4字节的write调用,再执行一个396字节的write调用,那么第二个写操作的数据将一直等到服务器的TCP确认了第一个写操作的4字节数据后才由客户的TCP发出。而且,由于服务器的应用程序难以在收到其余的396字节前对先收到的4字节数据进行操作,服务器的TCP将拖延该4字节数据的ACK(也就是说,暂时不会有从服务器到客户的任何数据可以捎带这个ACK)。
& & 1. 客户发送4字节给服务器。
& & 2. 服务器接收到4字节数据,但是由于ACK延滞算法,服务器将等待一段时间再发送ACK(服务器希望马上就有自身的数据发送给客户,从而捎带ACK,但实际上没有数据发送回去,因此服务器将等待一段时间后发送ACK)。
& & 3. 客户也将在收到ACK之后才发送396字节剩下的数据。
& & 有三种方法修正这类客户程序:
& & 1. 使用writev而不是两次调用write,对于本例子,单个writev调用最终导致调用TCP输出功能一次而不是两次,其结果是只产生一个TCP分节。这是首选办法。
& & 2. 把前4字节的数据和后396字节的数据拷贝到单个缓冲区,然后对该缓冲区调用一次write。
& & 3. 设置TCP_NODELAY套接口选项,继续调用write两次。这是最不可取的办法,而且有损于网络。
第八章 基本UDP套接口编程
& & & 服务器进程未运行
& & &利用sendto函数发送UDP数据报时,sendto的成功返回时仅仅意味着在接口输出队列中具有存放所导致IP数据报的空间。并不代表数据发送成功,如果服务器进程没有运行,那么在sendto返回成功后,服务器主机响应的是一个"port unreachable(端口不可达)"的ICMP错误,这个错误称为"异步错误"。
& & &一个基本的规则: 对于一个UDP套接口,由它引发的异步错误并不返回给它,除非它已连接(对UDP套接口调用connect)。
& & 从实现来看,对一个UDP套接口调用connect,就是创建了一个五元关系组,因此当发生错误的时候就可以通过这个五元关系组返回异步错误。
& & &UDP的connect函数
& & &对UDP调用connect:与TCP不同的是没有三次握手的过程。相反,内核只是检查是否存在立即可知的错误,例如一个不可达的目的地,记录对端的IP地址和端口号(取自传递给connect的套接口地址结构),然后立即返回到调用进程。
& & 如果是这样的话,那么我们必须区分:&未连接的UDP套接口 和 已连接UDP的套接口
& & 对于已连接的UDP套接口,与未连接的UDP套接口相比有三个变化:
& & 1. &我们再也不能给输出操作指定对端IP地址和端口号。也就是说,我们不使用sendto,而改用write或send。
& & 2. &我们不必使用recvfrom以获悉数据报的发送者,而改用read,recv 或 recvmsg。一个已连接的UDP套接口仅仅能够与一个IP地址交换数据报。
& & 3. &由已连接UDP套接口引发的异步错误返回给它们所在的进程。相反,未连接的UDP套接口不接收任何异步错误。
& & 对已连接的UDP套接口归纳的三点:
& & 应用程序首先调用connect指定对端的IP地址和端口号,然后使用read和write与对端进程交换数据。来自任何其他IP地址或端口的数据报不投递给这个已连接套接口,因为它们的源IP地址或源UDP套接口与connect到的协议地址不匹配。这些数据报可能投递给同一个主机上的其他某个UDP套接口。如果没有相匹配的其他套接口,UDP将丢弃它们并生成相应的ICMP端口不可达错误。
& &作为小结,我们可以说仅仅在使用自己的UDP套接口与确定的唯一对端进行通信的时候,才可以调用connect。
& & 给一个UDP套接口多次调用connect
& & 一个已连接的UDP套接口的进程可以为下列两个目的之一再次调用connect:
& & 1. 指定新的IP地址和端口号。
& & 2. 断开套接口。
& & &&为了断开一个已连接的UDP套接口,我们再次调用connect时把套接口地址结构的地址族成员( IPv4为sin_family,IPv6为sin6_family)设置为AF_UNSPEC。这么做可能返回一个EAFNOSUPPORT错误,不过没关系。使得套接口断开连接的是在已连接UDP套接口上调用connect的进程。
& & & 当应用进程在一个未连接的UDP套接口上调用sendto时,源自Berkeley的内核暂时连接该套接口,发送数据报,然后断开该连接。在一个未连接的UDP套接口上给两个数据报调用sendto函数涉及内核执行下列6个步骤:
& & &1. 连接套接口
& & &2. 输出第一个数据报
& & &3. 断开套接口连接
& & &4. 连接套接口
& & & 5. 输出第二个数据报
& & & 6. 断开套接口连接
& & & 当应用进程知道自己要给同一个对端地址发送多个数据报时,显式连接套接口效率更高。调用connect后调用两个write涉及内核执行如下步骤:
& & &1. 连接套接口
& & &2. 输出第一个数据报
& & &3. 输出第二个数据报
阅读(1480) | 评论(0) | 转发(1) |
上一篇:没有了
相关热门文章
给主人留下些什么吧!~~
请登录后评论。

我要回帖

更多关于 accept函数 的文章

 

随机推荐