常用的数据结构及函数
地址转换类
主机字节序与网络字节序的转换
主机序通常为小端,网络字节序为大端。下列函数在<netinet/in.h>包中:
- usinged long int htonl(unsigned long int hostlong) —— 主机序转网络序(host to network long)
- usinged short int htons(unsigned short int hostlong) —— 主机序转网络序(host to network short)
- usinged long int ntohl(unsigned long int hostlong) —— 网络序转主机序(network to host long)
- usinged short int ntohl(unsigned short int hostlong) —— 网络序转主机序(network to host short)
IP地址转换函数
计算机能够识别二进制的IP地址,但生活中我们常用点分十进制来表示IPv4地址,用十六进制表示IPv6地址,于是需要一些函数对他们进行一些转换,这些函数位于<arpa/inet.h>包中。
- in_addr_t inet_addr(const char* strprt) —— 将点分十进制IPv4地址转化成网络字节序的地址
- int inet_aton(const char cp, struct in_addr inp) —— 同上,将结果存在指针里
- char* inet_ntoa(struct in_addr in) —— 将二进制结果转为点分十进制
- int inet_pton(int af, const char src, void dst) —— 将十六进制地址转为二进制地址,并存在指针。其中af表示地址族,可以是AF_NET或AF_NET6。
- const char inet_ntop(int af, const void src, char* dst, socklen_t cnt) —— 将二进制地址转换为十六进制地址,其中cnt指定目标存储单元大小
socket地址结构
通用socket地址——sockaddr
#include <bits/socket.h>
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
其中,sa_family是地址族,包括:PF_UNIX(UNIX本地域协议族)、PF_INET(TCP/IPv4地址协议族)、PF_INET6(IPv6地址协议族)。注意:AF_xxx和PF_xxx在socket.h中值相同,因此常混用。
sa_data用于存放socket地址值,不同协议的地址具有不同含义和长度。PF_UNIX存放的是文件的路径名(疑问:在PF_UNIX中,socket是以一个什么样的形式存在的?),长度可达到108字节;在PF_INET中,存放16bit的端口号和32bit的IPv4地址;在AF_INET6中,存放的是128bit的IPv6地址、16bit端口号以及32bit的ID。
由于14字节显然无法容下所有地址,因此linux中sockaddr定义为:
#include <bits/socket.h>
struct sockaddr {
sa_family_t sa_family;
unsigned long int __ss_align;
char __ss_padding[128-sizeof(__ss_align)];
}
linux下各协议的socket地址
- PF_UNIX的socket地址结构:
#include <sys/un.h>
struct sockaddr_un {
sa_family_t sin_family;
char sun_path[108]; //文件路径名
}
- PF_INET的socket地址结构;
struct sockaddr_in {
sa_family_t sin_family;
u_int16_t sin_port; //端口号
struct in_addr sin_addr; //IPv4结构体
}
struct in_addr {
u_int32_t s_addr;
}
- PF_INET6的socket地址结构;
struct sockaddr_in6 {
sa_family_t sin_family;
u_int16_t sin_port; //端口号
u_int32_t sin6_flowinfo; //流信息,应设置为0
struct in6_addr sin6_addr; //IPv4结构体
u_in32_t sin6_scope_id;
}
struct in_addr {
unsigned char sa_addr[16];
}
注意:所有专用socket地址在实际使用时都需要强制转换为通用sockaddr!
socket通信常用基础函数
创建socket
int socket(int domain, int type, int protocol);
其中domain为底层协议族,即PF_UNIX, AF_INET, AF_NET6;
type为服务类型,主要分为SOCK_STREAM(流服务)和SOCK_UGRAM(数据报服务),TCP面向流,UDP面向数据报。
protocol表示在前两个参数构成的集合下,再选择一个具体的协议。但由于这个集合通常只有一个值,一般情况下protocol都设置为0。
该函数设置成功时会返回一个socket文件描述符,失败时会返回-1并设置errno
注意:若设置失败,可以使用perror()来打印具体的错误信息
socket命名(绑定)—— 仅服务端使用
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);
bind作用是将由socket()创建后的未经分配的文件描述符与一个ip地址绑定。注意:上面提到的需要强制地址转换就是在这里进行。addrlen直接用sizeof即可。
监听socket —— 仅服务端使用
int listen(int sockfd, int backlog)
此处的backlog指所有处于完全连接和半连接状态的客户端socket上限。最多可有backlog+1个socket请求连接。
接收连接 —— 仅服务端使用
int accept(int sockfd, struct sockaddr addr, socklen_t addrlen)
该函数作用是从listen()监听的队列中接收一个连接,接收后返回一个新的连接socket,服务器可通过读写该socket与客户端进行通信。
注意:对于客户端来说,accept仅仅只是从请求连接的socket中选一个出来进行连接,而不管客户端是否保持连接
发起连接 —— 仅客户端使用
int accept(int sockfd, struct sockaddr serv_addr, socklen_t addrlen)
关闭连接
int close(int fd)
TCP读写函数 —— send(),recv()
send(int sockfd, const void *buf, size_t len, int flags);
recv(int sockfd, void* buf, size_t len, int flags);
其中,flags参数提供了额外的控制,参数取值及含义如下:
- MSG_CONFIRM:指示数据链路层持续监听对方回应,直到得到答复。仅用于SOCK_DGRAM和SOCK_RAW类型的socket(针对send)
- MSG_DONTROUTE:不查看路由表,直接转发到本地域名网络的某个主机。(针对send)
- MSG_DONTWAIT:此次调用非阻塞
- MSG_MORE:告诉内核应用程序还有数据要发送,内核将超时等待新数据写入缓冲区后一并发送(针对send)
- MSG_TIMEALL:读取到指定数量的字节后才返回(针对recv)
- MSG_OOB:发送或接受紧急数据,此时会将TCP报文的URG位置1
UDP读写函数 —— recvfrom(),sendto()
recvfrom(int sockfd, void* buf, size_t len, int flags, struct sockaddr* src, socklen_t* addrlen);
sendto(int sockfd, const void buf, size_t len, int flags,const struct sockaddr* dest_addr, socklen_t* addrlen)
UDP读写最大的不同在于需要提供sock地址,若将后两个参数置为NULL,则与send和recv作用相同
通用读写函数 —— recvmsg(),sendmsg()
recvmsg(int sockfd, struct msghdr* msg, int flags);
sendmsg(int sockfd, struct msghdr* msg, int flags);
这两个函数将要传递的消息封装在msghdr结构体中,msgsdr结构如下:
struct msghdr {
void* msg_name; //socket地址
socklen_t msg_namelen; //socket地址长度
struct iovec* msg_iov; //分散的内存块
int msg_iovlen; //内存块数量
void* msg_control;
socklen_t msg_controllen;
int msg_flags;
};
其中,iovec结构体包含了内存的起始地址,以及这块内存的长度。
这两个函数的思路是将消息存放子啊iovec指向的几个分散的内存块中。对于sendmsg,这些 数据一并被发送,称为集中写;对于recvmsg,这些数据读取后存放在几个分散块中,称为分散读。
地址信息函数
getsockname(int sockfd, struct sockaddr* address, socklen_t address_len);
getpeername(int sockfd, struct sockaddr* address, socklen_t address_len);
含义:将sockfd指向的socket地址存放在address指向内存中,getsockname获取本地socket,getpeername获取远端socket。
socket选项
linux提供了两个系统调用来读取和设置socket文件描述符属性:
getsockopt(int sockfd, int level, int option_name, void* option_value, socklen_t* restrict option_len)
setsockopt(int sockfd, int level, int option_name, const void* option_value, socklen_t* restrict option_len)
level代表适用于哪个协议,《linux高性能服务器编程》中给出了level及其对应的option_name:
高级I/O函数
用于创建文件描述符的函数
- int pipe(int fd[2])
管道将直接创建两个文件描述符,这两个文件描述符可以通过read和write调用进行通信。但是,这个通信是单向的,即fd[0]只能从管道中读数据,fd[1]只能从管道中写数据。注:linux的管道默认大小为65536字节。
- int sockpair(int domain, int type, int protocol, int fd[2])
创建双向管道,这三个参数与socket()函数的三个参数含义完全相同,但domain只能使用AF_UNIX
- int dup(int file_descriptor)
关于重定向,常用于CGI编程,存疑。
用于读写文件描述符的函数
readv和writev
ssize_t readv(int fd, const struct iovec* vector, int count);
ssize_t writev(int fd, const struct iovec* vector, int count);
这两个函数和sendmsg以及recvmsg类似,都是通过iovec结构体来存储信息,同样是集中写和分散读。分散读不必说,就是读取后存入不同的内存块;而集中写就是在服务器发送时,将不同的内存块中的数据集中写入到iovector中。
但这两个函数缺点在于,在集中写的过程中,需要将要发送的文件拷贝到iovec指定的内存中,相当于多做了一次拷贝,因此传输效率较低。
sendfile()
ssize_t sendfile(int out_fd, int in_fd, off_t* offset, ssize_t count);
sendfile直接在内核进行,直接传递数据,避免了数据拷贝,效率较高。其中,out_fd必须是一个socket,offset代表从文件的哪个位置开始读,count指传输的字节数。
splice()
ssize_t splice(int fd, loff* off_in, int fd_out, loff_t* off_out, size_t len, unsigned int flags);
off_in/off_out表示从哪里开始读取/写数据,len指移动数据的长度。
该函数可搭配sendfile实现一个简单高效的回射服务器。
tee()
ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags);
该函数用于在两个fd之间拷贝数据
共享内存mmap()/munmap()
void* mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset)
int munmap(void* start,size_t length);
mmap可申请一段内存作为进程共享内存,而munmap用于释放这些内存。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。