Files
test-repo/docs/.vitepress/theme/components/appendix/computer-fundamentals/TcpUdpComparison.vue
T
2026-02-24 00:18:09 +08:00

630 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="tcp-udp-demo">
<div class="demo-header">
<span class="title">TCP vs UDP可靠 vs 快速</span>
<span class="subtitle">两种不同的传输策略</span>
</div>
<div class="demo-content">
<div class="comparison-tabs">
<button
:class="['tab-btn', { active: activeTab === 'tcp' }]"
@click="activeTab = 'tcp'"
>
<span class="tab-icon">📨</span>
<span>TCP (可靠)</span>
</button>
<button
:class="['tab-btn', { active: activeTab === 'udp' }]"
@click="activeTab = 'udp'"
>
<span class="tab-icon">📮</span>
<span>UDP (快速)</span>
</button>
</div>
<div v-if="currentProtocol" class="protocol-detail">
<div class="detail-header">
<span class="detail-name">{{ currentProtocol.name }}</span>
<span class="detail-full">{{ currentProtocol.fullName }}</span>
</div>
<div class="feature-grid">
<div
v-for="(feature, i) in currentProtocol.features"
:key="i"
class="feature-item"
>
<span class="feature-icon">{{ feature.icon }}</span>
<span class="feature-name">{{ feature.name }}</span>
<span class="feature-value">{{ feature.value }}</span>
</div>
</div>
<div class="mechanism-section">
<div class="mechanism-title">核心机制</div>
<div class="mechanism-list">
<div
v-for="(m, i) in currentProtocol.mechanisms"
:key="i"
class="mechanism-item"
>
<span class="mechanism-name">{{ m.name }}</span>
<span class="mechanism-desc">{{ m.desc }}</span>
</div>
</div>
</div>
<div class="use-cases">
<div class="use-title">适用场景</div>
<div class="use-tags">
<span
v-for="(use, i) in currentProtocol.useCases"
:key="i"
class="use-tag"
>{{ use }}</span>
</div>
</div>
</div>
<div class="visual-demo">
<div class="visual-title">传输过程演示</div>
<div class="transmission-demo">
<div class="sender">
<div class="node-label">发送方</div>
<div class="packets">
<div
v-for="(packet, i) in packets"
:key="i"
:class="[
'packet',
{ sent: packet.sent, acked: packet.acked, lost: packet.lost }
]"
>
{{ packet.seq }}
</div>
</div>
</div>
<div class="network-channel">
<div class="channel-label">网络通道</div>
<div class="channel-status" :class="{ congested: isCongested }">
{{ isCongested ? '拥堵' : '正常' }}
</div>
<button class="demo-btn" @click="runDemo">开始演示</button>
<button class="demo-btn" @click="toggleCongestion">
{{ isCongested ? '恢复网络' : '模拟丢包' }}
</button>
</div>
<div class="receiver">
<div class="node-label">接收方</div>
<div class="received-packets">
<div
v-for="(packet, i) in receivedPackets"
:key="i"
class="received-packet"
>
{{ packet }}
</div>
</div>
</div>
</div>
<div class="demo-log">
<div class="log-title">传输日志</div>
<div class="log-content">
<div v-for="(log, i) in logs" :key="i" class="log-item">
{{ log }}
</div>
</div>
</div>
</div>
<div class="comparison-table">
<div class="table-title">特性对比</div>
<table>
<thead>
<tr>
<th>特性</th>
<th>TCP</th>
<th>UDP</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, i) in comparisonData" :key="i">
<td class="feature-col">
{{ row.feature }}
</td>
<td :class="{ highlight: row.tcpBetter }">
{{ row.tcp }}
</td>
<td :class="{ highlight: !row.tcpBetter }">
{{ row.udp }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="info-box">
<strong>核心思想</strong>TCP 像挂号信确保送达但较慢UDP
像平信快速但不保证送达选择哪种协议取决于应用场景需要可靠性选
TCP需要实时性选 UDP
</div>
</div>
</template>
<script setup>
import { ref, computed, reactive } from 'vue'
const activeTab = ref('tcp')
const protocols = {
tcp: {
name: 'TCP',
fullName: 'Transmission Control Protocol',
features: [
{ icon: '✅', name: '可靠性', value: '保证数据送达' },
{ icon: '📊', name: '有序性', value: '按顺序重组' },
{ icon: '🔄', name: '重传机制', value: '丢包自动重传' },
{ icon: '⚖️', name: '流量控制', value: '防止接收方溢出' },
{ icon: '🚦', name: '拥塞控制', value: '避免网络拥堵' },
{ icon: '🤝', name: '连接导向', value: '需要建立连接' }
],
mechanisms: [
{ name: '三次握手', desc: '建立可靠连接,确保双方都能收发' },
{ name: '序列号', desc: '每个字节编号,保证有序和完整性' },
{ name: '确认应答', desc: '收到数据必须回复 ACK' },
{ name: '超时重传', desc: '未收到 ACK 则重传' },
{ name: '滑动窗口', desc: '控制发送速率,提高效率' }
],
useCases: ['网页浏览', '文件传输', '邮件发送', '数据库连接']
},
udp: {
name: 'UDP',
fullName: 'User Datagram Protocol',
features: [
{ icon: '⚡', name: '速度', value: '无连接开销' },
{ icon: '📦', name: '数据报', value: '独立的数据包' },
{ icon: '❌', name: '无保证', value: '不保证送达' },
{ icon: '🔀', name: '无序', value: '可能乱序到达' },
{ icon: '💡', name: '轻量', value: '头部仅 8 字节' },
{ icon: '🎯', name: '灵活', value: '应用层控制' }
],
mechanisms: [
{ name: '无连接', desc: '直接发送,无需建立连接' },
{ name: '校验和', desc: '检测数据是否损坏' },
{ name: '端口复用', desc: '支持多路复用' },
{ name: '应用层控制', desc: '由应用决定重传等策略' }
],
useCases: ['视频直播', '在线游戏', 'DNS 查询', 'VoIP 通话']
}
}
const currentProtocol = computed(() => protocols[activeTab.value])
const comparisonData = [
{ feature: '连接', tcp: '面向连接', udp: '无连接', tcpBetter: true },
{ feature: '可靠性', tcp: '可靠传输', udp: '不保证', tcpBetter: true },
{ feature: '顺序', tcp: '有序', udp: '可能乱序', tcpBetter: true },
{ feature: '速度', tcp: '较慢', udp: '快', tcpBetter: false },
{ feature: '头部开销', tcp: '20 字节', udp: '8 字节', tcpBetter: false },
{ feature: '流量控制', tcp: '有', udp: '无', tcpBetter: true },
{ feature: '拥塞控制', tcp: '有', udp: '无', tcpBetter: true },
{ feature: '广播/多播', tcp: '不支持', udp: '支持', tcpBetter: false }
]
const packets = ref([
{ seq: 1, sent: false, acked: false, lost: false },
{ seq: 2, sent: false, acked: false, lost: false },
{ seq: 3, sent: false, acked: false, lost: false },
{ seq: 4, sent: false, acked: false, lost: false }
])
const receivedPackets = ref([])
const logs = ref([])
const isCongested = ref(false)
const toggleCongestion = () => {
isCongested.value = !isCongested.value
logs.value.push(`网络状态: ${isCongested.value ? '拥堵(模拟丢包)' : '正常'}`)
}
const runDemo = async () => {
receivedPackets.value = []
logs.value = ['开始传输演示...']
for (let i = 0; i < packets.value.length; i++) {
packets.value[i].sent = false
packets.value[i].acked = false
packets.value[i].lost = false
}
const isTcp = activeTab.value === 'tcp'
for (let i = 0; i < packets.value.length; i++) {
const packet = packets.value[i]
packet.sent = true
if (isCongested.value && Math.random() > 0.5) {
packet.lost = true
logs.value.push(`${packet.seq} 丢失!`)
if (isTcp) {
await new Promise((r) => setTimeout(r, 500))
logs.value.push(`TCP 重传包 ${packet.seq}...`)
packet.lost = false
receivedPackets.value.push(packet.seq)
packet.acked = true
logs.value.push(`${packet.seq} 重传成功`)
}
} else {
receivedPackets.value.push(packet.seq)
packet.acked = true
logs.value.push(`${packet.seq} 送达`)
}
await new Promise((r) => setTimeout(r, 300))
}
if (isTcp) {
logs.value.push(
`TCP 完成: 收到 ${receivedPackets.value.length} 个包,顺序: ${receivedPackets.value.join(', ')}`
)
} else {
logs.value.push(`UDP 完成: 收到 ${receivedPackets.value.length} 个包`)
}
}
</script>
<style scoped>
.tcp-udp-demo {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
margin: 1rem 0;
}
.demo-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.demo-header .title {
font-weight: bold;
font-size: 1rem;
}
.demo-header .subtitle {
color: var(--vp-c-text-2);
font-size: 0.85rem;
margin-left: 0.5rem;
}
.comparison-tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.tab-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.5rem;
border: 2px solid var(--vp-c-divider);
background: var(--vp-c-bg);
border-radius: 6px;
cursor: pointer;
font-size: 0.9rem;
}
.tab-btn.active {
border-color: var(--vp-c-brand);
background: var(--vp-c-brand-soft);
}
.tab-icon {
font-size: 1.1rem;
}
.protocol-detail {
background: var(--vp-c-bg);
padding: 0.75rem;
border-radius: 6px;
margin-bottom: 1rem;
}
.detail-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}
.detail-name {
font-weight: bold;
font-size: 1.1rem;
}
.detail-full {
font-size: 0.75rem;
color: var(--vp-c-text-2);
}
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.feature-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 0.4rem;
background: var(--vp-c-bg-alt);
border-radius: 4px;
}
.feature-icon {
font-size: 1rem;
}
.feature-name {
font-size: 0.75rem;
color: var(--vp-c-text-2);
}
.feature-value {
font-size: 0.8rem;
font-weight: bold;
}
.mechanism-section {
margin-bottom: 0.75rem;
}
.mechanism-title,
.use-title {
font-weight: bold;
font-size: 0.85rem;
margin-bottom: 0.5rem;
}
.mechanism-list {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.mechanism-item {
display: flex;
gap: 0.5rem;
font-size: 0.8rem;
}
.mechanism-name {
font-weight: bold;
color: var(--vp-c-brand);
min-width: 70px;
}
.mechanism-desc {
color: var(--vp-c-text-2);
}
.use-tags {
display: flex;
gap: 0.25rem;
flex-wrap: wrap;
}
.use-tag {
padding: 0.2rem 0.5rem;
background: var(--vp-c-brand-soft);
border-radius: 4px;
font-size: 0.75rem;
}
.visual-demo {
background: var(--vp-c-bg);
padding: 0.75rem;
border-radius: 6px;
margin-bottom: 1rem;
}
.visual-title {
font-weight: bold;
font-size: 0.85rem;
margin-bottom: 0.5rem;
}
.transmission-demo {
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.sender,
.receiver {
flex: 1;
padding: 0.5rem;
background: var(--vp-c-bg-alt);
border-radius: 6px;
}
.node-label {
font-size: 0.8rem;
font-weight: bold;
margin-bottom: 0.25rem;
}
.packets,
.received-packets {
display: flex;
gap: 0.25rem;
flex-wrap: wrap;
}
.packet {
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
background: var(--vp-c-divider);
border-radius: 4px;
font-size: 0.8rem;
font-weight: bold;
}
.packet.sent {
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand);
}
.packet.acked {
background: var(--vp-c-brand);
color: white;
}
.packet.lost {
background: #ff6b6b;
color: white;
}
.received-packet {
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
background: var(--vp-c-brand);
color: white;
border-radius: 4px;
font-size: 0.8rem;
font-weight: bold;
}
.network-channel {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25rem;
padding: 0.5rem;
}
.channel-label {
font-size: 0.75rem;
color: var(--vp-c-text-2);
}
.channel-status {
font-size: 0.7rem;
padding: 0.15rem 0.4rem;
background: #51cf66;
color: white;
border-radius: 3px;
}
.channel-status.congested {
background: #ff6b6b;
}
.demo-btn {
padding: 0.25rem 0.5rem;
font-size: 0.7rem;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
border-radius: 4px;
cursor: pointer;
}
.demo-btn:hover {
background: var(--vp-c-bg-alt);
}
.demo-log {
background: var(--vp-c-bg-alt);
border-radius: 4px;
overflow: hidden;
}
.log-title {
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
background: var(--vp-c-divider);
}
.log-content {
padding: 0.5rem;
max-height: 100px;
overflow-y: auto;
}
.log-item {
font-size: 0.75rem;
font-family: monospace;
margin-bottom: 0.15rem;
}
.comparison-table {
background: var(--vp-c-bg-alt);
padding: 0.75rem;
border-radius: 6px;
}
.table-title {
font-weight: bold;
font-size: 0.85rem;
margin-bottom: 0.5rem;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 0.8rem;
}
th,
td {
border: 1px solid var(--vp-c-divider);
padding: 0.4rem;
text-align: center;
}
th {
background: var(--vp-c-bg);
}
.feature-col {
text-align: left;
font-weight: bold;
}
.highlight {
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand);
font-weight: bold;
}
.info-box {
background: var(--vp-c-bg-alt);
padding: 0.75rem;
border-radius: 6px;
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-top: 0.75rem;
display: flex;
gap: 0.25rem;
}
</style>