基本TCP套接字函数

2014-06-30 13:24:53   最后更新: 2015-10-18 19:48:15   访问数量:1673




 

socket 函数用来指定期望的通信协议类型

int socket (int family, int type, int protocol);

 

定义于<sys/socket.h>中,调用成功则返回非负的描述符,否则返回-1

 

  • family 参数指明协议族,为下列常数之一:

 

历史上曾经有一种想法:使用AF_前缀表示地址族,使用PF_前缀表示协议族,单个协议族可以指出多个地址族,使用PF_值来创建套接字,使用AF_值来创建套接字地址结构,但是实际上,从来没有一个协议支持多个地址族,所以这个想法也没有被实现过,在UNIX中一般来说PF_值总是与对应的AF_值是相等的,但是这并不能完全保证,不过,如果某个系统的实现让两个值不相等,那么现存的很多代码都将会崩溃

 

  • type 参数指明套接字类型,为下列常数之一:

 

  • protocol 参数指明协议类型,为下列常数之一也可以设置为0:

 

如果 protocol 设置为0,则选择 family 和 type 组合的系统默认值,如下图所示:

 

“有效”指的是虽然这样的组合是有效的,但是目前还没有对应的协议缩略词

“--”即是无效的组合

 

family 参数还有其他值,但是现在很少有人使用那些协议

 

socket 函数的返回的非负值被称为套接字描述符,简称 sockfd,在这个函数中,我们并没有指定协议的地址

 

TCP 客户使用 connect 函数来建立与 TCP 服务器的连接

int connect (int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);

 

定义于 <sys/socket.h> 头文件中,调用成功返回0,否则返回-1

 

sockfd 是由 socket 函数返回的套接字描述符,第二个、第三个参数分别是一个指向套接字地址结构的指针和该结构的大小,套接字地址结构必须含有服务器的IP地址和端口号

如果需要,内核会确定源IP地址,并选择一个临时端口作为源端口,因此客户在调用 connect 函数前没有必要调用 bind 函数

 

如果是 TCP 套接字,调用 connect 函数将激发 TCP 的三路握手过程,并且只在连接建立成功或者出错的情况下才会返回,出错有下面三种情况:

  1. 若 TCP 客户没有收到 SYN 分节的响应,客户会按一个递增的时间间隔重复发送,直到打到预定的时间之后仍然无响应,则返回 ETIMEDOUT 错误
  2. 若对客户的 SYN 的响应是 RST(复位信号),则表名该服务器主机在我们指定的端口上没有进程在等待与之连接(服务器进程可能没有在运行),这是一种“硬错误”,客户接收到 RST 后马上返回 ECONNREFUSED 错误。产生 RST 的三个条件是:目的地为某端口的 SYN 到达,然而该端口上没有正在监听的服务器;TCP想取消一个已有连接;TCP 接受到一个不存在的连接上的分节
  3. 若客户发出的 SYN 在中间某个路由器上引发了一个 ICMP 错误(目的地不可达),则被称为是一个“软错误”,客户主机内核保存该消息,并按第一种情况中所述的时间间隔继续发送 SYN,若在某个规定的时间后仍未收到响应,则把保存的消息作为 EHOSTUNREACH 或 ENETUNREACH 错误返回给进程

connect 函数导致当前套接字从 CLOSED 状态转移到 SYN_SENT 状态,若成功则再转移到 ESTABLISHED 状态,若 connect 失败则该套接字不再可用,必须 close 当前的套接字描述符并重新调用 socket 获取新的描述符

 

bind 函数把一个本地协议地址赋予一个套接字,对于网际网协议,协议地址是 32 位的 IPv4 地址或 128 位的 IPv6 地址与 16 位的 TPC 或 UDP 端口号的组合

int bind (int socketfd, const struct sockaddr *myaddr, socklen_t addrlen);

 

定义于 <sys/socket.h> 头文件中,调用成功返回0,否则返回-1

 

第二个参数是一个指向特定与协议的地址结构的指针,第三个参数是该地址结构的长度,对于 TCP,调用 bind 函数可以指定一个端口号,或指定一个IP地址,也可以两个都指定或都不指定

 

服务器在启动时需要捆绑他们的端口,如果一个 TCP 客户或服务器未曾调用 bind 函数捆绑一个端口,当调用 connect 或 listen 时,内核会为相应的套接字选择一个临时端口,让内核来选择临时端口对于 TCP 的客户来说是正常的,除非应用需要一个预留端口(即系统为特殊用途规定的端口号范围中的某个端口),然而,作为 TCP 服务器来说,因为服务器的端口总是要被客户了解的,所以 TCP 服务器通常需要首先调用 bind 函数绑定某个端口

 

进程也可以把一个特定的 IP 地址捆绑到他的套接字上,不过这个 IP 地址必须属于其所在主机的网络接口之一,对于 TCP 客户,这样内核就为套接字上发送的 IP 数据报指派了源 IP 地址,如果绑定 IP 地址,内核就把客户发送的 SYN 的目的地址作为服务器的源 IP 地址,因此 TCP 客户通常不将固定的 IP 地址捆绑到他的套接字上,而对于 TCP 服务器,这就限定该套接字只接收那些目的地为这个 IP 地址的客户连接

同时,也可以绑定通配地址,对于 IPv4 来说,通配地址由常量 INADDR_ANY 来指定,通常其值为0,告知内核自动选择 IP 地址:

struct sockaddr_in servaddr; servaddr.sin_addr.s_addr = htonl (INADDR_ANY);

 

由于 INADDR_常值都是按照主机字节序定义的,所以我们需要使用 htonl 来将其转换为网络字节序,虽然对于值为 0 的 INADDR_ANY 这是没有必要的

 

对于 IPv6 就不能这样赋值了,因为一个 IPv6 的地址是128位的,这个额地址存在与一个结构中,所以我们要这样写:

struct sockaddr_in6 serv; serv.sin6_addr = in6addr_any;

 

in6addr_any 是系统预先分配的变量,并将其值初始化为常值 IN6ADDR_ANY_INIT,其 extern 声明包含于头文件 <netinet/in.h> 中

 

如果被捆绑的地址已经被使用,则 bind 函数会返回一个 EADDRINUSE 错误

 

listen 函数让一个套接字处于监听到来的连接请求的状态

int listen (int sockfd, int backlog);

 

定义于 <sys/socket.h> 头文件中,调用正常返回0,否则返回-1

 

listen 函数仅由 TCP 服务器调用,完成两件事:

一旦通过调用 socket 函数使一个套接字被建立,这个套接字就是一个主动套接字,也就是说,他将被期待调用 connect 函数发起主动连接,而listen 函数则把一个主动套接字转换成一个被动套接字,只是内核应该接手指向该套接字的连接请求,调用 listen 函数后,套接字由 CLOSED 状态转换到 LISTEN 状态

第二个参数制订了内核应该为响应套接字排队的最大连接个数

 

这个函数通常在调用 socket 和 bind 两个函数之后,在调用 accept 函数之前调用

 

内核为任何一个给定的监听套接字维护两个队列:未完成连接队列和已完成连接队列,每个已经由某个客户发出并到达服务器并在等待相应的 TCP 三路握手过程的 SYN 分节都是未完成连接队列中的一项,这些套接字处于 SYN_RCVD 状态;而已经完成 TCP 三路握手的客户则是已完成连接队列中的一项,这些套接字处于 ESTABLISHED 状态

 

当来自客户的 SYN 到达时,TCP 在未完成连接队列中创建一个新项,然后响应以三路握手的第二个分节,知道三路握手的第三个分节到达或者该项超时,才从未完成连接队列中移出,如果三路握手正常进行,则该项被移到已完成连接队列的队尾,当进程调用 accept 函数时,已完成连接队列中的队头项将返回给进程,如果已完成连接队列为空,则进程被投入睡眠,直到 TCP 在该队列中放入下一项才唤醒该进程

 

listen 函数的 backlog 参数曾经被规定为这两个队列综合的最大值,但是对于为0的情况,不同的实现有不同的解释,所以不要把 backlog 设定为0,在三路握手正常完成的前提下,未完成连接队列中的任何一项在其中的存留时间就是一个 RTT,RTT 的值取决于特定的客户与服务器,传统的我们通常为 backlog 取5,但是交心的内核必须支持较大的 backlog,因为5是远远不够的

 

accept 函数由 TCP 服务器调用,用于从已完成连接队列队头返回下一个已完成连接,如果已完成连接队列为空,则进程被投入睡眠

int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

 

定义于 <sys/socket.h>,调用成功返回非负的描述符,若出错则返回-1

 

参数 cliaddr 和 addrlen 用来返回已连接的对端进程(客户)的协议地址,在函数调用前,我们将 addrlen 所指向的值设置为 cliaddr 所指套接字地址结构的长度,返回时,该整数值即为由内核存放在该套接字地质结构内的确切字节数,如果我们不需要函数返回客户协议地址,可以在调用时将 claddr 和 addrlen 两个参数都设置为 NULL

 

如果 accept 函数调用成功,则其返回值是由内核生成的一个全新的描述符,代表与所返回客户的 TCP 连接,因此这个返回的套接字被称为“已连接套接字”,而函数的第一个参数被称为“监听套接字”,一个服务器通常仅仅创建一个监听套接字,他在该服务器的生命期内一直存在,内核为每个由服务器进程接受的客户连接创建一个已连接套接字(他的TCP 三路握手过程已经完成),当服务器完成对某个给定客户的服务时,响应的已连接套接字就被关闭

 

close 函数用来关闭套接字并终止 TCP 连接

int close (int sockfd);

 

定义于 <unistd.h> 头文件中,调用成功返回0,否则返回-1

 

close 将一个 TCP 套接字的标记为已关闭,并立即返回到调用进程

 






读书笔记      技术帖      linux      unix      c++      cpp      c语言      计算机网络      unp      unix网络编程      tcp      网络编程      socket      龙潭书斋      套接字     


京ICP备15018585号