计算机网络学习指南计算机网络学习指南
首页
基础教程
进阶内容
实战案例
编程指南
首页
基础教程
进阶内容
实战案例
编程指南
  • 实战案例

    • 💼 实战案例
    • 🎯 学习目标
    • 🚀 学习路线
    • 📊 章节概览
  • 💡 学习建议
  • 🎓 学完之后
  • 第1章 - 前端开发的网络应用
  • 第2章 - 后端开发的网络应用
  • 第3章 - 运维与 DevOps
  • 第4章 - 测试与网络调试
  • 第5章 - 网络故障排查

第1章 - 前端开发中的网络应用

嗨,前端开发的朋友们!

作为前端开发者,你每天都在和网络打交道:发送 HTTP 请求、处理异步数据、优化加载速度、解决跨域问题……但你真的理解这些操作背后的网络原理吗?

这一章,我会用最实战的方式,带你把网络知识应用到实际的前端开发中。学完后,你会发现很多之前遇到的「奇怪问题」,其实都有迹可循!

🌐 HTTP 请求实战

Fetch API 详解

基本 GET 请求

// 最简单的 GET 请求
fetch('https://api.example.com/users')
  .then(response => {
    console.log('状态码:', response.status);        // 200
    console.log('状态文本:', response.statusText);  // OK
    console.log('响应头:', response.headers);       // Headers 对象
    
    // 检查响应是否成功
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    return response.json();  // 解析 JSON
  })
  .then(data => {
    console.log('数据:', data);
  })
  .catch(error => {
    console.error('请求失败:', error);
  });

// 使用 async/await(推荐)
async function fetchUsers() {
  try {
    const response = await fetch('https://api.example.com/users');
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    
    const data = await response.json();
    console.log('用户列表:', data);
    return data;
  } catch (error) {
    console.error('请求失败:', error);
  }
}

POST 请求提交数据

// 提交 JSON 数据
async function createUser(userData) {
  try {
    const response = await fetch('https://api.example.com/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer YOUR_TOKEN'
      },
      body: JSON.stringify(userData)
    });
    
    const result = await response.json();
    return result;
  } catch (error) {
    console.error('创建用户失败:', error);
  }
}

// 使用
const newUser = {
  name: '张三',
  email: 'zhangsan@example.com',
  age: 25
};

createUser(newUser);

上传文件

// 上传图片文件
async function uploadImage(file) {
  const formData = new FormData();
  formData.append('image', file);
  formData.append('userId', '123');
  
  try {
    const response = await fetch('https://api.example.com/upload', {
      method: 'POST',
      body: formData  // 不要设置 Content-Type,浏览器会自动设置
    });
    
    const result = await response.json();
    console.log('上传成功:', result.url);
    return result;
  } catch (error) {
    console.error('上传失败:', error);
  }
}

// HTML
// <input type="file" id="fileInput" accept="image/*">

document.getElementById('fileInput').addEventListener('change', (e) => {
  const file = e.target.files[0];
  if (file) {
    uploadImage(file);
  }
});

Axios 库使用

import axios from 'axios';

// 创建实例,配置基础 URL 和超时
const api = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 5000,
  headers: {
    'Content-Type': 'application/json'
  }
});

// 请求拦截器(添加 token)
api.interceptors.request.use(
  config => {
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    console.log('发送请求:', config.url);
    return config;
  },
  error => {
    return Promise.reject(error);
  }
);

// 响应拦截器(统一处理错误)
api.interceptors.response.use(
  response => {
    console.log('收到响应:', response.status);
    return response.data;  // 直接返回数据
  },
  error => {
    if (error.response) {
      // 服务器返回了错误状态码
      switch (error.response.status) {
        case 401:
          console.error('未授权,请登录');
          // 跳转到登录页
          window.location.href = '/login';
          break;
        case 404:
          console.error('资源不存在');
          break;
        case 500:
          console.error('服务器错误');
          break;
        default:
          console.error('请求失败:', error.response.data);
      }
    } else if (error.request) {
      // 请求已发送但没有收到响应
      console.error('网络错误,请检查连接');
    } else {
      // 请求配置出错
      console.error('请求配置错误:', error.message);
    }
    return Promise.reject(error);
  }
);

// 使用封装好的 API
async function getUsers() {
  try {
    const data = await api.get('/users');
    return data;
  } catch (error) {
    console.error('获取用户失败');
  }
}

async function createUser(userData) {
  try {
    const data = await api.post('/users', userData);
    return data;
  } catch (error) {
    console.error('创建用户失败');
  }
}

🚀 网络性能优化

1. 资源预加载

<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="//cdn.example.com">
<link rel="dns-prefetch" href="//api.example.com">

<!-- 预连接(DNS + TCP + TLS)-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

<!-- 预加载关键资源 -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/css/critical.css" as="style">
<link rel="preload" href="/js/main.js" as="script">

<!-- 预获取下一页资源 -->
<link rel="prefetch" href="/next-page.html">
<link rel="prefetch" href="/images/next-image.jpg">

2. 图片懒加载

// 方式1:使用 Intersection Observer API
class LazyLoad {
  constructor() {
    this.images = document.querySelectorAll('img[data-src]');
    this.observer = new IntersectionObserver(
      this.handleIntersection.bind(this),
      {
        rootMargin: '50px 0px',  // 提前 50px 开始加载
        threshold: 0.01
      }
    );
    
    this.observe();
  }
  
  handleIntersection(entries) {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        const src = img.dataset.src;
        
        // 创建新图片对象预加载
        const tempImg = new Image();
        tempImg.onload = () => {
          img.src = src;
          img.classList.add('loaded');
        };
        tempImg.src = src;
        
        // 停止观察已加载的图片
        this.observer.unobserve(img);
      }
    });
  }
  
  observe() {
    this.images.forEach(img => this.observer.observe(img));
  }
}

// 使用
new LazyLoad();

// HTML
// <img data-src="image.jpg" alt="懒加载图片" class="lazy">
<!-- 方式2:使用原生 loading 属性(简单但兼容性差)-->
<img src="image.jpg" loading="lazy" alt="图片">

3. 请求合并与缓存

// 请求合并(批量获取用户信息)
class RequestBatcher {
  constructor(fetchFn, delay = 50) {
    this.fetchFn = fetchFn;
    this.delay = delay;
    this.queue = [];
    this.timer = null;
  }
  
  add(id) {
    return new Promise((resolve, reject) => {
      this.queue.push({ id, resolve, reject });
      
      if (this.timer) {
        clearTimeout(this.timer);
      }
      
      this.timer = setTimeout(() => {
        this.flush();
      }, this.delay);
    });
  }
  
  async flush() {
    const items = this.queue.slice();
    this.queue = [];
    
    const ids = items.map(item => item.id);
    
    try {
      // 批量请求
      const results = await this.fetchFn(ids);
      
      // 将结果分配给各个 Promise
      items.forEach(item => {
        const result = results.find(r => r.id === item.id);
        item.resolve(result);
      });
    } catch (error) {
      items.forEach(item => item.reject(error));
    }
  }
}

// 批量获取用户信息
async function fetchUsersByIds(ids) {
  const response = await fetch(`/api/users?ids=${ids.join(',')}`);
  return response.json();
}

const userBatcher = new RequestBatcher(fetchUsersByIds);

// 使用(多次调用会自动合并)
async function displayUser(userId) {
  const user = await userBatcher.add(userId);
  console.log('用户信息:', user);
}

// 这些请求会被合并成一个
displayUser(1);
displayUser(2);
displayUser(3);
// 实际只发送一个请求: /api/users?ids=1,2,3

4. 缓存策略

// 带缓存的请求封装
class CachedRequest {
  constructor(ttl = 60000) {  // 默认缓存 60 秒
    this.cache = new Map();
    this.ttl = ttl;
  }
  
  async get(url) {
    const cached = this.cache.get(url);
    
    // 检查缓存是否有效
    if (cached && Date.now() - cached.timestamp < this.ttl) {
      console.log('使用缓存:', url);
      return cached.data;
    }
    
    // 发送请求
    console.log('发送请求:', url);
    const response = await fetch(url);
    const data = await response.json();
    
    // 存入缓存
    this.cache.set(url, {
      data,
      timestamp: Date.now()
    });
    
    return data;
  }
  
  clear(url) {
    if (url) {
      this.cache.delete(url);
    } else {
      this.cache.clear();
    }
  }
}

const cachedRequest = new CachedRequest(30000);  // 30秒缓存

// 使用
async function getUsers() {
  return await cachedRequest.get('/api/users');
}

🔒 跨域问题解决

什么是跨域?

// 同源策略:协议、域名、端口都相同才算同源

// 当前页面: https://www.example.com:443

// 同源 ✅
https://www.example.com/api/users
https://www.example.com:443/data

// 跨域 ❌
http://www.example.com      // 协议不同(https vs http)
https://api.example.com     // 域名不同(www vs api)
https://www.example.com:8080  // 端口不同(443 vs 8080)

解决方案1:CORS(后端配置)

// 前端代码(简单请求)
fetch('https://api.example.com/users')
  .then(response => response.json())
  .then(data => console.log(data));

// 后端需要配置响应头:
// Access-Control-Allow-Origin: *
// 或
// Access-Control-Allow-Origin: https://www.example.com
// 前端代码(预检请求)
fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123'
  },
  body: JSON.stringify({ name: '张三' })
});

// 浏览器会先发送 OPTIONS 预检请求
// 后端需要配置:
// Access-Control-Allow-Origin: https://www.example.com
// Access-Control-Allow-Methods: POST, GET, OPTIONS
// Access-Control-Allow-Headers: Content-Type, Authorization
// Access-Control-Max-Age: 86400

解决方案2:代理(开发环境)

// Vite 配置(vite.config.js)
export default {
  server: {
    proxy: {
      '/api': {
        target: 'https://api.example.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
}

// 前端代码
fetch('/api/users')  // 会被代理到 https://api.example.com/users
  .then(response => response.json());
// Webpack Dev Server 配置(webpack.config.js)
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'https://api.example.com',
        changeOrigin: true,
        pathRewrite: { '^/api': '' }
      }
    }
  }
}

解决方案3:JSONP(仅支持 GET)

// JSONP 原理:利用 <script> 标签不受跨域限制
function jsonp(url, callback) {
  const callbackName = 'jsonp_' + Date.now();
  
  // 创建全局回调函数
  window[callbackName] = (data) => {
    callback(data);
    document.body.removeChild(script);
    delete window[callbackName];
  };
  
  // 创建 script 标签
  const script = document.createElement('script');
  script.src = `${url}?callback=${callbackName}`;
  document.body.appendChild(script);
}

// 使用
jsonp('https://api.example.com/users', (data) => {
  console.log('数据:', data);
});

// 服务器需要返回:
// jsonp_1234567890({"users": [...]});

📡 WebSocket 实时通信

WebSocket 基础

class WebSocketClient {
  constructor(url) {
    this.url = url;
    this.ws = null;
    this.reconnectInterval = 5000;  // 5秒重连
    this.heartbeatInterval = 30000;  // 30秒心跳
    this.heartbeatTimer = null;
  }
  
  connect() {
    this.ws = new WebSocket(this.url);
    
    this.ws.onopen = () => {
      console.log('WebSocket 连接成功');
      this.startHeartbeat();
    };
    
    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      console.log('收到消息:', data);
      this.handleMessage(data);
    };
    
    this.ws.onerror = (error) => {
      console.error('WebSocket 错误:', error);
    };
    
    this.ws.onclose = () => {
      console.log('WebSocket 连接关闭');
      this.stopHeartbeat();
      // 自动重连
      setTimeout(() => {
        console.log('尝试重新连接...');
        this.connect();
      }, this.reconnectInterval);
    };
  }
  
  send(data) {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data));
    } else {
      console.error('WebSocket 未连接');
    }
  }
  
  startHeartbeat() {
    this.heartbeatTimer = setInterval(() => {
      this.send({ type: 'ping' });
    }, this.heartbeatInterval);
  }
  
  stopHeartbeat() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
    }
  }
  
  handleMessage(data) {
    switch (data.type) {
      case 'pong':
        console.log('心跳响应');
        break;
      case 'message':
        console.log('新消息:', data.content);
        break;
      default:
        console.log('未知消息类型:', data);
    }
  }
  
  close() {
    this.stopHeartbeat();
    if (this.ws) {
      this.ws.close();
    }
  }
}

// 使用
const ws = new WebSocketClient('wss://chat.example.com');
ws.connect();

// 发送消息
ws.send({
  type: 'message',
  content: '你好!'
});

聊天室示例

// HTML
/*
<div id="chat-container">
  <div id="messages"></div>
  <input type="text" id="messageInput" placeholder="输入消息...">
  <button id="sendBtn">发送</button>
</div>
*/

class ChatRoom {
  constructor(wsUrl) {
    this.ws = new WebSocket(wsUrl);
    this.messagesDiv = document.getElementById('messages');
    this.messageInput = document.getElementById('messageInput');
    this.sendBtn = document.getElementById('sendBtn');
    
    this.init();
  }
  
  init() {
    this.ws.onopen = () => {
      this.addSystemMessage('已连接到聊天室');
    };
    
    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      this.displayMessage(data);
    };
    
    this.ws.onerror = (error) => {
      this.addSystemMessage('连接错误', 'error');
    };
    
    this.ws.onclose = () => {
      this.addSystemMessage('连接已断开', 'error');
    };
    
    // 发送按钮
    this.sendBtn.addEventListener('click', () => {
      this.sendMessage();
    });
    
    // 回车发送
    this.messageInput.addEventListener('keypress', (e) => {
      if (e.key === 'Enter') {
        this.sendMessage();
      }
    });
  }
  
  sendMessage() {
    const text = this.messageInput.value.trim();
    if (!text) return;
    
    const message = {
      type: 'message',
      content: text,
      timestamp: Date.now()
    };
    
    this.ws.send(JSON.stringify(message));
    this.messageInput.value = '';
  }
  
  displayMessage(data) {
    const messageDiv = document.createElement('div');
    messageDiv.className = 'message';
    messageDiv.innerHTML = `
      <span class="user">${data.username}:</span>
      <span class="content">${this.escapeHtml(data.content)}</span>
      <span class="time">${this.formatTime(data.timestamp)}</span>
    `;
    this.messagesDiv.appendChild(messageDiv);
    this.messagesDiv.scrollTop = this.messagesDiv.scrollHeight;
  }
  
  addSystemMessage(text, type = 'info') {
    const messageDiv = document.createElement('div');
    messageDiv.className = `system-message ${type}`;
    messageDiv.textContent = text;
    this.messagesDiv.appendChild(messageDiv);
  }
  
  escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
  }
  
  formatTime(timestamp) {
    const date = new Date(timestamp);
    return date.toLocaleTimeString();
  }
}

// 使用
const chat = new ChatRoom('wss://chat.example.com');

📊 网络监控与调试

Performance API

// 监控页面加载性能
window.addEventListener('load', () => {
  const perfData = performance.timing;
  const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart;
  const dnsTime = perfData.domainLookupEnd - perfData.domainLookupStart;
  const tcpTime = perfData.connectEnd - perfData.connectStart;
  const requestTime = perfData.responseEnd - perfData.requestStart;
  const domParseTime = perfData.domComplete - perfData.domLoading;
  
  console.log('页面加载时间:', pageLoadTime + 'ms');
  console.log('DNS 解析时间:', dnsTime + 'ms');
  console.log('TCP 连接时间:', tcpTime + 'ms');
  console.log('请求响应时间:', requestTime + 'ms');
  console.log('DOM 解析时间:', domParseTime + 'ms');
});

// 监控资源加载
const resources = performance.getEntriesByType('resource');
resources.forEach(resource => {
  console.log(`${resource.name}: ${resource.duration}ms`);
});

Network Information API

// 检测网络状态
if ('connection' in navigator) {
  const connection = navigator.connection;
  
  console.log('网络类型:', connection.effectiveType);  // 4g, 3g, 2g, slow-2g
  console.log('下行速度:', connection.downlink + ' Mbps');
  console.log('往返时间:', connection.rtt + ' ms');
  console.log('省流模式:', connection.saveData);
  
  // 根据网络状况调整策略
  if (connection.effectiveType === 'slow-2g' || connection.saveData) {
    // 加载低质量图片
    loadLowQualityImages();
  } else {
    // 加载高质量图片
    loadHighQualityImages();
  }
  
  // 监听网络变化
  connection.addEventListener('change', () => {
    console.log('网络状况改变:', connection.effectiveType);
  });
}

在线/离线状态

// 检测在线状态
console.log('当前在线状态:', navigator.onLine);

window.addEventListener('online', () => {
  console.log('网络已连接');
  showNotification('网络已恢复', 'success');
  // 同步离线数据
  syncOfflineData();
});

window.addEventListener('offline', () => {
  console.log('网络已断开');
  showNotification('网络连接已断开', 'error');
  // 启用离线模式
  enableOfflineMode();
});

💡 实用技巧

1. 请求超时控制

// Fetch 超时控制
function fetchWithTimeout(url, options = {}, timeout = 5000) {
  return Promise.race([
    fetch(url, options),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('请求超时')), timeout)
    )
  ]);
}

// 使用
try {
  const response = await fetchWithTimeout('/api/users', {}, 3000);
  const data = await response.json();
} catch (error) {
  if (error.message === '请求超时') {
    console.error('请求超时,请稍后重试');
  }
}

2. 请求重试

// 自动重试
async function fetchWithRetry(url, options = {}, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const response = await fetch(url, options);
      if (response.ok) {
        return response;
      }
      throw new Error(`HTTP ${response.status}`);
    } catch (error) {
      console.log(`第 ${i + 1} 次尝试失败`);
      if (i === retries - 1) {
        throw error;  // 最后一次失败则抛出错误
      }
      // 等待后重试(指数退避)
      await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
    }
  }
}

3. 并发控制

// 控制并发请求数量
class RequestQueue {
  constructor(maxConcurrent = 3) {
    this.maxConcurrent = maxConcurrent;
    this.current = 0;
    this.queue = [];
  }
  
  async add(fn) {
    if (this.current >= this.maxConcurrent) {
      await new Promise(resolve => this.queue.push(resolve));
    }
    
    this.current++;
    
    try {
      return await fn();
    } finally {
      this.current--;
      const resolve = this.queue.shift();
      if (resolve) resolve();
    }
  }
}

const queue = new RequestQueue(3);

// 发送 10 个请求,但最多同时 3 个
const promises = [];
for (let i = 1; i <= 10; i++) {
  promises.push(
    queue.add(() => fetch(`/api/user/${i}`))
  );
}

const results = await Promise.all(promises);

📝 小结

这一章我们学习了:

✅ HTTP 请求:Fetch API、Axios 封装、请求拦截器
✅ 性能优化:资源预加载、图片懒加载、请求合并、缓存策略
✅ 跨域解决:CORS、代理、JSONP
✅ WebSocket:实时通信、聊天室实现、自动重连
✅ 网络监控:Performance API、网络状态检测、在线离线
✅ 实用技巧:超时控制、自动重试、并发控制

🎯 下一步

现在你已经掌握了前端网络开发的核心技能,接下来可以学习后端开发中的网络应用!

继续学习第2章:后端开发中的网络应用 →

最近更新: 2025/12/27 10:13
Contributors: 王长安
Prev
🎓 学完之后
Next
第2章 - 后端开发的网络应用