常用的数据结构及函数

地址转换类

主机字节序与网络字节序的转换

主机序通常为小端,网络字节序为大端。下列函数在<netinet/in.h>包中:

  1. usinged long int htonl(unsigned long int hostlong) —— 主机序转网络序(host to network long)
  2. usinged short int htons(unsigned short int hostlong) —— 主机序转网络序(host to network short)
  3. usinged long int ntohl(unsigned long int hostlong) —— 网络序转主机序(network to host long)
  4. usinged short int ntohl(unsigned short int hostlong) —— 网络序转主机序(network to host short)

IP地址转换函数

计算机能够识别二进制的IP地址,但生活中我们常用点分十进制来表示IPv4地址,用十六进制表示IPv6地址,于是需要一些函数对他们进行一些转换,这些函数位于<arpa/inet.h>包中。

  1. in_addr_t inet_addr(const char* strprt) —— 将点分十进制IPv4地址转化成网络字节序的地址
  2. int inet_aton(const char cp, struct in_addr inp) —— 同上,将结果存在指针里
  3. char* inet_ntoa(struct in_addr in) —— 将二进制结果转为点分十进制
  4. int inet_pton(int af, const char src, void dst) —— 将十六进制地址转为二进制地址,并存在指针。其中af表示地址族,可以是AF_NET或AF_NET6。
  5. 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参数提供了额外的控制,参数取值及含义如下:

  1. MSG_CONFIRM:指示数据链路层持续监听对方回应,直到得到答复。仅用于SOCK_DGRAM和SOCK_RAW类型的socket(针对send)
  2. MSG_DONTROUTE:不查看路由表,直接转发到本地域名网络的某个主机。(针对send)
  3. MSG_DONTWAIT:此次调用非阻塞
  4. MSG_MORE:告诉内核应用程序还有数据要发送,内核将超时等待新数据写入缓冲区后一并发送(针对send)
  5. MSG_TIMEALL:读取到指定数量的字节后才返回(针对recv)
  6. 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:
image.png

高级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用于释放这些内存。


SalvationN
1 声望0 粉丝