目录
理解源 IP 地址和目的 IP 地址
认识端口号
端口号范围划分
理解 "端口号" 和 "进程 ID"
理解源端口号和目的端口号
理解 socket
传输层的典型代表
认识 TCP 协议
认识 UDP 协议
网络字节序
socket 编程接口
socket 常见 API
sockaddr 结构
理解源 IP 地址和目的 IP 地址
IP 在网络中,用来标识主机的唯一性;
但是这里要思考一个问题:数据传输到主机是目的吗?不是的。因为数据是给人用的。比如:聊天是人在聊天,下载是人在下载,浏览网页是人在浏览?
但是人是怎么看到聊天信息的呢?怎么执行下载任务呢?怎么浏览网页信息呢?通过启动的 qq,迅雷,浏览器。
而启动的 qq,迅雷,浏览器都是进程。换句话说,进程是人在系统中的代表,只要把数据给进程,人就相当于就拿到了数据。
所以:数据传输到主机不是目的,而是手段。到达主机内部,在交给主机内的进程, 才是目的。
但是系统中,同时会存在非常多的进程,当数据到达目标主机之后,怎么转发给目标进程?这就要在网络的背景下,在系统中,标识主机的唯一性。
认识端口号
端口号(port)是传输层协议的内容.
(为了能在任意一个主机内部,标识某一个进程的唯一性,在计算机网络里引入了端口号)
1. 端口号是一个 2 字节 16 位的整数;(0到2^16-1)
2. 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
3. IP 地址 + 端口号能够标识网络上的某一台主机的某一个进程;
4. 一个端口号只能被一个进程占用.
端口号范围划分
1. 0 - 1023: 知名端口号, HTTP, FTP, SSH 等这些广为使用的应用层协议, 他们的端口号都是固定的. (就像打110就知道报警,打120知道医院救护车)
2. 1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的.
理解 "端口号" 和 "进程 ID"
之前在系统编程的时候, 知道 pid 表示唯一一个进程; 此处我们的端口号也是唯一表示一个进程. 那么这两者之间是怎样的关系?
1. 一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定;
2. 进程 ID 属于系统概念,技术上也具有唯一性,确实可以用来标识唯一的一个进程,但是这样做,会让系统进程管理和网络强耦合(例如pid变了那么port也要变,进程每次启动pid都会变),实际设计的时候,并没有选择这样做。主要是让系统与网络解耦;
3. 系统中所有进程都有pid,但并不是所有进程都想网络通信,只有少量的进程需要通信的才需要端口号。所以有端口号的才是想网络通信的。
理解源端口号和目的端口号
传输层协议(TCP 和 UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 "数据是谁发的, 要发给谁";源端口号和目的端口号在同一个报文中,它们是 TCP 或 UDP 数据报文头部的一部分
理解 socket
1. 综上,IP 地址用来标识互联网中唯一的一台主机,port 用来标识该主机上唯一的一个网络进程;
2. IP+Port 就能表示互联网中唯一的一个进程 ;
3. 所以,通信的时候,本质是两个互联网进程代表人来进行通信,{srcIp, srcPort,dstIp,dstPort}这样的 4 元组就能标识互联网中唯二的两个进程 ;
4. 所以,网络通信的本质,也是进程间通信 ;
5. 我们把 ip+port 叫做套接字 socket;
传输层的典型代表
此处先做了解;
传输层是属于内核的(传输层和网络层),那么我们要通过网络协议栈进行通信,必定调用的是传输层提供的系统调用,来进行的网络通信。
认识 TCP 协议
先对 TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识;
1. 传输层协议
2. 有连接
3. 可靠传输
4. 面向字节流
认识 UDP 协议
对 UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识;
1. 传输层协议
2. 无连接
3. 不可靠传输
4. 面向数据报
网络字节序
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分(大小端是数据的存储方式;), 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
TCP/IP 协议规定,网络数据流应采用大端字节序,即低地址高字节.
不管这台主机是大端机还是小端机, 都会按照这个 TCP/IP 规定的网络字节序来发送/接收数据;
如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;
为使网络程序具有可移植性,使同样的 C 代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
socket 编程接口
socket 常见 API
API(Application Programming Interface,应用程序编程接口)是一组定义不同软件组件之间交互规则的接口。简单来说,它是一种使得不同的软件系统、应用程序能够相互通信、共享数据和功能的方式。
C
// 创建 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);
sockaddr 结构
socket API 是一层抽象的网络编程接口,适用于各种底层网络协议,如 IPv4、IPv6,以及后面的 UNIX Domain Socket. 然而, 各种网络协议的地址格式并不相同.
1. IPv4 和 IPv6 的地址格式定义在 netinet/in.h 中,IPv4 地址用 sockaddr_in 结构体表示,包括 16 位地址类型, 16 位端口号和 32 位 IP 地址.
2. IPv4、IPv6 地址类型分别定义为常数 AF_INET、AF_INET6. 这样,只要取得某种 sockaddr 结构体的首地址,不需要知道具体是哪种类型的 sockaddr 结构体,就可以根据地址类型字段确定结构体中的内容.
3. socket API 可以都用 struct sockaddr *类型表示, 在使用的时候需要强制转化成 sockaddr_in; 这样的好处是程序的通用性, 可以接收 IPv4, IPv6, 以及 UNIX Domain Socket 各种类型的 sockaddr 结构体指针做为参数;
sockaddr 结构
sockaddr_in 结构
虽然 socket api 的接口是 sockaddr, 但是我们真正在基于 IPv4 编程时, 使用的数据结 构是 sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP 地址.
in_addr 结构
in_addr 用来表示一个 IPv4 的 IP 地址. 其实就是一个 32 位的整数;
Socket编程UDP
UDP比较简单,建立好后就可以连接了。
UDP 网络编程
V1 版本 - echo server
简单的回显服务器和客户端代码 备注: 代码中会用到地址转换函数
socket函数:
#include <sys/types.h>
#include <sys/socket.h>int socket(int domain, int type, int protocol);
验证文件描述符_socket是3;
bind 绑定端口号:一个用于将一个套接字(socket)与一个特定的地址绑定的系统调用。在网络编程中,通常用于在服务端应用中指定服务器的地址和端口。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind() 函数通常用于服务端在使用 UDP 或 TCP 协议时,绑定到一个特定的端口和地址。客户端则不需要调用 bind(),因为它通常会让操作系统自动为它选择一个可用端口。
ip字符串转化网络字节序列
验证绑定成功:
显示当前系统的网络连接、套接字以及相关信息,查看udp信息:
如果信息不全可以试试sudo,用管理员权限
recvfron函数:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
recvfrom 是一个系统调用,通常用于在网络编程中接收数据。它属于 套接字(socket)编程 的一部分,特别是用于 UDP 套接字(虽然它也可以用于其他协议)。recvfrom 函数能够接收来自远程主机的消息,并且除了接收到的数据,还可以获取消息的源地址和端口。