1. 网络发展背景
独立模式
早期计算机独立工作,无法实现信息共享,是计算机网络发展的萌芽阶段。
网络互联
随着计算机数量的增加,迫切需要一种方式将它们连接在一起,实现数据共享。这标志着计算机网络的初步形成。
局域网(LAN)与广域网(WAN)
随着技术的进步,局域网和广域网的概念应运而生。局域网通过交换机和路由器连接多台计算机,而广域网则将地理位置相隔较远的计算机连接在一起。
2. 网络协议初识
协议 是计算机网络中的一种重要约定,用于规定通信双方的数据格式、传输方式以及其他必要信息。协议的制定使得不同设备能够协同工作,实现数据的有序传输。
协议分层
为了更好地管理和维护网络协议,人们引入了分层设计的思想。这种分层模型有助于提高系统的可扩展性和可维护性。
OSI七层模型
OSI七层模型为网络协议提供了一种清晰的框架,将网络分为七个层次,每个层次负责特定的功能。然而,由于其复杂性,实际应用中并不普遍。
TCP/IP五层模型
TCP/IP是最为广泛使用的网络协议簇,采用五层模型,包括物理层、数据链路层、网络层、传输层和应用层。这种模型更为实用,是当前网络通信的基础。
3. 网络传输流程
网络传输流程图
通过网络传输分为同一网段内和跨网段的两种情况。数据在传输过程中经过不同层次的协议,通过封装和分用实现端到端的通信。
数据包封装和分用
在传输过程中,不同协议层对数据包有不同的称谓,如在传输层称为“段”、在网络层称为“数据报”、在链路层称为“帧”。数据在传输过程中通过封装和分用完成各层之间的交互。
认识IP地址
IP地址是用来标识网络中不同主机的地址。IPv4是32位的整数,通常以点分十进制表示。IPv6是IP协议的新版本,为网络提供更多的地址空间。
认识MAC地址
MAC地址用于识别数据链路层中相连的节点,长度为48位。它在网卡出厂时确定,通常是唯一的。MAC地址是数据链路层的硬件地址,与IP地址不同。
4. sockaddr结构
struct sockaddr 和其派生的具体地址结构(例如 struct sockaddr_in 和 struct sockaddr_un)用于在网络编程中表示不同类型的地址。
struct sockaddr 是一个通用的结构体,用于表示各种类型的套接字地址。
struct sockaddr {
sa_family_t sa_family; // 地址族,通常是 AF_INET 或 AF_UNIX
char sa_data[14]; // 具体的地址信息,大小为 14 字节
};
sa_family: 字段表示地址的协议族,通常是 AF_INET(IPv4)或 AF_UNIX(UNIX 域套接字)。
sa_data: 字段包含了具体的地址信息,大小为 14 字节。具体的协议族和地址信息的存储方式依赖于 sa_family 字段。
struct sockaddr_in 是用于表示 IPv4 地址的具体结构体。
struct sockaddr_in {
sa_family_t sin_family; // 地址族,始终为 AF_INET
in_port_t sin_port; // 16 位端口号,网络字节序
struct in_addr sin_addr; // IPv4 地址结构
char sin_zero[8]; // 用于填充,保持结构大小与 sockaddr 一致
};
struct in_addr {
in_addr_t s_addr; // IPv4 地址,32 位无符号整数,通常使用网络字节序存储
};
sin_family: 始终为 AF_INET,表示 IPv4 地址。
sin_port: 是 16位端口号,使用网络字节序(big-endian)存储。
in_addr: 是一个 struct in_addr 结构,表示 IPv4 地址。
sin_zero: 是用于填充,以保持结构大小与 struct sockaddr 一致。
struct sockaddr_un 是用于表示 UNIX 域套接字地址的具体结构体。
#define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t sun_family; // 地址族,始终为 AF_UNIX
char sun_path[UNIX_PATH_MAX]; // 路径名
};
sun_family 始终为 AF_UNIX,表示 UNIX 域套接字。
sun_path 包含了套接字文件的路径名,长度受限于UNIX_PATH_MAX。
sa_family 字段是这三个结构体中的一个共同成员,用于指示地址族或协议族。这个字段表示了套接字地址的类型,以便在处理套接字地址时正确地解释其余的结构体字段。以下是各结构体中的 sa_family 字段的关系:
struct sockaddr:
struct sockaddr 是一个通用结构体,其中的 sa_family 字段表示地址族。 不同的 sa_family
值对应不同的协议族或地址类型。例如,AF_INET 表示 IPv4 地址,AF_INET6 表示 IPv6 地址,AF_UNIX 表示
UNIX 域套接字地址等。
struct sockaddr_in:
struct sockaddr_in 是 struct sockaddr 的一个具体实现,用于表示 IPv4 地址。 sin_family字段表示地址族,它设置为 AF_INET,表示 IPv4 地址。
struct sockaddr_un:
sockaddr_un 是 struct sockaddr 的另一个具体实现,用于表示UNIX 域套接字地址。 sun_family 字段表示地址族,它始终为 AF_UNIX,表示 UNIX 域套接字。
5. sockaddr_in结构体详解
在网络编程中,sockaddr_in 结构体是一个关键的数据结构,用于表示 IPv4 地址和端口号。该结构体在套接字编程中扮演着重要的角色,用于指定网络通信的终端地址。
struct sockaddr_in {
sa_family_t sin_family; // 地址族,始终为 AF_INET
in_port_t sin_port; // 16 位端口号,网络字节序
struct in_addr sin_addr; // IPv4 地址结构
char sin_zero[8]; // 用于填充,保持结构大小与 sockaddr 一致
};
struct in_addr {
in_addr_t s_addr; // IPv4 地址,32 位无符号整数,通常使用网络字节序存储
};
- 设置地址族为IPv4
struct sockaddr_in server_addr; // 创建sockaddr_in结构体
bzero(&server_addr, sizeof(server_addr)); // 清零结构体
server_addr.sin_family = AF_INET;
- 设置端口号
uint16_t port = 8080;
server_addr.sin_port = htons(port);
端口号需要以网络字节序的形式传输。
网络字节序是一种用于确保不同计算机之间数据传输一致性的规范,特别针对不同字节序(大端序或小端序)的计算机。在网络通信中,使用网络字节序有助于正确解析传输的数据。
#include <arpa/inet.h>
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位短整数从网络字节序转换为主机字节序。
- 设置IP地址
const char* ip_str = "192.168.0.1";
inet_pton(AF_INET, ip_str, &(server_addr.sin_addr);
使用 inet_pton 函数将 IP 地址字符串转换为网络字节序的二进制形式,并设置到 sin_addr 字段中。这确保了在网络通信中正确解释 IP 地址。
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
参数说明:
af:地址族(Address Family),可以是 AF_INET(IPv4)或 AF_INET6(IPv6)。
src:待转换的 IP 地址字符串。
dst:转换后的二进制形式存储的目标内存空间。
返回值:
若成功转换,则返回 1。
若地址族无效或转换失败,则返回 0。
若发生错误,返回 -1,并设置 errno。
6. 网络编程接口
- socket() 函数用于创建一个套接字,为后续的网络通信提供文件描述符。
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数:
domain:指定套接字使用的协议族,可以是 AF_INET(IPv4)、AF_INET6(IPv6)、AF_UNIX(UNIX域套接字)等。
type:指定套接字的类型,可以是 SOCK_STREAM(流套接字,用于 TCP)或
SOCK_DGRAM(数据报套接字,用于 UDP)等。
protocol:指定使用的传输协议,一般为 0,表示使用默认协议。
返回值:
如果成功,返回一个非负整数,表示套接字文件描述符。
如果失败,返回 -1,表示发生错误。
- bind() 函数用于将套接字绑定到特定的网络地址和端口号,为服务器端指定监听的地址。
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd: 是套接字文件描述符。
addr: 是指向 struct sockaddr 类型的指针,用于指定要绑定的地址信息。
addrlen: 表示地址结构体的长度。
返回值:
如果成功,返回 0。
如果失败,返回 -1,表示发生错误。
- sendto() 函数用于通过UDP协议向指定的目标地址和端口发送数据。
#include <sys/types.h>
#include <sys/socket.h>
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 是目标地址的 struct sockaddr 结构体指针,包含了目标地址和端口信息。
addrlen 表示目标地址结构体的长度。
返回值:
如果成功,返回发送的字节数。
如果失败,返回 -1,表示发生错误。
- recvfrom() 函数用于通过UDP协议从指定的地址和端口接收数据。
#include <sys/types.h>
#include <sys/socket.h>
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_addr 是发送端地址的 struct sockaddr 结构体指针,用于存储发送端的地址信息。
addrlen 是一个指向整数的指针,用于存储 src_addr 结构体的长度。
返回值:
如果成功,返回接收的字节数。
如果失败,返回 -1,表示发生错误。
- listen() 函数用于TCP协议将套接字标记为被动套接字,即用于监听连接。
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
参数:
sockfd:套接字文件描述符,通常是通过 socket() 创建的监听套接字。
backlog:在连接队列中允许的未完成连接的最大数量。这指定了服务器愿意排队等待接受连接的客户端的最大数量。
返回值:
如果成功,返回 0。
如果失败,返回 -1,错误码存储在 errno 中。
- accept() 函数用于TCP协议接受客户端连接请求。它从未完成连接队列中取出一个连接请求,创建一个新的套接字用于与客户端通信,并返回这个新套接字的文件描述符。
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
sockfd:被监听的套接字文件描述符。
addr:指向 struct sockaddr 结构体的指针,用于存储客户端的地址信息。
addrlen:指向整数的指针,用于存储 addr 结构体的长度。
返回值:
如果成功,返回一个新的连接套接字文件描述符,用于与客户端通信。
如果失败,返回 -1,错误码存储在 errno 中。
- connect() 函数用于在 TCP 协议中建立与服务器的连接。
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd:套接字文件描述符,通常是通过 socket() 创建的套接字。
addr:指向 struct sockaddr 结构体的指针,包含了要连接的目标地址信息。
addrlen:addr 结构体的长度。
返回值:
如果成功,返回 0。
如果失败,返回 -1,错误码存储在 errno 中。