MyException - 我的异常网
当前位置:我的异常网» 操作系统 » 套接字创造、连接和关闭函数

套接字创造、连接和关闭函数

www.MyException.Cn  网友分享于:2013-08-22  浏览:0次
套接字创建、连接和关闭函数
    下图是一对 TCP 客户与服务器进程之间发生的一些典型事件的时间表。

    为执行网络 I/O,一个进程必须做的第一件事就是调用 socket 函数,指定期望的通信协议类型。
#include <sys/socket.h>
int socket(int family, int type, int protocol);
                                   /* 返回:若成功,则为非负描述符;否则为 -1 */

    其中,family 参数指明协议族,type 参数指明套接字类型,protocol 参数为某个协议类型常值,或者设为 0,以选择所给定 family 和 type 组合的系统默认值。
    下面各表分别给出了参数 family、type、protocol 及 family 和 type 的组合的值的情况。



    TCP 客户用 connect 函数来建立与 TCP 服务器的连接。
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
                           /* 返回:若成功则为 0;否则为 -1 */

    sockfd 是由 socket 函数返回的套接字描述符,第二个、第三个参数分别是一个指向包含有服务器的 IP 地址和端口号的套接字地址结构以及该结构的大小。
    客户在调用 connect 前不必非得调用 bind 函数,因为如果需要的话,内核会确定源 IP 地址,并选择一个临时端口作为源端口。如果是 TCP 套接字,调用 connect 函数将触发 TCP 的三路握手过程,而且仅在建立成功或出错时才返回。其中出错返回可能有以下几种情况。
    1、若 TCP 客户在一定的时间内没有收到 SYN 分节的响应,则返回 ETIMEDOUT 错误。
    2、若对客户的 SYN 的响应是 RST(表示复位),则表明该服务器主机在所指定的端口上没有进程在等待与之连接(例如服务器进程也许没有运行)。这是一种硬错误(hard error),客户一接收到 RST 就马上返回 ECONNREFUSED 错误。一般产生 RST 的三个条件是:目的地为某端口的 SYN 到达,然而该端口上没有正在监听的服务器;TCP 想取消一个已有连接;TCP 接收到一个根本不存在的连接上的分节。
    3、若客户发出的 SYN 在中间的某个路由器上引发了一个“目的地不可达”的 ICMP 错误,则认为是一种软错误(soft error),客户主机保存该消息,并按一定的时间间隔继续发送 SYN。若在某个规定的时间后仍未收到响应,则把保存的消息作为 EHOSTUNREACH 或 ENETUNREACH 错误返回给进程。另外,这两种情形也是有可能的:一是按照本地系统的转发表,根本没有到达远程系统的路径;二是 connect 调用根本不等待就返回。
    按照TCP 状态转换图,connect 函数导致当前套接字从 CLOSED 状态转移到 SYN_SENT 状态。若 connect 失败则该套接字不再可用,必须关闭,不能对这样的套接字再次调用 connect 函数。当循环调用该函数为给定主机尝试各个 IP 地址直到有一个成功时,在每次 connect 失败后,都必须 close 当前的套接字描述符并重新调用 socket。

    bind 函数把一个本地协议地址赋予一个套接字。
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
                                       /* 返回值:若成功,则为 0;否则为 -1 */

    其中第二个参数是一个指向特定于协议的地址结构的指针。对于 TCP,调用 bind 函数可以指定一个端口号,或指定一个 IP 地址,或两者都指定,也可以都不指定。下表汇总了如何根据预期的结果来设置 sin_addr/sin6_addr 和 sin_port/sin6_port 的值。

    如果指定端口号为 0,内核就在 bind 被调用时选择一个临时端口,bind 并不能返回该端口值。为得到该临时端口值,只能调用函数 getsockname 来返回协议地址。
    如果指定 IP 地址为通配地址,那么内核将等到套接字已连接(TCP)或已在套接字上发出数据报(UDP)时才选择一个本地 IP 地址。让内核来选择临时端口对于 TCP 客户来说是正常的,除非需要一个预留端口,而对于 TCP 服务器来说却极为罕见,因为服务器是通过它们的众所周知端口被认识的(该规则的例外是远程过程调用(Remote Procedure Call, RPC)服务器,它们通常就由内核为它们的监听套接字选择一个临时端口,而该端口随后被 RPC 端口映射器进行注册。客户在调用 connect 这些服务器之前,必须与端口映射器联系以获取它们的临时端口,这种情况也适用于 UDP 的 RPC 服务器)。
    对于 IPv4 地址来说,通配地址由常值 INADDR_ANY 来指定,其值一般为 0,它告知内核去选择 IP 地址。一般这样使用它:
            struct sockaddr_in    servaddr;
            servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    虽然无论是网络字节序还是主机字节序,INADDR_ANY 的值都一样,可以不必使用 htonl,但既然头文件 <netinet/in.h> 中的所有“INADDR_”常值都是按照主机字节序定义的,所以为了格式统一,也推荐都使用 htonl。
    而对于 IPv6,因为 128 位的 IPv6 地址是存放在一个结构中的(C 语言中赋值语句的右边无法表示常值结构),所以一般这样来使用通配地址:
            struct sockaddr_in6    serv;
            serv.sin6_addr = in6addr_any;
    头文件 <netinet/in.h> 中含有 in6addr_any 的 extern 声明,系统预先分配 in6addr_any 变量并将其初始化为常值 IN6ADDR_ANY_INIT。
    从 bind 函数返回的一个常见错误是 EADDRINUSE(“Address already in use”)。

    listen 函数仅由 TCP 服务器调用,而且应在调用 socket 和 bind 之后,并在调用 accept 函数之前调用。
#include <sys/socket.h>
int listen(int sockfd, int backlog);   /* 返回值:若成功,则为 0,否则为 -1 */

    该函数的第二个参数规定了内核应该为相应套接字排队的最大连接个数。为理解该参数,必须认识到内核为每一个给定的监听套接字维护两个队列:
    (1) 未完成连接队列。每个这样的 SYN 分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的 TCP 三路握手过程。这些套接字处于 SYN_RCVD 状态。
    (2) 已完成连接队列。每个已完成 TCP 三路握手的客户对应其中一项,这些套接字处于 ESTABLISHED 状态。
    下图描绘了监听套接字的这两个队列。

    当来自客户的 SYN 到达时,TCP 就在未完成连接队列中创建一个新项(若该队列是满的,TCP 就忽略该分节),直到三路握手正常完成时,才将该项移到已完成连接队列的队尾。当进程调用 accept 时,已完成连接队列中的队头项将返回给进程,或者如果队列为空,那么进程将被投入睡眠,直到 TCP 在该队列中放入一项才唤醒它(假定为默认的阻塞套接字)。
    在三路握手完成之后,但在服务器调用 accept 之前到达的数据应由服务器 TCP 排队,最大数据量为相应已连接套接字的接收缓冲区大小。

    accept 函数也是由 TCP 服务器调用,用于从已完成连接队列头返回下一个已完成连接。
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
                                    /* 返回值:若成功则为非负描述符,否则为 -1 */

    参数 cliaddr 和 addrlen 用来返回已连接的对端进程的协议地址,如果对返回客户协议地址不感兴趣,则可以将它们均置为空指针。addrlen 是值-结果参数:调用前,先将由 addrlen 所引用的整数值置为由 cliaddr 所值的套接字地址结构的长度,返回时,该整数值即为由内核存放在该套接字地址结构内的确切字节数。如果 accept 成功,那么返回一个由内核自动生成的一个全新描述符,代表与所返回客户的 TCP 连接。相比较于监听套接字描述符而言,我们称之为已连接套接字描述符。

    套接字的关闭则是使用通常的文件描述符关闭函数 close。
#include <unistd.h>
int close(int sockfd);       /* 返回值:若成功则为 0;否则为 -1 */

    套接字关闭后,该套接字描述符就不能再由调用进程使用,TCP 在尝试发送完已排队等待发送到对端的任何数据后,就会发送正常的 TCP 连接终止序列。不过其实准确说来,close 函数只是将套接字描述符的引用计数减 1,如果描述符的引用计数仍大于 0,该 close 调用并不引发 TCP 的四分组连接终止序列(如果确实想在某个 TCP 连接上发送一个 FIN,可以调用 shutdown 函数)。对于父进程与子进程共享已连接套接字的并发服务器来说,这也正是所期望的。所以在并发服务器中,父进程中的已连接描述符是必须显示关闭的,不然只在子进程中关闭(显式或隐式)也只是使它的引用计数由 2 减到 1 而已,该描述符将在父进程中一直占据着资源,最终随着越来越多的连接建立,父进程将耗尽可用描述符。因此典型的并发服务器的程序轮廓一般是如同下面这样的。
#include <unistd.h>    // fork 函数头文件

pid_t pid;
int	listenfd, connfd;
listenfd = socket(...);

/* 填充套接字地址及端口部分 */

bind(listenfd, ...);
listen(listenfd, LISTENQ);
for(;;){
	connfd = accept(listenfd, ...);
	if((pid=fork()) == 0){	// 子进程
		close(listenfd);	// 显示关闭共享的监听套接字,可省略
		/* 处理请求 */
		close(connfd);		// 显示关闭共享的已连接套接字,可省略
		exit(0);`	// 子进程退出,避免其继续往下执行
	}
	close(connfd);	// 父进程必须显示关闭已连接套接字描述符
}


    getsockname 函数可用来获取与某个套接字关联的本地协议地址,而 getpeername 则可用来获取与某个套接字关联的外地协议地址。
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
                               /* 返回值:若成功,都返回 0;否则,都返回 -1 */

文章评论

编程语言是女人
编程语言是女人
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
中美印日四国程序员比较
中美印日四国程序员比较
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
 程序员的样子
程序员的样子
程序员和编码员之间的区别
程序员和编码员之间的区别
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
老程序员的下场
老程序员的下场
我是如何打败拖延症的
我是如何打败拖延症的
总结2014中国互联网十大段子
总结2014中国互联网十大段子
如何成为一名黑客
如何成为一名黑客
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
程序员的鄙视链
程序员的鄙视链
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
我的丈夫是个程序员
我的丈夫是个程序员
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
那些争议最大的编程观点
那些争议最大的编程观点
10个调试和排错的小建议
10个调试和排错的小建议
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
程序员都该阅读的书
程序员都该阅读的书
鲜为人知的编程真相
鲜为人知的编程真相
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
为什么程序员都是夜猫子
为什么程序员都是夜猫子
旅行,写作,编程
旅行,写作,编程
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
漫画:程序员的工作
漫画:程序员的工作
一个程序员的时间管理
一个程序员的时间管理
Java程序员必看电影
Java程序员必看电影
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
程序员应该关注的一些事儿
程序员应该关注的一些事儿
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
每天工作4小时的程序员
每天工作4小时的程序员
程序员必看的十大电影
程序员必看的十大电影
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有