网络编程_高级函数
在上一篇文章中介绍了一些网络编程中的一些基础函数,这些函数在我们自己写网络程序的过程中基本都会用到。这篇文章将介绍一些实用的高级函数,可以使我们写出来的网络程序更加强大。
一:常用套接字选项:
getsockopt和setsockopt函数:
#include<sys/socket.h>
intgetsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
intsetsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
getsockopt和setsockopt函数仅用于套接字,其中sockfd指向一个打开的套接字描述符,level指定要操作那个协议选项(如与协议无关的通用选项,IPV4,TCP等), optname则指定选项名称,optval, optlen分别代码操作选项的值和长度,不同的选项具有不同的值和类型。
通用选项SOL_SOCKET:
SO_ERROR选项: 获取并消除错误状态。该选项只读,其中 optval为int型的值,返回0表示无错误选项,其他表示错误码。当套接字阻塞在select上且发生错误时,无论监听的是可读还是可写,都会导致select返回并是在其中一个或所有的的两个条件。此时可以通过SO_ERROR选项检测到出错状态,而不是等在该套接字上进行读写操作是才感知到。在非阻塞(后续章节会介绍)情况下的使用select监听正在连接的套接字返回时,可以通过该方式来检测是连接成功返回还是失败返回。
SO_LINGER选项:该选项控制系统调用close关闭TCP连接时的行为。默认情况下调用close关闭套接字时会立即返回,发送缓冲区还没来得及发送的数据由TCP模块负责发送完成。
该选项的optval参数对应linger类型的结构体,定义如下:
#include<sys.socket.h>
Structlinger
{
Intl_onoff;
Intl_linger; //posix 规定单位为秒
}
l_onoff:为0则不SO_LINGER不起作用,close保存默认行为。
l_linger:
a)l_onoff非0 是有效。此时当l_linger为0时,close调用立即返回,同时丢弃发送缓冲区的残留数据,并且给对端发送一个复位消息RST。该方式导致异常关闭连接。
b)当l_linger大于0时,对于非阻塞的套接字,close调用将直到数据发送完成且收到确认(缓冲区没数据是特殊情况)或到了l_linger指定的时间才返回。如果数据没有发送完成close将返回EWOULDBLOCK错误,缓冲区汇总未发送的数据丢弃,否则返回成功。对于非阻塞套接字而言,close的调用将直接返回,此时可以通过返回值和errno来确定是否完成数据的发送。
SO_SNDBUF和SO_RCVBUF选项:设置套接字的收发缓冲区大小,optval参数的为int型的值。每一个套接字都有一个接收队列和一个发送队列,对于TCP来说,接收缓冲区的大小限制了TCP 通告给对端的窗口大小,窗口大小不得大于接收缓冲区的大小,当缓冲区满了时窗口大小为0,此时如果对端无视通告的窗口而发送数据过来则会将数据丢弃。由于TCP中的窗口通告是在建立连接时的第一个SYN报文上就开始了,因此该选择需在服务端的listen函数调用之前就设置,在客户端的connect函数调用之前设置。
SO_REUSEADDR 选项:
a)允许监听和绑定处于TIME_WAIT的套接字对于的端口或者在该端口上存在套接字连接的端口。这是该选项最常用的方式。
b)允许同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP即可。
c)允许单个进程捆绑同一端口到多个套接字上,只有每次捆绑指定不同的本地IP地址即可。
d)允许完全重复的捆绑:当一个IP地址和端口已经绑定到某个套接字时,如果传输协议支持,同样的IP地址和端口还可以绑定到另外一个套接字。一般来说仅UDP套接字支持该特性。
SO_RCVTMEO和SO_SNDTIMEO选项:设置对于套接字上的收发超时时间。optval参数对应类型为timeval结构体。
二:地址相关函数
gethostbyname和 gethostbyaddr函数:
#include<netdb.h>
structhostent *gethostbyname(const char *hostname);
struct hostent* gethostbyaddr(const char * addr, socklen_t len, int family);
gethostbyname函数通过域名获取主机的IPV4地址,该函数默认情况下会先去/etc/hosts 文件中查询配置域名对应的地址信息,如果没有找到则想DNS服务发送请求。
gethostbyaddr则是通过主机IPV4地址获取对应的域名。流程和gethostbyname函数类似。
以上两个函数只支持IPV4,域名对应的IPV6信息则通过getaddrinfo函数活得,想了解的同学可以自己去找相关资料。下面给出 hostent结构体定义及说明:
struct hostent
{
char *h_name;
char**h_aliases;
int h_addrtype;
int h_length;
char**h_addr_list;
}
hostent->h_name
表示的是主机的规范名。例如www.baidu.com的规范名其实是www.a.shifen.com。
hostent->h_aliases
表示的是主机的别名列表,可以有多个。www.baidu.com就是baidu他自己的别名。
hostent->h_addrtype
表示的是主机ip地址的类型。只会是ipv4(AF_INET),这个函数处理不了ipv6
hostent->h_length
表示的是主机ip地址的长度。
hostent->h_addr_list
表示的是主机的ip地址列表,可以有多个。是网络字节序,需要通过inet_ntop函数转换。
getservbyname 和getservbyport函数:
#include <netdb.h>
struct servent *getservbyname(constchar *servname, const char *protoname);
struct servent *getservbyport(intport, const char *protoname);
getservbyname函数可以通过服务名来获取服务对应的端口信息。其中servname参数为服务名,protoname参数为指定的协议类型(”udp” or “tcp”)。
getservbyport函数的功能是通过指定的端口号(port参数指定)和协议类型(protoname参数指定)来获取对应的服务名。
getservbyname和getservbyport函数都是通过查找文件/etc/services获取相应的休息, 因此用户需要先将服务名对于的端口号的映射关系先写入到一个文件/etc/services,才能通过这两个函数获得想要的结果。
下面给出 servent结构体的定义及说明:
struct servent
{
char*s_name; //服务正式名字
char**s_aliases; // 服务的别名列表
ints_port; // 网络字节序的端口号
char*s_proto; //协议类型
};
getsocketname 和getperrname函数:
#include <sys/socket.h>
int getsockname(int sockfd, structsockaddr *addr, socklen_t *addrlen);
int getpeername(int sockfd, structsockaddr *addr, socklen_t *addrlen);
getsockname函数用来获取套接字(sockfd指定)本地出口的地址信息,返回的地址信息保存在addr参数中,addrlen则返回地址信息addr的长度。该函数只对调用了bind函数的服务端套接字,accept上来的服务端套接字,获取connect成功后的客户端套接字有效。(这里只讨论TCP啊)
getpeername函数用于获取已连接的套接字远端地址。参数同getsockname函数。只对已经建立好连接的套接字有效,所以对服务端的监听套接字无效,还没有完成连接的客户端套接字也无效。
三:高级IO函数
sendfile函数:
#include<sys/sendfile.h>
ssize_tsendfile(int out_fd, int in_fd, off_t* offset, size_t count);
sendfile函数在内核态从文件描述符in_fd到文件描述符out_fd传递数据,可以避免数据在用户态和内核态之间的拷贝。该函数是专门为网络上传输文件而设计的。其中in_fd参数必须是一个指向真实文件的文件描述符,out_fd必须是一个socket。
readv和writev函数:
#include<sys/uio.h>
ssize_treadv(int filedes, const struct iovec *iov, int iovcnt);
ssize_twritev(int filedes, const struct iovec *iov, int iovcnt);
readv从文件描述符中将数据读到不连续的多个内存块中,称做分散读;writev将不连续的多个内存块中的数据一并写入到指定文件描述符中,称作集中写,写的操作是原子的。其中参数iov中包含指定的内存块信息,是一个iovec结构体类型的一维数组,iovcnt参数指定数据中元素的个数。
writev以顺序iov[0]至iov[iovcnt-1]从缓冲区中聚集输出数据。writev返回输出的字节总数,通常,它应等于所有缓冲区长度之和。readv则将读入的数据按上述同样顺序散布读到缓冲区中。readv总是先填满一个缓冲区,然后再填写下一个。readv返回读到的总字节数。如果遇到文件结尾,已无数据可读,则返回0。
下面介绍一下参数iov对应的结构体:
structiovec
{
void *iov_base; //单个内存块的起始地址
size_t iov_len; // iov_base指向的内存块大小
}
recvmsg和sendmsg函数:
这个两个函数很强大,除了读写数据之外,还可以用来在不同进程间传递文件描述符。具体可以自己去了解。