一、网络概念
官方解释:网络是指一组彼此连接并能够相互发送数据的主机
1. 以访问一个网站为例
浏览器输入网址“www.baidu.com”
- 解析域名。
www.baidu.com是域名,需要找到对应的IP。- 先去本地
Host查看是否有对应域名的IP,查询到就返回 - 查询不到就访问
DNS服务器,向上级层层发送请求直到查询到IP,然后返回对应域名地址
- 先去本地
- 请求服务。将获得的
IP等端口信息进行Tcp封包,使用自身网卡将封包的数据通过wifi或蜂窝数据网络等发送给网络节点,经过一系列有协议的传输到达目标服务器。之后目标服务器发送服务,浏览器根据数据渲染界面
所有的通信网络设备都有一个IP地址,并且网络设备都是通过一根线连接起来进行通信,通信必须符合规则(也就是协议)。根据通信规模的大小,粗略划分为广域网WAN 和局域网LAN。互联网的核心是”互联网协议“,为了实现统一的标准,定义了OSI模型
| 层次 | 核心作用 | 设备/协议 | 数据单位 | 通俗类比 |
|---|---|---|---|---|
| 7. 应用层 | 直接给用户 / 应用程序提供网络服务(比如浏览网页、发邮件) | HTTP、HTTPS、FTP、SMTP、DNS | 应用数据(报文) | 用户(寄件人 / 收件人):明确要寄的东西 |
| 6. 表示层 | 处理数据的 “格式转换 + 加密”(比如把文字转成字节、HTTPS 加密数据) | 加密算法(AES/RSA)、字符编码(UTF-8) | 应用数据(经处理) | 把物品打包、加密 |
| 5. 会话层 | 建立 / 维护 / 终止 “通信会话”(比如确认双方是否在线、断开连接时清理资源) | TCP 会话、RPC(远程调用) | 应用数据 | 确认寄件 / 收件人身份、协调运输时机 |
| 4. 传输层 | 负责 “端到端的可靠传输”(比如确保数据不丢失、不重复、按顺序到达) | TCP(可靠)、UDP(不可靠) | 段(Segment) | 选择运输方式(顺丰 = TCP,普通快递 = UDP)、跟踪包裹 |
| 3. 网络层 | 负责 “跨网段路由”(找数据从 A 到 B 的最佳路径) | IP、路由器、ICMP(ping 命令用的) | 分组(Packet) | 根据地址选路线(北京→上海→杭州) |
| 2. 数据链路层 | 把网络层的数据包转成物理信号,处理局域网内的设备识别 | 交换机、MAC 地址、以太网协议(Ethernet) | 帧(Frame) | 投递快递(按门牌号 = MAC 地址) |
| 1. 物理层 | 把二进制数据转成电信号 / 光信号,通过网线、光纤等介质传递 | 网卡、网线、光纤、集线器、Wi-Fi 信号 | 比特(Bit) | 卡车+ 公路 |
2. TCP/IP模型
OSI模型主要用于理论,互联网常见实用的模型是TCP/IP模型,它们两个可以类比但实际不一样
3. 软硬件角度
程序员只需要在客户端/服务端完成代码的工作,并且调用套接字接口即可。TCP/IP封装在了内核代码中,之后的一系列底层操作都不用管
二、网络编程
网络编程就是基于各种协议,让数据在网络设备中传输和通信。简单说,就是写代码让两台(或多台)设备 “对话”,支撑网页访问、聊天、文件传输等所有网络功能
TCP编程
基于TCP 协议(可靠、面向连接),通过 Socket 接口实现客户端与服务器的双向通信
服务端
- 监听端口
- 接受客户端请求建立连接
- 创建协程处理连接
package main
import (
"bufio" // 提供带缓冲的读写工具,提升IO操作效率(尤其适合频繁小数据读写)
"log" // 日志输出包,自带时间戳,比fmt更适合调试和记录程序运行状态
"net" // 网络编程核心包,提供TCP/UDP监听、连接、读写等底层能力
)
// main函数:程序入口,TCP服务器的核心启动逻辑
func main() {
// 1. 创建TCP监听器,绑定本地地址和端口
// 参数1: "tcp" 指定使用TCP协议
// 参数2: "127.0.0.1:20000" 监听地址,127.0.0.1是本地回环地址(仅本机可访问),20000是端口号
// 返回值listen: 监听器对象,用于后续接受客户端连接;err: 监听过程中的错误(如端口被占用)
listen, err := net.Listen("tcp", "127.0.0.1:20000")
if err != nil {
// 监听失败则打印日志并终止程序(log.Fatal等价于log.Print+os.Exit(1))
log.Fatal(err)
}
// 2. 无限循环接受客户端连接(服务器持续运行,永不退出)
for {
// 阻塞等待客户端连接:程序运行到这里会暂停,直到有客户端发起连接请求
// 返回值conn: 连接对象,代表服务器与当前客户端的双向通信通道;err: 连接建立过程中的错误
conn, err := listen.Accept()
if err != nil {
// 连接建立失败(如客户端异常断开),打印日志后继续等待下一个连接
// 注意:生产环境建议用log.Println而非log.Fatal,避免单个连接失败导致服务器崩溃
log.Println("接受客户端连接失败:", err)
continue // 跳过当前错误连接,继续等待下一个
}
// 3. 启动协程(goroutine)处理当前客户端的通信逻辑
// 关键点:协程轻量高效,使服务器能同时处理多个客户端连接(并发核心)
// 每个客户端连接对应一个独立协程,互不干扰(比如同时处理100个客户端就启动100个协程)
go process(conn)
}
}
// process函数:处理单个客户端的读写通信逻辑(每个客户端连接对应一个协程执行此函数)
// 参数conn: 与客户端的连接对象,通过它实现数据读写
func process(conn net.Conn) {
// 延迟关闭连接:函数执行完毕(无论正常结束还是异常退出)时自动关闭conn
// 必须添加!避免客户端断开后服务器连接资源泄漏(端口、内存占用)
defer conn.Close()
// 无限循环读取客户端发送的数据(持续与客户端通信,直到连接断开)
for {
// 1. 基于连接创建带缓冲的读取器:减少系统调用次数,提升小数据读取效率
reader := bufio.NewReader(conn)
// 2. 定义数据缓冲区:用于存储从客户端读取的字节数据
// [1024]byte:创建固定大小为1024字节的数组,最多一次读取1024字节(超出部分分多次读取)
var buf [1024]byte
// 3. 从客户端读取数据到缓冲区:阻塞等待客户端发送数据
// 返回值n: 实际读取到的字节数;err: 读取过程中的错误(如客户端断开连接)
n, err := reader.Read(buf[:]) // buf[:]将数组转为切片,满足Read函数的参数要求
if err != nil {
// 读取失败(如客户端主动断开连接),打印日志后跳出循环,结束当前客户端的处理
log.Println("读取客户端数据失败:", err)
break
}
// 4. 将读取到的字节数据转为字符串(方便打印和处理)
v := string(buf[:n]) // 只取前n个字节(避免缓冲区剩余的空字符干扰)
// 5. 打印客户端发送的数据(包含客户端地址,方便调试)
log.Printf("接收到客户端[%s]的信息:%s \n", conn.RemoteAddr(), v)
// 6. 将收到的数据原封不动回传给客户端(实现"回声服务":客户端发什么,服务器回什么)
// buf[:n]:只发送实际读取到的n个字节,避免发送缓冲区中的空字符
_, err = conn.Write(buf[:n])
if err != nil {
log.Println("向客户端回复数据失败:", err)
break
}
}
}
客户端
- 建立和服务端的连接
- 收发消息
- 关闭连接
package main
import (
"bufio"
"log"
"net"
"os"
"strings"
)
func main() {
// 建立与服务器的TCP连接(目标:本地20000端口)
conn, err := net.Dial("tcp", "127.0.0.1:20000")
if err != nil {
log.Fatal("连接服务器失败: ", err) // 连接失败直接终止(比如服务器没启动)
}
defer conn.Close() // 程序退出前自动关闭连接,避免资源泄漏
// 创建终端输入读取器(读取用户在控制台输入的内容)
input := bufio.NewReader(os.Stdin)
log.Println("客户端启动成功,输入消息发送给服务器(输入Q退出):")
for {
// 1. 读取终端输入(直到用户按下回车键,包含\n)
readString, err := input.ReadString('\n')
if err != nil {
log.Printf("读取输入失败: %v", err)
break
}
// 2. 处理输入:去掉首尾的换行符(\r\n),避免多余字符
inputInfo := strings.Trim(readString, "\r\n")
// 3. 输入Q(不区分大小写)则退出循环,关闭连接
if strings.ToUpper(inputInfo) == "Q" {
log.Println("客户端主动退出")
break
}
// 4. 发送处理后的有效数据给服务器
_, err = conn.Write([]byte(inputInfo))
if err != nil {
log.Printf("发送数据失败: %v", err)
break
}
// 5. 读取服务器的回复(最多读1024字节)
var buf [1024]byte
n, err := conn.Read(buf[:])
if err != nil {
log.Printf("与服务器通信异常: %v", err)
break
}
// 6. 打印服务器的有效回复(只取实际读取到的n字节)
log.Printf("客户端接受到的内容: %s", string(buf[:n]))
}
}
检验