`

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

阅读更多
    下图是一对 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 而已,该描述符将在父进程中一直占据着资源,最终随着越来越多的连接建立,父进程将耗尽可用描述符。因此典型的并发服务器的程序轮廓一般是如同下面这样(为避免代码臃肿,在此没对 bind 等执行结果做判断)。
#include <unistd.h>    // fork 函数头文件

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

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

bind(listenfd, ...);
listen(listenfd, LISTENQ);
// 这里还应该在进入循环之前调用 signal 函数来捕获 SIGCHLD 信号,以免产生僵尸进程。
// 另外,多数系统上接收到的信号可能使父进程因 EINTR 错误(被中断的系统调用)而终
// 止,所以为了移植性还应该对标准库的 signal 函数做些处理,比如设置 sigaction 结构
// 的 sa_flags 标志为 SA_RESTART 或者 SA_INTERRUPT, 以让内核能自动重启被中断的系
// 统调用,继续先前阻塞的 accept 等慢系统调用,因为适用于慢系统调用的基本规则是:
// 当阻塞于某个慢系统调用的一个进程捕获某个信号且相应信号处理函数返回时,该系统调
// 用可能返回一个 EINTR 错误。
for(;;){
	connfd = accept(listenfd, ...);
	if((pid=fork()) == 0){	// 子进程
		close(listenfd);	// 显示关闭共享的监听套接字,可省略
		/* 处理请求 */
		close(connfd);		// 显示关闭共享的已连接套接字,可省略
		exit(0);`	// 子进程退出,避免其继续往下执行
	}
	close(connfd);	// 父进程必须显示关闭已连接套接字描述符
}

    通常终止网络连接的方法是调用 close 函数,不过 close 有两个限制,而这两个限制可以使用 shutdown 函数来避免。
    (1)close 把描述符的引用计数减 1,仅在该计数变为 0 时才关闭套接字,而 shutdown 可以不管引用计数就激发 TCP 的正常连接终止序列。
    (2)close 会一次性终止读和写两个方向的数据传送,而 shutdown 可以仅关闭一半 TCP 连接。
#include <sys/socket.h>
int shutdown(int sockfd, int howto);
                         /* 返回值:若成功,返回 0;否则,返回 -1 */

    参数 howto 的值可以决定 shutdown 函数的关闭行为。
    * SHUT_RD:表示关闭连接的读这一半——套接字中不再有数据可读,而且套接字接收缓冲区中的现有数据都会被丢弃。进程不能再对这样的套接字调用任何读函数,并且由该套接字接收的来自对端的任何数据都会被确认,然后悄然丢弃。
    * SHUT_WR:关闭连接的写这一半——对于 TCP 套接字,这称为半关闭。当前留在套接字发送缓冲区中的数据将被发送掉,后跟 TCP 的正常连接终止序列。不管套接字描述符的引用计数是否为 0,这样的写半部关闭都会照样执行。进程不能再对这样的套接字调用任何写函数。
    * SHUT_RDWR:连接的读半部和写半部都关闭。

    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 */
  • 大小: 24.8 KB
  • 大小: 14.2 KB
  • 大小: 33.1 KB
  • 大小: 18.1 KB
  • 大小: 17.5 KB
分享到:
评论

相关推荐

    套接字客户端和服务器java代码

    两个常用的构造函数是 Socket(InetAddress addr, int port) 和 Socket(String host, int port),两个构造函数都创建了一个基于Socket的连接服务器端流套接字的流套接字。对于第一个InetAddress子类对象通过addr参数...

    基于C语言实现TCP/IP协议的手法(源码)

    接受客户端连接:使用 accept 函数接受客户端的连接请求,并创建一个新的套接字用于与客户端进行通信。 从客户端接收数据:使用 read 函数从客户端接收数据。 向客户端发送消息:使用 send 函数向客户端发送消息。 ...

    Socket-Programming-C:套接字是大多数流行的操作系统提供的用于使程序访问网络的机制。 它允许在不同网络机器上的应用程序(不相关的进程)之间发送和接收消息。

    套接字编程C 套接字是大多数流行的操作系统提供的用于使程序访问网络的机制。 它允许在不同联网计算机上的... 使用connect()函数将套接字连接到服务器的地址; 通过read()和write()函数发送和接收数据。 socke

    C语言TCP实现代码案例

    关闭连接,使用close()函数关闭套接字。 客户端: 创建一个套接字(socket),使用socket()函数。 连接到服务器的IP地址和端口号,使用connect()函数。 和服务器进行数据传输,使用send()和recv()函数进行数据发送...

    基于C语言实现http请求(源码+示例)

    首先,使用 socket() 函数创建套接字,然后使用 connect() 函数连接到服务器。 然后,使用 send() 函数发送HTTP请求到服务器。 最后,使用 recv() 函数接收服务器的响应,并将其打印出来。 在接收响应时,我们使用...

    racket-socket-server-client:通过套接字使用 TCP 连接的服务器和客户端,内置 Racket

    球拍套接字服务器客户端Racket 内置的通过套接字使用 TCP 连接的服务器和客户端。 服务器只使用一个名为 racket/tcp 的额外库。 这是一个循环,其中第一行是一个阻塞语句,它接受来自侦听器的传入连接。 一旦新连接...

    基于TCP的C/S架构并发服务器

    4) accept(), 阻塞函数,阻塞等待客户端的连接请求,如果有客户端连接,则accept()函数返回,返回一个用于通信的套接字文件; 5) recv(), 接收客户端发来的数据; 6) send(), 发送数据; 7) close(), 关闭文件描述...

    网络编程实用教程(第三版).zip

    5.2.5 关闭套接字和清除相关的对象 122 5.3 CSocket类的编程模型 122 5.4 用CAsyncSocket类实现聊天室程序 123 5.4.1 实现目标 123 5.4.2 创建客户端应用程序 124 5.4.3 客户端程序的类与消息驱动 134 ...

    popen 实现c/s模式shell功能

    目标 完成一个以socket套接字为基础的c/s服务器程序,该程序通过客户端与服务器端连接后,实现一个交互式SHELL的... Server端具有设置client连接上限的功能,当达到上限时,关闭连接套接字,断开与client端的连接。

    在线聊天系统源代码

    (3)Java实现简单的服务器和客户端 利用Java实现一个简单的服务器需要5个步骤;...第二步,绑定和连接的套接字。用于数据的发送与接收。 第三步,获取用于与远程服务器通信的DataOutputstream对象和DateIntputstream对象

    VC++开发手机软件(PDF+源码).

    11.1.3 关闭套接字 11.1.4 绑定套接字 11.1.5 监听套接字 11.1.6 等待连接 11.1.7 建立连接 11.1.8 发送数据 11.1.9 接收数据 11.1.10 设置套接字模式 11.1.11 Select I/O模型 11.2 PING编程 11.2.1 PING...

    作品---聊天软件(服务端 客户端)V1.0.rar

    b、 创建套接字、检查输入的端口、与服务端进行连接以及循环接收来自服务端的消息。 c、 将应当禁用和开启的控件分别进行操作。 3、使用DisposeRecvMsg(Cstring str)对收到的消息进行处理。 三、 客户端单击“发送...

    精通Windows Sockets网络开发:基于Visual C++实现-带源码

    3.4基本tcp套接字编程 3.4.1wsastartup()函数 3.4.2socket()函数 3.4.3bind()函数 3.4.4listen()函数 3.4.5accept()函数 3.4.6recv()函数 3.4.7send()函数 3.4.8closesocket()函数 3.4.9shutdown...

    基于TCP的网络聊天室

    基于TCP/IP的通信基本上都是利用SOCKET套接字进行数据通讯,程序一般分为服务器端和用户端两部分。设计思路(VC6.0下): 第一部分 服务器端 一、创建服务器套接字(create)。 二、服务器套接字进行信息绑定...

    websocket-connection-stream:WebSocket连接实例周围的瘦流包装器

    主要的例外是模块将侦听提供的套接字的open事件并排队消息,直到关闭的套接字打开为止。 例如,这可以通过连接新的Websocket,将意外丢失的连接重新连接到服务器,而无需自动终止流并中断流水线中的任何流。 安装 ...

    windows网络编程技术

    原始套接字34013.1 原始套接字的创建34013.2 Internet控制消息协议34113.2.1 Ping示例34213.2.2 Traceroute示例35113.3 Internet组管理协议35213.4 IP_HDRINCL的使用35413.5 小结362第14章 Winsock 2服务提供者接口...

    Windows网络编程(PDF).rar

    31212.4.2 QoS通知 31412.4.3 QoS模板 31612.5 示例 31812.5.1 单播TCP 31812.5.2 单播UDP 33612.5.3 多播UDP 33712.6 ATM和QoS 33812.7 小结 339第13章 原始套接字 34013.1 原始套接字的创建 34013.2 Internet控制...

    windows网络编程(PDF)

    地址家族和名字解析1026.1 IP1026.1.1 TCP1026.1.2 UDP1026.1.3 定址1026.1.4 创建套接字1056.1.5 ...创建套接字1286.6.3 把套接字和SAP绑定在一起1296.6.4 名字解析1306.7 Winsock 2支持的其他函数1306.8 小结131第7章...

Global site tag (gtag.js) - Google Analytics