数据结构和字节序
基础数据结构
- 1基础数据结构和函数:地址 表示数据结构
struct sockaddr_in{
_SOCKADDR_COMMON(sin);
in_port_t sin_port; //端口号
struct in_addr sin_addr; // IP地址
}
典型的填充IP地址的数据结构如下:
struct sockaddr_in addr;
addr.sin_family =AF_INET;
addr.sinport = htons(80);
addr.sin_addr.s_addr = inet_addr("192.168.0.1");
sin_port 和 sin_addr.s_addr 两个值都是多字节的整数,socket 规定这里必须使
用网络字节序。
网络 字节序和本地字节序的转换
- 字节序:在网络应用中,字节序是一个必须考虑的因素,因为不同机器类型可能采用不同标准的
字节序,所以均须按照网络标准转化。网络传输的标准叫做网络字节序,实际上是大端序。而我们常用的 X86 或者 ARM 往往都是小端序。
手工进行字节序的转换往往是不方便的,对于可移植的程序来说更是如此。总是需要知
道自己的本地主机字节序也是很麻烦的。所以,系统提供了四个固定的函数,用来在本地字
节序和网络字节序之间转换。这四个函数包含在头文件<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);
这四个函数功能依次列举如下:
32 位整数从主机字节序转换为网络字节序;
16 位整数从主机字节序转换为网络字节序;
32 位整数从网络字节序转换为主机字节序;
16 位整数从网络字节序转换为主机字节序。
主机名和地址转换函数
在实际的网络编程中,往往需要在IP地址的点分十进制表示和二进制之间相互 转化,也需要进行主机名和地址的转换,系统提供了一系列的函数,一般需要包含头文件<netinet/in.h>和<arpa/inet.h>
传输中需要二进制,显示时需要十进制
in_addr_t inet_addr(const char *cp)
这个函数将一个点分十进制的 IP 地址字符串转换成 in_addr_t 类型,该类型实际上是一个 32 位无符号整数,事实上就是前文提到的 struct in_addr 结构中的 s_addr 域的数据类型。
注意这个二进制表示的 IP 地址是网络字节序的。这个函数其实在前文举例填充 struct sockaddr_in 的时候用过了。192.168.0.1 在 PC 上会被转换成 0x0100A8C0。
char *inet_ntoa(struct in_addr in)
此函数可以将结构 struct in_addr 中的二进制 IP 地址转换为一个点分十进制表示的字符
串,返回这个字符串的首指针。使用起来很方便。但是要注意,它返回的缓冲区是静态分配
的,在并发或者异步使用时要小心,因为缓冲区随时可能被其它调用改写。
- 通过主机名获取IP地址
实际应用中,很多时候得到的是通信另一方的主机名,所以需要将主机名转换为IP地址,传统上有两个函数生命在<netdb.h>中来进行这个 操作。
struct hostent *gethostbyname(const char *name);
直接根据主机名字符串返回一个 struct hostent 结构。
socket常用操作
- socket的常用操作 主要包括:
- 创建socket
- 绑定地址和端口
- 连接服务器
- 设置socket为监听模式
- 接收连接
- 网络数据的读和写
1.创建socket
创建socket的函数原型:
int socket(int domain, int type, int protocol);
其中,
- domain代表了socket使用的地址类型(IPV4 IPV6)ipV4使用AF_INET
- type代表了socket类型(UDP TCP)UDP:SOCK_DGRAM TCP:SOCK_STREAM
- protocol:协议类型,目前一般为0
TCP例子:
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
UDP例子:
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
2.绑定地址和端口
绑定函数原型:
int bind(int socket,const struct sockaddr *address,socklent address_len);
参数说明:
- socket指向socket的文件描述符
- address:指向struct sockaddr 结构的指针,如果填入IP地址,则为struct sockaddr_in 其中的const修饰的是常数,不能变化
- address_len address内容的长度
示例:
struct sockaddr_in server_addr;
(void)memset(&server_addr, 0, sock_len); //从server_addr开始,为连续的n个内存地址赋值0
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(80);
if (bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr))) {
perror("bind(2) error");
goto err;
}
3.连接服务器
函数原型:
int connect(int socket, const struct sockaddr *address, socklent address_len);
TCP示例:
struct sockaddr_in server_addr;
(void)memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(7007);
server_addr.sin_addr.s_addr = inet_addr("192.168.0.1");
if (connect(conn_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("connect(2) error");
goto err;
}
4.监听模式
函数原型:
int listen(int socket,int backlog);
- socket:指向socket的文件描述符
- 等待连接的队列长度,实际上队列会大于 这个数字,通常取5
5.接收连接
函数原型:
int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
- socket:指向socket的文件描述符
- address:指向struct sockaddr 结构的指针,如果填入IP地址,则为struct sockaddr_in
- address_len 与前面的函数不同,这里是一个指向 socklen_t 类型的指针,这个存储区域
用来返回上一个参数返回的地址数据结构的长度
struct sockaddr_in client_addr;
socklen_t sock_len;
„„
while (true) {
conn_sock = accept(server_sock, (struct sockaddr *)&client_addr, &sock_len);
if (conn_sock < 0) {
if (errno == EINTR) {
/* restart accept(2) when EINTR */
continue;
}
break;
}
}
6.读数据函数
- read(2)、recv(2)、recvfrom(2)和 recvmsg(2)
ssize_t read(int fd, void *buf, size_t count);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
对于这四个函数的解析:
write(2)、send(2)、sendto(2)和 sendmsg(2)
ssize_t write(int fd, const void *buf, size_t count);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
总结之相关操作的函数原型:
int socket(int domain, int type, int protocol);//创建socket
//sock_fd = socket(AF_INET, SOCK_STREAM, 0);
//sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
int bind(int socket, const struct sockaddr *address, socklent address_len);//绑定地址端口
int connect(int socket, const struct sockaddr *address, socklent address_len);//连接
//可以看出,其中的参数完全相同,
//bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr))
int listen(int socket, int backlog);//监听模式
int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);//接受连接