Linux网络编程—网络基础概念

身为计算机专业的学生,网络基础知识是我们必须掌握的核心内容。本文将从网络的发展历程讲起,逐步深入协议分层、数据传输流程、IP地址与MAC地址的区别,最后到Socket编程的预备知识,带你系统梳理计算机网络的基础概念。

一、计算机网络的发展历程

计算机网络并非一蹴而就,它经历了从独立到互联、从局域到广域的演进过程。理解这个发展脉络,有助于我们更好地把握网络技术的本质。

1.1 独立模式

最早的计算机之间是相互独立的,每台计算机各自持有自己的数据。如果用户需要在不同计算机上处理不同的业务,就必须 physically 移动到对应的主机前操作。

这种模式的问题很明显:

  • 数据无法共享,每个终端都要维护一份独立数据

  • 用户需要在多台机器间来回切换,效率低下

  • 多人使用同一台机器时需要排队等待

1.2 网络互联

为了解决独立模式的问题,人们开始将多台计算机连接在一起,实现数据共享。这就是网络互联的雏形。

在这个阶段,共享数据由服务器集中管理,每个人使用各自独立的计算机,业务之间可以随时自由切换。这是计算机网络发展的重要一步。

1.3 局域网LAN

随着计算机数量的增多,简单的互联已经无法满足需求,于是出现了局域网。局域网通过交换机和路由器将更多的计算机连接在一起。

局域网的特点:

  • 覆盖范围较小,通常在一个建筑物或校园内

  • 传输速率高,延迟低

  • 通过交换机实现数据链路层的转发

  • 通过路由器实现不同子网间的通信

1.4 广域网WAN

当需要将远隔千里的计算机连接在一起时,就有了广域网。广域网将分布在不同地理位置的局域网连接起来,形成更大范围的网络。

所谓"局域网"和"广域网"只是一个相对的概念。比如,一个大型企业的内部网络,虽然覆盖范围很广,但从技术角度看也可以看作一个比较大的局域网。

从独立模式到广域网,计算机网络的发展本质上是为了满足人们协同工作的需求。计算机是人的工具,人要协同工作,注定了网络的产生是必然的。


二、网络协议

2.1 什么是协议

"协议"本质上是一种约定。就像打电话时,我们约定电话铃响几声后接听一样,计算机之间的通信也需要约定。

计算机之间的传输媒介是光信号和电信号,通过"频率"和"强弱"来表示0和1这样的信息。要想传递各种不同的信息,就需要约定好双方的数据格式。

这里有一个值得思考的问题:只要通信的两台主机约定好协议就可以了吗?

答案是否定的。如果A用频率表示01,B用强弱表示01,就好比一个人说中文,另一个人说葡萄牙语------虽然大家都有自己的通信规则,但语言不同,还是无法正常通信。

所以,完善的协议需要更多更细致的规定,并且让所有参与的人都遵守。

2.2 协议标准化组织

计算机生产厂商有很多,操作系统有很多,网络硬件设备也有很多。如何让这些不同厂商生产的计算机能够相互顺畅地通信?这就需要有人站出来,约定一个共同的标准,大家都来遵守------这就是网络协议。

一般具有制定协议或标准资格的组织,都是业界公认的权威机构。以下是几类主要的标准制定组织:

组织类型 代表机构及主要贡献
国际标准化组织 IEEE (电气和电子工程师协会):制定了全世界电子、电气和计算机科学领域30%左右的标准,包括IEEE 802系列标准 ISO (国际标准化组织):以OSI七层模型著称,在学术和理论研究中占有重要地位 ITU(国际电信联盟):联合国下属机构,负责制定电信领域的国际标准
民间国际团体 IETF(互联网工程师任务组):负责开发和推广互联网协议,特别是TCP/IP协议族,通过RFC发布协议标准
区域标准化组织 ETSI (欧洲电信标准学会):由欧洲共同体各国政府资助 ASTAP(亚洲与泛太平洋电信标准化协会):加强亚太地区信息通信标准化协作
官方机构 FCC(联邦通信委员会):美国对通信技术的管理机构,对通信产品技术特性进行审查和监督

三、协议分层

3.1 为什么要分层

协议本质上也是软件,在设计上为了更好地进行模块化、解耦合,被设计成为层状结构。

我们可以用一个生活中的例子来理解分层的好处。假设两个人通过电话沟通,这里就有两层协议:

  • 语言层:双方约定使用同一种语言(如汉语)

  • 通信设备层:双方约定使用同一种通信设备(如电话机)

分层的最大好处就是解耦合

  • 如果想换一种语言(如从汉语换成英语),只需要修改语言层,通信设备层不受影响

  • 如果想换一种通信设备(如从电话机换成无线电),只需要修改设备层,语言层不受影响

这样,每一层都可以独立演进,大大降低了软件维护的成本。

3.2 OSI七层模型

OSI七层网络模型是一个逻辑上的定义和规范。它把网络从逻辑上分为了7层,每一层都有相关、相对应的物理设备。

OSI七层模型是一种框架性的设计方法,其最主要的功能就是帮助不同类型的主机实现数据传输。它的最大优点是将服务、接口和协议这三个概念明确地区分开来,概念清楚,理论也比较完整。

层级 分层名称 功能 典型协议/设备
7 应用层 针对特定应用的协议 HTTP、FTP、SMTP、Telnet
6 表示层 设备固有数据格式和网络标准数据格式的转换 数据加密、压缩、格式转换
5 会话层 通信管理,负责建立和断开通信连接 会话管理、同步
4 传输层 管理两个节点之间的数据传输,负责可靠传输 TCP、UDP
3 网络层 地址管理与路由选择 IP、ICMP、路由器
2 数据链路层 互连设备之间传送和识别数据帧 以太网、交换机
1 物理层 以"0"、"1"代表电压的高低、灯光的闪灭 网线、光纤、集线器

OSI模型理论上非常完善,但在实际操作中,会话层和表示层很难直接接入到操作系统中,所以在工程实践中,最终落地的是TCP/IP五层(或四层)协议。

3.3 TCP/IP四层模型

TCP/IP是一组协议的代名词,它还包括许多协议,组成了TCP/IP协议簇。TCP/IP通讯协议采用了5层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求。

由于物理层我们考虑得比较少(主要关注软件相关内容),因此很多时候我们直接称为TCP/IP四层模型

层级 主要功能 典型设备
应用层 负责应用程序间沟通,如HTTP、FTP、SMTP等 用户进程
传输层 负责两台主机之间的数据传输,如TCP、UDP 操作系统内核
网络层 负责地址管理和路由选择,如IP协议 路由器
数据链路层 负责设备之间的数据帧的传送和识别 交换机
物理层 负责光/电信号的传递方式 集线器、网线

一般而言:

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

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

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

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

当然这并不绝对,很多交换机也实现了网络层的转发(三层交换机),很多路由器也实现了部分传输层的内容(比如端口转发)。

3.4 OSI七层与TCP/IP四层对比

OSI七层模型 TCP/IP四层模型 对应关系说明
应用层 应用层 TCP/IP将OSI的应用层、表示层、会话层合并为一层
表示层 应用层 TCP/IP将OSI的应用层、表示层、会话层合并为一层
会话层 应用层 TCP/IP将OSI的应用层、表示层、会话层合并为一层
传输层 传输层 两者对应,功能基本一致
网络层 网络层 两者对应,功能基本一致
数据链路层 网络接口层 TCP/IP将数据链路层和物理层合并为网络接口层
物理层 网络接口层 TCP/IP将数据链路层和物理层合并为网络接口层

四、再识协

4.1 为什么要有TCP/IP协议

你可能会问:为什么一定要有TCP/IP协议?单机内部不也能正常工作吗?

其实,即便是单机,计算机内部也存在协议。比如,其他设备和内存通信会有内存协议,和磁盘通信会有SATA、IDE、SCSI等磁盘协议。只不过这些协议都在本地主机各自的硬件中,通信的成本和问题比较少,我们感知不到罢了。

而网络通信最大的特点,就是主机之间的距离变远了。任何通信特征的变化,一定会带来新的问题,有问题就得解决问题,所以需要新的协议。

具体来说,网络通信会面临以下几个核心问题:

  1. 寻址问题:目标主机离得太远,数据要先发给路由器,怎么找到目标主机?

  2. 定位问题:网上主机那么多,怎么定位到具体的某一台?

  3. 可靠性问题:数据发出去如果丢了怎么办?

  4. 交付问题:数据到达主机后,怎么知道该交给哪个进程处理?

这些不同性质、不同种类的问题,就需要通过协议分层来分别解决。所以说,TCP/IP协议能分层的前提,是因为问题本身就能分层。

从这个角度看,TCP/IP协议的本质就是一种解决方案,一套针对网络通信中各种问题的系统性解决方案。

4.2 协议与操作系统的关系

从宏观上看,TCP/IP协议栈和操作系统是什么关系呢?

从图中可以看出:

  • 操作系统可以不同(Windows、Linux等),但协议必须相同

  • 传输层和网络层的协议(TCP、IP)必须实现在内核中,无论什么操作系统,这部分都必须遵守协议,保持一致

  • 数据链路层的协议由网卡驱动实现

  • 应用层协议在用户空间,由用户实现

可以看出,整个协议栈涉及硬件、驱动、内核、用户多个层面,需要整个IT行业都来支持和配合。

4.3 协议的朴素理解

说了这么多,协议到底是什么?我们可以用代码的角度来朴素地理解一下。

假设我们定义一个这样的结构体:

cpp 复制代码
struct protocol {
    int a;
    int b;
    int c;
};

struct protocol data = {10, 20, 30};

如果主机A把这个结构体对象data发给主机B,主机B能识别data,并且准确提取出a=10、b=20、c=30吗?

答案是肯定的。因为双方都有同样的结构体类型struct protocol。也就是说,用同样的代码实现协议,用同样的自定义数据类型,天然就具有"共识",能够识别对方发来的数据------这不就是约定吗?

所以,关于协议的朴素理解:所谓协议,就是通信双方都认识的结构化的数据类型

因为协议栈是分层的,所以每层都有双方都认可的协议,同层之间互相可以认识对方的协议。


五、网络传输基本流程

5.1 局域网通信原理

我们先从最简单的情况说起:两台主机在同一个局域网内,能否直接通信?

答案是可以的。原理有点像上课:老师在讲台上说话,全班同学都能听到,但只有被点到名的同学才需要回应。

5.2 MAC地址

每台主机在局域网上,要有唯一的标识来保证主机的唯一性------这就是MAC地址

MAC地址的特点:

  • 用来识别数据链路层中相连的节点

  • 长度为48比特位,即6个字节

  • 一般用16进制数字加上冒号的形式表示,例如:08:00:27:03:fb:19

  • 在网卡出厂时就确定了,不能修改

小知识:虚拟机中的MAC地址不是真实的MAC地址,可能会冲突;也有些网卡支持用户配置MAC地址。

在Windows系统中,可以使用 ipconfig /all 命令查看本机的MAC地址。

局域网通信的过程:

  1. 主机A要给主机E发数据,会把数据发送到局域网中

  2. 局域网内所有的主机都能收到这个数据

  3. 每台主机检查数据中的目标MAC地址,如果不是自己的,就丢弃

  4. 只有目标主机E发现MAC地址匹配,才会接收并处理数据

这里有几个重要的概念:

  • 碰撞域:以太网中,任何时刻只允许一台机器向网络中发送数据。如果有多台同时发送,会发生数据干扰,称为数据碰撞。所有发送数据的主机都要进行碰撞检测和碰撞避免。没有交换机的情况下,一个以太网就是一个碰撞域。

  • 目标MAC地址:主机对收到的报文确认是否是发给自己的,是通过目标MAC地址判定的。

5.3 数据封装与解包

明白了局域网通信原理,我们再来看同一个网段内两台主机发送消息的完整过程。

数据从发送方的应用层开始,自上而下逐层传递,每一层都会加上自己的报头,这个过程叫做封装

数据到达接收方后,自下而上逐层传递,每一层都会剥掉对应的报头,这个过程叫做解包

从图中可以看到,每一层都认为自己在和对方的同层直接通信------这就是所谓的"对等层通信"的概念。

5.4 报文、报头与有效载荷

几个重要概念:

  • 报头:对应协议层的结构体字段,包含该层协议的控制信息

  • 有效载荷:除了报头之外,真正要传输的数据内容

  • 报文:报头 + 有效载荷

不同的协议层对数据包有不同的称谓:

层级 数据包名称 说明
传输层 段(Segment) TCP段、UDP数据报
网络层 数据报(Datagram) IP数据报
链路层 帧(Frame) 以太网帧

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


六、跨网络传输

6.1 IP地址概念

局域网内的通信我们理解了,那跨网络的通信呢?这就需要用到IP地址。

IP协议有两个版本:IPv4和IPv6。我们通常说的IP协议,如果没有特殊说明,默认都是指IPv4。

IP地址的特点:

  • 在IP协议中,用来标识网络中不同主机的地址

  • 对于IPv4来说,IP地址是一个4字节、32位的整数

  • 通常使用"点分十进制"的字符串表示,例如 192.168.0.1

  • 用点分割的每一个数字表示一个字节,范围是 0 - 255

6.2 路由器的作用

跨网段的主机数据传输,数据从一台计算机到另一台计算机传输过程中要经过一个或多个路由器。

从图中可以看到,路由器可以连接不同类型的网络。IP网络层的存在,屏蔽了底层网络的差异,让上层协议感觉不到底层网络的不同。

我们来具体看一下数据是怎么从一个局域网传到另一个局域网的:

发送过程:

  1. 用户A的应用层数据逐层向下传递

  2. 到网络层时,封装上源IP和目的IP

  3. 到数据链路层时,封装上源MAC和目的MAC

  4. 路由器收到数据后,解包到网络层,发现目的IP不是自己局域网的

  5. 路由器重新封装数据链路层的MAC地址,转发给下一跳

这个过程中,有一个非常重要的特点:

  • IP地址在整个路由过程中,一直不变

  • MAC地址一直在变,每经过一跳,MAC地址都会更新

6.3 IP地址 vs MAC地址

既然有了MAC地址,为什么还要有IP地址?两者有什么区别?

对比维度 IP地址 MAC地址
作用 标识互联网中主机的逻辑位置 标识局域网中主机的物理位置
层级 网络层 数据链路层
长度 32位(IPv4) 48位
可变性 可以改变(随网络环境变化) 出厂时固定,一般不可改
路由过程中 保持不变 每经过一跳都改变
类比 长远目标(最终目的地) 下一阶段目标(下一站怎么走)

简单来说:

  • 目的IP是一种长远目标,是路径选择的重要依据

  • MAC地址是下一阶段目标,是局域网转发的重要依据

IP网络层存在的意义:提供网络虚拟层,让全世界的网络看起来都是IP网络,屏蔽最底层网络的差异


七、Socket编程预备知识

7.1 端口号详解

我们先思考一个问题:数据传输到主机是目的吗?

不是的。因为数据是给人用的------聊天是人在聊天,下载是人在下载,浏览网页是人在浏览。而人是通过启动的进程(QQ、迅雷、浏览器)来使用这些服务的。

所以,数据传输到主机不是目的,而是手段。到达主机内部,再交给主机内的进程,才是目的

但是系统中同时会存在非常多的进程,当数据到达目标主机之后,怎么转发给目标进程?这就需要在系统中标识进程的唯一性------这就是端口号的作用。

端口号(port)的特点:

  • 是传输层协议的内容

  • 是一个2字节16位的整数(范围 0 - 65535)

  • 用来标识一个进程,告诉操作系统当前的数据要交给哪一个进程处理

  • IP地址 + 端口号能够标识网络上某一台主机的某一个进程

  • 一个端口号只能被一个进程占用

7.2 端口号范围划分

端口号并不是随便用的,它有一定的范围划分:

端口范围 类别 说明
0 - 1023 知名端口号 HTTP、FTP、SSH等广为使用的应用层协议,端口号都是固定的
1024 - 65535 动态分配端口号 操作系统动态分配,客户端程序的端口号通常从这个范围分配

常见的知名端口号:

端口号 协议/服务 用途
21 FTP 文件传输协议(控制连接)
22 SSH 安全远程登录
23 Telnet 远程登录协议
25 SMTP 简单邮件传输协议
53 DNS 域名系统
80 HTTP 超文本传输协议
110 POP3 邮局协议版本3
443 HTTPS 安全超文本传输协议
3306 MySQL MySQL数据库
6379 Redis Redis缓存数据库
8080 HTTP代理 常用的Web服务代理端口

7.3 端口号 vs 进程ID

我们之前学习系统编程时知道,PID可以唯一标识一个进程。那端口号也能唯一标识一个进程,这两者是什么关系?

答案是:

  • 一个进程可以绑定多个端口号

  • 但是一个端口号不能被多个进程绑定

那为什么不用PID来标识网络进程呢?

因为进程PID属于系统概念,技术上虽然具有唯一性,但这样做会让系统进程管理和网络强耦合。实际设计时,并没有选择这样做------端口号是网络层面的概念,和进程管理解耦了。

7.4 Socket概念

总结一下:

  • IP地址用来标识互联网中唯一的一台主机

  • 端口号用来标识该主机上唯一的一个网络进程

所以,IP + Port 就能表示互联网中唯一的一个进程

而网络通信的本质,其实就是两个互联网进程代表人来进行通信。{srcIp, srcPort, dstIp, dstPort} 这样的四元组,就能标识互联网中唯二的两个进程。

从这个角度看,网络通信的本质,也是进程间通信------只不过是跨主机的进程间通信。

我们把 IP + Port 叫做套接字

7.5 TCP与UDP对比

传输层有两个最重要的协议:TCP和UDP。我们先对它们有一个直观的认识。

TCP(Transmission Control Protocol,传输控制协议)

  • 传输层协议

  • 有连接

  • 可靠传输

  • 面向字节流

UDP(User Datagram Protocol,用户数据报协议)

  • 传输层协议

  • 无连接

  • 不可靠传输

  • 面向数据报

我们来详细对比一下:

对比维度 TCP UDP
连接方式 面向连接(三次握手建立连接,四次挥手断开) 无连接(发送数据前无需建立连接,直接发送)
可靠性 可靠传输(确认、重传、流量控制) 不可靠传输(不保证数据一定到达)
首部大小 20字节(固定部分)+ 选项 8字节(固定)
流量控制 支持(滑动窗口协议) 不支持
拥塞控制 支持(慢启动、拥塞避免等) 不支持
传输效率 相对较低(额外控制开销) 相对较高(无额外控制)
通信模式 仅支持点对点 支持一对一、一对多、多对多
应用场景 网页浏览、文件传输、电子邮件 实时音视频、在线游戏、DNS查询

简单记忆:TCP像打电话------建立连接,确认每句话,可靠但慢;UDP像寄明信片------扔进邮筒,不确认是否收到,快但可能丢。

7.6 网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分。网络数据流同样有大端小端之分,那么如何定义网络数据流的地址呢?

TCP/IP协议规定:网络数据流应采用大端字节序,即低地址高字节

不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据。如果当前发送主机是小端,就需要先将数据转成大端;否则就忽略,直接发送即可。

举个例子:将 0x1234abcd 写入到以 0x0000 开始的内存中:

内存地址 0x0000 0x0001 0x0002 0x0003
大端(big-endian) 0x12 0x34 0xab 0xcd
小端(little-endian) 0xcd 0xab 0x34 0x12

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换:

cpp 复制代码
#include <arpa/inet.h>

// 网络转主机:16位短整数
uint16_t ntohs(uint16_t netshort);

// 主机转网络:16位短整数
uint16_t htons(uint16_t hostshort);

// 网络转主机:32位长整数
uint32_t ntohl(uint32_t netlong);

// 主机转网络:32位长整数
uint32_t htonl(uint32_t hostlong);

这些函数名很好记:

  • h 表示 host(主机)

  • n 表示 network(网络)

  • l 表示 32位长整数

  • s 表示 16位短整数

例如 htonl 表示将32位的长整数从主机字节序转换为网络字节序。

网络规定:所有发送到网络上的数据,都必须是大端的!


八、Socket编程接口

8.1 常见API

了解了预备知识后,我们来看看Socket编程的常见接口:

cpp 复制代码
// 创建 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);

这些函数的作用我们后面会详细讲解,这里先有个印象。

8.2 sockaddr结构

Socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及UNIX Domain Socket。然而,各种网络协议的地址格式并不相同。

为了统一接口,Socket API使用了通用的 struct sockaddr 类型,而具体的IPv4地址用 struct sockaddr_in 表示。

三者的关系:

  • struct sockaddr:通用地址结构,所有Socket API都用这个类型

  • struct sockaddr_in:IPv4专用的地址结构,包含地址类型、端口号、IP地址

  • struct sockaddr_un:UNIX域套接字专用的地址结构

sockaddr_in 结构的主要字段:

cpp 复制代码
struct sockaddr_in {
    short sin_family;          // 地址类型,AF_INET表示IPv4
    unsigned short sin_port;   // 端口号,网络字节序
    struct in_addr sin_addr;   // IP地址
    char sin_zero[8];          // 填充,使结构大小与sockaddr一致
};

其中 in_addr 结构用来表示一个IPv4的IP地址,其实就是一个32位的整数:

cpp 复制代码
typedef uint32_t in_addr_t;

struct in_addr {
    in_addr_t s_addr;  // 32位IP地址,网络字节序
};

使用时的注意事项:

  • Socket API的参数类型都是 struct sockaddr *

  • 实际使用时,我们需要定义 struct sockaddr_in 类型的变量

  • 传入函数时,需要强制类型转换为 struct sockaddr *

  • 这样做的好处是程序的通用性,可以接收IPv4、IPv6以及UNIX Domain Socket各种类型的地址结构


九、代码实战

说了这么多理论,我们来写一个最简单的TCP通信程序,感受一下Socket编程。

9.1 TCP服务器端

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};
    const char *hello = "Hello from server";

    // 1. 创建socket文件描述符
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 2. 设置地址结构
    address.sin_family = AF_INET;         // IPv4
    address.sin_addr.s_addr = INADDR_ANY; // 监听所有地址
    address.sin_port = htons(PORT);       // 端口号,转网络字节序

    // 3. 绑定端口
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 4. 开始监听
    if (listen(server_fd, 3) < 0) {
        perror("listen failed");
        exit(EXIT_FAILURE);
    }
    printf("Server listening on port %d...\n", PORT);

    // 5. 接受连接
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        perror("accept failed");
        exit(EXIT_FAILURE);
    }
    printf("Connection accepted from %s:%d\n", 
           inet_ntoa(address.sin_addr), ntohs(address.sin_port));

    // 6. 读取客户端数据
    read(new_socket, buffer, BUFFER_SIZE);
    printf("Client says: %s\n", buffer);

    // 7. 发送响应
    send(new_socket, hello, strlen(hello), 0);
    printf("Hello message sent\n");

    // 8. 关闭连接
    close(new_socket);
    close(server_fd);

    return 0;
}

9.2 TCP客户端

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    const char *hello = "Hello from client";
    char buffer[BUFFER_SIZE] = {0};

    // 1. 创建socket
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 2. 设置服务器地址
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // 将IP地址从字符串转换为网络字节序
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        perror("invalid address");
        exit(EXIT_FAILURE);
    }

    // 3. 连接服务器
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("connection failed");
        exit(EXIT_FAILURE);
    }

    // 4. 发送数据
    send(sock, hello, strlen(hello), 0);
    printf("Hello message sent\n");

    // 5. 读取响应
    read(sock, buffer, BUFFER_SIZE);
    printf("Server says: %s\n", buffer);

    // 6. 关闭连接
    close(sock);

    return 0;
}

9.3 运行说明

编译运行步骤:

  1. 先编译并运行服务器端:gcc server.c -o server && ./server

  2. 再编译并运行客户端:gcc client.c -o client && ./client

  3. 观察两端的输出

这是一个最简单的TCP通信示例,实际的网络编程还需要考虑:

  • 多客户端并发处理

  • 数据的粘包问题

  • 异常处理和重连机制

  • 数据的序列化和反序列化

这些内容我们在后续的学习中会逐步深入。


十、总结

本文从网络的发展历程讲起,系统梳理了计算机网络的基础概念:

  1. 网络发展:从独立模式到网络互联,再到局域网、广域网,网络的发展是为了满足人们协同工作的需求

  2. 协议分层:OSI七层模型是理论参考,TCP/IP四层模型是实际标准。分层的核心思想是解耦,每一层专注解决自己的问题

  3. 传输流程:数据发送时自上而下封装,接收时自下而上解包。每层都有自己的报头和有效载荷

  4. 地址体系:IP地址标识主机的逻辑位置,MAC地址标识主机的物理位置。IP负责路径选择,MAC负责局域网转发

  5. Socket编程:IP+Port构成Socket,唯一标识网络中的一个进程。TCP提供可靠的面向连接服务,UDP提供高效的无连接服务

网络基础知识是计算机专业的核心内容,也是后续学习网络编程、网络安全、分布式系统等方向的基础。希望本文能帮助你建立起一个完整的知识框架。