CSDN论坛 > Windows专区 > Windows Server

一个关于FTP服务器软件使用过程中碰到的难题,希望各位大虾拉一把! [问题点数:50分,结帖人jiangyuer]

Bbs2
本版专家分:128
结帖率 100%
CSDN今日推荐
Bbs8
本版专家分:40508
Bbs5
本版专家分:4788
Bbs5
本版专家分:3153
Blank
蓝花 2002年1月 Windows专区大版内专家分月排行榜第三
Bbs1
本版专家分:51
Bbs4
本版专家分:1772
Bbs10
本版专家分:156287
Blank
进士 2004年 总版技术专家分年内排行榜第十
Blank
微软MVP 2006年1月 荣获微软MVP称号
2007年1月 荣获微软MVP称号
2004年9月 荣获微软MVP称号
Blank
红花 2006年4月 Windows专区大版内专家分月排行榜第一
2005年2月 Windows专区大版内专家分月排行榜第一
2003年9月 Windows专区大版内专家分月排行榜第一
Blank
黄花 2006年6月 Windows专区大版内专家分月排行榜第二
2005年11月 Windows专区大版内专家分月排行榜第二
2005年4月 Windows专区大版内专家分月排行榜第二
2005年1月 Windows专区大版内专家分月排行榜第二
2004年12月 Windows专区大版内专家分月排行榜第二
2004年11月 Windows专区大版内专家分月排行榜第二
2004年3月 Windows专区大版内专家分月排行榜第二
2003年11月 Windows专区大版内专家分月排行榜第二
2003年10月 Windows专区大版内专家分月排行榜第二
匿名用户不能发表回复!
其他相关推荐
Serv-U FTP Server 14.0.0.6 中文白金破解版 最好的FTP服务器软件
Serv-U FTP Server 14.0.0.6 中文白金破解版 最好的FTP服务器软件
Windows一键搭建FTP服务器工具
下载后解压,打开,配置之后即可使用 支持权限控制
FTP服务器 C#
用VS编写的FTP服务器软件,C#网络程序编程学习用。 代码: using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Net; using System.Net.Sockets; using System.Threading; using System.Windows.Forms; namespace FtpServer { public partial class FtpServerForm : Form { TcpListener myTcpListener = null; private Thread listenThread; // 保存用户名和密码 Dictionary<string, string> users; public FtpServerForm() { InitializeComponent(); // 初始化用户名和密码 users = new Dictionary<string, string>(); users.Add("admin", "admin"); // 设置默认的主目录 tbxFtpRoot.Text = "F:/MyFtpServerRoot/"; IPAddress[] ips = Dns.GetHostAddresses(""); tbxFtpServerIp.Text = ips[5].ToString(); tbxFtpServerPort.Text = "21"; lstboxStatus.Enabled = false; } // 启动服务器 private void btnFtpServerStartStop_Click(object sender, EventArgs e) { if (myTcpListener == null) { listenThread = new Thread(ListenClientConnect); listenThread.IsBackground = true; listenThread.Start(); lstboxStatus.Enabled = true; lstboxStatus.Items.Clear(); lstboxStatus.Items.Add("已经启动Ftp服务..."); btnFtpServerStartStop.Text = "停止"; } else { myTcpListener.Stop(); myTcpListener = null; listenThread.Abort(); lstboxStatus.Items.Add("Ftp服务已停止!"); lstboxStatus.TopIndex = lstboxStatus.Items.Count - 1; btnFtpServerStartStop.Text = "启动"; } } // 监听端口,处理客户端连接 private void ListenClientConnect() { myTcpListener = new TcpListener(IPAddress.Parse(tbxFtpServerIp.Text), int.Parse(tbxFtpServerPort.Text)); // 开始监听传入的请求 myTcpListener.Start(); AddInfo("启动FTP服务成功!"); AddInfo("Ftp服务器运行中...[点击”停止“按钮停止FTP服务]"); while (true) { try { // 接收连接请求 TcpClient tcpClient = myTcpListener.AcceptTcpClient(); AddInfo(string.Format("客户端({0})与本机({1})建立Ftp连接", tcpClient.Client.RemoteEndPoint, myTcpListener.LocalEndpoint)); User user = new User(); user.commandSession = new UserSeesion(tcpClient); user.workDir = tbxFtpRoot.Text; Thread t = new Thread(UserProcessing); t.IsBackground = true; t.Start(user); } catch { break; } } } // 处理客户端用户请求 private void UserProcessing(object obj) { User user = (User)obj; string sendString = "220 FTP Server v1.0"; RepleyCommandToUser(user, sendString); while (true) { string receiveString = null; try { // 读取客户端发来的请求信息 receiveString = user.commandSession.streamReader.ReadLine(); } catch(Exception ex) { if (user.commandSession.tcpClient.Connected == false) { AddInfo(string.Format("客户端({0}断开连接!)", user.commandSession.tcpClient.Client.RemoteEndPoint)); } else { AddInfo("接收命令失败!" + ex.Message); } break; } if (receiveString == null) { AddInfo("接收字符串为null,结束线程!"); break; } AddInfo(string.Format("来自{0}:[{1}]", user.commandSession.tcpClient.Client.RemoteEndPoint, receiveString)); // 分解客户端发来的控制信息中的命令和参数 string command = receiveString; string param = string.Empty; int index = receiveString.IndexOf(' '); if (index != -1) { command = receiveString.Substring(0, index).ToUpper(); param = receiveString.Substring(command.Length).Trim(); } // 处理不需登录即可响应的命令(这里只处理QUIT) if (command == "QUIT") { // 关闭TCP连接并释放与其关联的所有资源 user.commandSession.Close(); return; } else { switch (user.loginOK) { // 等待用户输入用户名: case 0: CommandUser(user, command, param); break; // 等待用户输入密码 case 1: CommandPassword(user, command, param); break; // 用户名和密码验证正确后登陆 case 2: switch (command) { case "CWD": CommandCWD(user, param); break; case "PWD": CommandPWD(user); break; case "PASV": CommandPASV(user); break; case "PORT": CommandPORT(user, param); break; case "LIST": CommandLIST(user, param); break; case "NLIST": CommandLIST(user, param); break; // 处理下载文件命令 case "RETR": CommandRETR(user, param); break; // 处理上传文件命令 case "STOR": CommandSTOR(user, param); break; // 处理删除命令 case "DELE": CommandDELE(user, param); break; // 使用Type命令在ASCII和二进制模式进行变换 case "TYPE": CommandTYPE(user, param); break; default: sendString = "502 command is not implemented."; RepleyCommandToUser(user, sendString); break; } break; } } } } // 想客户端返回响应码 private void RepleyCommandToUser(User user, string str) { try { user.commandSession.streamWriter.WriteLine(str); AddInfo(string.Format("向客户端({0})发送[{1}]", user.commandSession.tcpClient.Client.RemoteEndPoint, str)); } catch { AddInfo(string.Format("向客户端({0})发送信息失败", user.commandSession.tcpClient.Client.RemoteEndPoint)); } } // 向屏幕输出显示状态信息(这里使用了委托机制) private delegate void AddInfoDelegate(string str); private void AddInfo(string str) { // 如果调用AddInfo()方法的线程与创建ListView控件的线程不在一个线程时 // 此时利用委托在创建ListView的线程上调用 if (lstboxStatus.InvokeRequired == true) { AddInfoDelegate d = new AddInfoDelegate(AddInfo); this.Invoke(d, str); } else { lstboxStatus.Items.Add(str); lstboxStatus.TopIndex = lstboxStatus.Items.Count - 1; lstboxStatus.ClearSelected(); } } #region 处理各个命令 #region 登录过程,即用户身份验证过程 // 处理USER命令,接收用户名但不进行验证 private void CommandUser(User user, string command, string param) { string sendString = string.Empty; if (command == "USER") { sendString = "331 USER command OK, password required."; user.userName = param; // 设置loginOk=1为了确保后面紧接的要求输入密码 // 1表示已接收到用户名,等到接收密码 user.loginOK = 1; } else { sendString = "501 USER command syntax error."; } RepleyCommandToUser(user, sendString); } // 处理PASS命令,验证用户名和密码 private void CommandPassword(User user, string command, string param) { string sendString = string.Empty; if (command == "PASS") { string password = null; if (users.TryGetValue(user.userName, out password)) { if (password == param) { sendString = "230 User logged in success"; // 2表示登录成功 user.loginOK = 2; } else { sendString = "530 Password incorrect."; } } else { sendString = "530 User name or password incorrect."; } } else { sendString = "501 PASS command Syntax error."; } RepleyCommandToUser(user, sendString); // 用户当前工作目录 user.currentDir = user.workDir; } #endregion #region 文件管理命令 // 处理CWD命令,改变工作目录 private void CommandCWD(User user, string temp) { string sendString = string.Empty; try { string dir = user.workDir.TrimEnd('/') + temp; // 是否为当前目录的子目录,且不包含父目录名称 if (Directory.Exists(dir)) { user.currentDir = dir; sendString = "250 Directory changed to '" + dir + "' successfully"; } else { sendString = "550 Directory '" + dir + "' does not exist"; } } catch { sendString = "502 Directory changed unsuccessfully"; } RepleyCommandToUser(user,sendString); } // 处理PWD命令,显示工作目录 private void CommandPWD(User user) { string sendString = string.Empty; sendString = "257 '" + user.currentDir + "' is the current directory"; RepleyCommandToUser(user, sendString); } // 处理LIST/NLIST命令,想客户端发送当前或指定目录下的所有文件名和子目录名 private void CommandLIST(User user, string parameter) { string sendString = string.Empty; DateTimeFormatInfo dateTimeFormat = new CultureInfo("en-US", true).DateTimeFormat; // 得到目录列表 string[] dir = Directory.GetDirectories(user.currentDir); if (string.IsNullOrEmpty(parameter) == false) { if (Directory.Exists(user.currentDir + parameter)) { dir = Directory.GetDirectories(user.currentDir + parameter); } else { string s = user.currentDir.TrimEnd('/'); user.currentDir = s.Substring(0, s.LastIndexOf("/") + 1); } } for (int i = 0; i < dir.Length; i++) { string folderName = Path.GetFileName(dir[i]); DirectoryInfo d = new DirectoryInfo(dir[i]); // 按下面的格式输出目录列表 sendString += @"dwr-\t" + Dns.GetHostName() + "\t" + dateTimeFormat.GetAbbreviatedMonthName(d.CreationTime.Month) + d.CreationTime.ToString(" dd yyyy") + "\t" + folderName + Environment.NewLine; } // 得到文件列表 string[] files = Directory.GetFiles(user.currentDir); if (string.IsNullOrEmpty(parameter) == false) { if (Directory.Exists(user.currentDir + parameter + "/")) { files = Directory.GetFiles(user.currentDir + parameter + "/"); } } for (int i = 0; i < files.Length; i++) { FileInfo f = new FileInfo(files[i]); string fileName = Path.GetFileName(files[i]); // 按下面的格式输出文件列表 sendString += "-wr-\t" + Dns.GetHostName() + "\t" + f.Length + " " + dateTimeFormat.GetAbbreviatedMonthName(f.CreationTime.Month) + f.CreationTime.ToString(" dd yyyy") + "\t" + fileName + Environment.NewLine; } // List命令指示获得FTP服务器上的文件列表字符串信息 // 所以调用List命令过程,客户端接受的指示一些字符串 // 所以isBinary是false,代表传输的是ASCII数据 // 但是为了防止isBinary因为 设置user.isBinary = false而改变 // 所以事先保存user.IsBinary的引用(此时为true),方便后面下载文件 bool isBinary = user.isBinary; user.isBinary = false; RepleyCommandToUser(user, "150 Opening ASCII data connection"); InitDataSession(user); SendByUserSession(user, sendString); RepleyCommandToUser(user, "226 Transfer complete"); user.isBinary = isBinary; } // 处理RETR命令,提供下载功能,将用户请求的文件发送给用户 private void CommandRETR(User user, string filename) { string sendString = ""; // 下载的文件全名 string path = user.currentDir + filename; FileStream filestream = new FileStream(path, FileMode.Open, FileAccess.Read); // 发送150到用户,表示服务器文件状态良好,将要打开数据连接传输文件 if (user.isBinary) { sendString = "150 Opening BINARY mode data connection for download"; } else { sendString = "150 Opening ASCII mode data connection for download"; } RepleyCommandToUser(user, sendString); InitDataSession(user); SendFileByUserSession(user, filestream); RepleyCommandToUser(user, "226 Transfer complete"); } // 处理STOR命令,提供上传功能,接收客户端上传的文件 private void CommandSTOR(User user, string filename) { string sendString = ""; // 上传的文件全名 string path = user.currentDir + filename; FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write); // 发送150到用户,表示服务器状态良好 if (user.isBinary) { sendString = "150 Opening BINARY mode data connection for upload"; } else { sendString = "150 Opeing ASCII mode data connection for upload"; } RepleyCommandToUser(user, sendString); InitDataSession(user); ReadFileByUserSession(user, fs); RepleyCommandToUser(user,"226 Transfer complete"); } // 处理DELE命令,提供删除功能,删除服务器上的文件 private void CommandDELE(User user, string filename) { string sendString = ""; // 删除的文件全名 string path = user.currentDir + filename; AddInfo("正在删除文件" + filename + "..."); File.Delete(path); AddInfo("删除成功"); sendString = "250 File " + filename + " has been deleted."; RepleyCommandToUser(user, sendString); } #endregion #region 模式设置命令 // 处理PASV命令, 使用被动模式进行传输 private void CommandPASV(User user) { string sendString = string.Empty; IPAddress localip = Dns.GetHostEntry("").AddressList[5]; // 被动模式,即服务器接收客户端的连接请求 // 被动模式下FTP服务器使用随机生成的端口进行传输数据 // 而主动模式下FTP服务器使用端口20进行数据传输 Random random = new Random(); int random1, random2; int port; while (true) { // 随机生成一个端口进行数据传输 random1 = random.Next(5, 200); random2 = random.Next(0, 200); // 生成的端口号控制>1024的随机端口 // 下面这个运算算法只是为了得到一个大于1024的端口值 port = random1 << 8 | random2; try { user.dataListener = new TcpListener(localip, port); AddInfo("TCP 数据连接已打开(被动模式)--" + localip.ToString() + ":" + port); } catch { continue; } user.isPassive = true; string temp = localip.ToString().Replace('.', ','); // 必须把端口号IP地址告诉客户端,客户端接收到响应命令后, // 再通过新的端口连接服务器的端口P,然后进行文件数据传输 sendString = "227 Entering Passive Mode(" + temp + "," + random1 + "," + random2 + ")"; RepleyCommandToUser(user, sendString); user.dataListener.Start(); break; } } // 处理PORT命令,使用主动模式进行传输 private void CommandPORT(User user, string portstring) { // 主动模式时,客户端必须告知服务器接收数据的端口号,PORT 命令格式为:PORT address // address参数的格式为i1、i2、i3、i4、p1、p2,其中i1、i2、i3、i4表示IP地址 // 下面通过.字符串来组合这四个参数得到IP地址 // p1、p2表示端口号,下面通过int.Parse(temp[4]) << 8) | int.Parse(temp[5] // 这个算法来获得一个大于1024的端口来发送给服务器 string sendString = string.Empty; string[] temp = portstring.Split(','); string ipString = "" + temp[0] + "." + temp[1] + "." + temp[2] + "." + temp[3]; // 客户端发出PORT命令把客户端的IP地址和随机的端口告诉服务器 int portNum = (int.Parse(temp[4]) << 8) | int.Parse(temp[5]); user.remoteEndPoint = new IPEndPoint(IPAddress.Parse(ipString), portNum); sendString = "200 PORT command successful."; // 服务器以接受到的客户端IP地址和端口为目标发起主动连接请求 // 服务器根据客户端发送过来的IP地址和端口主动发起与客户端建立连接 RepleyCommandToUser(user, sendString); } // 处理TYPE命令,设置数据传输方式 private void CommandTYPE(User user, string param) { string sendstring = ""; if (param == "I") { // 二进制 user.isBinary = true; sendstring = "220 Type set to I(Binary)"; } else { // ASCII方式 user.isBinary = false; sendstring = "330 Type set to A(ASCII)"; } RepleyCommandToUser(user, sendstring); } #endregion #endregion // 初始化数据连接 private void InitDataSession(User user) { TcpClient client = null; if (user.isPassive) { AddInfo("采用被动模式返回LIST目录和文件列表"); client = user.dataListener.AcceptTcpClient(); } else { AddInfo("采用主动模式向用户发送LIST目录和文件列表"); client = new TcpClient(); client.Connect(user.remoteEndPoint); } user.dataSession = new UserSeesion(client); } // 使用数据连接发送字符串 private void SendByUserSession(User user, string sendString) { AddInfo("向用户发送(字符串信息):[" + sendString + "]"); try { user.dataSession.streamWriter.WriteLine(sendString); AddInfo("发送完毕"); } finally { user.dataSession.Close(); } } // 使用数据连接发送文件流(客户端发送下载文件命令) private void SendFileByUserSession(User user, FileStream fs) { AddInfo("向用户发送(文件流):[..."); try { if (user.isBinary) { byte[] bytes = new byte[1024]; BinaryReader binaryReader = new BinaryReader(fs); int count = binaryReader.Read(bytes, 0, bytes.Length); while (count > 0) { user.dataSession.binaryWriter.Write(bytes, 0, count); user.dataSession.binaryWriter.Flush(); count = binaryReader.Read(bytes, 0, bytes.Length); } } else { StreamReader streamReader = new StreamReader(fs); while (streamReader.Peek() > -1) { user.dataSession.streamWriter.WriteLine(streamReader.ReadLine()); } } AddInfo("...]发送完毕!"); } finally { user.dataSession.Close(); fs.Close(); } } // 使用数据连接接收文件流(客户端发送上传文件功能) private void ReadFileByUserSession(User user, FileStream fs) { AddInfo("接收用户上传数据(文件流):[..."); try { if (user.isBinary) { byte[] bytes = new byte[1024]; BinaryWriter binaryWriter = new BinaryWriter(fs); int count = user.dataSession.binaryReader.Read(bytes, 0, bytes.Length); while (count > 0) { binaryWriter.Write(bytes, 0, count); binaryWriter.Flush(); count = user.dataSession.binaryReader.Read(bytes, 0, bytes.Length); } } else { StreamWriter streamWriter = new StreamWriter(fs); while (user.dataSession.streamReader.Peek() > -1) { streamWriter.Write(user.dataSession.streamReader.ReadLine()); streamWriter.Flush(); } } AddInfo("...]接收完毕"); } finally { user.dataSession.Close(); fs.Close(); } } private void label3_Click(object sender, EventArgs e) { } } }
一个关于积分的公式实现
一个关于积分的公式,不知道怎么用实现,求助各位大虾帮忙
最好的FTP服务器软件
最好的FTP服务器软件,服务器工具,局域网 工具
FTPserver(迷你FTP服务器) v1.1 绿色单文件中文免费版
FTPserver这是一款免费的、绿色的(无需安装,只有一个文件)、小巧的(84KB)的FTP服务器软件。可以轻松地将它放在U盘里,邮箱里,网盘里,或者网站上随时下载,这样,就有了一个可以随身携带的FTP服务器软件。 功能说明: 1、提供文件(文件夹)的下载、上传、删除、改名功能。 2、支持多用户访问,可以设置最大连接用户。 3、支持账户/密码访问和权限控制,同样支持匿名访问。 4、配置信息自动保存,下次不用重新输入,用户名清空自动恢复匿名访问。 5、最小化至托盘图标,不占用桌面空间。
免费简易版ftp服务器
软件类型 : 免费软件 软件内容 : 守望FTP服务器.exe (FTP服务器程序) 守望FTP使用说明.txt (本说明文件) ========================================= 说明: 这个FTP服务器是用C语言开发的,其特点是界面精美,易于使用,还有信息流量统计。整个FTP服务器就是一个EXE可执行程序, 无需任何安装,不修改注册表,删除时直接删除所有相关文件就行了。程序放在任何目录均可运行。这是一个小巧灵活的FTP服务器工具, 占用系统资源少,可以方便网友间传输文件交流,建议不要开匿名系统盘根目录FTP。可快速建立小型的FTP服务器 ,可以方便应用局域网内用户互相传送文件,不用再为如何另外安装FTP服务器上浪费脑筋。 主要应用: 1、建立简单FTP服务器 2、用于局域网,互相点对点传送文件 3、建立临时FTP服务器,供大家临时下载文件等 ========================================= 程序设置: 启动软件后,需要作一些设置: 1. 设置FTP服务器的根目录。以后所有的请求都会以此FTP根目录为基础查找文件。 2. 设置FTP服务器的登录帐号,如果不设置即为允许匿名登录FTP服务器。 3. 设置FTP服务器端口,默认为 21 设置完后点击 启动按钮 即可,然后你可以点 测试 按钮,查看下运行状态!
FTP服务器软件 Serv-U
FTP服务器软件 Serv-U FTP服务器软件 Serv-U FTP服务器软件 Serv-U
FTP专业服务器软件.
FTP专业服务器软件.FTP专业服务器软件.FTP专业服务器软件.FTP专业服务器软件.
FTP服务器软件serv-u 最新版, 已汉化、正版激活、免注册
我的服务器IP:127.0.0.1 或 192.168.1.209 匿名帐号: Anonymous 密码为空 FTP服务器软件serv-u 最新版 已汉化、正版激活、免注册 附:建FTP服务器教程 非常好的FTP服务器软件,它设置简单,功能强大,性能稳定。你现在就可以建立你自己的FTP服务器了。 设置简单,但功能却不错。做个人的 FTP 服务器是很好的选择。 它并不是简单地提供文件的下载,还为用户的系统安全提供了相当全面的保护。例如:您可以为您的 FTP 设置密码、设置各种用户级的访问许可等等。 FTP是一种文件传输协议,它支持两种模式,一种方式叫做Standard (也就是 Active,主动方式),一种是 Passive (也就是PASV,被动方式)。 Standard模式 FTP的客户端发送 PORT 命令到FTPserver。Passive模式FTP的客户端发送 PASV命令到 FTP Server。 下面介绍一个这两种方式的工作原理: Standard模式FTP 客户端首先和FTP Server的TCP 21端口建立连接,通过这个通道发送命令,客户端需要接收数据的时候在这个通道上发送PORT命令。 PORT命令包含了客户端用什么端口接收数据。在传送数据的时候,服务器端通过自己的TCP 20端口发送数据。 FTP server必须和客户端建立一个新的连接用来传送数据。 Passive模式在建立控制通道的时候和Standard模式类似,当客户端通过这个通道发送PASV 命令的时候,FTP server打开一个位于1024和5000之间的随机端口并且通知客户端在这个端口上传送数据的请求,然后FTP server 将通过这个端口进行数据的传送,这个时候FTP server不再需要建立一个新的和客户端之间的连接。 现在的FTP软件里面包括在IE5以上的版本里面也已经支持这两种模式了。一般一些FTP客户端的软件就比较好设置了,一般都有一个PASV的选项,比如CuteFTP,传输的方式都有Standard和PASV的选项,可以自己进行选择;另外在IE里面如果要设置成PASV模式的话可以选中工具-Internet选项-高级-为FTP站点启用文件夹视图,否则就采用Standard模式。 很多防火墙在设置的时候都是不允许接受外部发起的连接的,所以FTP的Standard模式在许多时候在内部网络的机器通过防火墙出去的时候受到了限制,因为从服务器的TCP 20无法和内部网络的客户端建立一个新的连接,造成无法工作。当然也可以设置成功,首先要创建一条规则就是允许内部的IP连接外部的IP的21端口;第二条就是禁止外部IP的TCP 20端口连接内部IP的<1024的端口,这条是为了防止外部连接内部的常规端口;第三条验证ACK是否等于1,这个的原理就参见TCP建立连接的三次握手吧。所以如果安全的配置的话非常困难,这个时候就想起来了PASV模式,因为不用建立新的连接,所以也就不会涉及到后面的问题了。但是管理员可能不想使用PASV模式,因为这个时候FTP Server会开放一个随机的高端口,尽管在IIS4和IIS5里面端口的范围是1024-5000,但是许多FTP Server的端口范围达到了1024-65535,这个时候在这个主动开放的随机端口上是有完全的访问权限的,如果IIS也要设置成开放的端口为1024-65535,具体方法如下: 1. regedt32 2. 找到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters 3. 编辑-添加-数值 Value Name: MaxUserPort Data Type: REG_DWORD Value: 65534 <for example&g t; 所以如果遇到了有防火墙的话或者怕配置麻烦的话还是采用PASV模式比较好些,但是如果真的对安全的需求很高的话建议采用Standard模式。 FTP服务器端软件Serv-U教程
关闭
关闭