这里,由于套接字函数会涉及到tcp连接状态转换。所以这里先将tcp连接状态转换图贴上来。
#include
int socket(int family, int type, int protocol);
family:
AF_INET IPv4协议
AF_INET6 IPv6协议
AF_LOCAL Unix域协议
AF_ROUTE 路由套接字
AF_KEY 密钥套接字
type:
SOCK_STREAM 字节流套接字
SOCK_DGRAM 数据包套接字
SOCK_RAW 原始套接字
SOCK_SEQPACKET 有序分组套接字
protocol:
IPPROTO_TCP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_SCTP SCTP传输协议
一般情况下protocol参数可以设为0,表示有内核选择所给定的family和type组合的系统默认值。
为了执行网络I/O,第一件事就要调用socket函数,指定期望的通信协议类型。
返回值:成功时返回一个小的非负整数,称为套接字描述符。这里,只指定了协议族和套接字类型。
#include
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addr_len);
返回值:成功返回0,失败返回-1
sockfd是socket函数返回的套接字描述符,剩下的2个参数分别是一个指向套接字地址结构的指针和该结构的大小。
connect函数将激发TCP的三次握手过程,而且仅在连接建立成功或出错时才返回,其中出错有如下几种情况:
1)若TCP客户没有收到SYN包的响应,则返回ETIMEDOUT错误。如,调用该函数时,内核发送一个SYN,若无响应则等待6s后再发一个,若仍无响应,则等待24s再发一个,若总共等了75s后仍未收到响应消息则返回该错误(因内核而异)。
2)若响应时RST,表明该服务器主机在我们指定的端口上没有进程等待,客户收到RST包后马上返回ECONNREFUSED错误。
3)若客户发出的SYN在中间的路由器上引发了一个“destination unreachable”的ICMP错误,则按第一种情形继续发送SYN,若在规定的时间内没有收到回应,则将ICMP错误作为EHOSTUNREACH或ENETUNREACH错误返回。
connect函数将导致当前的套接字状态从CLOSED转移到SYN_SENT状态,若成功则再转移到ESTABLISHED状态。若connect失败则该套接字不再可用,必须关闭,我们不能对这样的套接字再次调用connect函数。
//循环连接,直到成功为止
do
{
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sockfd < 0)
continue;
if(connect(sockfd, &serveraddr, sizeof(struct sockaddr)) == 0) //sucess
break;
close(sockfd);
}while(1)
#include
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen)
返回值:成功返回0,失败返回-1
bind函数把一个本地协议地址赋予一个套接字。对于网际协议,协议地址是一个ip地址和一个端口号。
事实上,调用bind可以指定IP地址和端口,可以两者都指定,也可以两者都不指定。
ip地址 端口 结果
通配地址 0 内核选择IP地址和端口
通配地址 非0 内核选择IP地址,进程指定端口
本地IP地址 0 进程指定IP地址,内核指定端口
本地IP地址 非0 进程指定IP地址和端口
对于内核指定的ip地址和端口,我们可以使用getsockname来返回协议地址。
#include
int listen(int sockfd, int backlog);
返回值:成功返回0,失败返回-1
listen函数把一个未连接的套接字转换成一个被动套接字,指示内核应接受该套接字的连接请求。调用listen导致套接字从CLOSED状态转换到LISTEN状态。
第二个参数规定了内核应该为相应套接字排队的最大连接个数。
要理解backlog参数,我们要知道内核为任何一个给定的监听套接字维护2个队列:
1)未完成连接队列。客户和服务器之间的tcp三次握手并未完成。
2)已完成连接队列。tcp的三次握手已经完成,处于ESTABLISHED状态。
在三次握手完成之后,在服务器调用accept之前,到达的数据应有服务器TCP排队,最大数据量为相应已连接套接字的接收缓冲区大小。
然而,不同的系统下,backlog参数取值的不同,定义的已排队连接的最大数目也不同。以linux2.4.7和fredbsd4.8为例
backlog linux2.4.7 freebsd
1 4 2
2 5 3
3 6 4
#include
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
参数cliaddr和addrlen返回已连接的客户的协议地址,如果对客户的协议地址不感兴趣,可以置为空。
返回值:成功返回非负的描述符,表示与客户的TCP连接。出错返回-1
#include
int close(int sockfd);
返回值:成功返回0,失败返回-1
close一个TCP套接字的默认行为是把该套接字标记成已关闭,当调用close函数时,正常情况下会发送一个FIN包,随后是正常的TCP终止序列。但是,事实上,系统中的每一个文件或套接字都有一个引用计数,表示当前打开着引用该文件或套接字的个数。所以,当调用close时,首先会将该套接字的计数减1,如果该计数为0,则进行清理工作,即发送TCP终止序列,否则,什么都不做。如果通信完成不调用close,那么最终会消耗机器资源。
#include
int shutdown(int sockfd, int howto);
返回值:成功返回0,失败返回-1
shutdown可以不管套接字的计数,直接激发TCP正常连接终止序列。close终止的是读和写两个方向的数据传送,但实际上,我们有时需要通知对方,我已经完成了数据的发送。
howto:
SHUT_RD:关闭读的这一半,套接字不再接收数据,而且套接字接收到的缓冲区的现有数据全部丢弃。
SHUT_WR:关闭写的这一半,又称半关闭。当前留在缓冲区中的数据将被发送完,进程将不能够再对该套接字调用任何写函数。
SHUT_RDWR:读和写都关闭。
#include
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
这两个函数分别是获取某个套接字关联的本地协议地址和与某个套接字关联的外地协议地址。
需要这两个函数,有如下的理由:
1)在一个没有调用bind的TCP客户上,connect成功返回后,getsockname用于返回内核赋予该连接的本地IP地址和本地端口号。
2)在以端口号0调用后,getsockname用于返回内核赋予的本地端口号
3)一旦连接建立,获取客户身份便可以调用getpeername。
阅读(2566) | 评论(0) | 转发(0) |