文章目录
- 前言:
- 1. 预备知识
- 1.1 理解源IP地址和目的IP地址
- 1.2 认识端口号
- 1.3 理解"端口号"和"进程ID"
- 1.4 理解源端口号和目的端口号
- 1.5 认识TCP协议
- 1.6 认识UDP协议
- 1.6 TCP vs UDP 可靠性
- 1.7 网络字节序
- 2. socket 编程接口
- 2.1 socket 常见API
- 2.2 sockaddr结构
- 3. 简单的UDP网络程序
- 3.1 UDP实现简易聊天室:
- 总结:
前言:
在现代信息技术飞速发展的今天,网络通信已经成为我们日常生活和工作中不可或缺的一部分。无论是通过电子邮件、社交媒体还是在线会议,网络通信都扮演着至关重要的角色。而在这背后,是复杂的网络协议和编程技术支撑着这一切的运行。本文旨在深入探讨网络编程的基础知识,特别是UDP和TCP这两种常用的传输层协议,以及它们在socket编程中的应用。通过本文,读者将能够理解源IP地址、目的IP地址、端口号等概念,并学习如何使用socket编程接口来创建网络应用程序。此外,本文还将介绍网络字节序的概念,以及如何通过转换函数确保网络程序的可移植性。最后,通过一个简单的UDP网络程序实例,我们将展示如何实现一个回声服务器和简易聊天室,让读者对网络编程有更直观的认识。
1. 预备知识
1.1 理解源IP地址和目的IP地址
在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址。
思考: 我们光有IP地址就可以完成通信了嘛? 想象一下发qq消息的例子, 有了IP地址能够把消息发送到对方的机器上,但是还需要有一个其他的标识来区分出, 这个数据要给哪个程序进行解析。
1.2 认识端口号
- 我们上网,无非两种动作:a. 把远端的数据拉取到本地 b. 把我的数据发送到远端
- 大部分的网络通信行为,都是用户触发的。计算机中,谁表示用户呢?进程!(客户端服务,服务端服务)
- 把数据发送到目标主机,不是目的,是手段。正真的目的,是把数据交给这个主机上的某一个服务(进程)(服务必须具有唯一的标识:端口号)
- 网络通信的本质,其实是进程帮我们进行网络通信,无论是对于C还是S
- IP(唯一的一台主机)+ port(该主机的唯一的一个进程) = 互联网中唯一的一个进程
- client -> server: client进程 -> server进程
client进程 = client ip + client port = client是互联网中唯一的一个进程
server进程 = server ip + server port = server是互联网中唯一的一个进程
唯一的找到彼此(src ip, src port; dst ip, dst port)(socket通信)
结论: 网络通信的本质:其实就是进程间通信!
进程间通信:看到公共的资源(网络)
1.3 理解"端口号"和"进程ID"
我们之前在学习系统编程的时候, 学习了 pid 表示唯一一个进程; 此处我们的端口号也是唯一表示一个进程. 那么这两者之间是怎样的关系?
- PID 是操作系统用来标识一个进程的唯一编号。
- 端口号 是网络通信中用来标识主机上特定服务的数字。
它们之间没有直接关系,PID用于操作系统内部管理,而端口号用于网络通信。另外, 一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定。
1.4 理解源端口号和目的端口号
传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 “数据是谁发的, 要发给谁”;
1.5 认识TCP协议
此处我们先对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识; 后面我们再详细讨论TCP的一些细节问题.
- 传输层协议
- 有连接
- 可靠传输
- 面向字节流
1.6 认识UDP协议
此处我们也是对UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识; 后面再详细讨论.
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
1.6 TCP vs UDP 可靠性
TCP 要保证可靠性,就需要做更多的工作——TCP协议一定更复杂——接口会更多一些。
UDP 协议一定更简单。
1.7 网络字节序
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
- 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
- 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
- TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
- 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
- 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
- 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
- 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
- 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回 ;
- 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。
2. socket 编程接口
2.1 socket 常见API
// 创建 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);
2.2 sockaddr结构
socket编程,是有不同种类的,有的是专门用来进行本地通信的(unix socket),有的是用来专门跨网络通信的(inet socket),有的是用来进行网络管理的(raw socket)。
统一接口 -> C语言写的 -> 统一类型 -> struct sockaddr
3. 简单的UDP网络程序
bind: socket = ip + port; 文件信息和网络信息给关联起来
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
填充信息:
sin_addr —— 16位地址类型: AF_INET、 32位IP地址
sin_port —— 16位端口号
sin_zero —— 8字节填充
如何理解 “192.168.1.2” <==> 4字节IP地址之间互相转换
// 4字节 转 字符串
struct IP
{
uint8_t p1;
uint8_t p2;
uint8_t p3;
uint8_t p4;
}
struct IP *temp = (struct IP*)&ipaddr;
to_string(temp->p1) + "." + to_string(temp->p2) + ...
// 字符串 转 4字节
struct IP temp;
temp.p1 = stoi(substr("."));
uint32_t ipint = (int)temp;
127.0.0.1
: 本地环回:
./udpserver 127.0.0.1 8888
127.0.0.1
: 本地环回,可以实现本地通信,常用于进行代码测试
recvfrom(接收消息):
// 接收信息
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
// buf: 输出型缓冲区
// len: 期望长度
// 返回值: 实际读到的长度
// src_addr: 输出型参数(ip 与 port)
// addrlen: 输出型参数
有人给你发了消息,你想不想知道谁给你发的? 为什么? 因为我还要给别人回消息
你通过什么信息,只到只到对方是谁? socket 对方的 IP 和 port!
sendto(发送消息):
// 发送信息
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
使用公网ip,我们的服务器,无法直接bind公网ip(云服务器不允许),也严重不推荐,bind公网ip,或者任何一个确定的ip。
实现一个回声服务器:
终端输出也是一个文件,不同的终端窗口是不同的文件。
3.1 UDP实现简易聊天室:
gitee:https://gitee.com/q-haodong/test_-linux/tree/master/20240702_udp_echo_sever
总结:
本文从网络通信的基本概念出发,逐步深入到TCP和UDP协议的细节,并探讨了它们在socket编程中的应用。通过预备知识的介绍,我们理解了源IP地址、目的IP地址、端口号等在网络通信中的重要性。同时,我们也学习了网络字节序的概念,以及如何通过特定的函数进行主机字节序和网络字节序之间的转换,以确保程序的兼容性和可移植性。在socket编程接口部分,我们介绍了创建socket文件描述符、绑定端口号、监听socket、接收请求、建立连接等常见API的使用。最后,通过实现一个UDP回声服务器和简易聊天室的示例,我们展示了UDP协议在实际网络编程中的应用。通过本文的学习,读者不仅能够掌握网络编程的基础知识,还能够获得实际编程的经验,为进一步深入学习和探索网络编程打下坚实的基础。