linux网络API分为三个方面:
(1)socket地址api。socket最开始的含义是一个IP地址和端口对(ip,port)。它唯一地表示了使用TCP通信的一端。称为socket地址。
(2)socket基础api。socket主要api定义在sys/socket.h这个头文件中,包括创立socket,命名socket,接受连接,读写数据,获取地址信息,检测带外标记,以及读取和设置socket选项。
(3)网络信息api。linux提供了一套网络信息api,已实现主机名和IP地址的转换,以及服务器名称和端口号之间的转换。这些api都定义在netdb.h中。
一,通用socket地址
socket网络编程接口中表示socket地址的是sockaddr,其定义如下:
#include
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
}
sa_family表示地址族类型。sa_data成员用于存放socket地址值。
由上表可见,14字节的sa_data根本无法容纳多数协议族的地址值,因此linux定义了下面这个新的通用socket结构体:
#include
struct sockaddr_storage
{
sa_family_t sa_family;
unsigned long int _ss_align;
char _ss_padding[128-sizeof(__ss_align)];
}
二,专用socket地址
通用socket一般情况下不太使用linux下的网络开发,因为在设置和获取IP地址和端口需要执行很繁琐的操作,所以linux为各个协议族提供了专门的socket地址结构体:
(1)UNIX本地协议族:
#include
struct sockaddr_un
{
sa_family_t sin_family; //地址族AF_UNIX
char sun_path[108]; //文件路径名
}
(2)TCP/IP协议族,用于IPv4
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地址,要用网络字节序表示
}
(3)TCP/IP协议族,用于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;
u_int32_t sin6_scope_id; //scope ID
}
struct in_addr
{
unsigned char sa_addr[16];
}
所以专业socket地址类型的变量在实际使用中都需转化为通用socket地址类型sockaddr(强制转换即可),因为所有的socket编程接口使用的地址参数类型都是sockaddr。
三,IP地址转换函数
#include
in_addr_t inet_addr(const char* strptr);
int inet_aton(const char*cp,struct in_addr* inp);
char* inet_ntoa(struct in_addr in);
inet_addr函数将用点分十进制字符串表示的IPv4地址转换为网络字节序整数表示的IPv4地址,它失败时返回INAADDR_NONE。
inet_aton函数完成和inet_addr同样的功能,但是转换的结果存储于参数inp指向地址结构中,成功是返回1,失败返回0;
inet_ntoa函数将用网络字节序整数表示的IPv4地址转化为用点分十进制字符串表示的IPv4地址,但需要注意的是,该函数内部用一个静态变量存储转化结果,函数的返回值指向该静态内存,因此inet_ntoa是不可重入的。
char* szValue1=inet_ntoa("1.2.3.4");
char* szValue2=inet_ntoa("10.194.71.60");
printf("address 1: %s\n",szValue1);
printf("address 2: %s\n",szValue2);
运行后结果为:
address1: 10.194.71.60
address2: 10.194.71.60
创建socket:
#include
#include
int socket(int domain,int type,int protocol);
domain参数为协议族,type参数为服务类型,主要有SOCK_STREAM(流服务),和SOCK_UGTAM(数据报服务)。protocol参数通常设为0;
命名socket:
#include
#include
int bind(int sockfd,const struct sockaddr* my_addr,socklen_t addrlen);
bind将my_addr所指的socket地址分配给未命名的sockfd文件描述符,addrlen参数指出该socket地址的长度。
bind成功时返回0,失败返回-1,并设置errno。
其中两种常见的errno分别是:
EACCES:被绑定的地址是受保护的地址,仅超级用户访问,如将socket绑定到0~1023端口上。
EADDRINUSE:该绑定的地址正在使用中,比如将socket绑定到一个处于TIME_WAIT状态的socket地址。
监听socket:
#include
int listen(int sockfd,int backlog);
sockfd表示被监听的socket,backlog表示内核监听队列最大长度。如果超过backlog,服务器将不受理新的客户连接,客户端也将收到ECONNREFUSED错误信息。在内核版本2。2之前的linux中,backlog参数是指所有处于连接状态(SYN_RCVD)和完全连接状态(ESTABLISHED)的socket的上限。但自内核版本2.2之后,它只表示处于完全连接状态的上限,处于半连接状态的socket的上限则由/proc/sys/net/ipv4/tcp_max_backlog内核参数定义。backlog参数的典型值是5。
listen成功时返回0,失败则返回-1,并设置errno。
接受连接:
#include
#include
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
sockfd参数是执行过listen系统调用socket。addr参数是用来获取被接受连接的远端socket地址,该socket地址的长度由addrlen参数指出。accept成功时返回一个新的连接socket,该socket唯一的标识了被接收的这个连接,服务器可通过读写该socket来与被接受连接对应的客户端通信。accept失败时返回-1并设置errno。
发起连接:
#include
#include
int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen);
sockfd参数由socket系统调用返回一个socket。serv_addr参数是服务器监听的socket地址,addrlen参数则指定这个地址的长度。connect成功时返回0。一旦成功连接,sockfd就唯一的标识了这个连接,客户端就可以通过读写sockfd来与服务器通信,connect失败时则返回-1并设置errno。
关闭连接:
#include
int close(int fd);
fd参数是待关闭的socket,但是close系统不是关闭一个连接,而是将引用技术减一,只有当fd的引用计数为0,才真正关闭连接。
如果想要立刻终止连接可以使用shutdown。
#include
int shutdown(int sockfd,int howto);
成功返回0,失败返回-1并设置errno。howto可选值为SHUT_RD:关闭sockfd上读的这一半,SHUT_WR:关闭sockfd上写的这一半,SHUT_RDWR:同时关闭读和写。
数据读写:
对文件的读写操作read和write可以对socket进行读写操作,但是socket编程接口提供了几个专门用于socket数据读写的系统调用。它们增加了对数据读写的控制。其中用于TCP流数据读写的系统调用是:
#include
#include
ssize_t recv(int sockfd,void *buf,size_t len,int flags);
ssize_t send(int sockfd,const void *buf,size_t len,int flags);
recv读取sockfd上的数据,buf和len参数分别指定都缓冲区的位置和大小,flags参数通常设置为0。
recv成功是表示实际读取数据的长度,它可能小于我们期待的长度len。因此我们可能要多次调用recv,才能读取到完整的数据。recv可能返回0,表示通信对方已经关闭连接了,recv出错时返回-1并设置errno。
send往sockfd上写入数据,buf和len参数分别指定写缓冲区的位置和大小。send成功时返回实际写入的数据和长度,失败则返回-1,并设置errno。