【Linux】网络通信

【Linux】网络通信

1、网络基础

1.1 计算机网络

计算机网络的发展可以从独立模式逐步演进为网络互连模式,这个过程可以分为以下几个阶段:

  1. 独立模式
    在计算机网络的早期阶段,每台计算机都是相对独立的实体,没有连接到其他计算机。每台计算机仅用于单独的任务,数据和资源的共享非常有限。这个阶段中,计算机主要用于科学计算和数据处理。

  2. 点对点连接
    随着计算机数量的增加,人们开始意识到将计算机连接起来可以实现更高效的资源共享和通信。于是,点对点连接的网络模式出现了,其中两台计算机通过专用的通信线路直接连接,可以进行数据传输。这种模式在早期局域网中比较常见。

  3. 局域网(LAN)的兴起
    随着计算机数量的进一步增加,局域网(LAN)开始兴起。局域网允许位于同一地理位置的计算机互相连接,实现资源共享和通信。以太网技术的发展使得计算机可以通过共享的物理媒介(如同一根电缆)进行通信,从而使局域网得以实现。

  4. 广域网(WAN)的出现
    随着计算机网络的发展,人们希望能够跨越较大的地理范围进行通信。广域网(WAN)应运而生,通过连接多个局域网和点对点连接,实现了更大范围内的计算机互连。这种网络模式通过使用路由器和交换机来实现数据在不同网络之间的传输。

  5. 互联网的崛起
    互联网是计算机网络发展的最高阶段。它将全球范围内的计算机网络互相连接,实现了无缝的数据交换和资源共享。互联网使用 TCP/IP 协议套件作为通信基础,允许不同类型的网络和设备互相通信,从而形成了全球性的信息交流平台。

1.2 网络模型

计算机网络模型是一种抽象框架,用于描述计算机网络中各个组件之间的关系和通信方式。常见的计算机网络模型有以下几种:

  1. OSI模型(开放系统互联模型)
    OSI模型是一个由国际标准化组织(ISO)制定的七层网络模型,每一层都代表着特定的网络功能。从下到上分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。这个模型的主要目标是促进不同厂商开发的设备能够相互通信。然而,实际上,大多数网络采用的是下面提到的TCP/IP模型。

  2. TCP/IP模型
    TCP/IP模型是实际上在互联网上使用的模型,它由四个层次组成:网络接口层、网络层、传输层和应用层。虽然层数较少,但它包含了与OSI模型中相似的功能。这个模型的名称来自于它的两个核心协议:传输控制协议(TCP)和Internet协议(IP)。

  3. 四层网络模型
    有一些网络模型将网络分为四个层次:物理层、数据链路层、网络层和应用层。这个模型省略了会话层、表示层和传输层,更简化了网络的结构。

  4. 五层网络模型
    这个模型将网络分为五个层次:物理层、数据链路层、网络层、传输层和应用层。它在功能上与TCP/IP模型相似,但只包含五个层次。

image-20230810113050172

TCP/IP是一组协议的代名词,它还包括许多协议,组成了TCP/IP协议簇。 TCP/IP协议簇分为四层,IP位于协议簇的第二层(对应OSI的第三层),TCP位于协议簇的第三层 (对应OSI的第四层)。TCP/IP通讯协议采用了4层的层级结构,每一层都呼叫它的下一层所提供 的网络来完成自己的需求。这4层分别为:

  • 应用层:应用程序间沟通的层,如简单电子邮件传输(SMTP)、文件传输协议(FTP)、 网络远程访问协议(Telnet)等。
  • 传输层:在此层中,它提供了节点间的数据传送服务,如传输控制协议(TCP)、 用户数据报协议(UDP)等,TCP和UDP给数据包加入传输数据并把它传输到下一层中, 这一层负责传送数据,并且确定数据已被送达并接收。
  • 网络互连层:负责提供基本的数据封包传送功能,让每一块数据包都能够到达目 的主机(但不检查是否被正确接收),如网际协议(IP)。
  • 主机到网络层:对实际的网络媒体的管理,定义如何使用实际网络 (如Ethernet、Serial Line等)来传送数据。

TCP & UDP

1)IP地址

img

2)端口

1. 用于区分不同的应用程序

2. 端口号的范围为0-65535,其中0-1023未系统的保留端口,我们的程序尽可能别使用这些端口!

3. IP地址和端口号组成了我们的Socket,Socket是网络运行程序间双向通信链路的终结点, 是TCP和UDP的基础!

4. 常用协议使用的端口:HTTP:80,FTP:21,TELNET:23

img

3)TCP协议与UDP协议的比较

TCP协议流程详解:

首先TCP/IP是一个协议簇,里面包括很多协议的。UDP只是其中的一个。之所以命名为TCP/IP协议, 因为TCP,IP协议是两个很重要的协议,就用他两命名了。

下面我们来讲解TCP协议和UDP协议的区别:

TCP(Transmission Control Protocol,传输控制协议)是面向连接的协议,即在收发数据钱 ,都需要与对面建立可靠的链接,这也是面试经常会问到的TCP的三次握手以及TCP的四次挥手三次握手: 建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立, 在Socket编程中,这一过程由客户端执行connect来触发,具体流程图如下:

img

  • 第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server, Client进入SYN_SENT状态,等待Server确认。
  • 第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位 SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求 ,Server进入SYN_RCVD状态。
  • 第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK 置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则 连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以 开始传输数据了。
  • 四次挥手: 终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。 在Socket编程中,这一过程由客户端或服务端任一方执行close来触发,具体流程图如下:

img

  • 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入 FIN_WAIT_1状态
  • 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同, 一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
  • 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK 状态。
  • 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。 另外也可能是同时发起主动关闭的情况:

img

另外还可能有一个常见的问题就是:为什么建立连接是三次握手,而关闭连接却是四次挥手呢? 答:因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里 发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还 能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些 数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。


UDP协议详解

UDP(User Datagram Protocol)用户数据报协议,非连接的协议,传输数据之前源端和终端不 建立连接,当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。 在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、计算机的能力和传输带宽 的限制;在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。 相比TCP就是无需建立链接,结构简单,无法保证正确性,容易丢包

1.3 网络传输

网络传输和网络模型之间存在密切的联系,网络模型为网络传输提供了一种结构化的框架和指导原则,帮助理解和组织网络中的数据传输过程。

TCP/IP协议簇是一组协议,它包括物理层、数据链路层、网络层、传输层和应用层五个层次。每一层都有特定的功能和任务:

  1. 物理层:负责传输光/电信号,包括以太网、光纤、无线网络等。集线器工作在此层。

  2. 数据链路层:传送和识别数据帧,处理帧同步、冲突检测、数据差错校验。交换机工作在此层。

  3. 网络层:进行地址管理和路由选择,通过IP地址标识主机,并规划数据传输路径。路由器工作在此层。

  4. 传输层:负责主机间数据传输,如TCP协议确保数据可靠传输。

  5. 应用层:处理应用程序间的通信,如电子邮件传输(SMTP)、文件传输(FTP)、远程访问(Telnet)等。

这五层结构协同工作,构成了TCP/IP协议簇,支撑了现代计算机网络的各项通信和数据传输功能。

对于一台主机, 它的操作系统内核实现了从传输层到物理层的内容

对于一台路由器, 它实现了从网络层到物理层

对于一台交换机, 它实现了从数据链路层到物理层

对于集线器, 它只实现了物理层

1.3.1 传输逻辑

数据传输的逻辑可以归纳如下:

  1. 准备阶段

    • 在应用层,准备要传输的数据,定义数据格式和处理逻辑。
    • 选择适当的应用层协议,如HTTP、FTP等,以确定数据的传输方式和规则。
  2. 传输层协议选择

    • 根据应用需求选择传输层协议,如可靠性要求高则选择TCP,实时性要求高则选择UDP。
  3. 建立连接和监听

    • 对于TCP,建立连接是必要的。发送端和接收端之间建立连接,以确保数据的可靠传输。
    • 对于UDP,连接不是必需的,但需要确定数据发送和接收的目标地址。
  4. 数据封装和加头部信息

    • 在发送端,将应用层数据封装为数据包,并添加适当的传输层和网络层头部信息。
    • 头部信息包括IP地址、端口号等,用于路由和目标设备的标识。
  5. 数据传输

    • 传输层协议负责将封装后的数据包传输到目标设备,同时执行错误检测、流量控制等操作。
    • 网络层协议根据目标IP地址选择适当的路由,确定数据包的传输路径。
  6. 物理传输

    • 链路层将数据包封装为帧,添加链路层头部和尾部,通过物理介质传输到接收端。
  7. 数据解封和处理

    • 在接收端,链路层接收帧并解析,将数据交给网络层。
    • 网络层根据IP地址确定目标设备,将数据交给传输层。
  8. 数据重组和解析

    • 传输层协议负责将接收到的数据包按序重组,执行错误检测和纠正,将数据交给应用层。
  9. 应用层处理

    • 应用程序解析数据包,根据定义的数据格式处理数据内容,执行相应的业务逻辑。

这个逻辑流程涵盖了数据传输从应用层到物理层的各个阶段和过程,确保数据能够按照一定的规则和流程进行传输和处理。

1.3.2 传输条件

将数据传输的必备条件按照应用层、传输层、网络层和链路层进行分类如下:

应用层

  1. 通信设备:需要至少两台通信设备。
  2. IP地址和端口号:每台设备需要唯一的IP地址和适当的端口号。
  3. 数据格式定义:需要定义数据的格式,以便发送方和接收方正确解析和处理数据。
  4. 安全性考虑:需要考虑数据的安全性和加密,特别是对于敏感信息的传输。

传输层

  1. 协议选择:需要选择适当的传输层协议,如TCP或UDP,根据数据传输需求进行选择。
  2. 传输机制:需要了解如何建立连接、传输数据、管理流量和关闭连接等操作。
  3. 错误处理机制:需要考虑数据传输中可能发生的错误情况,如丢包、重传等。

网络层

  1. 网络连接:通信设备需要通过网络连接在一起,可以是有线或无线连接。
  2. IP地址:每台设备需要唯一的IP地址,用于在网络中标识设备。
  3. 路由选择:需要使用路由协议选择数据包的传输路径。

链路层

  1. 物理连接:通信设备需要通过物理层面的连接,如以太网、无线电波等。
  2. 链路地址:链路层需要确定每个设备的物理地址,如MAC地址。

1.3.3 传输流程

日常生活中发送电子邮件时,TCP/IP协议传输的具体流程如下表所示:

步骤 层次 动作与报头
1 应用层 编写邮件,准备邮件内容。
2 应用层 使用电子邮件客户端。
3 应用层 封装邮件数据。
4 传输层 选择TCP协议。
5 传输层 建立TCP连接。
6 传输层 封装邮件数据为TCP数据包。
7 网络层 选择IP地址。
8 网络层 封装TCP数据包为IP数据包。
9 链路层 分帧和添加链路层头部。
10 链路层 通过物理介质传输数据。
11 链路层 解析数据帧,移除链路层头部。
12 网络层 解析IP数据包,获取目标IP。
13 传输层 TCP重组和校验数据包。
14 传输层 将数据包传递给应用层。
15 应用层 解析邮件数据,处理邮件内容。
16 应用层 继续处理邮件,可能转发。

1.4 地址管理

网络地址是为了在计算机网络中标识不同设备和主机而存在的。不同层次的网络模型在数据传输过程中都需要使用网络地址来确定数据的发送和接收方。

  • 物理层:物理层负责信号传输,但为了确保信号到达正确的设备,每个设备都需要在网络中有唯一的物理地址,即MAC地址。这是一个在数据链路层中使用的地址,用于在局域网中识别设备。

  • 数据链路层:在数据链路层,数据帧需要带有目标MAC地址和源MAC地址,以便设备可以正确地发送和接收数据。这些地址用于在局域网内识别设备。

  • 网络层:在网络层,IP地址被用来标识不同的主机和路由器。IP地址用于在全球范围内寻找目标设备,使数据能够跨越不同网络进行传输。

  • 传输层:在传输层,数据包含源端口号和目标端口号。这些端口号一起构成了一个套接字,用于标识不同的应用程序和服务。它们帮助传输层将数据传递给正确的应用程序。

总之,地址在不同的网络层中扮演着关键的角色,用于标识不同的设备、主机和服务。它们帮助确保数据在网络中正确传输,并使通信能够顺利进行。

2、网络编程

2.1 基本概念

2.1.1 IP地址

IP地址(Internet Protocol Address)是用于在计算机网络中唯一标识和定位设备的数字标签。它是Internet协议族的一部分,用于在网络中进行数据包的路由和传递。IP地址充当了类似于邮寄地址的作用,以确保数据能够准确地从发送者传递到接收者。

以下是IP地址的基本概念:

  1. 唯一标识性: 每个设备(如计算机、服务器、路由器、移动设备等)在网络中都被分配了一个唯一的IP地址。这有助于确保数据包能够正确地路由和传递。

  2. 分层结构: IP地址通常被分为两个主要版本:IPv4(Internet Protocol version 4)和IPv6(Internet Protocol version 6)。IPv4使用32位二进制数表示,通常以点分十进制表示法(例如,192.168.1.1)呈现。IPv6使用128位二进制数表示,以一种更复杂的方式显示(例如,2001:0db8:85a3:0000:0000:8a2e:0370:7334)。

  3. IPv4地址枯竭: 由于互联网的迅速扩展,IPv4地址空间已经基本上耗尽。这导致了IPv6的引入,因为它提供了更大的地址空间,能够支持未来更多的互联设备。

  4. 公共和私有地址: IP地址可以是公共的或私有的。公共IP地址是全球范围内唯一的,用于直接访问互联网。私有IP地址在局域网(例如家庭或企业内部网络)中使用,不直接暴露给互联网。

  5. 子网掩码: 子网掩码是与IP地址结合使用的参数,用于指示哪些部分是网络部分,哪些部分是主机部分。它有助于将IP地址划分为网络和主机,以便在子网内进行更有效的路由。

  6. DHCP和静态IP: IP地址可以通过动态主机配置协议(DHCP)自动分配,也可以手动配置为静态IP地址。DHCP允许网络设备在加入网络时自动获取IP地址和其他网络配置信息。

  7. 网络层地址: IP地址位于网络层(第三层)协议中,负责将数据包从源主机传输到目标主机。它与MAC地址(在数据链路层,第二层)一起协作,以实现数据在不同网络之间的传递。

IP地址是互联网通信的基础,通过它,数据能够在全球范围内进行准确和可靠的传递。不论是浏览网页、发送电子邮件还是进行视频通话,IP地址在背后都发挥着至关重要的作用。

与协议关系

IP地址是互联网协议族中的一个关键概念,它与其他多个协议相互配合,以实现数据在网络中的传输和通信。以下是IP地址与其他一些重要协议之间的关系:

  1. TCP/IP协议栈: TCP/IP是一组网络协议,它们一起构成了互联网的基础。IP地址是TCP/IP协议栈中的核心组成部分。TCP(Transmission Control Protocol)负责在通信双方建立可靠的连接和数据传输,而IP则负责将数据包从源主机路由到目标主机。

  2. MAC地址: MAC地址(Media Access Control Address)是数据链路层(第二层)的一个标识符,用于在局域网中唯一标识网络设备。IP地址(位于网络层,第三层)与MAC地址相互配合,通过ARP(Address Resolution Protocol)等机制,将IP地址映射到对应的MAC地址,以实现数据在局域网内的传输。

  3. ARP协议: ARP(Address Resolution Protocol)用于将IP地址解析为对应的MAC地址。当设备需要发送数据到另一个设备时,需要知道目标设备的MAC地址。ARP协议帮助主机在同一局域网中查找IP地址对应的MAC地址。

  4. DHCP协议: DHCP(Dynamic Host Configuration Protocol)用于自动分配IP地址和其他网络配置信息给新加入网络的设备。DHCP服务器分配可用的IP地址给客户端,使设备能够快速连接网络而不需要手动配置IP地址。

  5. DNS协议: DNS(Domain Name System)将易于记忆的域名转换为IP地址。在互联网上,人们更容易记住域名(如www.example.com),而不是复杂的IP地址。DNS协议允许将域名解析为相应的IP地址,使得用户能够访问网站和资源。

  6. ICMP协议: ICMP(Internet Control Message Protocol)用于在IP网络中传递错误消息和控制消息。例如,当数据包在传输过程中发生问题时,ICMP可以生成错误消息并通知源主机。

这些协议相互合作,共同构成了互联网通信的基础架构。IP地址在这些协议之间扮演着桥梁的角色,使得数据能够在不同层级的网络中传输和交换。

2.1.2 端口号

在计算机网络中,端口号是用于标识特定应用程序或服务的数字标识符。它是在传输层协议中使用的概念,例如在TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)中。通过将IP地址与端口号结合使用,网络中的不同应用程序可以在同一设备上同时运行,实现数据的多路复用和分发。

以下是端口号的一些关键含义:

  1. 标识应用程序: 端口号用于标识在一个设备上运行的不同应用程序或服务。每个应用程序可以监听一个或多个特定的端口号,以便与其他设备通信。

  2. 多路复用: 通过使用端口号,多个应用程序可以在同一设备上同时运行,而无需相互干扰。传输层协议可以根据目标端口号将数据包传递给正确的应用程序。

  3. 数据传输: 端口号与IP地址一起构成了网络套接字(socket),从而允许应用程序通过网络进行数据传输。发送端使用源端口号,接收端使用目标端口号,以确定数据应该交付给哪个应用程序。

  4. 服务识别: 一些标准端口号已经被分配给特定的服务或协议。例如,HTTP通常使用端口号80,HTTPS使用端口号443,SMTP(邮件传输)使用端口号25等。这样,网络设备和应用程序可以识别常见的服务并将数据传递给正确的端口。

  5. 安全性: 端口号也与网络安全有关。防火墙和网络安全设备可以基于端口号来控制数据流量,允许或阻止特定的应用程序或服务通信。

  6. 动态分配: 除了一些常见的标准端口号外,应用程序也可以使用动态分配的端口号。这些端口号在通信过程中临时分配给应用程序,以便在通信结束后可以被释放并重新使用。

端口号是网络通信中的一个重要概念,它使得不同的应用程序能够在同一设备上通过网络进行通信,并确保数据能够准确传递到目标应用程序。

2.1.3 网络字节序

网络字节序(Network Byte Order)是在讨论在计算机网络中跨不同计算机体系结构(如大端序和小端序)传输数据时的字节顺序问题。不同的计算机体系结构在内存中以不同的方式存储多字节数据(如整数、浮点数等)的字节顺序,这可能导致在网络通信中数据的混淆和解释错误。

网络通信涉及多台计算机或设备之间的数据交换。如果不同的设备使用不同的字节序,那么在发送数据时可能会导致以下问题:

  1. 数据解释错误: 接收方可能会错误地解释数据,因为字节的顺序与发送方不同。这会导致数据在接收方的应用程序中被错误地解释和处理。

  2. 数据截断: 如果数据在发送和接收时以不同的字节序传输,可能会导致数据的部分截断或丢失。这会损坏数据的完整性。

为了解决这些问题,网络通信需要使用一种一致的字节序,以确保数据可以在不同设备之间正确传递和解释。这种一致的字节序被称为网络字节序,通常采用大端字节序。

网络字节序的讨论涉及如何在数据传输过程中,确保发送和接收双方都能正确地解释数据。为了实现这一点,需要使用字节序转换函数(如htonl()htons()ntohl()ntohs())来在不同字节序之间进行转换。这样可以确保数据在网络中的传输和解释是一致的,无论使用的是大端序还是小端序。

2.2 基于TCP的socket通信

2.2.1 认识socket

img

Socket(套接字)是一种用于在计算机网络中进行通信的编程接口(API)。它提供了一种机制,使得不同计算机之间可以通过网络进行数据传输,实现进程间的通信。

背景和问题: 在早期的计算机网络中,需要一种方法来在不同设备之间进行通信。最初的网络通信是通过低层的网络协议进行的,但这对于应用程序来说过于繁琐。开发人员需要一种更高层次的抽象来处理通信细节,从而实现应用程序之间的数据交换。

解决问题和方法: Socket接口的出现解决了上述问题。它提供了一种标准的API,使得应用程序可以方便地创建、连接、发送和接收数据。通过Socket,应用程序不需要关心底层的网络细节,而只需要使用Socket函数来实现通信。

作用: Socket在网络通信中起到了关键作用,它使得应用程序能够轻松地在不同设备之间进行数据交换。通过Socket,开发人员可以构建各种网络应用,如网络游戏、聊天应用、文件传输工具、网络服务器等。

发展过程和变化: Socket的发展经历了不同的阶段。起初,它是基于传统的Unix文件I/O,后来随着计算机网络的发展,Socket逐渐被标准化,出现了不同的Socket类型(如流套接字、数据报套接字等)和Socket函数(如socket()、bind()、connect()、send()、recv()等)。随着网络技术的进步,Socket在不同操作系统和编程语言中得到了广泛支持。

此外,随着互联网的迅速发展,Socket的应用范围变得更加广泛,从传统的网络通信延伸到了Web开发、实时音视频传输、云计算等领域。同时,各种高级通信协议也在Socket基础上构建,如HTTP、FTP、SMTP等。

总之,Socket是网络编程中的核心概念,它在计算机网络通信中扮演了极其重要的角色,提供了一种通用的接口,使得应用程序可以方便地进行跨网络通信,不需要处理底层细节。从最初的Unix文件I/O发展到现代的网络通信,Socket一直在不断演进,适应不断变化的网络需求。

2.2.2 通信模型

img

Socket通信实现步骤解析

Step 1:创建ServerSocket和Socket

Step 2:打开连接到的Socket的输入/输出流

Step 3:按照协议对Socket进行读/写操作

Step 4:关闭输入输出流,以及Socket

接下来写一个简单的例子,开启服务端后,客户端点击按钮然后链接服务端, 并向服务端发送一串字符串,表示通过Socket链接上服务器

2.2.3 服务端编写

服务端要做的事有这些

Step 1:创建ServerSocket对象,绑定监听的端口

Step 2:调用accept()方法监听客户端的请求

Step 3:连接建立后,通过输入流读取客户端发送的请求信息

Step 4:通过输出流向客户端发送响应信息

Step 5:关闭相关资源

代码实现:

#include <iostream>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
    int serverSocket, clientSocket;
    struct sockaddr_in serverAddr;
    struct sockaddr_in clientAddr;
    socklen_t clientAddrLen = sizeof(clientAddr);
    
    // 1. 创建服务器端 Socket
    serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    
    // 配置服务器地址
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY; // 任意地址
    serverAddr.sin_port = htons(12345); // 端口号转换为网络字节序
    
    // 2. 绑定端口和地址
    bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
    
    // 3. 监听连接请求
    listen(serverSocket, 5); // 允许最大排队连接数为5
    
    std::cout << "~~~ 服务端已就绪,等待客户端接入 ~~~" << std::endl;
    
    // 4. 等待客户端连接
    clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrLen);
    
    char clientIp[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &(clientAddr.sin_addr), clientIp, INET_ADDRSTRLEN);
    std::cout << "客户端接入,客户端 IP 地址:" << clientIp << std::endl;
    
    // 5. 读取客户端信息
    char buffer[1024];
    memset(buffer, 0, sizeof(buffer));
    
    while (recv(clientSocket, buffer, sizeof(buffer), 0) > 0) {
        std::cout << "客户端发送过来的信息:" << buffer << std::endl;
        memset(buffer, 0, sizeof(buffer));
    }
    
    // 6. 关闭连接
    close(clientSocket);
    close(serverSocket);
    
    return 0;
}

2.2.4 客户端编写

客户端要做的事有这些

Step 1:创建Socket对象,指明需要链接的服务器的地址和端号

Step 2:链接建立后,通过输出流向服务器发送请求信息

Step 3:通过输出流获取服务器响应的信息

Step 4:关闭相关资源

代码实现:

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
    // 创建客户端 Socket
    int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (clientSocket == -1) {
        std::cerr << "Error creating socket!" << std::endl;
        return 1;
    }
    
    // 配置服务器地址和端口
    struct sockaddr_in serverAddr;
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(12345); // 端口号转换为网络字节序
    if (inet_pton(AF_INET, "172.16.2.54", &serverAddr.sin_addr) <= 0) {
        std::cerr << "Invalid address or address family!" << std::endl;
        return 1;
    }
    
    // 连接到服务器
    if (connect(clientSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
        std::cerr << "Error connecting to server!" << std::endl;
        close(clientSocket);
        return 1;
    }
    
    // 获取客户端 IP 地址
    char clientIp[INET_ADDRSTRLEN];
    if (inet_ntop(AF_INET, &serverAddr.sin_addr, clientIp, INET_ADDRSTRLEN) == nullptr) {
        std::cerr << "Error converting IP address!" << std::endl;
        close(clientSocket);
        return 1;
    }
    
    // 发送消息到服务器
    const char* message = "客户端:";
    send(clientSocket, message, strlen(message), 0);
    send(clientSocket, clientIp, strlen(clientIp), 0);
    send(clientSocket, " 接入服务器!", strlen(" 接入服务器!"), 0);
    
    // 关闭连接
    close(clientSocket);
    
    return 0;
}

2.3 基于UDP

2.3.1 服务端实现

服务端实现步骤:

Step 1:创建DatagramSocket,指定端口号
Step 2:创建DatagramPacket
Step 3:接收客户端发送的数据信息
Step 4:读取数据

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
    /*
     * 接收客户端发送的数据
     */
    // 1. 创建服务器端套接字
    int serverSocket = socket(AF_INET, SOCK_DGRAM, 0);
    if (serverSocket == -1) {
        std::cerr << "Error creating socket!" << std::endl;
        return 1;
    }

    // 2. 配置服务器地址
    struct sockaddr_in serverAddr;
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY; // 使用本地所有可用地址
    serverAddr.sin_port = htons(12345); // 端口号转换为网络字节序

    // 3. 绑定端口和地址
    if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
        std::cerr << "Error binding socket!" << std::endl;
        close(serverSocket);
        return 1;
    }

    // 4. 接收客户端发送的数据
    struct sockaddr_in clientAddr;
    socklen_t clientAddrLen = sizeof(clientAddr);
    char data[1024];
    memset(data, 0, sizeof(data));

    std::cout << "**** 服务器端已经启动,等待客户端发送数据 ****" << std::endl;
    ssize_t bytesRead = recvfrom(serverSocket, data, sizeof(data), 0, (struct sockaddr*)&clientAddr, &clientAddrLen);
    if (bytesRead == -1) {
        std::cerr << "Error receiving data!" << std::endl;
        close(serverSocket);
        return 1;
    }
    data[bytesRead] = '\0'; // 添加字符串结尾标志
    std::cout << "我是服务器,客户端说:" << data << std::endl;

    /*
     * 向客户端响应数据
     */
    // 1. 获取客户端的地址和端口
    struct sockaddr_in responseAddr = clientAddr;
    socklen_t responseAddrLen = sizeof(responseAddr);

    // 2. 定义响应的数据
    const char* responseMsg = "欢迎您!";

    // 3. 响应客户端
    ssize_t bytesSent = sendto(serverSocket, responseMsg, strlen(responseMsg), 0, (struct sockaddr*)&responseAddr, responseAddrLen);
    if (bytesSent == -1) {
        std::cerr << "Error sending response!" << std::endl;
    }

    // 4. 关闭套接字
    close(serverSocket);

    return 0;
}

2.3.2 客户端实现

客户端实现步骤:

Step 1:定义发送信息
Step 2:创建DatagramPacket,包含将要发送的信息
Step 3:创建DatagramSocket
Step 4:发送数据

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
    /*
     * 向服务器端发送数据
     */
    // 1. 定义服务器地址和端口号
    struct sockaddr_in serverAddr;
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8800); // 端口号转换为网络字节序
    if (inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr) <= 0) {
        std::cerr << "Invalid address or address family!" << std::endl;
        return 1;
    }

    // 2. 定义要发送的数据
    const char* data = "用户名:admin;密码:123";

    // 3. 创建 UDP 套接字
    int clientSocket = socket(AF_INET, SOCK_DGRAM, 0);
    if (clientSocket == -1) {
        std::cerr << "Error creating socket!" << std::endl;
        return 1;
    }

    // 4. 发送数据报到服务器
    ssize_t bytesSent = sendto(clientSocket, data, strlen(data), 0, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
    if (bytesSent == -1) {
        std::cerr << "Error sending data!" << std::endl;
        close(clientSocket);
        return 1;
    }

    /*
     * 接收服务器端响应的数据
     */
    // 1. 创建用于接收数据的缓冲区
    char reply[1024];
    memset(reply, 0, sizeof(reply));

    // 2. 接收服务器响应的数据
    struct sockaddr_in responseAddr;
    socklen_t responseAddrLen = sizeof(responseAddr);
    ssize_t bytesRead = recvfrom(clientSocket, reply, sizeof(reply), 0, (struct sockaddr*)&responseAddr, &responseAddrLen);
    if (bytesRead == -1) {
        std::cerr << "Error receiving data!" << std::endl;
    } else {
        std::cout << "我是客户端,服务器说:" << reply << std::endl;
    }

    // 3. 关闭套接字
    close(clientSocket);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_64893500/article/details/132228200