字节序是一个处理器架构特性,用于指示像整数这样的大数据类型内部的字节如何排序。分为大端字节序和小端字节序,对应大端机器和小端机器。
大端字节序:即把数据的高字节放到低地址中
小段字节序:高字节放到高地址中
我们可以使用一串简单的代码来确定我们的机器是小端机器还是大端机器:
#include
using namespace std;
int main()
{
union Data
{
int a;
char b;
}data;
data.a = 1;
if(data.b == 1)
{
cout<<"小端"<<endl;
}
else
{
cout<<"大端"<<endl;
}
return 0;
}
主机字节序:指的是机器本身的字节序,如果是大端,主机字节序就是大端,反之就是小端
网络字节序:规定网络当中传输的字节序使用大端,意味着如果是小段机器在传输的时候,需要将数据转化成大端字节序传输,对端机器默认传输过来的数据是大端字节序
前面我们了解到网络传输中数据的五元组就和寄快递一样,我们的收件人要知道寄件人的姓名和地址等信息才能告诉寄件人他是否收到了快递,计算机之间通过网络进行通信时就要知道IP地址等信息它才知道是谁给它发了信息,并作出回应,而源数据要通过网络进行传递就需要把它转化为网络字节序来进行传输。
综上我们得出结论:如果通信双方都是小段机器的时候,对于网络报头当中的IP和port还是必须遵循网络字节序的格式进行传输,否则网络链路上面的转发设备就无法正确转发该条数据,但因为双方都是小段机器,我们传输的数据可以不用进行字节序替换。
对于主机字节序和网络字节序之间的相互转换,我们用到下面的函数:
主机字节序转换为网络:
函数原型:
#include
uint32_t htonl(uint32_t hostlong);
返回值:
以网络字节序表示的32位整数
uint16_t htons(uint16_t hostshort);
返回值:
以网络字节序表示的16位整数
uint32_t ntohl(uint32_t netlong);
返回值:
以主机字节序表示的32位整数
uint16_t ntohs(uint16_t netshort);
返回值:
以主机字节序表示的16位整数
这4个函数记忆起来也不难,我们只要记住h表示“主机”,n表示网络,l表示32位“长”整数,s表示16位“短”整数。如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。
客服端:创建套接字、绑定地址信息(不推荐)、发送数据、接收数据、关闭套接字
服务端:服务端:创建套接字、绑定地址信息、发送数据、接受数据、关闭套接字
接下来我们就按照这个接口一步一步的介绍相关接口
函数原型:
#include
#include
int socket(int domain, int type, int protocol);
参数说明:
domain:地址域,指定网络层到底使用什么协议,例如AF_INET表示ipv4版本的ip协议,AF_INET6表示ipv6版本的IP协议
type:套接字类型,例如SOCK_DGRAM指用户数据报套接字SOCK_ATREAM指流式套接字
protocol:协议,例如:SOCK_DGRAM:指定为0,表示采用默认协议(默认是UDP协议),SOCK_STREAM:指定为0,表示采用默认协议(TCP)
返回值:返回一个套接字操作句柄,本质是一个文件描述符,返回值大于0成功,小于0失败
对于protocol,它其实是一个枚举类型
也就是说这里我们创建套接字时可以有三种表达方式,他们的意义都是一样的
例如:
socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
或者
socket(AF_INET,SOCK_DGRAM,0);
或者
socket(AF_INET,SOCK_DGRAM,17);
我们已经知道怎么创建一个套接字了,那么创建套接字的意义是什么呢?在此之前我们需要知道一台设备要进行网络通讯,一定要有一个网卡设备来发送和接受数据,而每一个网卡都有一个MAC地址,称为物理地址,它是唯一的。
而创建套接字的意义就是将进程和网卡绑定起来,进程可以从网卡中接收数据,也可以通过网卡进行数据的发送。
服务器想要提供某些服务,必须要让客服端知道怎么将数据传输服务端,而网络当中传输数据的时候,是按照ip地址和port来进行传输的。(在整个网络链路当中的传输,ip地址起到了作用,当数据到达目标主机之后,是根据端口号来识别到底是哪个进程的数据)
函数原型:
#include
#include
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
参数说明:
sockfd:套接字描述符(socket函数的返回值)
addr:告诉操作系统内核,当前进程要绑定的地址信息是啥
addrlen:绑定的地址信息长度是多少
返回值:函数执行成功返回0,否则返回-1, 并设置错误代码.
我们知道ipv4版本的IP协议的地址信息是保存在sockaddr_in这个结构体中的,对于sockaddr结构体和sockaddr_in结构体我们呢可以在vim/usr/include/netinet/in.h里面找到,socketaddr可能在这里面找不到,可以man 2 bind看看。
存储数据图示:
存储数据图示:
IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.
注意:struct socketaddr这个结构体是一个通用的结构体,并不是某一个具体的地址信息。
使用bind函数的时候要将struct sockaddr_in结构体强转为struct sockaddr结构体。
接口:
原型:
#include
#include
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
参数说明:
sockfd:套接字描述符
buf:待发送数据
len:数据长度
flags:标志位,0表示阻塞发送
dest_addr:消息接收方的地址信息结构
addrlen:地址信息长度
接口:
#include
#include
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
参数说明:
sockfd:套接字描述符
buf:缓冲区,接受回来的数据存放的缓冲区
len:最大接收能力
flags:标志位,0表示阻塞
src_add:消息发送方的信息地址结构,它本质上是一个出参,由recvfrom函数进行填充数据结构
addrlen:输入输出型参数,一方面告诉recvfrom函数我们准备的地址信息结构的长度,另一方面,recvfrom函数告诉我们发送方具体的地址信息长度
关闭套接字只需要用我们之前学过的close函数就可以完成了。
int close(int fd);