接上一篇:linux_进程锁与文件锁-pthread_mutexattr_init函数-pthread_mutexattr_setpshared函数
今天开始分享网络通信了,主要是就是socket套接字通信,本篇先分享一些预备知识,有网络字节序以及一些IP地址转换函数,话不多说,开始上菜:
此博主在CSDN发布的文章目录:我的CSDN目录,作为博主在CSDN上发布的文章类型导读
1.套接字概念
Socket本身有“插座”的意思,在Linux环境下,用于表示进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。
既然是文件,那么理所当然的,我们可以使用文件描述符引用套接字。与管道类似的,Linux系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操作一致。区别是管道主要应用于本地进程间通信,而套接字多应用于网络进程间数据的传递。
套接字的内核实现较为复杂,不宜在学习初期深入学习。
在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程。
“IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系。
套接字通信原理如下图所示:
在网络通信中,套接字一定是成对出现的。一端的发送缓冲区对应对端的接收缓冲区。我们使用同一个文件描述符索发送缓冲区和接收缓冲区。
TCP/IP协议最早在BSD UNIX上实现,为TCP/IP协议设计的应用层编程接口称为socket API。
2.网络字节序
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。
网络数据流同样有大端小端之分,那么如何定义网络数据流的地址呢?发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存,因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址。
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。例如UDP段格式,地址0-1是16位的源端口号,如果这个端口号是1000(0x3e8),则地址0是0x03,地址1是0xe8,也就是先发0x03,再发0xe8,这16位在发送主机的缓冲区中也应该是低地址存0x03,高地址存0xe8。
但是,如果发送主机是小端字节序的,这16位被解释成0xe803,而不是1000。因此,发送主机把1000填到发送缓冲区之前需要做字节序的转换。
同样地,接收主机如果是小端字节序的,接到16位的源端口号也要做字节序的转换。如果主机是大端字节序的,发送和接收都不需要做转换。同理,32位的IP地址也要考虑网络字节序和主机字节序的问题。
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
h表示host,n表示network,l表示32位长整数,s表示16位短整数。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
int a = 7845;//0x1EA5(两个字节)
大端字节序:高位字节在前,低位字节在后,这是人类读写数值的方法,存储方式:1EA5。
小端字节序:低位字节在前,高位字节在后,存储方式:A51E。(计算机存储方式)
2.1.htonl函数
函数作用:
将无符号整数hostlong从主机字节顺序转换为网络字节顺序。
头文件:
#include <arpa/inet.h>
函数原型:
int32_t htonl(uint32_t hostlong);
函数参数:
hostlong:需要转换的无符号主机字节
返回值:
返回一个网络字节顺序的值。
2.2.htons函数
函数作用:
从主机字节转换无符号短整数hostshort顺序到网络字节顺序。
头文件:
#include <arpa/inet.h>
函数原型:
uint16_t htons(uint16_t hostshort);
函数参数:
hostshort:需要转换的无符号短整数主机字节
返回值:
返回一个网络字节顺序的值。
2.3.ntohl函数
函数作用:
从网络字节顺序转换无符号整数netlong到主机字节顺序。
头文件:
#include <arpa/inet.h>
函数原型:
uint32_t ntohl(uint32_t netlong);
函数参数:
netlong:需要转换的无符号整数网络字节顺序的值。
返回值:
返回主机字节顺序的值。(32位)
注意:
2.4.ntohs函数
函数作用:
从网络字节转换无符号短整数netshort顺序到主机字节顺序。
头文件:
#include <arpa/inet.h>
函数原型:
uint16_t ntohs(uint16_t netshort);
函数参数:
netshort:需要转换的无符号短整数网络字节顺序的值。
返回值:
返回主机字节顺序的值。(16位)
3.IP地址转换函数
早期:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
char *inet_ntoa(struct in_addr in);
①只能处理IPv4的ip地址
②是不可重入函数
③注意参数是struct in_addr
现在:
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
①支持IPv4和IPv6
②可重入函数
③其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr。
因此函数接口是void *addrptr。
3.1.inet_pton函数
函数作用:
将点分文本的IP地址转换为二进制网络字节序”的IP地址。
头文件:
#include <arpa/inet.h>
函数原型:
int inet_pton(int af, const char *src, void *dst);
函数参数:
af:地址簇
取值:AF_INET --表示IPV4
取值:AF_INET6 --表示IPV6
src:源地址
dst:转换后的地址
返回值:
返回1:成功;
返回0:输入不是有效表达式;
返回-1:失败。
例如:
struct sockaddr_in serv_addr;
char buf[BUFSIZ];
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr);
serv_addr.sin_port = htons(SERV_PORT);
3.2.inet_ntop函数
函数作用:
将数值格式转化为点分十进制的ip地址格式。
头文件:
#include <arpa/inet.h>
函数原型:
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
函数参数:
af:地址簇
取值:AF_INET --表示IPV4
取值:AF_INET6 --表示IPV6
src:源地址
dst:转换后的地址
返回值:
若成功则为指向结构的指针,若出错则为NULL,将errno置为EAFNOSUPPORT。
注意:
3.3.bzero函数
函数作用:
将指定内存清0。
头文件:
#include <strings.h>
函数原型:
void bzero(void *s, size_t n);
函数参数:
s:需要清空内存的首地址;
n:s的大小
返回值:
无。
例如:
struct sockaddr_in serv_addr;
bzero(&serv_addr, sizeof(serv_addr));
4.sockaddr数据结构
strcut sockaddr 很多网络编程函数诞生早于IPv4协议,那时候都使用的是sockaddr结构体,为了向前兼容,现在sockaddr退化成了(void *)的作用,传递一个地址给函数,至于这个函数是sockaddr_in还是sockaddr_in6,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。
sockaddr数据结构:
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
使用 sudo grep -r “struct sockaddr_in {” /usr 命令可查看到struct sockaddr_in结构体的定义。一般其默认的存储位置:/usr/include/linux/in.h 文件中。
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */ 地址结构类型
__be16 sin_port; /* Port number */ 端口号
struct in_addr sin_addr; /* Internet address */ IP地址
/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
struct in_addr { /* Internet address. */
__be32 s_addr;
};
struct sockaddr_in6 {
unsigned short int sin6_family; /* AF_INET6 */
__be16 sin6_port; /* Transport layer port # */
__be32 sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
__u32 sin6_scope_id; /* scope id (new in RFC2553) */
};
struct in6_addr {
union {
__u8 u6_addr8[16];
__be16 u6_addr16[8];
__be32 u6_addr32[4];
} in6_u;
#define s6_addr in6_u.u6_addr8
#define s6_addr16 in6_u.u6_addr16
#define s6_addr32 in6_u.u6_addr32
};
#define UNIX_PATH_MAX 108
struct sockaddr_un {
__kernel_sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
Pv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位端口号和32位IP地址,IPv6地址用sockaddr_in6结构体表示,包括16位端口号、128位IP地址和一些控制字段。
UNIX Domain Socket的地址格式定义在sys/un.h中,用sock-addr_un结构体表示。各种socket地址结构体的开头都是相同的,前16位表示整个结构体的长度(并不是所有UNIX的实现都有长度字段,如Linux就没有),后16位表示地址类型。
IPv4、IPv6和Unix Domain Socket的地址类型分别定义为常数AF_INET、AF_INET6、AF_UNIX。这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。
因此,socket API可以接受各种类型的sockaddr结构体指针做参数,例如bind、accept、connect等函数,这些函数的参数应该设计成void *类型以便接受各种类型的指针,但是sock API的实现早于ANSI C标准化,那时还没有void *类型,因此这些函数的参数都用struct sockaddr *类型表示,在传递参数之前要强制类型转换一下,
例如:文章来源:https://www.toymoban.com/news/detail-770339.html
struct sockaddr_in servaddr;
bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)); /* initialize servaddr */
以上就是本次的分享了,希望对大家有所帮助,欢迎关注博主一起学习更多的新知识!文章来源地址https://www.toymoban.com/news/detail-770339.html
到了这里,关于linux_网络通信-套接字通信socket-网络字节序-IP地址转换函数-inet_pton函数-htonl函数-htons函数-ntohl函数-ntohs函数的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!