Linux网络API主要可分为:
- socket地址API
- socket基础API
- 网络信息API
两种字节序
- 大端字节序:整数的高位字节存储在内存的低地址处。
- 小端字节序:整数的高位字节存储在内存的高地址处。
主机字节序(小端)
现代PC大多采用小端字节序,因此小端字节序又被称为主机字节序。
网络字节序(大端)
发送方主机通过网络传递数据时,将机器字节序转化为大端字节序然后进行传输;接收方接收大端数据后,将转化为本地机器字节序。
机器字节序判断
#include <stdio.h>
void byteorder()
{
union
{
short value;
char union_bytes[sizeof(short)];
} test;
test.value = 0x0102;
if ((test.union_bytes[0] == 1) && (test.union_bytes[1] == 2))
{
printf("big endian\n");
}
else if ((test.union_bytes[0] == 2) && (test.union_bytes[1] == 1))
{
printf("little endian\n");
}
else
{
printf("unknown...\n");
}
}
字节序的转换
以下4个函数可用来完成主机字节序和网络字节序之间的转换:
#include<netinet/in.h>
unsigned long int htonl( unsigned long int hostlong) // host to network long
unsigned short int htons( unsigned short int hostshort) // host to network short
unsigned long int ntohl( unsigned long int netlong) // network to host long
unsigned short int ntohs( unsigned short int hostlong) // network to host short
通用socket地址
socket地址结构体
#inlcude<bits/socket.h>
//旧,sa_data太小,无法存放部分协议的地址值
struct sockaddr{
sa_family_t sa_family; // 协议簇
char sa_data[14]; // 存放socket地址值
}
//新
struct sockaddr_storage{
sa_family_t sa_family; //协议簇
unsigned long int __ss_align; //用于内存对齐
char __ss_padding[128-sizeof(__ss_align)]; //存放地址值
}
协议簇及其地址值
注:所有专用socket地址类型的变量在实际使用时都需要转化为通用socket地址类型sockaddr(强制转换即可),因为所有socket编程接口使用的地址参数类型都是sockaddr。
通用socket地址结构使用麻烦,设置IP地址和端口号需要执行繁琐的位操作。Linux为各个协议簇提供了专门的socket地址结构体。
- UNIX本地协议簇
#include <sys/un.h>
struct sockaddr_un{
sa_family_t sin_family; // 地址簇:AF_UNIX
char sun_path[108]; // 文件路径名
}
- TCP/IPv4协议簇
#include <netinet/in.h>
struct sockaddr_in
{
sa_family_t sin_family; //地址族:AF_INET
u_int16_t sin_port; //端口号,要用网络字节序表示
struct in_addr sin_addr; //IPV4地址结构体
}
struct in_addr
{
u_int32_t s_addr; //ipv4地址,要用网络字节序
}
- TCP/IPv6协议簇
struct sockaddr_in6
{
sa_family_t sin6_family; //地址族:AF_INET6
u_int16_t sin6_port; //端口号,要用网络字节序表示
u_int32_t sin6_flowinfo; //流信息,应设置为0
struct in6_addr sin6_addr; //IPv6地址结构体
u_int32_t sin6_scope_id;
};
struct in6_addr
{
unsigned char sa_addr[16]; //IPv6地址,要用网络字节序表示
}
IP地址转换函数
IP地址的表示方式
- 点分十进制:便于阅读
- 二进制整数(网络字节序):便于机器表示与处理
表示法之间的转换
//IPv4地址和网络字节序整数表示的IPv4地址之间的转换:
#include <arpa/inet.h>
//点分十进制 转 网络字节序整数
in_addr_t inet_addr(const char* strptr);
//功能同于inet_addr,并将结果存于inp
int inet_aton(const char* cp, strcut in_addr* inp);
//网络字节序整数 转 点分十进制,函数内部使用静态变量存储转化结果(不可重入)
char* inet_ntoa(struct in_addr in);
socket基础API
在linux中,socket是一个可读、可写、可控制、可关闭的文件描述符。
创建socketsocket系统调用创建socket,创建时指定协议簇、服务类型。
#include <sys/types.h>
#include <sys/socket.h>
// domain: 使用哪个底层协议簇。 PF_INET、PF_INET6或PF_UNIX
// type: 指定服务类型。 SOCK_STREAM(流服务)、SOCK_UGRAM(数据报)
// protocol: 几乎所有情况下,设置为0
int socket(int domain, int type, int protocol);
注:协议簇为PF_INET(IPv4)时,SOCK_STREAM和SOCK_DGRAM分别表示TCP与UDP。
命名socket命名
与socket地址绑定后的socket,被称为命名socket。
- 服务端:使用命名socket,绑定固定地址,便于客户端连接。
- 客户端:使用匿名socket,操作系统自动分配的socket地址。
地址绑定
bind系统调用将socket绑定固定IP地址。
#include <sys/types.h>
#include <sys/socket.h>
// sockfd: socket文件描述符
// sockaddr: socket地址
// addrlen: socket地址长度
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);
监听socket
listen系统调用创建一个监听队列以存放待处理的客户连接。
#include <sys/socket.h>
// sockfd: socket文件描述符
// backlog: 内核监听队列的最大长度(2.2以上版本指处于完全连接状态的socket的上限)
int listen(int sockfd, int backlog);
接受连接
accept系统嗲用从listen监听队列中接受一个连接。
#include <sys/types.h>
#include <sys/socket.h>
// sockfd: 监听socket文件描述符
// addr: 客户端socket地址
// addrlen: 客户端socket地址长度
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
注:accept只是从监听队列中取出连接,而不论连接处于何种状态(完全连接、半连接或关闭),更不关心任何网络状况的变化。
发起连接connect系统调用用于与目标服务器建立连接。
#include <sys/types.h>
#include <sys/socket.h>
//sockfd: socket文件描述符
//serv_addr: 服务器监听socket地址
//addrlen: 服务器监听socket地址长度
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
关闭连接
- close:可用于关闭连接对应的socket,引用计数减1,引用计数为0是真正关闭连接。
#include <unistd.h>
int close(int fd);
- shutdown:shutdown系统调用可立即终止连接。
#include <sys/socket.h>
int shutdown(int sockfd, int howto); //howto参数决定了shutdown的行为
读写socket可用文件读写系统调用(read/write),也可用专用的读写系统调用(recv/send)。
- 通用读写接口read/write
- socket专用读写系统调用
recv /send系统调用
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int socket, void *buf, size_t len, int flags);
ssize_t send(int socket, const void *buf, size_t len, int flags);
UDP数据读写
recvfrom/sendto系统调用用于UDP数据报读写。
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int socket, void *buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen);
ssize_t sendto(int socket, const void *buf, size_t len, int flags, struct sockaddr* dest_addr, socklen_t addrlen);
通用数据读写
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr* msg, int flags);
地址信息函数
-
getsockname:获取sockfd对应的本端socket地址
-
getpeername:后去sockfd对应的远端socket地址
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr* address, socklen_t* address_len);
int getpeername(int sockfd, struct sockaddr* address, socklen_t* address_len);
带外标记
socket选项
网络信息API
获取主机信息
- gethostbyname: 根据主机名称获取主机的完整信息。优先本地的/etc/hosts文件中查找,不存在则访问DNS服务器。
- gethostaddr:根据IP地址获取主机的完整信息
#include <netdb.h>
struct hostent* gethostbyname(const char* name);
struct hostent* gethostbyaddr(const void* addr, size_t len, int type);
获取服务信息
- getservbyname:根据名称获取某个服务的完整信息
- getservbyport:根据端口号获取某个服务的完整信息
注:两者都是通过读取/etc/services文件来获取服务的信息的。
#include <netdb.h>
struct servent* getservbyname(const char* name, const char* proto);
struct servent* getservbyport(int port, const char* proto);
getaddrinfo
- 获取IP地址:通过主机名获取IP地址,内部调用gethostbyname
- 获取端口号:通过服务名获取端口号,内部调用getservbyname
#include <netdb.h>
int getaddrinfo(const char* hostname, const char* service, const struct addrinfo* hints, struct addrinfo** result);
getnameinfo
通过socket地址同时获得以字符串表示的主机名(调用gethostbyaddr)和服务名(调用getservbyport)。
#inlcude <netdb.h>
int getnameinfo(const struct sockaddr* sockaddr, socklen_t addrlen, char* host, socklen_t hostlen, char* serv, socklen_t servlen, int flags);