通用socket地址
要学习socket地址API,先要理解主机字节序和网络字节序
字节序概念个人理解:数据由字节的形式存在,字节在内存的排序方式就是字节序。分为大端字节序和小端字节序,大端字节序就是将一个有序列的字节逆序按照高位字节放在内存的低地址处,简而言之就是逆序放;小端字节序就是将字节的顺序按照先来后到的方式放,低位的字节放在内存的低地址处。听着还挺绕,看图!
大端字节序称为网络字节序,小端字节序称为主机字节序,现在PC大多采用小端字节序,通常发送端总是要将数据转换成大端字节序数据再发送,所以接收端也需要看自己是否需要将数据进行转换(如果你的机器是本来就是采用大端字节序就不需要,反之就需要)
结构体sockaddr为通用socket通用地址,定义如下:
#include<bits/socket.h>
struct sockaddr
{
sa_family_t sa_family;//地址族类型变量
char sa_data[14];//存放socket地址值
}
常见的协议族和对应的地址族如表所示:
协议族 | 地址族 | 描述 |
---|---|---|
PF_UNIX | AF_UNIX | UNIX本地域协议族 |
PF_INET | AF_INET | TCP/IPv4协议族 |
PF_INET6 | AF_INET6 | TCP/IPv6协议族 |
协议族及其地址值的相关知识,如下表
协议族 | 地址值含义和长度 |
---|---|
PF_UNIX | 文件的路径名 |
PF_INET | 16bit端口号和32bit IPv4地址,共6字节 |
PF_INET6 | 16bit端口号,32bit流标识,128bit IPv6地址,32bit 范围ID,共26字节 |
由于上一个结构体的地址值空间不够,所以Linux新定义了下面新的通用的socket地址结构体。
#include<bits/socket.h>
struct sockaddr_storage
{
sa_family_t sa_family;
unsigned long int __ss_align;
char __ss_padding[128-sizeof(__ss_align)];
专用socket地址
Unix本地域协议族使用如下专用socket地址结构体:
#include<bits/socket.h>
struct sockaddr_un
{
sa_family_t sin_family;
char sun_path[108];
}
IPv4 协议族专用地址结构体
#include<bits/socket.h>
struct sockaddr_in
{
sa_family_t sin_family;//地址族:AF_INET
u_int16_t sin_port;//端口号,要用网络字节序表示
struct in_addr sin_port;//地址结构体
};
struct in_addr
{
u_int32_t s_addr;
};
IPv6 协议族专用地址结构体
struct sockaddr_in6
{
sa_family_t sin6_family;//地址族:AF_INET6
u_int16_t sin6_port;//端口号
u_int32_t sin6_flowinfo;//流信息
struct in6_addr sin6_addr;//IPv6 地址结构体
u_int32_t sin6_scope_id;//scope id
};
struct in6_addr
{
unsigned char sa_addr[16];//IPv6地址
};
常用的IP地址转换函数
#include<arpa/inet.h>
/*将字符串表示的IP地址src转换成用网络字节序表示的ip地址,并将转换结果存储于dst指向的内存中,af为地址族AF_INET或者AF_INET6*/
int inet_pton( int af, const char* src, void* dst);
//网络字节序表示的ip地址转换成字符串表示的IP地址
const char* inet_ntop( int af,const void* src,char* dst, socklen_t cnt);
创建socket
#include<sys/types.h>
#include<sys/socket.h>
int socket( int domain, int type, int protocol );
//domain为底层协议族,type为指定服务类型,主要有SOCK_STREAM(流服务)和SOCK_UGRAM(数据报)服务,protocol参数是在前两个参数构成的集合下,再选择一个具体的协议,不过这个值通常都是唯一的,所以通常设置为0就可以,表示使用默认协议。此接口成功则返回一个socket文件描述符,失败返回-1并设置errno.
命名socket
在创建socket的时候我们指定了协议族,但是还没有指定那个具体的socket地址,所以我们需要将socket与socket地址进行绑定,这就是给socket命名。在服务器程序中,我们通常要命名socket,因为只有命名后客户端才知道如何连接它。客户端则通常不需要命名socket,而是采用匿名方式,即采用操作系统自动分配的socket地址。
#include<sys/types.h>
#include<sys/socket.h>
int bind( int sockfd, const struct sockaddr* my_addr,socklen_t addrlen);
bind将my_addr地址与sockfd进行绑定,成功返回0,失败-1并设置errno
监听socket
socket被命名后还不能接受与客户进行连接,还需要使用如下系统调用来创建监听队列来存放待处理的客户连接。
#include<sys/socket.h>
int listen(int sockfd, int backlog );//sockfd为被监听的socket,backlog为内核监听队列的最大长度。超过backlog,服务器将不受理新的客户连接,客户端也会收到ECONNREFUSED错误信息