大端存储:高位字节存在低地址处
小端存储:低位字节存在低地址处
现代pc大多数采用小端存储,所以小端也被称为主机字节序
所以发送方一般都要把小段转化为大端再发送
接收方根据自身情况进行决定
大端存储就被称为网络字节序 (同一主机不同语言编写的也要考虑字节序的问题)
java是大端
主机字节序和网络字节序的转换
linux提供四个函数转换字节序
#incldue<netinet/in.h>
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);
unsigned long int ntohl(unsigned long int hostlong);
unsigned short int ntohs(unsigned short int hostshort);
通常长整型用来转换ip地址
短整型用来转换port
通用socket地址
1.#include<bits/socket.h>
struct sockaddr{
sa_family_t sa_family; //地址族类型
char sa_data[14]; //存放socket地址值
}
tip:先分类型,再识别值
注意这是一个结构体 因为14字节存不下,所以用下面的
2.
struct sockaddr_storage{
sa_family_t sa_family;
unsigned long int __ss_align;
char __ss_padding[128-sizeof(__ss_align)];
}
这个结构体提供了足够大的空间,而且内存是对齐的(__ss_align)的作用
地址族类型 与 协议族类型(domain)的对应
AF_UNIX PF_UNIX 本地域 //协议族 protocol family
AF_INET PF_INET IPV4
AF_INET6 PF_INET6 IPV6
PF和AF存在<bits/socket.h>中,而且他俩有完全相同的值所以常常混用
PF_UNIX:文件路径名,可长达108字节
AF_INET:16bit端口加32bit ipv4地址,一共48bit,6字节
AF_INET6:16端口号,32流标识,128的ipv6地址,32的范围id,一共26字节
专用socket地址
因为通用地址不好操作
linux为各个协议族提供了专门的socket地址结构
1.UNIX:
#include<sys/un.h>
struct sockaddr_un{
sa_family_t sin_family; //地址族AF_UNIX
char sun_path[108]; //文件路径名
}
2.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.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地址,要用网络字节序表示
}
所有 专用类型的socket类型的变量在实际使用的时候都要转换为通用socket地址类型sockaddr(强转)
因为各种接口接收的参数类型都是sockaddr类型
ip地址转换函数
网络字节序整数(二进制)<-------->点分十进制字符串
ipv4:
#include<arpa/inet.h>
in_addr_t inet_addr(const char*strptr) 10->2 失败返回INADDR_NONE
int inet_aton(const char *cp,struct in_addr *inp) 10->2 结果存在inp指向的地址
成功返回1,失败返回0
char *inet_ntoa(struct in_addr in ); 2-->10
该函数内部用一个静态变量存储转化的结果,所以不可重入,只能设置一次
ipv4和ipv6
#include<arpa/inet.h>
int inet_pton(int af,const char * src,void *dst) 10--->2
结果存入dst指向的内存中
af指定地址族;AF_INET/AF_INET6
成功返回1,失败返回0
const char *inet_ntop(int af,const void *src,char *dst,socklen_t cnt);
2----》10
cnt指定目标存储单元的大小
成功返回目标存储单元的地址
创建socket
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain,int type,int protocol);
domain:协议族类型,tcp/ip使用PF_INET(ipv4)
/PF_INET6(ipv6)
/PF_UNIX
type:服务类型 SOCK_STREAM SOCK_DGRAM
流服务 数据报
对于tcp/ip而言,流服务对应着tcp协议
数据报对应着udp协议
SOCK_NONBLOCK:新建的socket设为非阻塞
SOCK_CLOEXEC:用fork调用创建子进程时在子进程中关闭该socket
protocol:在前两个参数的情况下,再选择一个具体的协议,前两个参数已经决定她的值
几乎所有的情况下都设为0,表示使用默认协议
成功返回一个文件描述符,失败返回-1
命名socket(bind)
只有命名了,客户端才知道怎么去链接这个socket
#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd,const struct socketaddr * my_addr,socklen_t addrlen)
把sockfd和my_addr地址绑定,addrlen指出socket地址的长度
成功返回0,失败返回-1并设置errno:
EACCES:地址受保护,进超级用户可访问
EADDRINUSE:被绑定的地址正在使用
监听socket(listen)
#include<sys/socket.h>
int listen(int sockfd,int backlog);
sockfd 被监听的socket
backlog内核监听队列的最大长度
监听队列如果超过backlog,客户端将受到ECONNREFUSED
Linux内核2.2之前,backlog指处于半连接(syn_recv)和全连接(established)的socket的上限,
在2.2之后,它表示处于完全链接的socket的上限
处于半连接的上限由tcp_max_syn_backlog内核参数定义,典型值为5
listen成功返回0,失败返回-1
接收连接accept
#include<sys/types.h>
#include<sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd是执行过listen系统调用监听的socket
addr用来获取被接受连接的远端的socket地址
addrlen 表示该地址的长度
accept连接成功返回一个新的连接标识符(socket),可以通过这个新的文件描述符来进行通信
失败返回-1
accept只是从监听队列中取出连接,而不管连接处于何种状态,更不关心网络状况的变化
客户端:发起连接connect
服务端通过listen调用被动的接收连接,那么客户端需要调用connect主动连接。
int connect(int sockfd, const strucr sockaddr *server_addr,socklen_t addrlen);
socketfd参数:系统调用返回一个scoket
server_addr:是服务器监听的socket地址
addrlen:这个地址的长度
connect成功时返回0
一旦成功,sockfd就唯一标识了这个连接,客户端就可以读写socket和服务端通信
失败返回-1,errno:ECONNREFUSED 目标端口不存在
ETIMEDOUT 超时
关闭连接
int clsoe(int sockfd);
close 系统调用并非总是立即关闭一个连接,而是将sockfd引用-1,只有sockfd变为0,才真正关闭连接
在多进程中,一次fork会使父进程的fd+1,所以我们必须对父子进程的fd都执行close
(如要立即终止,使用shutdown)
int shutdown(int fd,int howto)
howto:SHUT_RD 只关闭socketfd上读的这一块,程序不能再执行读操作,且缓冲区中的数据被丢弃
SHUT_WR 关闭写的
SHUT_RDWR 读写同时关闭