并发服务器,每个客户链接都迫使服务器为它派生一个新的进程(或者可以使用线程)。
----------------------------------------------------------------
socket函数
#include
int socket(int family, int type, int protocol);
socket中的family常值:AF_INET(IPv4协议)、AF_INET6(IPv6协议)、AF_LOCAL(Unix域协议)、AF_ROUTE(路由套接字)、AF_KEY(密钥套接字)
socket中的type常值:SOCK_STREAM(字节流套接字)、SOCK_DGRAM(数据报套接字)、SOCK_SEQPACKET(有序分组套接字)、SOCK_RAW(原始套接字)
socket中AF_INET或AF_INET6的protocal常值:IPPROTO_TCP(TCP传输协议)、IPPROTO_UDP、IPPROTO_SCTP.
AF_XXX和PF_XXX:前者表示地址族,后者表示协议族。以前希望单个协议族可以支持多个地址族,但实际上,支持多个地址族的协议序从来就未实现过,两个值一般相等,尽管这种相等关系并不一定永远成立,但若有人试图给已有的协议改变这种约定,则许多代码就会崩溃。
-----------------------------------------------------------------
connect函数
#include
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
客户在调用函数connect前不必非得调用bind函数,因为如果需要的话,内核会确定源IP地址,并选择一个临时端口作为源端口。
如果是TCP套接字,调用connect函数将激发TCP的三路握手过程,而且仅在连接建立成功或出错时才返回,其中出错返回可能有以下几个情况:
(1)若TCP客户端没有收到SYN分节的响应,则返回ETIMEDOUT错误。调用connect函数时,4.4BSD内核发送一个SYN,若无响应则等待6秒后再发送一个,若仍无响应则等待24秒后再发送一个,总共等待75s后仍未收到响应则返回本错误。
(2)若对客户的SYN的响应是RST,则表明该服务器主机在我们指定的端口上没有进程在等待与之连接。这是一种应错误,客户一收到RST就马上返回ECONNREFUSED错误。
RST是TCP在发生错误时发送的一种TCP分节。产生RST的三个条件是:目的地为某端口的SYN到达,然而该端口上没有正在监听的服务器;TCP想取消一个已有连接;TCP接收到一个根本不存在的连接上的分节
(3)若客户发出的SYN在中间的某个路由器上引发了一个“destination unreachable”(目的地不可达)ICMP错误,则认为是一种软错误。客户主机内核保存该消息,并按第一种情况中所述的时间间隔继续发送SYN。若在某个规定的时间后仍未收到响应,则把保存的消息作为EHOSTUNREACH或ENETUNREACH错误返回给进程。以下两种情形也是有可能的:一是按照本地系统的转发表,根本没有到达远程系统的路径;二是connect调用根本不等待就返回。
若connect失败则该套接字不再可用,必须关闭,我们不能对这样的套接字再次调用connect函数。当循环调用函数connect为给定主机尝试各个IP地址知道有一个成功时,在每次connect失败后,都必须close当前的套接字描述符并重新效用socket。
---------------------------------------------------------------
bind函数
把一个本地协议地址赋予一个套接字。
#include
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
服务器在启动时捆绑它们的众所周知的端口。如果一个TCP客户或服务器未曾调用bind捆绑一个端口,当调用connect或listen时,内核就要为相应的套接字选择一个临时端口。
如果指定端口号为0,那么内核就在bind被调用时选择一个临时端口。如果指定IP地址为通配地址,那么内核将等到套结字已连接(TCP)或已在套结字上发出数据报(UDP)时才选择一个本地地址。对于IPv4来说,通配地址由常值INADDR_ANY来指定,其值一般为0。
struct sockaddr_in servaddr;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //wildcard
如此赋值对IPv4可行,因为其IP地址是一个32位的值,可以用一个简单的数字常值表示,对于IPv6,不能这样做,128位的IPv6地址是存放在一个结构中的。
-----------------------------------------------------------------
listen
第二个参数规定了内核应该为相应套接字排队的最大连接数。
内核为任何一个给定的监听套接字维护两个队列:
(1)未完成连接队列,每个这样的SYN分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三路握手过程。这些套件字处于SYN_RCVD状态。
(2)已完成连接队列,每个已完成TCP三路握手过程的客户对应其中一项。这些套接字处于ESTABLISHED状态。
两个队列之和不超过backlog(listen函数的第二个参数)
当进程调用accept时,已完成连接队列中的列头项将返回给进程,或者如果该队列为空,那么进程将被投入睡眠,直到TCP在该队列中放入一项才唤醒它。
一般使用环境变量LISTENQ来设置backlog
当一个客户SYN到达时,若这些队列是满的,TCP就忽略该分节,也就是步发送RST。这么做是因为:这种情况是暂时的,客户将重发SYN,期望不久就能在这些队列中找到可用空间。要是服务器TCP立即响应一个RST,客户的connect调用就会立即返回一个错误,强制应用程序处理这种情况,而不是让TCP的正常重传机制来处理。另外,客户无法区别响应SYN的RST究竟意味着“该端口没有服务器在监听”,还是意味着“该端口有服务器在监听,不过它的队列满了”。
在三路握手完成之后,但在服务器调用accept之前到达的数据应由服务器TCP排队,最大数据量为相应已连接套接字的接收缓冲区大小。
------------------------------------------------------------------------
accept函数
#include
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
cliaddr表示该连接客户端的地址,addrlen是长度,如果不感兴趣,可以置为NULL。
------------------------------------------------------------------------
close函数
close一个TCP套接字的默认行为是把该套接字标记为已关闭,然后立即返回到调用进程。该套接字描述符不能再由调用进程使用。然而TCP将尝试发送已排队等待发送到对端的任何数据,发送完毕后发生的是正常的TCP连接终止序列。可以使用SO_LINGER套接字选项来改变TCP套接字的默认行为(7.5)。
并发服务器中父进程关闭已连接套接字只是导致相应描述符的引用计数值减1.既然引用计数值仍大于0,这个close调用并不引发TCP的四分组链接终止序列。如果确实想在某个TCP连接上发送一个FIN,可以使用shutdown。如果父进程对每个由accept返回的已连接套接字都不调用close,那么?首先,父进程最终将耗尽可用描述符,因为任何进程在任何时刻可拥有的打开着的描述符数通常是有限的。没有一个客户连接会被终止。当子进程关闭已连接套接字时,引用套接字减1。TCP链接终止序列永远不会发生。
----------------------------------------------------------
getsockname和getpeername函数
这两个函数或者返回与某个套接字关联的本地协议地址(getsockname),或者返回与某个套接字关联的外地协议地址(getpeername)
#include
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
这两个函数返回与某个网络连接的两端中任何一端相关联的协议地址,对于IPv4和IPv6来说,就是IP地址和端口号组合。
需要这两个函数的理由:
-
在一个没有调用bind的TCP客户上,connect成功返回后,getsockname用于返回由内核赋予该连接的本地IP地址和本地端口号
-
在以端口号0调用bind(告知内核去选择本地端口号)后,getsockname用于返回由内核赋予的本地端口号
-
getsockname可用于获取某个套接字的地址族
-
在一个以通配IP地址调用bind的TCP服务器上,与某个客户的连接一旦建立(accept成功返回),getsockname就可以用于返回由内核赋予该连接的本地IP地址。在这样的调用中,套接字描述符参数就必须是已连接套接字的描述符,而不是监听套接字的描述符
-
当一个服务器是由调用过accept的某个进程通过调用exec执行程序时,它能够获取客户身份的唯一途径便是调用getpeername
阅读(1752) | 评论(0) | 转发(0) |