济南城乡住房建设厅网站,网站建设简介淄博,企业网站怎么维护,东莞市桥头镇网络工程公司欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab#xff0c;机器人运动控制、多机器人协作#xff0c;智能优化算法#xff0c;滤波估计、多传感器信息融合#xff0c;机器学习#xff0c;人工智能等相关领域的知识和… 欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab机器人运动控制、多机器人协作智能优化算法滤波估计、多传感器信息融合机器学习人工智能等相关领域的知识和技术。 网络套接字、网络字节序、sockaddr结构 套接字概念网络字节序IP地址转换函数sockaddr数据结构 专栏《网络编程》 套接字概念
Socket本身有“插座”的意思在Linux环境下用于表示进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。 既然是文件那么理所当然的我们可以使用文件描述符引用套接字。与管道类似的Linux系统将其封装成文件的目的是为了统一接口使得读写套接字和读写文件的操作一致。区别是管道主要应用于本地进程间通信而套接字多应用于网络进程间数据的传递。 套接字的内核实现较为复杂不宜在学习初期深入学习。 在TCP/IP协议中“IP地址TCP或UDP端口号”唯一标识网络通讯中的一个进程。“IP地址端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系。 套接字通信原理如下图所示 在网络通信中套接字一定是成对出现的。一端的发送缓冲区对应对端的接收缓冲区。我们使用同一个文件描述符索发送缓冲区和接收缓冲区。 TCP/IP协议最早在BSD UNIX上实现为TCP/IP协议设计的应用层编程接口称为socket API。本章的主要内容是socket API主要介绍TCP协议的函数接口最后介绍UDP协议和UNIX Domain Socket的函数接口。
网络字节序
我们已经知道内存中的多字节数据相对于内存地址有大端和小端之分磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。网络数据流同样有大端小端之分那么如何定义网络数据流的地址呢发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出接收主机把从网络上接到的字节依次保存在接收缓冲区中也是按内存地址从低到高的顺序保存因此网络数据流的地址应这样规定先发出的数据是低地址后发出的数据是高地址。 TCP/IP协议规定网络数据流应采用大端字节序即低地址高字节。例如上一节的UDP段格式地址0-1是16位的源端口号如果这个端口号是10000x3e8则地址0是0x03地址1是0xe8也就是先发0x03再发0xe8这16位在发送主机的缓冲区中也应该是低地址存0x03高地址存0xe8。但是如果发送主机是小端字节序的这16位被解释成0xe803而不是1000。因此发送主机把1000填到发送缓冲区之前需要做字节序的转换。同样地接收主机如果是小端字节序的接到16位的源端口号也要做字节序的转换。如果主机是大端字节序的发送和接收都不需要做转换。同理32位的IP地址也要考虑网络字节序和主机字节序的问题。 为使网络程序具有可移植性使同样的C代码在大端和小端计算机上编译后都能正常运行可以调用以下库函数做网络字节序和主机字节序的转换。
#include arpa/inet.huint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);h表示hostn表示networkl表示32位长整数s表示16位短整数。 如果主机是小端字节序这些函数将参数做相应的大小端转换然后返回如果主机是大端字节序这些函数不做转换将参数原封不动地返回。
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。 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 108struct sockaddr_un {__kernel_sa_family_t sun_family; /* AF_UNIX */char sun_path[UNIX_PATH_MAX]; /* pathname */
};IPv4和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 *类型表示在传递参数之前要强制类型转换一下例如
struct sockaddr_in servaddr;
bind(listen_fd, (struct sockaddr *)servaddr, sizeof(servaddr)); /* initialize servaddr */