在之后的文章中我们将来讲解网络编程中的相关知识点,再本文中我们首先来讲解一下网络编程中的预备知识:
在IP数据包中有两个IP地址分别是源IP地址和目的IP地址,此时这里就会出现一个问题就是:如果我们光有IP地址,是无法完成通信的。有了IP地址只能够将消息发送到对方的机器上,但是还徐亚欧有一个其他的标识位来进行区分,这个数据需要发送给哪一个程序进行解析。
端口号是(port)是传输层协议的内容
之前我们在学习系统编程的时候,学习过在Linux中pid可以表示一个进程;此处在网络编程中端口号也表示唯一的一个进程,那么这两者有什么关系?对于此处的理解在我看来就是:我们使用的操作系统有很多,那么就意味着每一个操作系统都有着自己的进程ID的处理方式,但是网络在目前只有一个,所有的操作系统都需要接入这个网络,有了端口号就可以让网络在各个操作系统上都可以正常的运行,不用让网络来对操作系统进行适配,同时可以让网络与操作系统进行解耦。
在此处我们简单直观的了解一下TCP协议和UDP协议,后面我们再对其进行详细的学习。
在之前学习C语言的时候,我们遇到过一个问题就是:计算机内存中的多字节序对于内存地址有大段和小段之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大段小段之分。对于网络数据流来说同样有着大段小段之分。
big-endian | little-endian | |
---|---|---|
0x0000 | 0x12 | 0xcd |
0x0001 | 0x34 | 0xab |
0x0002 | 0xab | 0x34 |
0x0003 | 0xcd | 0x12 |
为了使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用一下的库函数做网络字节序和主机字节序的转换。 |
#include
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为短整数
// htonl 表示将32位的长整数从主机字节序转换成网络字节序,例如将IP地址转化后准备发送
// 如果主机是小段字节序,这些函数将参数做相应的大小端转换然后返回
// 如果主机是大段字节序,这些函数将不做转换,将参数原封不动的返回
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
在上述的API中我们都可以看到一个类型 – struct sockaddr结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、 IPv6。然而, 各种网络协议的地址格式并不相同。
struct sockaddr和struct sockaddr_in这两个结构体用来处理网络通信的地址。
/* Structure describing a generic socket address. */
struct sockaddr
{
__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
};
虽然socket api的接口是sockaddr,但是我们真正在基于IPv4编程时,使用的结构是sockaddr_in,这个结构体中主要有三部分信息:地址类型,端口号,IP地址
/* Structure describing an Internet socket address. */
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
// -----------------------------------------------------------------
// __SOCKADDR_COMMON (sin_);的定义如下:
/*
#define __SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family
sa_prefix##family 是一个预处理宏中的宏替换语法。它的作用是将两个标识符合并成一个单词,并用于创建结构体的字段名。在这个语法中:
sa_prefix 是一个标识符(通常是一个结构体字段的前缀),它是宏的一个参数。
## 是预处理器中的连接运算符,用于将两个标识符连接在一起,形成一个新的标识符。
family 是另一个标识符(通常是表示套接字地址族的字段名称),它在这里与 sa_prefix 合并。
// Type to represent a port.
typedef uint16_t in_port_t;
// Internet address.
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
*/
后面的文章中我们将开始使用UDP协议、TCP协议等来进行代码的编写。