基于TCP客户/服务器程序的套接字函数图如下:
执行网络I/O,一个进程必须做的第一件事就是调用socket函数,指定期望的通信协议类型。
#include <sys/socket.h>
int socket(int family, int type, int protocol);/*返回值:若成功则为非负描述符,若出错则为-1*/
socket函数成功时返回一个小的非负整数值,它与文件描述符类似,把它称为套接字描述符,简称sockfd。family参数指明协议族,被称为协议域。type参数指明套接字类型。protocol参数应该是某个协议类型常值,或者为0,以选择所给定family和type组合的系统默认值。各参数列于一下表格:
family | 说明 | type | 说明 | protocol | 说明 |
AF_INET | IPv4协议 | SOCKET_STREAM | 字节流套接字 | IPPROTO_TCP | TCP传输协议 |
AF_INET6 |
IPv6协议 | SOCK_DGRAM | 数据报套接字 | IPPROTO_UDP | UDP传输协议 |
AF_LOCAL | Unix域协议 | SOCK_SEQPACKET | 有序分组套接字 | IPPROTO_SCTP | SCTP传输协议 |
AF_ROUTE | 路由套接字 | SOCK_RAM | 原始套接字 | ||
AF_KEY | 秘钥套接字 |
TCP客户用connect函数来建立与TCP服务器的链接。
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen); /*返回:若成功则为0,若出错则为-1*/
sockfd是由socket函数返回的套接字描述符,第二个、第三个参数分别是一个指向套接字地址结构的指针和该结构的大小。客户在调用函数connect前不必非得调用bind函数,因为如果需要的话,内核会确定源IP地址,并选择一个临时端口号作为源端口。如果是TCP套接字,调用connect函数将激发TCP的三路握手过程,而且仅在连接建立成功或出错时才返回,其中出错返回可能有以下几种情况:
a、若TCP客户没有收到SYN分节的响应,则返回ETIMEDOUT错误。
b、若对客户的SYN的响应是RST(表示复位),则表明该服务器主机在我们指定的端口上没有进程在等待与之连接。
c、若客户发出的SYN在中间的某个路由器上引发了一个“destination unreachable”ICMP错误,则认为是一个软错误。
bind函数把一个本地协议地址赋予一个套接字。对于网际网协议,协议地址是32位的IPv4地址与16位的TCP或UDP端口号的组合。
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);/*返回,成功则为0,出错则为-1*/
第二个参数是一个指向特定于协议的地址结构的指针,第三个参数是该地址结构的长度,对于TCP,调用bind函数可以指定一个端口号,或指定一个IP地址,也可以两者都指定,还可以都不指定。
服务器在启动时捆绑它们的众所周知端口。如果一个TCP客户或服务器未曾调用bind捆绑一个端口,当调用connect或listen时,内核就要为相应的套接字选择一个临时端口号。让内核选择临时端口对于TCP客户来说是正常的,除非应用需要一个预留端口;而毁于TCP服务器来说却极为罕见,因为服务器是通过他们的众所周知端口被大家认识的。
进程可以把一个特定的IP地址捆绑到它的套接字上,不过这个IP地址必须属于其所在主机的网络接口之一。
如果指定端口号为0,那么内核就bind被调用时选择一个临时端口。然而如果指定IP地址为通配地址,那么内核将等到套接字已连接TCP或已在套接字上发出数据报时才选择一个IP地址。对于IPv4来说,通配地址由常量INADDR_ANY来指定,其值为0。
注意:如果让内核来为套接字选择一个临时端口号,那么必须注意,函数bind并不返回所选择的值。实际上,由于bind函数的第二个参数有const限定词,它无法返回所选之值。为了得到内核所选择的这个临时端口值,必须调用函数getsockanme来返回协议地址。
listen函数仅由TCP服务器调用,它做两件事:
1、当socket函数创建一个套接字时,它被假设为一个主动套接字,也就是说,它是一个将调用connect发起连接的客户套接字。listen函数把一个未连接的套接字转换成一个被动套接字,指示内核应该受指向该套接字的连接请求。
2、本函数的第二个参数规定了内核应该为相应套接字排队的最大连接个数。
#include <sys/socket.h>
int listen(int sockfd, int backlog);/*返回:若成功则为0,出错则为-1*/
本函数通常应该在调用socket和bind这两个函数之后,并在调用accept函数之前调用。
为理解backlog参数,必须认识到内核为任何一个给定的监听套接字维护两个队列:
1、未完成连接队列,每个这样的SYN分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三路握手过程,这些套接字处于SYN_RCVD状态
2、已完成连接队列,每个已完成TCP三路握手过程的客户对应其中一项。这些套接字处于ESTBLISHED状态。
accept函数由TCP服务器调用,用于从已完成连接队列返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠。
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); /*返回:若成功则为负描述符,若出错则为-1*/
参数cliaddr和addrlen用来返回已连接的对端进程协议地址。如果accept成功,那么其返回值是由内核自动生成的一个全新描述符,代表与所返回客户的TCP链接。在讨论accept函数时,称第一个参数为监听套接字描述符,称返回值为已连接套接字描述符。区分这两个套接字非常重要。一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字。当服务器完成对某个给定客户的服务时,相应的一两节套接字就被关闭。
本函数最多返回3个值:一个既可能是新套接字描述符也可能是出错只是的整数、客户进程的协议地址以及该地址的大小。如果对返回客户协议地址不感兴趣,可以把cliaddr和addrlen均置为空指针。
close函数用来关闭套接字,并终止TCP连接。int close(int sockfd);返回:成功则为0,出错则为-1。