http://network.51cto.com/art/201412/459619_all.htm
基于 TCP 的套接字编程的所有客户端和服务器端都是从调用socket开始,它返回一个套接字描述符。客户端随后调用connect 函数,服务器端则调用 bind、listen 和accept函数。套接字通常使用标准的close 函数关闭,但是也可以使用 shutdown函数关闭套接字。下面针对套接字编程实现过程中所调用的函数进程分析。以下是基于 TCP 套接字编程的流程图:
填写图片摘要(选填)socket 函数
套接字是通信端点的抽象,实现端对端之间的通信。与应用程序要使用文件描述符访问文件一样,访问套接字需要套接字描述符。任何套接字编程都必须调用socket函数获得套接字描述符,这样才能对套接字进行操作。以下是该函数的描述:
填写图片摘要(选填)connect 函数
在处理面向连接的网络服务时,例如 TCP ,交换数据之前必须在请求的进程套接字和提供服务的进程套接字之间建立连接。TCP客户端可以调用函数connect 来建立与 TCP 服务器端的一个连接。该函数的描述如下:
填写图片摘要(选填)
TCP 客户端在调用函数 connect 前不必非得调用 bind 函数,因为内核会确定源 IP地址,并选择一个临时端口作为源端口号。若 TCP 套接字调用connect 函数将建立 TCP连接(执行三次握手),而且仅在连接建立成功或出错时才返回,其中出错返回可能有以下几种情况:
若 TCP 客户端没有收到 SYN 报文段的响应,则返回 ETIMEOUT 错误;
若客户端的 SYN 报文段的响应是 RST(表示复位),则表明该服务器主机在我们指定的端口上没有进程在等待与之连接。只是一种硬错误,客户端一接收到 RST就立即返回ECONNERFUSED 错误;
RST 是 TCP 在发生错误时发送的一种 TCP 报文段。产生 RST 的三个条件时:
目的地为某端口的 SYN 到达,然而该端口上没有正在监听的服务器;
TCP 想取消一个已有连接;
TCP 接收到一个不存在的连接上的报文段;
若客户端发出的 SYN 在中某个路由器上引发一个目的地不可达的 ICMP错误,这是一个软错误。客户端主机内核保存该消息,并在一定的时间间隔继续发送 SYN(即重发)。在某规定的时间后仍未收到响应,则把保存的消息(即 ICMP 错误)作为EHOSTUNREACH 或ENETUNREACH错误返回给进行。
bind 函数
调用函数 socket创建套接字描述符时,该套接字描述符是存储在它的协议族空间中,没有具体的地址,要使它与一个地址相关联,可以调用函数bind使其与地址绑定。客户端的套接字关联的地址一般可由系统默认分配,因此不需要指定具体的地址。若要为服务器端套接字绑定地址,可以通过调用函数bind 将套接字绑定到一个地址。下面是该函数的描述:
填写图片摘要(选填)对于 TCP 协议,调用 bind 函数可以指定一个端口号,或指定一个 IP 地址,也可以两者都指定,还可以都不指定。若 TCP客户端或服务器端不调用bind 函数绑定一个端口号,当调用connect 或 listen函数时,内核会为相应的套接字选择一个临时端口号。一般 TCP 客户端使用内核为其选择一个临时的端口号,而服务器端通过调用bind函数将端口号与相应的套接字绑定。进程可以把一个特定的 IP 地址捆绑到它的套接字上,但是这个 IP地址必须属于其所在主机的网络接口之一。对于 TCP 客户端,这就为在套接字上发送的 IP 数据报指派了源 IP 地址。对于 TCP服务器端,这就限定该套接字只接收那些目的地为这个 IP 地址的客户端连接。TCP 客户端一般不把 IP地址捆绑到它的套接字上。当连接套接字时,内核将根据所用外出网络接口来选择源 IP地址,而所用外出接口则取决于到达服务器端所需的路径。若 TCP 服务器端没有把 IP 地址捆绑到它的套接字上,内核就把客户端发送的SYN 的目的 IP 地址作为服务器端的源 IP 地址。
在地址使用方面有下面一些限制:
在进程所运行的机器上,指定的地址必须有效,不能指定其他机器的地址;
地址必须和创建套接字时的地址族所支持的格式相匹配;
端口号必须不小于1024,除非该进程具有相应的特权(超级用户);
一般只有套接字端点能够与地址绑定,尽管有些协议允许多重绑定;
listen 函数
在编写服务器程序时需要使用监听函数 listen。服务器进程不知道要与谁连接,因此,它不会主动地要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后响应该连接请求,并对它做出处理,一个服务进程可以同时处理多个客户进程的连接。listen函数描述如下:
填写图片摘要(选填)listen 函数仅由 TCP 服务器调用,它有以下两种作用:
当 socket 函数创建一个套接字时,若它被假设为一个主动套接字,即它是一个将调用connect发起连接的客户端套接字。listen 函数把一个未连接的套接字转换成一个被动套接字,指示内核应该接受指向该套接字的连接请求;
listen 函数的第二个参数规定内核应该为相应套接字排队的最大连接个数;
listen 函数一般应该在调用socket 和bind 这两个函数之后,并在调用 accept 函数之前调用。内核为任何一个给定监听套接字维护两个队列:
未完成连接队列,每个这样的 SYN 报文段对应其中一项:已由某个客户端发出并到达服务器,而服务器正在等待完成相应的 TCP三次握手过程。这些套接字处于 SYN_REVD 状态;
已完成连接队列,每个已完成 TCP 三次握手过程的客户端对应其中一项。这些套接字处于 ESTABLISHED 状态;
accept 函数
accept 函数由 TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠。该函数的返回值是一个新的套接字描述符,返回值是表示已连接的套接字描述符,而第一个参数是服务器监听套接字描述符。一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字(表示TCP 三次握手已完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字就会被关闭。该函数描述如下:
填写图片摘要(选填)fork 和 exec 函数
填写图片摘要(选填)exec6个函数在函数名和使用语法的规则上都有细微的区别,下面就从可执行文件查找方式、参数传递方式及环境变量这几个方面进行比较。
查找方式:前4个函数的查找方式都是完整的文件目录路径 pathname,而最后两个函数(也就是以p结尾的两个函数)可以只给出文件名 filename,系统就会自动按照环境变量 “$PATH”所指定的路径进行查找。
参数传递方式:exec序列函数的参数传递有两种方式:一种是逐个列举的方式,而另一种则是将所有参数整体构造指针数组传递。在这里是以函数名的第5位字母来区分的,字母为“l”(list)的表示逐个列举参数的方式,其语法为 const char *arg;字母为“v”(vertor)的表示将所有参数整体构造指针数组传递,其语法为 char *const argv[]。读者可以观察execl()、execle()、execlp() 的语法与 execv()、execve()、execvp()的区别。这里的参数实际上就是用户在使用这个可执行文件时所需的全部命令选项字符串(包括该可执行程序命令本身)。要注意的是,这些参数必须以NULL结束。
环境变量:exec 序列函数可以默认系统的环境变量,也可以传入指定的环境变量。这里以“e”(environment)结尾的两个函数 execle() 和 execve() 就可以在 envp[]中指定当前进程所使用的环境变量。
填写图片摘要(选填)并发服务器
当要求一个服务器同时为多个客户服务时,需要并发服务器。TCP 并发服务器,它们为每个待处理的客户端连接调用 fork函数派生一个子进程。当一个连接建立时,accept 返回,服务器接着调用 fork函数,然后由子进程服务客户端,父进程则等待另一个连接,此时,父进程必须关闭已连接套接字。
close 和 shutdown 函数
当要关闭套接字时,可使用 close 和 shutdown 函数,其描述如下:
填写图片摘要(选填)getsockname 和 getpeername 函数
为了获取已绑定到套接字的地址,我们可以调用函数 getsockname 来实现:
填写图片摘要(选填