0. TCP\IP模型
1. 应用层
- 功能:
- 直接为用户应用程序提供网络服务(如文件传输、电子邮件、网页浏览)。
- 定义数据格式和会话规则(如HTTP的请求/响应模型)。
- 主要协议:
- HTTP/HTTPS:网页浏览。
- FTP:文件传输。
- SMTP/POP3/IMAP:电子邮件收发。
- DNS:域名解析。
- DHCP:动态IP分配。
- SSH/Telnet:远程登录。
2. 传输层
- 功能:
- 提供端到端(进程到进程)的数据传输服务。
- 保证可靠性(如TCP的重传机制)或高效性(如UDP的低延迟)。
- 通过端口号区分不同应用程序。
- 主要协议:
- TCP:面向连接、可靠传输(如网页、文件下载)。
- UDP:无连接、高效传输(如视频流、DNS查询)。
3. 网络层
- 功能:
- 负责数据包的路由选择和逻辑寻址(如IP地址)。
- 解决跨网络的通信问题(如路由器工作在这一层)。
- 主要协议:
- IP(IPv4/IPv6):数据包的路由和寻址。
- ICMP:网络状态检测(如`ping`)。
- ARP:IP地址 → MAC地址解析。
- RIP/OSPF/BGP:动态路由协议。
4. 数据链路层
- 功能:
- 在同一局域网内传输数据帧(通过MAC地址)。
- 提供错误检测(如CRC校验),但不纠正错误。
- 管理物理层设备(如交换机工作在这一层)。
- 主要协议/技术:
- Ethernet(IEEE 802.3):有线局域网。
- Wi-Fi(IEEE 802.11):无线局域网。
- PPP:点对点协议(如拨号上网)。
- VLAN(IEEE 802.1Q):虚拟局域网。
5. 物理层(Physical Layer)
- 功能:
- 定义物理介质(如电缆、光纤)的电气/光学特性。
- 传输原始比特流(0和1)。
关键点总结:
1. 应用层:用户交互,协议定义应用行为。
2. 传输层:进程间通信,选择可靠(TCP)或高效(UDP)。
3. 网络层:跨网络寻址和路由(IP是核心)。
4. 数据链路层:局域网内帧传输(MAC地址)。
5. 物理层:物理介质和比特流传输。 去掉文中的*和#
1.网络传输基本流程
1.1.mac地址
(1)基本概念
每台主机在局域网上,有唯一的标识来保证主机的唯一性:mac 地址。
- 在网卡出厂时就确定了,不能修改。mac 地址通常是唯一的。
- MAC 地址用来识别数据链路层中相连的节点;
- 长度为 48 位, 即 6 个字节。一般用 16 进制数字加上冒号的形式来表示(例如:08:00:27:03:fb:19)
(2)网络传输相关概念
- 以太网中,任何时刻,只允许一台机器向网络中发送数据
- 如果有多台同时发送,会发生数据干扰,我们称之为数据碰撞
- 所有发送数据的主机要进行碰撞检测和碰撞避免
- 没有交换机的情况下,一个以太网就是一个碰撞域
- 局域网通信的过程中,主机对收到的报文确认是否是发给自己的,是通过目标mac地址判定
以太网的本质就是共享的资源,也就是临界资源,那上面所说的数据碰撞不就是互斥属性吗!
(3)数据传输流程
每层都有协议,所以当进行上述传输流程的时候,要进行封装和解包

报头部分,就是对应协议层的结构体字段,我们一般叫做报头,除了报头,剩下的叫做有效载荷,所以,报文 = 报头 + 有效载荷。
下面,我们再明确一下不同层的完整报文的叫法
- 不同的协议层对数据包有不同的称谓。在传输层叫做段(segment)。在网络层叫做数据报(datagram)。在链路层叫做帧(frame)。
- 应用层数据通过协议栈发到网络上时,每层协议都要加上一个数据首部 (header),称为封装(Encapsulation)。
- 首部信息中包含了一些类似于首部有多长、载荷有多长、上层协议是什么等信息。
- 数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部,根据首部中的 “上层协议字段” 将数据交给对应的上层协议处理。
1.2.数据包封装和分用

在网络传输的过程中,数据不是直接发送给对方主机的,而是先要自定向下将数据交付给下层协议,最后由底层发送,然后由对方主机的底层来进行接受,在自底向上进行向上交付。
下图为数据封装的过程
下图为数据分用的过程

我们学习任何协议,都要先宏观上建立这样的认识:
1.要学习的协议,是如何做到解包的?只有明确了解包,封包也就能理解
2.要学习的协议,是如何做到将自己的有效载荷,交付给上层协议的?
1.3.IP地址
IP 协议有两个版本,IPv4 和 IPv6。下面默认指 IPv4。
- IP 地址是在 IP 协议中,用来标识网络中不同主机的地址;
- 对于 IPv4 来说,IP 地址是一个 4 字节,32 位的整数;
- 我们通常也使用 "点分十进制" 的字符串表示 IP 地址,例如 192.168.0.1;用点分割的每一个数字表示一个字节,范围是 0 - 255。
下面是一张跨网络传输流程图

路由器负责在不同网络之间转发数据,单层网络(如局域网)无法直接跨网通信。
目的IP的作用是明确数据包最终要到达的设备,并指导路由器如何转发。

对比路由过程中,报文中记录的 IP 地址与 MAC 地址的区别:
- IP 地址在整个路由过程中,一直不变(目前先这样说明,后面再修正) ,Mac 地址一直在变
- 目的 IP 是一种长远目标,Mac 是下一阶段目标,目的 IP 是路径选择的重要依据,mac 地址是局域网转发的重要依据,只在本局域网中有效。

IP 网络层存在的意义:提供网络虚拟层,让世界的所有网络都是 IP 网络,屏蔽最底层网络的差异
2.socket编程预备
数据传输到主机是目的吗?不是的。因为数据是给人用的。比如:聊天是人在聊天,下载是人在下载,浏览网页是人在浏览。
但是人是怎么看到聊天信息的呢?怎么执行下载任务呢?怎么浏览网页信息呢?通过启动的QQ、迅雷、浏览器。而启动的QQ、迅雷、浏览器都是进程。换句话说,进程是人在系统中的代表,只要把数据给进程,人就相当于拿到了数据。
所以:数据传输到主机不是目的,而是手段。到达主机内部,再交给主机内的进程,才是目的。
但是系统中,同时会存在非常多的进程,当数据到达目标主机之后,怎么转发给目标进程?这就要在网络的背景下,在系统中,标识主机的唯一性,也就是源 IP 地址和目的 IP 地址存在的意义。

网络通信的本质就是让两个不同主机的进程进行数据交互,即进程间通信, 让不同进程看到同一份资源。
2.1.端口号

端口号进程划分:
0 - 1023:知名端口号,HTTP,FTP,SSH 等这些广为使用的应用层协议, 他们的端口号都是固定的。
1024 - 65535:操作系统动态分配的端口号。客户端程序的端口号,就是由操作系统从这个范围分配的。
pid 表示唯一一个进程;此处我们的端口号也是唯一表示一个进程。那么这两者之间是怎样的关系?
端口号与进程ID的区别:
- 端口号是网络概念,PID是系统概念
- 一个进程可以绑定多个端口,但一个端口同一时间只能被一个进程占用
- 端口号仅在网络通信期间有效,而进程ID从进程启动到结束一直存在
传输层协议(TCP 和 UDP)的数据段中有两个端口号,分别叫做源端口号和目的端口号。就是在描述 "数据是谁发的,要发给谁"。
综上,IP 地址用来标识互联网中唯一的一台主机,port 用来标识该主机上唯一的一个网络进程,所以 IP+Port 就能表示互联网中唯一的一个进程。
所以,通信的时候,本质是两个互联网进程代表人来进行通信,{srcIP,srcPort,dstIP,dstPort}这样的 4 元组就能标识互联网中唯二的两个进程。
所以,网络通信的本质,也是进程间通信,我们把 ip+port 叫做套接字 socket。
3. 网络字节序
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分,网络数据流同样有大端小端之分。那么如何定义网络数据流的地址呢?
大端模式(Big-Endian)
高字节存储在低地址,符合人类阅读习惯。
小端模式(Little-Endian)
低字节存储在低地址,兼容CPU计算方式(如x86/ARM)。
网络数据传输的字节序规则:
1. 数据发送与接收的地址顺序
- 发送主机:
- 将发送缓冲区中的数据按内存地址从低到高的顺序发出。
- 先发出的数据是低地址,后发出的数据是高地址。
- 接收主机:
- 把从网络上接收到的字节依次保存在接收缓冲区中,同样按内存地址从低到高的顺序保存。
2. 网络字节序规定
- TCP/IP 协议明确规定,网络数据流必须采用大端字节序进行传输。
- 无论主机是大端机还是小端机,都必须按照TCP/IP规定的网络字节序来发送/接收数据。
- 如果发送主机是小端机,需要先将数据转成大端序;如果是大端机则可直接发送。
为使网络程序具有可移植性,使同样的 C 代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
、
h表示 host,n 表示 network,l 表示 32 位长整数,s 表示 16 位短整数。
例如 htonl 表示将 32 位的长整数从主机字节序转换为网络字节序。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
上述函数可用于端口号的转换,ip地址的转换可以使用一以下函数:
点分十进制字符串->网络字节序二进制
- inet_addr(仅支持 IPv4)
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
- inet_pton 支持 IPv4和 IPv6
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
af:地址族(AF_INET 表示 IPv4,AF_INET6 表示 IPv6)。
src:指向 IP 字符串的指针(IPv4 为点分十进制,IPv6 为冒分十六进制)。
dst:指向输出缓冲区的指针(存储转换后的网络字节序二进制地址,IPv4 用 struct in_addr*,IPv6 用 struct in6_addr*)。
返回值:成功返回 1;输入无效返回 0;系统错误返回 -1(需配合 errno 查看具体错误)。
网络字节序二进制->点分十进制字符串
- inet_ntoa(只支持IPv4 ,内部使用静态缓冲区,线程不安全,后续调用会覆盖之前的结果)
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr in);
- inet_ntop(支持 IPv4/IPv6,线程安全)
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
af:地址族(AF_INET 或 AF_INET6)。
src:指向网络字节序二进制地址的指针(IPv4 为 struct in_addr*,IPv6 为 struct in6_addr*)。
dst:用户提供的缓冲区,用于存储转换后的字符串。
size:缓冲区大小(IPv4 建议用 INET_ADDRSTRLEN,IPv6 建议用 INET6_ADDRSTRLEN)。
返回值:成功返回指向 dst 的指针;失败返回 NULL(需配合 errno 查看错误)。
4.socket编程接口
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);
4.1.sockaddr 结构
sockaddr 是通用地址结构体(适配所有协议),sockaddr_in 是 IPv4 专用地址结构体(仅适配 TCP/IP 协议)。

- IPv4 和 IPv6 的地址格式定义在头文件 netinet/in.h 中,IPv4 地址用 sockaddr_in 结构体表示,包括 16 位地址类型,16 位端口号和 32 位 IP 地址。
- IPv4、IPv6 地址类型分别定义为常数 AF_INET、AF_INET6。这样,只要取得某种 sockaddr 结构体的首地址,不需要知道具体是哪种类型的 sockaddr 结构体,就可以根据地址类型字段确定结构体中的内容。
- socket API 可以都用 struct sockaddr *类型表示,在使用的时候强制转化成对应类。
sockaddr_in
- sockaddr_in 用于网络通信,sockaddr_un 用于本地通信。
sockaddr 结构
通用的套接字地址结构(所有协议族的基础结构)
struct sockaddr {
sa_family_t sa_family; 地址族(如 AF_INET)
char sa_data[14]; 协议特定地址数据
};
在基于 IPv4 编程时,使用的数据结构是 sockaddr_in 和 sockaddr_un,但都需要强制转换为struct sockaddr *,这样不同协议族的地址结构能统一通过 socket API 传递。
sockaddr_in 结构里主要有三部分信息:地址类型,端口号,IP 地址
struct sockaddr_in {
sa_family_t sin_family; // 地址族(AF_INET)
in_port_t sin_port; // 16位端口号(网络字节序)
struct in_addr sin_addr; // 32位IP地址
unsigned char sin_zero[8]; // 填充字段(保证与sockaddr大小一致)
};
必须设置 sin_family = AF_INET(IPv4协议)。
sin_port 和 sin_addr.s_addr 需转换为网络字节序(htons()/htonl())。
sin_zero 仅用于填充,通常置零(memset(&addr, 0, sizeof(addr)))。
typedef uint16_t in_port_t; 无符号16位整数
typedef unsigned short int sa_family_t; 无符号短整数
typedef uint32_t in_addr_t; 无符号32位整数
in_addr:用来表示一个 IPv4 的 IP 地址,其实就是一个 32 位的整数
4.2.套接字相关函数
4.2.1.创建套接字:socket()

参数:
domain:地址族。
常用取值:
AF_INET IPv4 协议族(最常用),地址结构为 struct sockaddr_in。
AF_INET6 IPv6 协议族,地址结构为 struct sockaddr_in6。
AF_UNIX/AF_LOCAL 本地进程间通信(Unix Domain Socket),地址为文件路径。
AF_PACKET 底层数据包接口(如原始套接字,可监听网卡流量)。
type:套接字类型,UDP 使用 SOCK_DGRAM。
protocol:通常为 0(自动选择协议,UDP 对应 IPPROTO_UDP)。
当 protocol = 0 时,系统会根据 domain 和 type 自动选择协议:
AF_INET + SOCK_DGRAM → 自动选择 IPPROTO_UDP。
AF_INET + SOCK_STREAM → 自动选择 IPPROTO_TCP。
返回值:成功返回套接字描述符(sockfd),失败返回 -1。
在 Linux 编程中,套接字描述符(sockfd) 是一个整数,它是内核中套接字数据结构的引用。
这个数据结构包含:
本地地址和端口(通过 bind() 设置)。
远程地址和端口(通过 connect() 或 sendto() 设置)。
协议类型(如 UDP/TCP)。
缓冲区(用于存储待发送或已接收的数据)。
状态标志(如是否已连接、是否可读 / 写)。
4.4.2.绑定地址:bind()
将套接字绑定到本地IP和端口(服务端必选,客户端可选)。

参数:
sockfd:由 socket() 返回的套接字描述符,用于指定要绑定的套接字。
它是输入参数,表示 “对哪个套接字进行绑定操作”。
addr:指向 struct sockaddr_in(IPv4)或 sockaddr_in6(IPv6)的指针。
addrlen:地址结构体长度。
返回值:成功返回 0,失败返回 -1。
4.2.3.发送数据:sendto()

参数:
buf:待发送数据的缓冲区。
len:数据长度。
dest_addr:目标地址结构体。
addrlen:目标地址长度。
flags:操作标志,可设置为 0(默认阻塞接收)或MSG_DONTWAIT(非阻塞)等。
返回值:成功返回发送的字节数,失败返回 -1。
4.2.4.接受数据:recvfrom()

参数:
buf:接收数据的缓冲区。
len:缓冲区最大长度。
src_addr:用于保存发送方地址(可为 NULL)。
addrlen:输入时为缓冲区长度,输出时为实际地址长度。
返回值:成功返回接收的字节数,失败返回 -1。
UDP编程流程
服务端
socket() → bind() → recvfrom()/sendto() → close()。
客户端
socket() → sendto()/recvfrom() → close()(无需 bind,系统自动分配端口)。
4.2.5.TCP相关函数
int listen(int socket, int backlog);
用于服务器端:将一个已绑定地址的套接字设置为监听状态,准备接受客户端连接。
socket:文件描述符,由 socket() 创建。
backlog:内核为该套接字维护的 已完成连接队列 和 未完成连接队列 的长度上限。
成功时返回 0,失败时返回 -1 并设置 errno。
int accept(int socket, struct sockaddr* address, socklen_t* address_len);
用于 服务器端:从监听套接字的已完成连接队列中取出一个连接请求,
建立与客户端的新连接,并返回新的套接字描述符用于后续通信。
socket:监听套接字(由 listen() 设置的)。
address:指向结构体,用于获取客户端的地址信息。
address_len:输入输出参数,指定结构体大小并返回实际填充的大小。
返回值:新的连接套接字;若出错返回 -1。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
用于 客户端:主动发起连接请求到服务器端指定地址,将套接字 sockfd 连接到远程服务器。
sockfd:由 socket() 创建的套接字。
addr:指向服务器地址结构体,如 sockaddr_in。
addrlen:地址结构体的大小。
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
recv() 是用于从已连接的套接字接收数据的系统调用,主要用于 TCP 通信。
lags: 控制接收行为的标志位(常用值如下):
0: 默认行为(阻塞模式)
MSG_PEEK: 查看数据但不从接收队列移除
MSG_WAITALL: 等待直到请求的所有数据都到达
MSG_DONTWAIT: 非阻塞接收
返回值
成功时:返回接收到的字节数
连接关闭:返回 0(对端正常关闭连接)
出错时:返回 -1,并设置 errno
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
send() 是用于通过已连接的套接字发送数据的系统调用,主要用于 TCP 通信。
flags: 控制发送行为的标志位(常用值如下):
0: 默认行为(阻塞模式)
MSG_DONTWAIT: 非阻塞发送
MSG_NOSIGNAL: 发送失败时不产生 SIGPIPE 信号
MSG_OOB: 发送带外数据(紧急数据)
返回值
成功时:返回实际发送的字节数(可能小于请求的 len)
出错时:返回 -1,并设置 errno
4.2.6 其他辅助函数
地址转换
(1)inet_pton() 是一个用于将 人类可读的IP地址字符串 转换为 二进制网络字节序 的函数,通常在套接字编程中用于设置 struct sockaddr_in 或 struct sockaddr_in6 的地址字段。
n 代表 Network(网络),即二进制形式的 IP 地址(如 struct in_addr 或 struct in6_addr)。
p 代表 Presentation(表示),即人类可读的字符串形式(如 "192.168.1.1" 或 "2001:db8::1")。
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
af地址族:AF_INET(IPv4), AF_INET6(IPv6)
const char* src:源字符串,即点分十进制的IPv4地址(如 "192.168.1.1")或IPv6地址。
dst:目标缓冲区指针,用于存储转换后的二进制地址:
IPv4:struct in_addr
IPv6:struct in6_addr
返回值
1 转换成功,结果存储在 dst 中。
0 输入的地址字符串不合法(与 af 指定的地址族不匹配)。
-1 错误发生(如地址族不支持),可通过 errno 获取具体错误原因。
(2)inet_ntop():将二进制IP地址转换为字符串形式
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
参数size表示缓冲区 dst 的大小(防止溢出)。
4.3.网络命令
1. ifconfig(Interface Config)
用于查看和配置网络接口信息。
2. netstat(Network Statistics)
用于显示网络连接、路由表、接口状态等信息(被 ss 命令取代,但仍可用)。
-t:仅显示TCP监听状态的连接
-u:仅显示UDP监听状态的连接
-l:仅显示所有监听状态(Listen)的连接
-n:拒绝显示别名,能显示数字的全部转化成数字
-p:显示建立相关链接的程序名
3. ping
用于测试网络连接性和主机可达性。通过向目标主机发送 ICMP(Internet Control Message Protocol,互联网控制消息协议 )回显请求数据包,等待目标主机返回 ICMP 回显应答数据包,以此判断网络是否畅通、延迟情况等。
-c :指定发送 ICMP 请求包的数量。
-w :设置等待响应的超时时间(单位为秒)。

4. pidof
ps axj | head -1 && ps ajx | grep tcp_server
PPID PID PGID SID TTY TPGID STAT UID TIME
COMMAND
2958169 2958285 2958285 2958169 pts/2 2958285 S+ 1002
0:00 ./tcp_server 8888
pidof tcp_server
2958285
4. telnet
用于测试端口连通性,尤其是 TCP 端口。
telnet [IP地址] [端口号]
例子:测试本机的 80 端口是否可连接:
telnet 127.0.0.1 80
如果连接成功,会看到类似于:
Trying 127.0.0.1...
Connected to 127.0.0.1.
如果没有安装 telnet,可以用:
nc -vz 127.0.0.1 80
5. UDPsocket的封装
UdpServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"
using namespace LogModule;
const int defaultfd = -1;
using func_t = std::function<std::string(const std::string &)>;
class UdpServer
{
public:
UdpServer(uint16_t port, func_t func)
: _sockfd(defaultfd),
//_ip(ip),
_port(port),
_isrunning(false),
_func(func)
{
}
void Init()
{
// 1. 创建套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(_sockfd == -1)
{
LOG(LogLevel::FATAL) << "socket error";
exit(1);
}
LOG(LogLevel::INFO) << "socket create success" << _sockfd;
//2. 绑定接口 本地ip和端口
//2.1 填充sockaddr_in结构体
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
//本地格式-》网络序列
local.sin_port = htons(_port);
//IP转换为网络序列
//local.sin_addr.s_addr = inet_addr(_ip.c_str());
local.sin_addr.s_addr = INADDR_ANY; //宏对应的值是0
int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
if(n == -1)
{
LOG(LogLevel::FATAL) << "bind error";
exit(2);
}
LOG(LogLevel::INFO) << "bind success, sockfd" << _sockfd;
}
void Start()
{
_isrunning = true;
while(_isrunning)
{
char buffer[1024];
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
//1. 收消息
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
if(s > 0)
{
int peer_port = ntohs(peer.sin_port);
std::string peer_ip = inet_ntoa(peer.sin_addr);
buffer[s] = 0;
//LOG(LogLevel::INFO) << "[" << peer_ip << ":" << peer_port << "buffer:" << buffer;
LOG(LogLevel::INFO) << "buffer:" << buffer;
std::string result = _func(buffer);
//2. 发消息
//std::string echo_string = "server echo@";
//echo_string += buffer;
sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr *)&peer, len);
}
}
}
~UdpServer() {}
private:
int _sockfd;
uint16_t _port;
//std::string _ip; // 用的是字符串风格,点分十进制
bool _isrunning;
func_t _func;
};
UdpServer.cc
#include <iostream>
#include <memory>
#include <string>
#include "UdpServer.hpp"
std::string defaulthandler(const std::string &str)
{
std::string ret = "hello, ";
ret += str;
return ret;
}
int main(int argc, char *argv[])
{
if(argc != 2)
{
std::cerr << "Usage:" << argv[0] << " port" << std::endl;
return 1;
}
//std::string ip = argv[1];
uint16_t port = std::stoi(argv[1]);
Enable_Console_Log_Strategy();
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, defaulthandler);
usvr->Init();
usvr->Start();
return 0;
}
UdpClient.cc
#include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
int main(int argc, char *argv[])
{
if(argc != 3)
{
std::cerr << "Usage:" << argv[0] << "server_ip server_port" << std::endl;
return 1;
}
std::string server_ip = argv[1];
uint16_t server_port = std::stoi(argv[2]);
std::cout << "server_ip: " << server_ip << " server_port: " << server_port <<std::endl;
//1. 创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
std::cerr << "socket error" << std::endl;
return 2;
}
std::cout << "sockfd: " << sockfd << std::endl;
//客户端无需显示低绑定,首次发送消息,客户端会自动给client进行bind
//,OS知道IP,采用随机端口号的方式,client的端口号只要是唯一的就行
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(server_port);
server.sin_addr.s_addr = inet_addr(server_ip.c_str());
std::cout << "sin_port: " << server.sin_port << " sin_addr: " << server.sin_addr.s_addr << std::endl;
while(true)
{
std::string input;
std::cout << "Please Enter:";
std::getline(std::cin, input);
int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));
char buffer[1024];
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if(m > 0)
{
buffer[m] = 0;
std::cout << buffer << std::endl;
}
}
return 0;
}



6万+

被折叠的 条评论
为什么被折叠?



