计算机网络基础

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)。
 - 首部信息中包含了一些类似于首部有多长,载荷(payload)有多长,上层协议是什么等信息。
 - 数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部,根据首部中的 “上层协议字段” 将数据交给对应的上层协议处理。

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 端口号

端口号(port)是传输层协议的内容。
 - 端口号是一个 2 字节 16 位的整数
 - 端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理
 - IP 地址 + 端口号能够标识网络上的某一台主机的某一个进程
 - 一个端口号只能被一个进程占用

端口号进程划分:

0 - 1023: 知名端口号,HTTP,FTP,SSH 等这些广为使用的应用层协议, 他们的端口号都是固定的。
1024 - 65535: 操作系统动态分配的端口号。客户端程序的端口号,就是由操作系统从这个范围分配的。
pid 表示唯一一个进程此处我们的端口号也是唯一表示一个进程. 那么这两者之间是怎样的关系?

端口号与进程ID的区别:

 - 端口号是网络概念,PID是系统概念

 - 一个进程可以绑定多个端口,但一个端口同一时间只能被一个进程占

 - 端口号仅在网络通信期间有效,而进程ID从进程启动到结束一直存在

传输层协议(TCP UDP)的数据段中有两个端口号分别叫做源端口号和目的端口号。就是在描述 "数据是谁发的, 要发给谁"。

综上,IP 地址用来标识互联网中唯一的一台主机,port 用来标识该主机上唯一的一个网络进程,所以 IP+Port 就能表示互联网中唯一的一个进程。

所以,通信的时候,本质是两个互联网进程代表人来进行通信,{srcIP,srcPort,dstIPdstPort}这样的 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 位的长整数从主机字节序转换为网络字节序。

如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

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 结构

 - 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结构

    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:目标地址长度。
    
    返回值:成功返回发送的字节数,失败返回 -1。

    4.2.4 接受数据:recvfrom()

    参数:
    
    buf:接收数据的缓冲区。
    len:缓冲区最大长度。
    
    src_addr:用于保存发送方地址(可为 NULL)。
    addrlen:输入时为缓冲区长度,输出时为实际地址长度。
    
    返回值:成功返回接收的字节数,失败返回 -1。

     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 的大小(防止溢出)。

      字节序转换

      htons()、htonl():主机字节序 → 网络字节序(大端)。

      ntohs()、ntohl():网络字节序 → 主机字节序。

      ​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

      用于查找指定名称的进程的进程 ID
      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

      UDP编程流程

      服务端

      socket() → bind() → recvfrom()/sendto() → close()。

      客户端

      socket() → sendto()/recvfrom() → close()(无需 bind,系统自动分配端口)。

      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;
      }

      评论
      添加红包

      请填写红包祝福语或标题

      红包个数最小为10个

      红包金额最低5元

      当前余额3.43前往充值 >
      需支付:10.00
      成就一亿技术人!
      领取后你会自动成为博主和红包主的粉丝 规则
      hope_wisdom
      发出的红包
      实付
      使用余额支付
      点击重新获取
      扫码支付
      钱包余额 0

      抵扣说明:

      1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
      2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

      余额充值