由于CPU采用的架构或者配置的不同,不同的CPU中数据存储的大端模式、小端模式是不同的,特定的主机上使用的字节序被称为主机字节序。而网络中IP、端口号等信息必须以统一的模式在网络中传输,故称此为网络字节序,网络字节序是大端模式的。IP地址、端口号信息,在存储到struct sockaddr 之前需要转化为网络字节序。当主机字节序与网络字节序相同时,转换函数原样返回传入的参数。
#include
uint16_t htons(uint16_t host_uint16); //host to net short 将16位主机字符顺序转换成网络字符顺序,常用于端口号的转换
uint32_t htonl(uint32_t host_uint32); //host to net long 将32位主机字符顺序转换成网络字符顺序,常用于IP地址的转换
uint16_t ntohs(uint16_t net_uint16); //net to host short 将16位网络字符顺序转换成主机字符顺序,常用于端口号的转换
uint32_t ntohl(uint32_t net_uint32); //net to host long 将32位网络字符顺序转换成主机字符顺序,常用于IP地址的转换
注意:下述函数,仅能用于IPv4型地址类型的转换。下述函数均属于已经过时的函数,最新的标准中已被废弃。新函数可参考下节内容。
点分十进制表示"192.168.1.80"是用一段字符串表示,二进制形势表示是以一个 uint32_t 表示一个IP地址。
#include
#include
#include
unsigned long int inet_addr(const char *cp); //将cp指向的字符串内容转化为**网络字节序**的二进制IP值 例: int ip = inet_addr("192.168.1.80");
int inet_aton(const char * cp,struct in_addr *addr); //将cp指向的字符串内容转化为**网络字节序**的二进制IP值,并将地址存储于addr的结构体中 in_addr结构参考我的上一篇博客
char * inet_ntoa(struct in_addr addr); //将addr中的IPv4地址转化为点分十进制字符串形式,并返回字符串的指针。
注意:
1. inet_addr()、inet_aton()转化得到的IP值都是网络字节序的。
2. inet_ntoa()转化得到的字符串存储在函数中的一个静态地址中,转化后需要立刻使用或取出,因为后续的调用会覆盖该值。
注意:下述函数,即可以实现IPv4型的二进制IP地址转换为点分十进制字符串,也可以实现IPv6的地址转换。这是最新的函数标准。
#include
/*
* @Description: 将src_str中的点分十进制IP地址转化为二进制IP,存储到addrptr中。
* @Para : int domain IP地址类型,主要有如下
* AF_INET(IPv4)
* AF_INET6(IPv6)
* const char * src_str 点分十进制的IP地址字符串
* void * addrptr 存储转化为二进制IP地址值,如果domain为IPv4型则
* 该地址结构为struct in_addr,IPv6为 struct in6_addr。
*
* @return : 成功返回1,src_str不合法时返回0,失败返回-1,失败的错误码在errno中
**/
int inet_pton(int domain, const char * src_str, void *addrptr);
/*
* @Description: 将二进制IP转化为src_str中的点分十进制,存储到dst_str中。
* @Para : int domain IP地址类型,主要有如下
* AF_INET(IPv4)
* AF_INET6(IPv6)
* void * addrptr 存储转化为二进制IP地址值,如果domain为IPv4型则该
* 地址结构为struct in_addr,IPv6为 struct in6_addr。
* char * src_str 点分十进制的IP地址字符串存储缓存区
* size_t len 缓冲区src_str的大小
*
* @return : 成功返回dst_str,失败返回NULL,失败的错误码在errno中
**/
const char * inet_ntop(int domain, const void * addrptr, char * dst_str, size_t len);
Socket选项能够影响到Socket操作的多个功能,具体功能可参考optname参数的设置。
#include
#include
/*
* @Description: 设置socket配置选项
* @Para : int sockfd 要设置的socket文件描述符
* int level 代表欲设置的网络层,一般设成 SOL_SOCKET 以存取socket层
* int optname 代表欲设置的选项,有下列几种数值:
* SO_DEBUG 打开或关闭排错模式
* SO_REUSEADDR 允许在bind()过程中本地地址可重复使用 注意:该特性的重要性在上篇博客中强调过
* SO_TYPE 返回socket形态。
* SO_ERROR 返回socket已发生的错误原因
* SO_DONTROUTE 送出的数据包不要利用路由设备来传输。
* SO_BROADCAST 使用广播方式传送
* SO_SNDBUF 设置送出的暂存区大小
* SO_RCVBUF 设置接收的暂存区大小
* SO_KEEPALIVE 定期确定连线是否已终止。
* SO_OOBINLINE 当接收到OOB 数据时会马上送至标准输入设备
* SO_LINGER 确保数据安全且可靠的传送出去。
* SO_RCVTIMEO 接收数据阻塞进程(线程)超时时间设置
* SO_SNDTIMEO 发送数据阻塞进程(线程)超时时间设置
* const void * optval 代表欲设置值的结构体指针
* socklen_t optlen 为optval的数据长度
*
* @return : 成功返回0,失败返回-1,失败的错误码在errno中
**/
int setsockopt(int sockfd, int level, int optname, const void * optval, socklen_t optlen);
/*例: 设置SO_REUSEADDR,允许在bind()过程中本地地址可重复使用*/
int optval = 1;
if(setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1)
printf("socket SO_REUSEADDR option set failed!\r\n");
/*例: 接收超时时间设置(accept()阻塞、recv()阻塞均受该配置影响)*/
struct timeval timeout={5,0}; //接收的timeout延时为5s
//服务端接收客户端的数据超时时间为5s
int ret = setsockopt(ClientNow -> client_fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout,sizeof(timeout));
if(ret != 0) //配置失败
{
DeleteClient(ClientNow);
break;
}
/*
* @Description: 获取socket配置选项
* @Para : int sockfd 要获取参数的socket文件描述符
* int level 代表欲获取的网络层,一般设成 SOL_SOCKET 以存取socket层
* int optname 代表欲获取的选项,具体数值参考setsockopt():
* const void * optval 代表存储要获取值的结构体的指针
* socklen_t * optlen 调用函数前需要设置optlen指向的内容为optval结构的缓存区长度,
* 调用函数后,该参数为写入到optval中的数据长度。
* (注意:optlen参数既是一个输入参数也是一个输出参数)
*
* @return : 成功返回0,失败返回-1,失败的错误码在errno中
**/
int getsockopt(int s,int level,int optname,void* optval,socklen_t* optlen);
//例:获取socket的TCP类型或者UDP类型,类型最终返回到optval中,
//TCP类型返回SOCK_STRREAM, UDP类型返回SOCK_DGRAM
int optval;
socklen_t optlen;
optlen = sizeof(optval);
if(getsockopt(server_fd, SOL_SOCKET, SO_TYPE, &optval, &optlen) == -1)
errExit("getsockopt");
getsockname()用于获取本地的IP地址参数,getpeername()用于获取远端地址参数。有时地址参数使用的是临时参数,并不是用户预先设置的,如果想获取临时的地址参数,则可采用该函数。
#include
/*
* @Description: 获取本地socket的地址参数
* @Para : int sockfd 要获取参数的socket文件描述符
* struct sockaddr * addr 存储获取地址的结构体指针
* socklen_t * addrlen 调用函数前需要设置addrlen指向的内容为addr结构的缓存区长度,
* 调用函数后,该参数为写入到addr中的数据长度。
* (注意:addrlen参数既是一个输入参数也是一个输出参数)
*
* @return : 成功返回0,失败返回-1,失败的错误码在errno中
**/
int getsockname(int sockfd, struct sockaddr * addr, socklen_t * addrlen);
// getpeername()函数参数含义与getsockname()参数含义相同
int getpeername(int sockfd, struct sockaddr * addr, socklen_t * addrlen);
在Web服务器或者文件服务器中,往往需要将磁盘上的文件内容不做修改的通过socket传输出去。通过常规的额recv() send()方法也能够实现该传输,但是普通方法传输磁盘文件需要在内核空间和用户空间来回拷贝数据,效率不高。当使用sendfile()函数传输,文件内容仅在内核空间传输,直接在内核缓存区通过socket发送出去,从而提高传输效率。
注意:sendfile()仅能用于将文件传输到socket上,反过来则不行,也不能在两个socket间传送数据。
#include
/*
* @Description: 发送文件到socket上
* @Para : int out_fd 必须指向一个socket描述符
* int in_fd 要传送的文件描述符
* off_t * offset 起始文件的偏移量,即从in_fd指向的文件的这个位置开始传送数据,从文件首部开始可设置为NULL。
* size_t count 要传送的字节数
*
* @return : 成功返回已经传送的字节数,失败返回-1,失败的错误码在errno中
**/
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
如果socket上传输的是以换行符分隔的文本,那么使用readline()读取一行是比较便捷的。
#include "read_line.h"
/*
* @Description: 从socket描述符中读取字节直到碰到换行符为止
* @Para : int fd 要读取的socket描述符
* void * buffer 读取的字节缓存区,返回的字符串总以null结尾。
* size_t n 要读取的字节数,参数为n,则最多能读取n-1个字节,并返回(n-1)。
* 结尾的null字节不会记为返回的字节数。
*
* @return : 成功返回已经保存到buffer的字节数,读取到EOF则返回0,失败返回-1,失败的错误码在errno中
**/
ssize_t readline(int fd, void * buffer, size_t n);
注意:如果实际一行的字节数大于缓冲区的个数(n-1),则readline()会丢弃多余的字节(包括换行符)。可以通过buffer中结尾null字节前是否是一个换行符来判断readline()是否丢弃字节
如果使用close()关闭socket连接,通道的两端都会关闭。shutdown()提供了只关闭一端的功能。
#include
/*
* @Description: 根据how参数关闭连接的一端或者两端
* @Para : int sockfd 要关闭的socket连接
* int how 关闭方式
* SHUT_RD 关闭连接的读端
* SHUT_WR 关闭连接的写端
* SHUT_RDWR 关闭连接的读端和写端
*
* @return : 成功返回0,失败返回-1,失败的错误码在errno中
**/
int shutdown(int sockfd, int how);