您的位置:首页 > 理论基础 > 计算机网络

unix网络编程的基本函数

2015-09-01 08:19 337 查看



一 socket


#include <sys/socket.h>

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

返回:若成功则为套接字描述符,若出错则返回-1.

说明:family参数指明协议族,type参数指明套接字类型,protocol参数应设为某个协议类型常值,或设为0以选择所给定family和type组合的系统默认值。

socket函数在成功时返回一个小的非负整数值,它与文件描述符类似,我们把它称为套接字描述符,简称sockfd。为了得到这个套接字描述符,我们只是指定了协议族(IPv4 IPv6或Unix)和套接字类型(字节流 数据报或原始套接字)。我们并没有指定本地协议地址或远程协议地址。

注意:并非所有套接字family与type的组合都是有效的,下图给出了有效的组合和对应的真正协议,其中标为“是”的项也是有效的,但还没有找到便捷的缩略词,而空白项则是无效组合。


family

AF_INET IPv4协议

AF_INET6 IPv6协议

AF_LOCAL Unix域协议

AF_ROUTE 路由套接字

AF_KEY 密钥套接字

type

SOCK_STREAM 字节流套接字(TCP)

SOCK_DGRAM 数据报套接字(UDP)

SOCK_SEQPACKET 有序分组套接字

SOCK_RAW 原始套接字

protocol

IPPROTO_CP TCP传输协议

IPPROTO_UDP UDP传输协议

IPPROTO_SCTP SCTP传输协议

protocol 值为0的话就是默认采用前面的协议。

二 connect

#include <sys/socket.h>

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

返回:若成功则为0,若出错则为-1

说明:sockfd是由socket函数返回的套接字描述符,第二个 第三个参数分别是一个指向套接字地址结构的指针和该结构的大小。该函数是客户端建立与TCP服务器连接,套接字地址结构必须含有服务器的IP地址和端口号。

客户端在调用函数connect前不必非得调用bind函数,因为内核会确定源IP地址,并选择一个临时端口号作为源端口。

如果是TCP套接字,调用connect函数将激发TCP的三次握手过程,而且仅在连接建立成功或出错时才返回,其中出错返回可能有以下几种情况:
(1)若TCP客户端没有收到SYN分节的响应,则返回ETIMEDOUT错误。
(2)若对客户端的SYN的响应是RST,则表明该服务器主机在我们指定的端口上没有进程在等待与之连接,这是一种硬错误,客户一收到RST就马上返回ECONNREFUSED错误。

(3)若客户端发出的SYN在中间的某个路由器上引发一个“destination unreachable”(目的地不可达)ICMP错误,这认为是一种软错误。客户主机内核保存该消息并按协议规定的时间间隔继续发送SYN,若在某个规定的时间后仍未收到响应,则把保存的消息作为EHOSTUNREACH或ENETUNREACH错误返回给进程。
按照TCP状态转换图,connect函数导致当前套接字从CLOSED状态(该套接字自从由socket函数创建以来一直所处的状态)转移到SYN_SENT状态,若成功则再转移到ESTABLISHED状态。若connect失败则该套接字不再可用,必须关闭,我们不能对这样的套接字再次调用connect函数,这时必须重新调用socket。

三 bind

#include <sys/socket.h>

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

返回:若成功则为0,若出错则为-1.

说明:bind函数将一个协议地址(ip与port的组合)赋予一个套接字。第二个参数是一个指向特定于协议的地址结构的指针,第三个参数是该地址结构的长度。对于TCP,调用bind函数可以指定一个端口号,或指定一个IP地址,也可以两者都指定,还可以都不指定,不指定的就由内核来进行分配。下面是这四种情况的说明:


对于IPv4来说,通配地址由常值INADDR_ANY来指定,其值一般为0,它告知内核去选择IP地址。如下所示:

struct sockaddr_in servaddr;

servaddr.sin_addr.s_addr = htol( INADDR_ANY );

对于IPv6,我们就不能这么做了,因为128位的IPv6地址是存放在一个结构中的,为了解决这个问题,需要改写为:

struct sockaddr_in6 serv;

serv.sin6_addr = in6addr_any;

系统预先分配in6addr_any变量并将其初始化为常值IN6ADDR_ANY_INIT。头文件<netinet/in.h>中含有in6addr_any的extern声明。
如果让内核来为套接字选择一个临时端口号,必须注意的是函数bind并不返回所选择的值。实际上,由于bind函数的第二个参数有const限定词,它无法返回所选之值。为了得到内核所选择的这个临时端口值,必须调用函数getsockname来返回协议地址。
一般情况是服务器绑定一个固定的端口号,以方便客户端连接,但客户端一般由内核选择一个临时端口就可以了。不过这个规则的例外是远程过程调用服务器。他们通常就由内核为他们的监听套接字选择一个临时端口,而该端口随后通过RPC端口映射器进行注册。客户在connect这些服务器之前,必须与端口映射器取得联系以获取他们的临时端口,这种情况也适用于使用UDP的RPC服务器。

从bind函数返回的一个常见错误是EADDRINUSE(“address already in use”)。

四 listen


#include <sys/socket.h>

int listen( int sockfd, int backlog) ; (backlog 后面需要研究一下)

返回:若成功则为0,若出错则为-1.

说明:本函数仅由TCP服务器调用,通常应该在调用socket和bind这两个函数之后,并在调用accept函数之前调用。它做两件事情:

(1).当socket函数创建一个套接字时,它被假设为一个主动套接字,也就是说它是一个将调用connect发起连接的客户端套接字。listen函数把一个主动套接字转换成一个被动套接字,指示内核应接受指向该套接字的连接请求。根据TCP状态转换图,调用listen使套接字从CLOSED状态转换到LISTEN状态。

(2). 本函数的第二个参数规定了内核应该为相应套接字排队的最大连接个数。内核为任何一个给定的监听套接字维护两个队列:

未完成连接队列,每个这样的SYN分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三路握手过程,这些套接字处于SYN_RCVD状态。
已完成连接队列,每个已完成TCP三路握手过程的客户对应其中一项,这些套接字处于ESTABLISHED状态。



当未完成连接完成3次握手后就会被自动放入到已连接队列中,完全是自动的,无需服务器进程插手。当进程调用accept时,已完成连接队列中的队头项将返回给进程,如果该队列为空,那么进程将被投入睡眠,直到TCP在该队列中放入一项唤醒它。

在三路握手正常完成的前提下(也就是没有重传),未完成连接队列中的任何一项在其中的存留时间就是一个RTT,而RTT的值取决于特定的客户与服务器。
历来在系统中一般是将backlog的值设为5,但互联网的发展使得这个值远远不够,一般采用的方法是设定一个默认值,通过命令行选项或环境变量覆写该默认值。指定一个比内核能够支持的值还要大的backlog也是可接受的,因为内核应该悄然把所指定的偏大值截成自身支持的最大值,而不返回错误。指定较大backlog值的理由在于:随着客户端SYN分节的到达,未完成连接队列中的项目数可能增长,如果在一个客户SYN到达时这些队列是满的,TCP就忽略该分节,也不发送RST。
说了这么多也没说明白backlog的一个真正解释是什么,历史上曾被规定为这两个队列总和的最大值,但现在每个系统对backlog的解释都不一样,但基本上都是采用不同的算法来确定实际已排队连接的最大数目。或许这也是一个比较复杂的小问题,以后有时间了可以好好研究一下。



五 accept

#include <sys/socket.h>

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

返回:若成功则为非负描述符,若出错则为-1

说明:本函数由TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠(假定套接字为默认的阻塞模式)。

参数cliaddr和addrlen用来返回已连接的对端进程(客户)的协议地址。addrlen是值-结果参数:调用前,我们将由*addrlen所引用的整数值置为由cliaddr所指的套接字地址结构的长度,返回时,该整数值即为由内核存放在该套接字地址结构内的确切字节数。
如果accept成功,那么其返回值是由内核自动生成的一个全新描述符,代表与所返回客户的TCP连接。在讨论accept函数时,我们称它的第一个参数为监听套接字描述符(由socket创建),称它的返回值为已连接套接字描述符。区分这两个套接字非常重要。一个服务器通常仅仅创建一个监听套接字,它在服务器的生命周期内一直存在。内核会为每个由服务器进程接受的客户连接创建一个已连接套接字(也就是说对于它的TCP三路握手过程已经完成)。当服务器完成对某个给定客户的服务时,相应的已连接套接字就被关闭。
本函数最多返回三个值:一个既可能是新套接字描述符也可能是出错指示的整数 客户进程的协议地址(由cliaddr指针所指)以及该地址的大小(由addrlen指针所指)。如果我们对返回客户协议地址不感兴趣,那么可以把cliaddr和addrlen均置为空指针。

六 close

#include <unistd.h>

int close( int sockfd );

返回:若成功则为0,若出错则为-1

说明:close函数也用来关闭套接字,并终止TCP连接。关闭后该套接字描述符将不能再由调用进程使用,也就是说它不能再作为read或write的第一个参数。然而TCP将尝试发送已排队等待发送到对端的任何数据,发送完毕后才是正常的TCP连接终止序列。

最后

#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

说明:getsockname用于获取本地地址的IP地址和端口号以及套接字的地址族,getpeername用于获取远程地址的IP地址和端口号,要注意的是在服务器端调用getpeername函数传入的套接字描述符参数必须是已连接套接字描述符而不是监听套接字描述符。

注意,这两个参数的最后一个参数都是值-结果参数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: