Socket详解
1、基础概念
(1)概念
- Socket位于应用层与传输层之间,作为应用程序与TCP/IP协议栈的桥梁。
- 通过 IP地址 + 协议类型(TCP/UDP)+ 端口号 三元组唯一标识网络中的进程。
- 在C#中通过
System.Net.Sockets 命名空间下的 Socket 类实现。 - 计算机网络相关知识请参考:https://blog.csdn.net/liyou123456789/article/details/122731144
(2)通信协议支持
| 特性 | TCP Socket | UDP Socket |
|---|
| 连接方式 | 面向连接(可靠传输) | 无连接(不可靠) |
| 数据保证 | 有序、不丢失 | 可能乱序、丢失 |
| 适用场景 | 文件传输、网页访问(HTTP) | 视频流、实时游戏 |
| 复杂度 | 高(需维护连接状态) | 低(轻量级) |
(3)应用场景
- 即时通讯工具(如微信、QQ):基于TCP保证消息可靠送达,通过长连接实现实时双向通信。
- Web服务器(HTTP服务):HTTP协议底层依赖TCP Socket,服务器监听80端口处理请求。
- 物联网设备控制:设备作为Socket客户端定时上报数据,服务器远程发送指令。
- 实时数据推送:服务端主动向客户端推送消息(如股票行情),需WebSocket等基于Socket的扩展。
2、通信流程
(1)流程图

(2)服务端
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint localEP = new IPEndPoint(IPAddress.Any, 9000);
serverSocket.Bind(localEP);
serverSocket.Listen(10);
serverSocket.BeginAccept(new AsyncCallback(OnClientConnected), null);
private void OnClientConnected(IAsyncResult ar) {Socket clientSocket = serverSocket.EndAccept(ar);Thread clientThread = new Thread(HandleClient);clientThread.Start(clientSocket);
}
(3)客户端
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
clientSocket.Connect("127.0.0.1", 9000);
byte[] buffer = new byte[1024];
clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(OnDataReceived), buffer);
(4)关键技术
- 异步通信模型
- 原理:使用
BeginAccept/BeginReceive 避免阻塞主线程,通过回调函数处理事件。 - 优势:支持高并发,适用于服务端处理多客户端请求。
- 数据边界处理
- TCP粘包问题:需自定义协议(如消息头声明长度)。
- UDP数据报:天然有边界,但需处理丢包和乱序。
- 跨线程UI更新 :WinForm中必须通过
Control.Invoke 避免线程冲突:
this.Invoke((MethodInvoker)delegate {txtChatBox.Text += "收到消息: " + message;
});
(5)注意事项
socket.Shutdown(SocketShutdown.Both);
socket.Close();
- 端口占用问题:服务端关闭后需等待2MSL(约1-4分钟)才能复用端口,可通过设置选项解决:
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
- 数据编码与解析:网络字节序需统一(大端序),文本数据建议用JSON/Protobuf格式化。
- 调试工具推荐
- Wireshark:抓包分析协议细节。
- 日志记录:关键操作(连接/断开/异常)写入日志文件。
- 性能优化
- 使用缓冲区池(Buffer Pool)减少内存分配开销。
- 异步IO配合
SocketAsyncEventArgs 替代APM模型(更高性能)。
3、聊天室功能
(1)项目概述

(2)服务端(控制台)
using System.Net;
using System.Net.Sockets;
using System.Text;namespace Server
{internal class Program{static List<Socket> clientSockets = new List<Socket>();static readonly object lockObj = new object();static void Main(){const int PORT = 3000;Socket serverSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);IPEndPoint localEP = new IPEndPoint(IPAddress.Any, PORT);serverSocket.Bind(localEP);serverSocket.Listen(10);Console.WriteLine($"服务器启动,监听端口 {PORT}...");while (true){Socket clientSocket = serverSocket.Accept();lock (lockObj) clientSockets.Add(clientSocket);Console.WriteLine($"客户端接入: {clientSocket.RemoteEndPoint}");Thread clientThread = new Thread(() => HandleClient(clientSocket));clientThread.Start();}}static void HandleClient(Socket clientSocket){byte[] buffer = new byte[1024];try{while (true){int bytesRead = clientSocket.Receive(buffer);if (bytesRead == 0) break; string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);Console.WriteLine($"收到消息: {message}");BroadcastMessage(message, clientSocket);}}catch (Exception ex){Console.WriteLine($"错误: {ex.Message}");}finally{lock (lockObj) clientSockets.Remove(clientSocket);clientSocket.Close();Console.WriteLine($"客户端断开: {clientSocket.RemoteEndPoint}");}}static void BroadcastMessage(string message, Socket senderSocket){byte[] data = Encoding.UTF8.GetBytes(message);lock (lockObj){foreach (Socket client in clientSockets){if (client != senderSocket && client.Connected){try{client.Send(data);}catch { }}}}}}
}
(3)客户端(控制台)
using System.Net.Sockets;
using System.Text;namespace Client
{internal class Program{static Socket clientSocket;static void Main(){const string SERVER_IP = "127.0.0.1";const int PORT = 3000;clientSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);try{clientSocket.Connect(SERVER_IP, PORT);Console.WriteLine("已连接到服务器,输入消息开始聊天 (输入exit退出)");Thread receiveThread = new Thread(ReceiveMessages);receiveThread.IsBackground = true;receiveThread.Start();while (true){string input = Console.ReadLine();if (input.ToLower() == "exit") break;byte[] data = Encoding.UTF8.GetBytes(input);clientSocket.Send(data);}}catch (Exception ex){Console.WriteLine($"连接错误: {ex.Message}");}finally{clientSocket?.Close();}}static void ReceiveMessages(){byte[] buffer = new byte[1024];try{while (true){int bytesRead = clientSocket.Receive(buffer);if (bytesRead == 0) break;string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);Console.WriteLine($"收到: {message}");}}catch{Console.WriteLine("与服务器断开连接");}}}
}
(4)核心功能说明
- 服务端机制
- 使用
Accept()阻塞监听客户端连接 - 为每个客户端创建独立线程处理通信
- 通过客户端列表
clientSockets实现消息广播 - 线程锁
lockObj保证多线程安全
- 客户端机制
- 主线程处理用户输入和发送
- 后台线程持续接收服务器消息
- 使用
Encoding.UTF8解决中文乱码问题
- 广播策略
- 遍历所有客户端Socket发送消息
- 跳过消息发送者自身(senderSocket)
- 异常处理避免断开客户端导致崩溃
(5)使用步骤

- 启动多个客户端程序(连接127.0.0.1:3000)



4、大文件传输
(1)项目概述
- 本Socket文件传输程序实现了高效、可靠的文件传输功能,包含三个核心特性:基于SHA256的分片校验机制、断点续传功能以及传输速度优化。

(2)接收端(控制台)
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;namespace FileReceiver
{internal class Program{private const int BufferSize = 64 * 1024; private const int Port = 4000;private static readonly string SavePath = @"D:\ReceivedFiles\";static void Main(){Directory.CreateDirectory(SavePath);// 创建监听SocketTcpListener listener = new TcpListener(IPAddress.Any, Port);listener.Start();Console.WriteLine($"文件接收服务已启动,监听端口: {Port}");while (true){using (TcpClient client = listener.AcceptTcpClient())using (NetworkStream stream = client.GetStream()){Console.WriteLine($"客户端已连接: {client.Client.RemoteEndPoint}");client.NoDelay = true; client.ReceiveBufferSize = 64 * 1024; byte[] headerBuffer = new byte[512];int headerSize = stream.Read(headerBuffer, 0, headerBuffer.Length);string header = Encoding.UTF8.GetString(headerBuffer, 0, headerSize);string[] headerParts = header.Split('|');if (headerParts.Length < 3){Console.WriteLine("无效文件头格式");continue;}string fileName = headerParts[1];long fileSize = long.Parse(headerParts[2]);string filePath = Path.Combine(SavePath, fileName);bool isResume = false;long resumePosition = 0;if (headerParts[0] == "RESUME"){isResume = true;resumePosition = long.Parse(headerParts[3]);Console.WriteLine($"收到断点续传请求: {fileName},从 {resumePosition} 字节处继续接收");if (File.Exists(filePath)){FileInfo fileInfo = new FileInfo(filePath);if (fileInfo.Length == resumePosition){byte[] confirmBytes = Encoding.UTF8.GetBytes("OK");stream.Write(confirmBytes, 0, confirmBytes.Length);Console.WriteLine($"开始接收: {fileName} ({fileSize}字节),继续从 {resumePosition} 字节处接收");}else{byte[] rejectBytes = Encoding.UTF8.GetBytes("REJECT");stream.Write(rejectBytes, 0, rejectBytes.Length);Console.WriteLine("文件大小不匹配,将重新开始传输");isResume = false;}}else{byte[] rejectBytes = Encoding.UTF8.GetBytes("REJECT");stream.Write(rejectBytes, 0, rejectBytes.Length);Console.WriteLine("文件不存在,将重新开始传输");isResume = false;}}else if (headerParts[0] == "FILE"){Console.WriteLine($"开始接收: {fileName} ({fileSize}字节)");}else{Console.WriteLine("无效的文件头类型");continue;}FileMode fileMode = isResume ? FileMode.Append : FileMode.Create;using (FileStream fs = new FileStream(filePath, fileMode))using (SHA256 sha256 = SHA256.Create()){byte[] buffer = new byte[BufferSize];long totalReceived = isResume ? resumePosition : 0;if (isResume){fs.Seek(0, SeekOrigin.End);}while (totalReceived < fileSize){byte[] checksumLengthBytes = new byte[sizeof(int)];int checksumLengthRead = stream.Read(checksumLengthBytes, 0, sizeof(int));if (checksumLengthRead != sizeof(int)){Console.WriteLine("接收校验和长度失败");break;}int checksumLength = BitConverter.ToInt32(checksumLengthBytes, 0);byte[] receivedChecksum = new byte[checksumLength];int checksumRead = stream.Read(receivedChecksum, 0, checksumLength);if (checksumRead != checksumLength){Console.WriteLine("接收校验和失败");break;}byte[] dataLengthBytes = new byte[sizeof(int)];int dataLengthRead = stream.Read(dataLengthBytes, 0, sizeof(int));if (dataLengthRead != sizeof(int)){Console.WriteLine("接收数据长度失败");break;}int dataLength = BitConverter.ToInt32(dataLengthBytes, 0);int bytesRead = stream.Read(buffer, 0, dataLength);if (bytesRead != dataLength){Console.WriteLine("接收数据失败");break;}byte[] computedChecksum = sha256.ComputeHash(buffer, 0, bytesRead);bool checksumMatch = receivedChecksum.SequenceEqual(computedChecksum);if (!checksumMatch){Console.WriteLine("校验和不匹配,传输错误");break;}fs.Write(buffer, 0, bytesRead);totalReceived += bytesRead;Console.Write($"\r进度: {totalReceived * 100 / fileSize}%");}fs.Flush();}Console.WriteLine($"\n文件接收完成: {filePath}");}}}}
}
(3)发送端(控制台)
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;namespace FileSender
{internal class Program{private const int BufferSize = 64 * 1024; private const int Port = 4000;private const string ServerIP = "127.0.0.1";static void Main(){string filePath = "test_file.txt";if (!File.Exists(filePath)){Console.WriteLine("文件不存在");return;}Console.WriteLine($"使用测试文件: {filePath}");FileInfo fileInfo = new FileInfo(filePath);string fileName = fileInfo.Name;long fileSize = fileInfo.Length;int totalChunks = (int)Math.Ceiling((double)fileSize / BufferSize);try{using (TcpClient client = new TcpClient(ServerIP, Port))using (NetworkStream stream = client.GetStream()){client.NoDelay = true; client.SendBufferSize = 64 * 1024; string tempFilePath = Path.Combine(Path.GetTempPath(), $"{fileName}.temp");long startPosition = 0;if (File.Exists(tempFilePath)){using (BinaryReader reader = new BinaryReader(File.OpenRead(tempFilePath))){startPosition = reader.ReadInt64();}if (startPosition < fileSize){Console.WriteLine($"检测到未完成的传输,将从 {startPosition} 字节处继续传输");string resumeHeader = $"RESUME|{fileName}|{fileSize}|{startPosition}";byte[] resumeHeaderBytes = Encoding.UTF8.GetBytes(resumeHeader);stream.Write(resumeHeaderBytes, 0, resumeHeaderBytes.Length);byte[] confirmBuffer = new byte[10];stream.Read(confirmBuffer, 0, confirmBuffer.Length);string confirm = Encoding.UTF8.GetString(confirmBuffer).Trim();if (confirm != "OK"){Console.WriteLine("服务器不支持断点续传,将重新开始传输");startPosition = 0;}}else{Console.WriteLine("文件已传输完成,无需再次传输");return;}}if (startPosition == 0){string header = $"FILE|{fileName}|{fileSize}";byte[] headerBytes = Encoding.UTF8.GetBytes(header);stream.Write(headerBytes, 0, headerBytes.Length);}Console.WriteLine($"开始发送: {fileName} ({fileSize}字节)");using (FileStream fs = File.OpenRead(filePath))using (SHA256 sha256 = SHA256.Create()){if (startPosition > 0){fs.Seek(startPosition, SeekOrigin.Begin);}byte[] buffer = new byte[BufferSize];int bytesRead;long totalSent = startPosition;using (BinaryWriter progressWriter = new BinaryWriter(File.Open(tempFilePath, FileMode.Create))){while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0){byte[] checksum = sha256.ComputeHash(buffer, 0, bytesRead);stream.Write(BitConverter.GetBytes(checksum.Length), 0, sizeof(int));stream.Write(checksum, 0, checksum.Length);stream.Write(BitConverter.GetBytes(bytesRead), 0, sizeof(int));stream.Write(buffer, 0, bytesRead);totalSent += bytesRead;progressWriter.Seek(0, SeekOrigin.Begin);progressWriter.Write(totalSent);progressWriter.Flush();Console.Write($"\r进度: {totalSent * 100 / fileSize}%");}stream.Flush();}if (totalSent == fileSize && File.Exists(tempFilePath)){File.Delete(tempFilePath);}}Console.WriteLine("\n文件发送完成");}}catch (Exception ex){Console.WriteLine($"传输错误: {ex.Message}");}}}
}
(4)核心功能说明
- 分片校验机制
- 功能说明:使用SHA256加密算法对每个数据分片进行校验,确保文件传输的完整性和可靠性。
- 发送端(FileSender)实现:
- 在发送每个数据分片前,使用SHA256算法计算该分片的校验和
- 按照固定格式发送数据:先发送校验和长度,然后是校验和本身,接着是数据长度,最后是实际数据内容
- 使用64KB作为分片大小,既保证了传输效率,也确保了校验准确性
- 接收端(FileReceiver)实现:
- 按照发送端定义的格式接收数据:先接收校验和长度,然后接收校验和,接着接收数据长度,最后接收实际数据
- 使用相同的SHA256算法计算接收到的数据分片的校验和
- 将计算得到的校验和与接收到的校验和进行比对,确保数据完整性
- 如果校验失败,可触发重传机制(当前版本未实现自动重传)
- 代码实现关键点:
byte[] checksum = sha256.ComputeHash(buffer, 0, bytesRead);
stream.Write(BitConverter.GetBytes(checksum.Length), 0, sizeof(int));
stream.Write(checksum, 0, checksum.Length);
stream.Write(BitConverter.GetBytes(bytesRead), 0, sizeof(int));
stream.Write(buffer, 0, bytesRead);
- 断点续传功能
- 功能说明:在文件传输过程中如果发生中断(如网络故障、程序崩溃等),支持从中断位置继续传输,而无需重新开始。
- 发送端(FileSender)实现:
- 使用临时文件记录每次传输的进度位置
- 程序启动时检查是否存在未完成的传输任务
- 如果存在未完成传输,发送特殊的RESUME头信息给接收端,包含文件名、总大小和期望的起始位置
- 等待接收端确认是否可以继续传输
- 如果接收端同意继续,从指定位置开始读取和发送文件内容
- 传输完成后自动删除临时进度文件
- 接收端(FileReceiver)实现:
- 解析接收到的文件头信息,区分普通传输请求(FILE)和断点续传请求(RESUME)
- 对于断点续传请求,检查目标文件是否存在且大小与请求的起始位置匹配
- 根据检查结果,向发送端发送确认(OK)或拒绝(REJECT)响应
- 如果确认继续传输,使用FileMode.Append模式打开文件,并从文件末尾开始写入数据
- 代码实现关键点:
string tempFilePath = Path.Combine(Path.GetTempPath(), $"{fileName}.temp");
if (File.Exists(tempFilePath))
{using (BinaryReader reader = new BinaryReader(File.OpenRead(tempFilePath))){startPosition = reader.ReadInt64();}string resumeHeader = $"RESUME|{fileName}|{fileSize}|{startPosition}";
}
if (headerParts[0] == "RESUME")
{resumePosition = long.Parse(headerParts[3]);FileMode fileMode = isResume ? FileMode.Append : FileMode.Create;
}
- 传输速度优化
- 功能说明:通过一系列配置优化,显著提高文件传输速度。
- 增大缓冲区大小:
- 将原有的4KB缓冲区增大到64KB,减少网络交互次数
- 在发送端和接收端统一使用相同的缓冲区大小,确保最佳匹配
- 禁用Nagle算法:
- Nagle算法会延迟小数据包的发送以提高吞吐量,但会增加延迟
- 在文件传输场景中,禁用Nagle算法可以减少延迟,提高实时性
- 优化Socket缓冲区设置:
- 显式设置发送和接收缓冲区大小为64KB,确保与分片大小匹配
- 减少操作系统层面的缓冲区管理开销
- 代码实现关键点:
client.NoDelay = true;
client.SendBufferSize = 64 * 1024;
(5)技术架构与流程
- 整体架构
- 发送端(FileSender):负责读取本地文件,计算校验和,发送文件数据,并支持断点续传
- 接收端(FileReceiver):负责监听连接,接收文件数据,验证校验和,保存文件,并支持断点续传
- 通信协议:基于TCP协议实现可靠传输,自定义简单协议头格式
- 传输流程
- 连接建立阶段:
- 接收端启动并监听指定端口
- 发送端连接到接收端
- 双方配置Socket参数(禁用Nagle算法,设置缓冲区大小等)
- 文件头传输阶段:
- 发送端确定使用普通传输(FILE头)还是断点续传(RESUME头)
- 发送端发送文件头信息
- 接收端解析文件头并做相应处理
- 对于断点续传请求,接收端发送确认/拒绝响应
- 文件内容传输阶段:
- 发送端以64KB为单位分片读取文件
- 对每个分片计算SHA256校验和
- 按顺序发送校验和长度、校验和、数据长度和数据内容
- 接收端接收并验证每个分片
- 发送端实时保存传输进度
- 传输完成阶段:
- 发送端完成所有数据发送
- 发送端删除临时进度文件
- 双方关闭连接
(6)使用说明
- 配置与依赖
- 开发环境:.NET Framework/.NET Core
- 依赖库:System.Net.Sockets、System.Security.Cryptography、System.IO
- 端口配置:默认使用4000端口(可在代码中修改Port常量)
- 保存路径:接收端默认保存到D:\ReceivedFiles\(可在代码中修改SavePath常量)
- 运行方法
dotnet run --project FileReceiver\FileReceiver.csproj
- 然后启动发送端程序:* 发送端会自动使用项目目录下的test_file.txt作为测试文件进行传输
dotnet run --project FileSender\FileSender.csproj
(7)扩展建议
- 实现自动重传机制,在校验失败时自动请求重传
- 添加用户认证机制,增强安全性
- 支持多文件并发传输
- 添加图形用户界面,提高易用性
- 实现传输限速功能,避免占用过多网络带宽