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

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

第2章 - 后端开发中的网络应用

嗨,后端开发的朋友们!

作为后端开发者,你需要设计和实现高性能、高可用的网络服务。从 RESTful API 到 WebSocket 服务器,从数据库连接到微服务通信,网络知识贯穿整个后端开发流程。

这一章,我会带你深入后端网络编程的实战应用,让你的服务更快、更稳定!

🌐 RESTful API 设计与实现

Express.js API 服务器

基础服务器搭建

// app.js
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');

const app = express();

// 安全中间件
app.use(helmet());

// CORS 配置
app.use(cors({
  origin: ['https://example.com', 'https://app.example.com'],
  credentials: true,
  maxAge: 86400  // 24小时
}));

// 解析 JSON
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));

// 请求日志
app.use((req, res, next) => {
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
  next();
});

// 限流配置
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15分钟
  max: 100,  // 最多100个请求
  message: '请求过于频繁,请稍后再试'
});

app.use('/api/', limiter);

// 健康检查
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: Date.now() });
});

// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`服务器运行在端口 ${PORT}`);
});

module.exports = app;

RESTful 路由设计

// routes/users.js
const express = require('express');
const router = express.Router();
const { body, param, validationResult } = require('express-validator');

// 模拟数据库
const users = [
  { id: 1, name: '张三', email: 'zhangsan@example.com' },
  { id: 2, name: '李四', email: 'lisi@example.com' }
];

// GET /users - 获取用户列表
router.get('/', (req, res) => {
  const { page = 1, limit = 10, search } = req.query;
  
  let result = users;
  
  // 搜索过滤
  if (search) {
    result = result.filter(u => 
      u.name.includes(search) || u.email.includes(search)
    );
  }
  
  // 分页
  const start = (page - 1) * limit;
  const end = start + parseInt(limit);
  const paginatedUsers = result.slice(start, end);
  
  res.json({
    success: true,
    data: paginatedUsers,
    pagination: {
      page: parseInt(page),
      limit: parseInt(limit),
      total: result.length
    }
  });
});

// GET /users/:id - 获取单个用户
router.get('/:id', 
  param('id').isInt().withMessage('ID必须是整数'),
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ success: false, errors: errors.array() });
    }
    
    const user = users.find(u => u.id === parseInt(req.params.id));
    
    if (!user) {
      return res.status(404).json({ success: false, message: '用户不存在' });
    }
    
    res.json({ success: true, data: user });
  }
);

// POST /users - 创建用户
router.post('/',
  body('name').notEmpty().withMessage('姓名不能为空'),
  body('email').isEmail().withMessage('邮箱格式不正确'),
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ success: false, errors: errors.array() });
    }
    
    const newUser = {
      id: users.length + 1,
      name: req.body.name,
      email: req.body.email
    };
    
    users.push(newUser);
    
    res.status(201).json({ success: true, data: newUser });
  }
);

// PUT /users/:id - 更新用户
router.put('/:id',
  param('id').isInt(),
  body('name').optional().notEmpty(),
  body('email').optional().isEmail(),
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ success: false, errors: errors.array() });
    }
    
    const user = users.find(u => u.id === parseInt(req.params.id));
    
    if (!user) {
      return res.status(404).json({ success: false, message: '用户不存在' });
    }
    
    if (req.body.name) user.name = req.body.name;
    if (req.body.email) user.email = req.body.email;
    
    res.json({ success: true, data: user });
  }
);

// DELETE /users/:id - 删除用户
router.delete('/:id',
  param('id').isInt(),
  (req, res) => {
    const index = users.findIndex(u => u.id === parseInt(req.params.id));
    
    if (index === -1) {
      return res.status(404).json({ success: false, message: '用户不存在' });
    }
    
    users.splice(index, 1);
    
    res.status(204).send();
  }
);

module.exports = router;

统一错误处理

// middleware/errorHandler.js
class ApiError extends Error {
  constructor(statusCode, message) {
    super(message);
    this.statusCode = statusCode;
  }
}

const errorHandler = (err, req, res, next) => {
  console.error('错误:', err);
  
  // 已知错误
  if (err instanceof ApiError) {
    return res.status(err.statusCode).json({
      success: false,
      message: err.message
    });
  }
  
  // 数据库错误
  if (err.name === 'MongoError') {
    return res.status(500).json({
      success: false,
      message: '数据库操作失败'
    });
  }
  
  // JWT 错误
  if (err.name === 'JsonWebTokenError') {
    return res.status(401).json({
      success: false,
      message: 'Token 无效'
    });
  }
  
  // 未知错误
  res.status(500).json({
    success: false,
    message: process.env.NODE_ENV === 'production' 
      ? '服务器内部错误' 
      : err.message
  });
};

module.exports = { ApiError, errorHandler };

🔌 数据库连接管理

MySQL 连接池

// db/mysql.js
const mysql = require('mysql2/promise');

// 创建连接池
const pool = mysql.createPool({
  host: process.env.DB_HOST || 'localhost',
  port: process.env.DB_PORT || 3306,
  user: process.env.DB_USER || 'root',
  password: process.env.DB_PASSWORD || '',
  database: process.env.DB_NAME || 'myapp',
  waitForConnections: true,
  connectionLimit: 10,  // 最大连接数
  queueLimit: 0,  // 队列限制(0表示无限制)
  enableKeepAlive: true,
  keepAliveInitialDelay: 0
});

// 测试连接
pool.getConnection()
  .then(connection => {
    console.log('MySQL 数据库连接成功');
    connection.release();
  })
  .catch(err => {
    console.error('MySQL 连接失败:', err);
  });

// 查询封装
const query = async (sql, params) => {
  try {
    const [rows] = await pool.execute(sql, params);
    return rows;
  } catch (error) {
    console.error('查询失败:', error);
    throw error;
  }
};

// 事务封装
const transaction = async (callback) => {
  const connection = await pool.getConnection();
  
  try {
    await connection.beginTransaction();
    const result = await callback(connection);
    await connection.commit();
    return result;
  } catch (error) {
    await connection.rollback();
    throw error;
  } finally {
    connection.release();
  }
};

module.exports = { pool, query, transaction };

MongoDB 连接

// db/mongodb.js
const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGODB_URI, {
      maxPoolSize: 10,  // 连接池大小
      minPoolSize: 5,
      socketTimeoutMS: 45000,
      serverSelectionTimeoutMS: 5000,
      family: 4  // 使用 IPv4
    });
    
    console.log('MongoDB 连接成功');
  } catch (error) {
    console.error('MongoDB 连接失败:', error);
    process.exit(1);
  }
};

// 监听连接事件
mongoose.connection.on('error', err => {
  console.error('MongoDB 错误:', err);
});

mongoose.connection.on('disconnected', () => {
  console.log('MongoDB 断开连接');
});

// 优雅关闭
process.on('SIGINT', async () => {
  await mongoose.connection.close();
  console.log('MongoDB 连接已关闭');
  process.exit(0);
});

module.exports = connectDB;

Redis 缓存

// db/redis.js
const redis = require('redis');

// 创建 Redis 客户端
const client = redis.createClient({
  host: process.env.REDIS_HOST || 'localhost',
  port: process.env.REDIS_PORT || 6379,
  password: process.env.REDIS_PASSWORD,
  db: 0,
  retry_strategy: (options) => {
    if (options.error && options.error.code === 'ECONNREFUSED') {
      return new Error('Redis 服务器拒绝连接');
    }
    if (options.total_retry_time > 1000 * 60 * 60) {
      return new Error('Redis 重连超时');
    }
    if (options.attempt > 10) {
      return undefined;  // 停止重试
    }
    return Math.min(options.attempt * 100, 3000);  // 重试间隔
  }
});

client.on('connect', () => {
  console.log('Redis 连接成功');
});

client.on('error', (err) => {
  console.error('Redis 错误:', err);
});

// 缓存封装
const cache = {
  // 获取缓存
  async get(key) {
    return new Promise((resolve, reject) => {
      client.get(key, (err, data) => {
        if (err) reject(err);
        resolve(data ? JSON.parse(data) : null);
      });
    });
  },
  
  // 设置缓存
  async set(key, value, ttl = 3600) {
    return new Promise((resolve, reject) => {
      const data = JSON.stringify(value);
      client.setex(key, ttl, data, (err, reply) => {
        if (err) reject(err);
        resolve(reply);
      });
    });
  },
  
  // 删除缓存
  async del(key) {
    return new Promise((resolve, reject) => {
      client.del(key, (err, reply) => {
        if (err) reject(err);
        resolve(reply);
      });
    });
  },
  
  // 批量删除(使用模式匹配)
  async delPattern(pattern) {
    return new Promise((resolve, reject) => {
      client.keys(pattern, (err, keys) => {
        if (err) reject(err);
        if (keys.length > 0) {
          client.del(keys, (err, reply) => {
            if (err) reject(err);
            resolve(reply);
          });
        } else {
          resolve(0);
        }
      });
    });
  }
};

// 缓存中间件
const cacheMiddleware = (ttl = 3600) => {
  return async (req, res, next) => {
    const key = `cache:${req.originalUrl}`;
    
    try {
      const cached = await cache.get(key);
      
      if (cached) {
        console.log('使用缓存:', key);
        return res.json(cached);
      }
      
      // 重写 res.json 以自动缓存
      const originalJson = res.json.bind(res);
      res.json = (data) => {
        cache.set(key, data, ttl).catch(err => console.error('缓存失败:', err));
        return originalJson(data);
      };
      
      next();
    } catch (error) {
      console.error('缓存中间件错误:', error);
      next();
    }
  };
};

module.exports = { client, cache, cacheMiddleware };

🚀 WebSocket 服务器

Socket.IO 实现

// server.js
const express = require('express');
const http = require('http');
const socketIO = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = socketIO(server, {
  cors: {
    origin: "*",
    methods: ["GET", "POST"]
  },
  pingTimeout: 30000,
  pingInterval: 25000
});

// 在线用户
const onlineUsers = new Map();

// 房间管理
const rooms = new Map();

// 连接处理
io.on('connection', (socket) => {
  console.log('用户连接:', socket.id);
  
  // 用户登录
  socket.on('login', (data) => {
    const { userId, username } = data;
    
    onlineUsers.set(userId, {
      socketId: socket.id,
      username,
      loginTime: Date.now()
    });
    
    socket.userId = userId;
    socket.username = username;
    
    // 广播用户上线
    io.emit('user-online', {
      userId,
      username,
      onlineCount: onlineUsers.size
    });
    
    console.log(`${username} 上线,当前在线 ${onlineUsers.size} 人`);
  });
  
  // 加入房间
  socket.on('join-room', (roomId) => {
    socket.join(roomId);
    
    if (!rooms.has(roomId)) {
      rooms.set(roomId, new Set());
    }
    rooms.get(roomId).add(socket.userId);
    
    // 通知房间内其他人
    socket.to(roomId).emit('user-joined', {
      userId: socket.userId,
      username: socket.username
    });
    
    console.log(`${socket.username} 加入房间 ${roomId}`);
  });
  
  // 离开房间
  socket.on('leave-room', (roomId) => {
    socket.leave(roomId);
    
    if (rooms.has(roomId)) {
      rooms.get(roomId).delete(socket.userId);
      if (rooms.get(roomId).size === 0) {
        rooms.delete(roomId);
      }
    }
    
    // 通知房间内其他人
    socket.to(roomId).emit('user-left', {
      userId: socket.userId,
      username: socket.username
    });
  });
  
  // 发送消息
  socket.on('send-message', (data) => {
    const { roomId, message } = data;
    
    const messageData = {
      id: Date.now(),
      userId: socket.userId,
      username: socket.username,
      message,
      timestamp: Date.now()
    };
    
    // 发送到房间
    io.to(roomId).emit('new-message', messageData);
    
    console.log(`消息 [${roomId}] ${socket.username}: ${message}`);
  });
  
  // 私聊
  socket.on('private-message', (data) => {
    const { toUserId, message } = data;
    
    const toUser = onlineUsers.get(toUserId);
    if (toUser) {
      io.to(toUser.socketId).emit('private-message', {
        fromUserId: socket.userId,
        fromUsername: socket.username,
        message,
        timestamp: Date.now()
      });
    }
  });
  
  // 断开连接
  socket.on('disconnect', () => {
    if (socket.userId) {
      onlineUsers.delete(socket.userId);
      
      // 从所有房间移除
      rooms.forEach((users, roomId) => {
        if (users.has(socket.userId)) {
          users.delete(socket.userId);
          socket.to(roomId).emit('user-left', {
            userId: socket.userId,
            username: socket.username
          });
        }
      });
      
      // 广播用户下线
      io.emit('user-offline', {
        userId: socket.userId,
        username: socket.username,
        onlineCount: onlineUsers.size
      });
      
      console.log(`${socket.username} 下线,当前在线 ${onlineUsers.size} 人`);
    }
  });
});

// 启动服务器
const PORT = 3000;
server.listen(PORT, () => {
  console.log(`WebSocket 服务器运行在端口 ${PORT}`);
});

🔐 认证与授权

JWT Token 认证

// middleware/auth.js
const jwt = require('jsonwebtoken');

const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
const JWT_EXPIRES_IN = '7d';

// 生成 Token
const generateToken = (userId, username) => {
  return jwt.sign(
    { userId, username },
    JWT_SECRET,
    { expiresIn: JWT_EXPIRES_IN }
  );
};

// 验证 Token
const verifyToken = (token) => {
  try {
    return jwt.verify(token, JWT_SECRET);
  } catch (error) {
    return null;
  }
};

// 认证中间件
const authMiddleware = (req, res, next) => {
  // 从 Header 获取 Token
  const authHeader = req.headers.authorization;
  
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({
      success: false,
      message: '未提供认证令牌'
    });
  }
  
  const token = authHeader.substring(7);
  
  const decoded = verifyToken(token);
  
  if (!decoded) {
    return res.status(401).json({
      success: false,
      message: '认证令牌无效或已过期'
    });
  }
  
  // 将用户信息挂载到 req
  req.user = decoded;
  next();
};

// 可选认证(有Token就验证,没有也放行)
const optionalAuth = (req, res, next) => {
  const authHeader = req.headers.authorization;
  
  if (authHeader && authHeader.startsWith('Bearer ')) {
    const token = authHeader.substring(7);
    const decoded = verifyToken(token);
    if (decoded) {
      req.user = decoded;
    }
  }
  
  next();
};

module.exports = {
  generateToken,
  verifyToken,
  authMiddleware,
  optionalAuth
};

登录接口

// routes/auth.js
const express = require('express');
const router = express.Router();
const bcrypt = require('bcrypt');
const { generateToken } = require('../middleware/auth');

// POST /auth/register - 注册
router.post('/register', async (req, res) => {
  try {
    const { username, email, password } = req.body;
    
    // 验证输入
    if (!username || !email || !password) {
      return res.status(400).json({
        success: false,
        message: '请填写完整信息'
      });
    }
    
    // 检查用户是否存在
    // const existingUser = await User.findOne({ email });
    // if (existingUser) { ... }
    
    // 加密密码
    const hashedPassword = await bcrypt.hash(password, 10);
    
    // 创建用户
    // const user = await User.create({ username, email, password: hashedPassword });
    
    // 生成 Token
    const token = generateToken('user123', username);
    
    res.status(201).json({
      success: true,
      data: {
        userId: 'user123',
        username,
        email,
        token
      }
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      message: '注册失败'
    });
  }
});

// POST /auth/login - 登录
router.post('/login', async (req, res) => {
  try {
    const { email, password } = req.body;
    
    // 查找用户
    // const user = await User.findOne({ email });
    // if (!user) { ... }
    
    // 验证密码
    // const isValidPassword = await bcrypt.compare(password, user.password);
    // if (!isValidPassword) { ... }
    
    // 模拟验证成功
    const token = generateToken('user123', '张三');
    
    res.json({
      success: true,
      data: {
        userId: 'user123',
        username: '张三',
        email,
        token
      }
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      message: '登录失败'
    });
  }
});

module.exports = router;

⚡ 性能优化

接口限流

// middleware/rateLimit.js
const redis = require('redis');
const client = redis.createClient();

// 滑动窗口限流
const rateLimitMiddleware = (options = {}) => {
  const {
    windowMs = 60000,  // 时间窗口(1分钟)
    max = 100,  // 最大请求数
    keyGenerator = (req) => req.ip,  // 生成限流key的函数
    handler = (req, res) => {  // 超限处理
      res.status(429).json({
        success: false,
        message: '请求过于频繁,请稍后再试'
      });
    }
  } = options;
  
  return async (req, res, next) => {
    const key = `ratelimit:${keyGenerator(req)}`;
    const now = Date.now();
    const windowStart = now - windowMs;
    
    try {
      // 使用 Redis sorted set 实现滑动窗口
      await new Promise((resolve, reject) => {
        // 删除窗口外的记录
        client.zremrangebyscore(key, 0, windowStart, (err) => {
          if (err) return reject(err);
          
          // 添加当前请求
          client.zadd(key, now, `${now}-${Math.random()}`, (err) => {
            if (err) return reject(err);
            
            // 获取当前窗口内的请求数
            client.zcard(key, (err, count) => {
              if (err) return reject(err);
              
              // 设置过期时间
              client.expire(key, Math.ceil(windowMs / 1000));
              
              if (count > max) {
                return handler(req, res);
              }
              
              resolve();
            });
          });
        });
      });
      
      next();
    } catch (error) {
      console.error('限流中间件错误:', error);
      next();
    }
  };
};

module.exports = rateLimitMiddleware;

请求压缩

const compression = require('compression');

// 压缩中间件
app.use(compression({
  filter: (req, res) => {
    // 只压缩大于 1KB 的响应
    if (req.headers['x-no-compression']) {
      return false;
    }
    return compression.filter(req, res);
  },
  level: 6,  // 压缩级别 (0-9)
  threshold: 1024  // 大于 1KB 才压缩
}));

HTTP/2 支持

const http2 = require('http2');
const fs = require('fs');

const server = http2.createSecureServer({
  key: fs.readFileSync('server.key'),
  cert: fs.readFileSync('server.crt')
}, app);

server.listen(443, () => {
  console.log('HTTP/2 服务器运行在端口 443');
});

📊 监控与日志

请求日志

const morgan = require('morgan');
const fs = require('fs');
const path = require('path');

// 创建日志目录
const logDirectory = path.join(__dirname, 'logs');
if (!fs.existsSync(logDirectory)) {
  fs.mkdirSync(logDirectory);
}

// 创建写入流
const accessLogStream = fs.createWriteStream(
  path.join(logDirectory, 'access.log'),
  { flags: 'a' }
);

// 自定义日志格式
morgan.token('body', (req) => JSON.stringify(req.body));

const logFormat = ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent" - :response-time ms - :body';

// 使用日志中间件
app.use(morgan(logFormat, { stream: accessLogStream }));
app.use(morgan('dev'));  // 控制台输出

性能监控

// middleware/monitor.js
const responseTime = require('response-time');

// 监控响应时间
app.use(responseTime((req, res, time) => {
  const stat = `${req.method} ${req.url}: ${time.toFixed(2)}ms`;
  console.log(stat);
  
  // 记录慢查询
  if (time > 1000) {
    console.warn(`[慢查询] ${stat}`);
  }
}));

// 健康检查端点
app.get('/metrics', (req, res) => {
  res.json({
    uptime: process.uptime(),
    memory: process.memoryUsage(),
    timestamp: Date.now()
  });
});

💡 最佳实践

1. 连接池配置

// 数据库连接池配置建议
const poolConfig = {
  min: 2,  // 最小连接数
  max: 10,  // 最大连接数(根据服务器配置)
  idle: 30000,  // 空闲超时(30秒)
  acquire: 60000,  // 获取连接超时(60秒)
  evict: 1000  // 检查空闲连接间隔
};

2. 优雅关闭

// 优雅关闭服务器
const gracefulShutdown = () => {
  console.log('开始优雅关闭...');
  
  server.close(() => {
    console.log('HTTP 服务器已关闭');
    
    // 关闭数据库连接
    mongoose.connection.close(false, () => {
      console.log('数据库连接已关闭');
      process.exit(0);
    });
  });
  
  // 超时强制关闭
  setTimeout(() => {
    console.error('超时,强制关闭');
    process.exit(1);
  }, 30000);
};

process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);

3. 环境配置

// config/index.js
require('dotenv').config();

module.exports = {
  env: process.env.NODE_ENV || 'development',
  port: parseInt(process.env.PORT, 10) || 3000,
  
  db: {
    host: process.env.DB_HOST || 'localhost',
    port: parseInt(process.env.DB_PORT, 10) || 3306,
    name: process.env.DB_NAME || 'myapp',
    user: process.env.DB_USER || 'root',
    password: process.env.DB_PASSWORD || ''
  },
  
  redis: {
    host: process.env.REDIS_HOST || 'localhost',
    port: parseInt(process.env.REDIS_PORT, 10) || 6379,
    password: process.env.REDIS_PASSWORD
  },
  
  jwt: {
    secret: process.env.JWT_SECRET || 'your-secret-key',
    expiresIn: process.env.JWT_EXPIRES_IN || '7d'
  }
};

📝 小结

这一章我们学习了:

✅ RESTful API 设计:路由设计、参数验证、错误处理
✅ 数据库连接:MySQL 连接池、MongoDB 连接、Redis 缓存
✅ WebSocket 服务器:实时通信、房间管理、在线状态
✅ 认证授权:JWT Token、登录注册、权限验证
✅ 性能优化:接口限流、请求压缩、HTTP/2
✅ 监控日志:请求日志、性能监控、健康检查

🎯 下一步

接下来学习运维工程师如何配置和管理网络服务!

继续学习第3章:运维中的网络配置 →

最近更新: 2025/12/27 10:13
Contributors: 王长安
Prev
第1章 - 前端开发的网络应用
Next
第3章 - 运维与 DevOps