网络编程-数据结构与函数详解
在网络编程-一个简单的客户端与服务器程序(0),(看这篇就够了)中已经对程序整体有了宏观的认识。这篇主要对数据结构与函数详解。
数据结构与函数详解
IPv4相关结构:
struct in_addr
{
in_addr_t s_addr; //表示32位的IP地址,32位无符号整型
}
struct sockaddr_in
{
uint8_t sin_len; //表示该结构体的长度,8位无符号整型
sa_family_t sin_family; //表示套接口使用的协议族,8位无符号整型
in_port_t sin_port; //表示套接口使用的端口号,16位无符号整型
struct in_addr sin_addr; //表示IP地址,32位无符号整型
char sin_zero[8]; //该成员基本不使用,总是置为0
}
sockaddr_in是IPV4套接字地址结构。
sin_len 成员是不要求一定存在的,即便这个成员存在,也无需设置它或者检查它。换句话说就是一般情况下,我们用不到该成员。
这三个sin_family、sin_port、sin_addr成员是必须存在的,分别表示套接字使用的协议族、端口号、还有IP地址。
sin_zero 该成员不使用,总是设置为0
htonl/htons/ntohl/ntohs
本地序(也称主机序)即指处理器本身所采用的字节序,因此有的大端序,有的小端序。而网络序,是指网络传输采用的字节序。所幸,网络序是标准化的,即一般统一采用大端序。因此,发送网络数据之前需要将数据转换为网络序。
htonl、htons用于本地序转网络序。
ntohl、ntohs用于网络序转本地序。
#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);
例子:
网络字节是0x0600,而在主机内部所读的字节是0x06.
简单而言,htons()是将一个数的高低位进行互换:(如:06 00 --> 00 06)
inet_ntop/inet_pton
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *restrict src,
char *restrict dst, socklen_t size);
int inet_pton(int af, const char *restrict src, void *restrict dst);
inet_ntop()函数将一个数字地址转换成一个适合显示的文本字符串。
inet_pton()函数应该将标准文本表示形式的地址转换为数字二进制形式。
af参数根据协议族的不同,选择AF_INET或AF_INET6
src参数指向的字符串,转为数值,存放在addstr指向的内存中
inet_pton/inet_ntop分别用于将字符串ip地址转为4字节大小的无符号整型和将无符号整型转换为ip地址字符串。例子为
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[]){
char ip[] = "192.168.0.114";
struct in_addr addr;
int ret = inet_pton(AF_INET, ip, (void *)&addr); //IP字符串 ——>网络字节流
if(0 == ret){
printf("inet_pton error, return 0\n");
return -1;
}else{
printf("inet_pton ip: %ld\n", addr.s_addr);
printf("inet_pton ip: 0x%x\n", addr.s_addr);
}
const char *pstr = inet_ntop(AF_INET, (void *)&addr, ip, sizeof(ip)); //网络字节流 ——>IP字符串
if(NULL == pstr){
printf("inet_ntop error, return NULL\n");
return -1;
}else{
printf("inet_ntop ip: %s\n", ip);
}
return 0;
}
编译运行:
从运行结果中可以清晰看到两者之间的转换。
socket 函数
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
socket()函数应该在一个通信域中创建一个未绑定的套接字,并返回一个文件描述符,该文件描述符可以在以后对套接字进行操作的函数调用中使用。文件描述符应按文件描述符分配中描述的方式分配。
domain:
协议域,决定了 socket 的地址类型,在通信中必须采用对应的地址。常用的协议族有:AF_INET(ipv4地址与端口号的组合)、AF_INET6(ipv6地址与端口号的组合)、AF_LOCAL(绝对路径名作为地址)。
type:
常用的类型有:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。
其中 SOCK_STREAM 表示提供面向连接的稳定数据传输,即 TCP 协议。SOCK_DGRAM表示数据报套接字,即UDP协议。
protocol:
常用的协议有:IPPROTO_TCP(TCP协议)、IPPTOTO_UDP(UDP协议)、IPPROTO_SCTP(STCP协议)。
当值位 0 时,会自动选择 type 类型对应的默认协议。
socket 起源于 UNIX,而 UNIX/Linux 基本哲学之一就是「一切皆文件」,socket 即是一种特殊的文件,一些 socket 函数就是对其进行的操作。 都可以用「open → write/read → close」模式来操作。
bind 函数
调用socket函数之后已经确定了协议族和传输协议,但是还没有确定本地协议,即套接字地址信息。
#include <sys/socket.h>
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
socket:
即 socket 描述字,由 socket() 函数创建。
*address:
一个 const struct sockaddr 指针,指向要绑定给 sockfd 的协议地址。
这个地址结构根据地址创建 socket 时的地址协议族不同而不同,例如 ipv4 对应 sockaddr_in,ipv6 对应 sockaddr_in6
这几个结构体在使用的时候,都可以强制转换成 sockaddr。
下面是这几个结构体对应的所在的头文件:
1、sockaddr: sys/socket.h
2、sockaddr_in: netinet/in.h
3、sockaddr_in6: netinet6/in.h
_in 后缀意义:互联网络(internet)的缩写,而不是输入(input)的缩写。
listen 函数
服务器调用,该套接字可以接收来自客户端的连接请求。
#include <sys/socket.h>
int listen(int socket, int backlog);
socket:
即 socket 描述字,由 socket() 函数创建。
backlog:
指定在请求队列中的最大请求数,进入的连接请求将在队列中等待 accept() 它们。
connect 函数
由客户端调用,与目的服务器的套接字建立一个连接。
#include <sys/socket.h>
int connect(int socket, const struct sockaddr *address,
socklen_t address_len);
socket:
目的服务器的 socket 描述符 。
*address
一个 const struct sockaddr 指针,包含了目的服务器 IP 和端口。
address_len:
协议地址的长度,如果是 ipv4 的 TCP 连接,一般为 sizeof(sockaddr_in);
accept 函数
accept函数在服务端调用,它用于接受来自客户端的连接,从已完成连接队列返回一个已完成连接。
#include <sys/socket.h>
int accept(int socket, struct sockaddr *restrict address,
socklen_t *restrict address_len);
socket:
服务器的 socket 描述字,由 socket() 函数创建。
*restrict address:
一个 struct sockaddr 指针,用来存放提出连接请求客户端的主机的信息
*restrict address_len:
协议地址的长度,如果是 ipv4 的 TCP 连接,一般为 sizeof(sockaddr_in)。
close 函数
在数据传输完成之后,手动关闭连接。
#include <unistd.h>
int close(int fildes);
fd:
需要关闭的连接 socket 描述符 。
网络 I/O 函数
当客户端和服务器建立连接后,可以使用网络 I/O 进行读写操作。
网络 I/O 操作有下面几组:
read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()
最常用的是 read()/write()
ssize_t read(int fildes, void *buf, size_t nbyte);
ssize_t write(int fildes, const void *buf, size_t nbyte);
鉴于该文是侧重于描述 数据结构与函数详解的工作原理,就不再详细描述这些函数了。
(微信公众号【程序猿编码】)
(添加本人微信号,备注加群,进入程序猿编码交流群,领取学习资料,获取每日干货)
微信公众号【程序猿编码】,这里Linux c/c++ 、Go语言、数据结构与算法、网络编程相关知识,常用的程序员工具。还有汇聚精炼每日时政、民生、文化、娱乐新闻简报,即刻知晓天下事!
上一篇: SNS的价值在于改变互联网信息传播的方式