Windows服务完成Server端的Socket通信

1、打开Visual Studio.Net2003,单击菜单中的“文件”——>“新建”——>“项目”,在弹出的对话框左侧选择“Visual C#项目”,在右侧选择“类库”,下方的名称中输入“SocketLibrary”,位置自己随便选择一个路径。
2、删除自动创建的Class1.cs,新建如下的类名文件“Client.cs”、“Connection.cs”、“ConnectionCollection.cs”、“Message.cs”、“MessageCollection.cs”、“Server.cs”、“SocketBase.cs”和“SocketFactory.cs”。
3、各个类文件的代码如下:

(1)、Client.cs文件中的代码:

using System;
using System.Net;
using System.Net.Sockets;

namespace SocketLibrary
{

    public class Client:SocketBase
    {
        public const int CONNECTTIMEOUT = 10;
        public Client()
        {
            
        }
        public Connection StartClient(IPAddress ipaddress,int port) {
            TcpClient client = new TcpClient();
            client.SendTimeout = CONNECTTIMEOUT;
            client.ReceiveTimeout = CONNECTTIMEOUT;

            client.Connect(ipaddress,port);

            this._connections.Add(new Connection(client.GetStream()));
            this.StartListenAndSend();
            return new Connection(client.GetStream());
        }
        public void StopClient() {
            

            this.EndListenAndSend();     
        }
    }
}

(2)、Connection.cs文件中的代码:

using System;
using System.Net;
using System.Net.Sockets;

namespace SocketLibrary
{
    /// <summary>
    /// Connection 的摘要说明。
    /// </summary>
    public class Connection
    {
        public NetworkStream NetworkStream {
            get{return _networkStream;}
            set{_networkStream = value;}
        }
        private NetworkStream _networkStream;
        public string ConnectionName {
            get{return _connectionName;}
            set{_connectionName = value;}
        }
        private string _connectionName;
        public Connection(NetworkStream networkStream,string connectionName)
        {
            this._networkStream = networkStream;
            this._connectionName = connectionName;
        }
        public Connection(NetworkStream networkStream):this(networkStream,string.Empty) {
        }
    }
}


(3)、ConnectionCollection.cs文件中的代码:

using System;

namespace SocketLibrary {
    public class ConnectionCollection:System.Collections.CollectionBase {
        public ConnectionCollection() {
            
        }
        public void Add(Connection value) {
            List.Add(value); 
        }
        public Connection this[int index] {
            get {
                return List[index] as Connection;     
            }
            set{
                List[index] = value;
            }
        }
        public Connection this[string connectionName] {
            get {
                foreach(Connection connection in List) {
                    if(connection.ConnectionName == connectionName)
                        return connection;
                }
                return null;
            }
        }
    }
}


(4)、Message.cs文件中的代码:

using System;

namespace SocketLibrary
{
    public class Message
    {
        public enum CommandHeader:byte {
            SendMessage = 1     
        }
        public Connection SendToOrReceivedFrom;
        public int MessageLength;
        public CommandHeader Command; 
        public byte MainVersion;
        public byte SecondVersion;

        public string MessageBody;

        public bool Sent;

        public Message()
        {
            SendToOrReceivedFrom = null;
            Sent = false;
        }
        public Message(CommandHeader command,byte mainVersion,byte secondVersion,string messageBody):this() {
            this.Command = command;
            this.MainVersion = mainVersion;
            this.SecondVersion = secondVersion;
            this.MessageBody = messageBody;
        }
        public byte[] ToBytes() {
            this.MessageLength = 7 + SocketFactory.DefaultEncoding.GetByteCount(this.MessageBody);//计算消息总长度。消息头长度为7加上消息体的长度。
            byte[] buffer = new byte[this.MessageLength];
            //先将长度的4个字节写入到数组中。
            BitConverter.GetBytes(this.MessageLength).CopyTo(buffer,0);
            //将CommandHeader写入到数组中
            buffer[4] = (byte)this.Command;
            //将主版本号写入到数组中
            buffer[5] = (byte)this.MainVersion;
            //将次版本号写入到数组中
            buffer[6] = (byte)this.SecondVersion;

            //消息头已写完,现在写消息体。
            byte[] body = new byte[this.MessageLength - 7];
            SocketFactory.DefaultEncoding.GetBytes(this.MessageBody).CopyTo(buffer,7);
            return buffer;     
        }
        public static Message Parse(Connection connection) {
            Message message = new Message();
            //先读出前四个字节,即Message长度
            byte[] buffer = new byte[4];
            if(connection.NetworkStream.DataAvailable) {
                int count = connection.NetworkStream.Read(buffer,0,4);
                if(count == 4) {
                    message.MessageLength = BitConverter.ToInt32(buffer,0);     
                }
                else
                    throw new Exception("网络流长度不正确");
            }
            else
                throw new Exception("目前网络不可读");
            //读出消息的其它字节
            buffer = new byte[message.MessageLength - 4];
            if(connection.NetworkStream.DataAvailable) {
                int count = connection.NetworkStream.Read(buffer,0,buffer.Length);
                if(count == message.MessageLength -4) {
                    message.Command = (CommandHeader)buffer[0];
                    message.MainVersion = buffer[1];
                    message.SecondVersion = buffer[2];

                    //读出消息体
                    message.MessageBody = SocketFactory.DefaultEncoding.GetString(buffer,3,buffer.Length - 3);
                    message.SendToOrReceivedFrom = connection;

                    return message;
                }
                else
                    throw new Exception("网络流长度不正确");
            }
            else
                throw new Exception("目前网络不可读");
        }

    }
}

(5)、MessageCollection.cs文件中的代码:

using System;

namespace SocketLibrary
{
    public class MessageCollection:System.Collections.CollectionBase
    {
        public MessageCollection()
        {
            
        }
        public void Add(Message value) {
            List.Add(value); 
        }
        public Message this[int index] {
            get {
                return List[index] as Message;     
            }
            set{
                List[index] = value;
            }
        }
        public MessageCollection this[Connection connection] {
            get {
                MessageCollection collection = new MessageCollection();
                foreach(Message message in List) {
                    if(message.SendToOrReceivedFrom == connection)
                        collection.Add(message);
                }
                return collection;
            }
        }
    }
}


(6)、Server.cs文件中的代码:

using System;
using System.Net;
using System.Net.Sockets;

namespace SocketLibrary
{
    public class Server:SocketBase
    {
        

        private TcpListener _listener;
        public Server()
        {
            this._connections = new ConnectionCollection();
        }
        protected System.Threading.Thread _listenConnection;

        public void StartServer(int port) {
            _listener = new TcpListener(IPAddress.Any, port);
            _listener.Start();

            _listenConnection = new System.Threading.Thread(new System.Threading.ThreadStart(Start));
            _listenConnection.Start();

            this.StartListenAndSend();
        }
        public void StopServer() {
            _listenConnection.Abort();
            this.EndListenAndSend();
            
        }
        private void Start() {
            try {
                while(true) {
                    if(_listener.Pending()) {
                        TcpClient client = _listener.AcceptTcpClient();
                        NetworkStream stream = client.GetStream();
                        Connection connection = new Connection(stream);
                        this._connections.Add(connection);
                        this.OnConnected(this,new ConnectionEventArgs(connection,new Exception("连接成功")));
                    }
                    System.Threading.Thread.Sleep(200);
                }    
            }
            catch {
                 }
        }
        
    }
}


(7)、SocketBase.cs文件中的代码:

using System;

namespace SocketLibrary
{
    /// <summary>
    /// SocketBase 的摘要说明。
    /// </summary>
    public class SocketBase
    {
        public class MessageEventArgs:EventArgs {
            public Message Message;
            public Connection Connecction;
            public MessageEventArgs(Message message,Connection connection) {
                this.Message = message;
                this.Connecction = connection;
            }
        }
        public delegate void MessageEventHandler(object sender,MessageEventArgs e);

        public class ConnectionEventArgs:EventArgs {
            public Connection Connection;
            public Exception Exception;
            public ConnectionEventArgs(Connection connection,Exception exception) {
                this.Connection = connection;
                this.Exception = exception;
            }
        }
        public delegate void ConnectionHandler(object sender,ConnectionEventArgs e);

        public ConnectionCollection Connections {
            get{return _connections;}
            set{_connections = value;}
        }
        protected ConnectionCollection _connections;

        public MessageCollection messageQueue {
            get{return _messageQueue;}
            set{_messageQueue = value;}
        }
        protected MessageCollection _messageQueue;

        public SocketBase()
        {
            this._connections = new ConnectionCollection();
            this._messageQueue = new MessageCollection();
        }
        protected void Send(Message message) {
            this.Send(message,message.SendToOrReceivedFrom);     
        }
        protected void Send(Message message,Connection connection) {
            byte[] buffer = message.ToBytes();
            lock(this) {
                connection.NetworkStream.Write(buffer,0,buffer.Length);
            }
        }

        protected System.Threading.Thread _listenningthread;
        protected System.Threading.Thread _sendthread;
        protected virtual void Sendding() {
            try {
                while(true) {
                    System.Threading.Thread.Sleep(200);
                    for(int i = 0 ; i < this.messageQueue.Count ; i++) {
                        if(this.messageQueue[i].SendToOrReceivedFrom != null) {
                            this.Send(this.messageQueue[i]);
                            this.OnMessageSent(this,new MessageEventArgs(this.messageQueue[i],this.messageQueue[i].SendToOrReceivedFrom));
                        }
                        else {//对每一个连接都发送此消息
                            for(int j = 0 ; j < this.Connections.Count ; j++) {
                                this.Send(this.messageQueue[i],this.Connections[j]);
                                this.OnMessageSent(this,new MessageEventArgs(this.messageQueue[i],this.Connections[j]));
                            }
                        }
                        this.messageQueue[i].Sent = true;
                    }
                    //清除所有已发送消息
                    for(int i = this.messageQueue.Count - 1 ; i > -1 ; i--) {
                        if(this.messageQueue[i].Sent)
                            this.messageQueue.RemoveAt(i);
                    }
                }
            }catch{
                 }
        }
        protected virtual void Listenning() {
            try {
                while(true) {
                    System.Threading.Thread.Sleep(200);
                    foreach(Connection connection in this._connections) {
                        if(connection.NetworkStream.CanRead && connection.NetworkStream.DataAvailable) {
                            try {
                                Message message = Message.Parse(connection); 
                                this.OnMessageReceived(this,new MessageEventArgs(message,connection));
                            }
                            catch(Exception ex) {
                                connection.NetworkStream.Close();
                                this.OnConnectionClose(this,new ConnectionEventArgs(connection,ex));
                            }
                        }
                    }
                }
            }
            catch {
            }
        }

        protected void StartListenAndSend() {
            _listenningthread = new System.Threading.Thread(new System.Threading.ThreadStart(Listenning));
            _listenningthread.Start();
            _sendthread = new System.Threading.Thread(new System.Threading.ThreadStart(Sendding));
            _sendthread.Start();
        }
        public void EndListenAndSend() {
            _listenningthread.Abort();
            _sendthread.Abort();
        }



        public event MessageEventHandler MessageReceived;
        public event MessageEventHandler MessageSent;
        public event ConnectionHandler ConnectionClose;
        public event ConnectionHandler Connected;

        public void OnMessageReceived(object sender,MessageEventArgs e) {
            if(MessageReceived != null)
                this.MessageReceived(sender,e);
        }
        public void OnMessageSent(object sender,MessageEventArgs e) {
            if(MessageSent != null)
                this.MessageSent(sender,e);
        }
        public void OnConnectionClose(object sender,ConnectionEventArgs e) {
            if(ConnectionClose != null)
                this.ConnectionClose(sender,e);
        }
        public void OnConnected(object sender,ConnectionEventArgs e) {
            if(Connected != null)
                this.Connected(sender,e);
        }
    }
}


(8)、SocketFactory.cs文件中的代码:

using System;
using System.Net;
using System.Net.Sockets;

namespace SocketLibrary {
    /// <summary>
    /// 网络通信组件
    /// </summary>
    public class SocketFactory {
        public static System.Text.Encoding DefaultEncoding = System.Text.Encoding.GetEncoding("GB2312");
    }
}

4、将文件和代码建立和粘贴完成后,运行此类库,会在程序所在路径文件夹里的“bin\Debug\”下找到“SocketLibrary.dll”文件,下面开发的Windows服务就需要引用这个dll文件。

      第二步再来开发出Windows服务。
1、打开Visual Studio.Net2003,单击菜单中的“文件”——>“新建”——>“项目”,在弹出的对话框左侧选择“Visual C#项目”,在右侧选择“Windows服务”,下方的名称中输入“SocketService”,位置自己随便选择一个路径。
2、删除自动创建的服务文件,新建如下的Windows服务文件“SocketService.cs”。
3、在SocketService.cs文件的设计界面,选择开发环境右侧的“属性”页卡,会在页卡底部发现“添加安装程序(I)”,单击此处后会在解决方案的SocketService工程下多一个服务文件“ProjectInstaller.cs”。
4、在ProjectInstaller.cs文件的设计界面,会看见两个组件“serviceProcessInstaller1”和“serviceInstaller1”。将serviceProcessInstaller1组件的Account属性设置为“LocalSystem”(表示本地系统服务,NetworkService表示网络服务,LocalService表示本地服务,User表示用户服务);再将serviceInstaller1组件的StartType属性设置为“Automatic”(表示服务自动启动,Manual表示手动启动,Disabled表示禁用)。
5、右击解决方案中的“引用”,选择添加引用,在弹出的对话框中选择“浏览”,将前面的“SocketLibrary.dll”添加到此引用。
6、在系统C盘下建立两个文件“SocketService.ini”和“SocketService.log”。SocketService.ini文件是配置SocketService监听的端口号,打开该ini文件在其中编辑下面的内容“端口号:8088”(双引号请不要输入在ini文件的内容里),而SocketService.log是为了记录有关信息的日志。
7、删除SocketService.cs文件的所有代码,将下面的代码拷贝到该文件中: 

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
using System.IO;
using SocketLibrary;

namespace SocketService
{
    public class SocketService : System.ServiceProcess.ServiceBase
    {
        /// <summary> 
        /// 必需的设计器变量。
        /// </summary>
        private System.ComponentModel.Container components = null;
        
        public SocketService()
        {
            // 该调用是 Windows.Forms 组件设计器所必需的。
            InitializeComponent();

            // TODO: 在 InitComponent 调用后添加任何初始化
        }

        #region 组件设计器生成的代码
        /// <summary> 
        /// 设计器支持所需的方法 - 不要使用代码编辑器 
        /// 修改此方法的内容。
        /// </summary>
        private void InitializeComponent()
        {
            // 
            // SocketService
            // 
            this.ServiceName = "SocketService";

        }
        #endregion

        /// <summary>
        /// 清理所有正在使用的资源。
        /// </summary>
        protected override void Dispose( bool disposing ) 
        {
            if( disposing )
            {
                if (components != null) 
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        [STAThread]
        static void Main() 
        {
            System.ServiceProcess.ServiceBase.Run(new SocketService());
        }

        SocketLibrary.Server _server = new SocketLibrary.Server();

        /// <summary>
        /// 设置具体的操作,以便服务可以执行它的工作。
        /// </summary>
        protected override void OnStart(string[] args)
        {
            // TODO: 在此处添加代码以启动服务。
            try
            {
                StreamReader ConfigReader = new StreamReader("C:\\SocketService.ini",System.Text.Encoding.GetEncoding("GB2312"));
                string str = ConfigReader.ReadToEnd();
                string[] Str = str.Split(':');
                if (Str.Length == 2)
                {
                    if(Str[0] == "端口号")
                    {
                        
                        _server.StartServer(Convert.ToInt32(Str[1]));
                        StreamWriter LogWriter = new StreamWriter("C:\\SocketService.log",true,System.Text.Encoding.GetEncoding("GB2312"));
                        LogWriter.Write(DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToShortTimeString() + "   SocketService服务已启动\n");
                        LogWriter.Close();
                        _server.MessageReceived += new SocketLibrary.SocketBase.MessageEventHandler(_server_MessageReceived);
                        _server.Connected += new SocketLibrary.SocketBase.ConnectionHandler(_server_Connected);
                    }
                    else
                    {
                        StreamWriter LogWriter = new StreamWriter("C:\\SocketService.log",true,System.Text.Encoding.GetEncoding("GB2312"));
                        LogWriter.Write(DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToShortTimeString() + "   SocketService.ini文件中无端口号属性,格式应为\"端口号:[输入的端口号]\"\n");
                        LogWriter.Close();
                        OnStop();
                    }
                }
                else
                {
                    StreamWriter LogWriter = new StreamWriter("C:\\SocketService.log",true,System.Text.Encoding.GetEncoding("GB2312"));
                    LogWriter.Write(DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToShortTimeString() + "   SocketService.ini文件中属性过多,格式应为\"端口号:[输入的端口号]\"\n");
                    LogWriter.Close();
                    OnStop();
                }
            }
            catch (Exception e)
            {
                StreamWriter LogWriter = new StreamWriter("C:\\SocketService.log",true,System.Text.Encoding.GetEncoding("GB2312"));
                LogWriter.Write(DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToShortTimeString() + "   SocketService服务启动时有错误,错误信息:" + e.Message + "\n");
                LogWriter.Close();
                OnStop();
            }

        }
 
        /// <summary>
        /// 停止此服务。
        /// </summary>
        protected override void OnStop()
        {
            // TODO: 在此处添加代码以执行停止服务所需的关闭操作。
            _server.StopServer();
            StreamWriter LogWriter = new StreamWriter("C:\\SocketService.log",true,System.Text.Encoding.GetEncoding("GB2312"));
            LogWriter.Write(DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToShortTimeString() + "   SocketService服务已停止\n");
            LogWriter.Close();
        }

        private void _server_MessageReceived(object sender, SocketLibrary.SocketBase.MessageEventArgs e) 
        {
            StreamWriter LogWriter = new StreamWriter("C:\\SocketService.log",true,System.Text.Encoding.GetEncoding("GB2312"));
            LogWriter.Write(DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToShortTimeString() + "   " + e.Message.MessageBody + "\n");
            LogWriter.Close();
        }
        private void _server_Connected(object sender, SocketLibrary.SocketBase.ConnectionEventArgs e) 
        {
            StreamWriter LogWriter = new StreamWriter("C:\\SocketService.log",true,System.Text.Encoding.GetEncoding("GB2312"));
            LogWriter.Write(DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToShortTimeString() + "   " + e.Exception.Message + "\n");
            LogWriter.Close();
        }
    }
}

8、在Visual Studio.Net2003的菜单上选择“生成”——>“重新生成解决方案”,当生成成功后,会在程序所在路径文件夹里的“bin\Debug\”下找到“SocketService.exe”文件,记住这个路径和文件名,下一步需要用到。
9、打开“Visual Studio .NET 2003 命令提示”(应该都知道如何打开吧,不知道的可以在评论中询问)。在其中输入“installutil [第8步中的路径和文件名]”(例如:installutil C:\SocketService\bin\Debug\SocketService.exe),按下回车,将会在本机上安装刚开发好的Windows服务,如果要卸载安装过的Windows服务请在“Visual Studio .NET 2003 命令提示”中输入“installutil /u [第8步中的路径和文件名]”(例如:installutil /u C:\SocketService\bin\Debug\SocketService.exe)。
10、制作Windows服务的安装部署工程(略)。

      第三步最后开发出一个Socket通信的客户端来验证此服务。
1、和以往建立C/S程序的过程一样,只是将工程名称改为“SocketClientTest”。
2、删除自动创建的Form1,新建窗体“CFrom”。
3、窗体控件与布局见下图:

4、在引用中添加“SocketLibrary.dll”引用(添加方法和SocketService工程添加方法相同)
5、后台代码如下:

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace SocketClientTest
{
    /// <summary>
    /// Form1 的摘要说明。
    /// </summary>
    public class CForm : System.Windows.Forms.Form
    {
        /// <summary>
        /// 必需的设计器变量。
        /// </summary>
        private System.ComponentModel.Container components = null;

        public CForm()
        {
            //
            // Windows 窗体设计器支持所必需的
            //
            InitializeComponent();

            //
            // TODO: 在 InitializeComponent 调用后添加任何构造函数代码
            //
        }

        /// <summary>
        /// 清理所有正在使用的资源。
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if (components != null) 
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        #region Windows 窗体设计器生成的代码
        /// <summary>
        /// 设计器支持所需的方法 - 不要使用代码编辑器修改
        /// 此方法的内容。
        /// </summary>
        private void InitializeComponent()
        {
            this.button1 = new System.Windows.Forms.Button();
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.button2 = new System.Windows.Forms.Button();
            this.button3 = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(40, 24);
            this.button1.Name = "button1";
            this.button1.TabIndex = 0;
            this.button1.Text = "连接";
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // 
            // textBox1
            // 
            this.textBox1.Location = new System.Drawing.Point(24, 80);
            this.textBox1.Name = "textBox1";
            this.textBox1.TabIndex = 1;
            this.textBox1.Text = "textBox1";
            // 
            // button2
            // 
            this.button2.Location = new System.Drawing.Point(144, 80);
            this.button2.Name = "button2";
            this.button2.TabIndex = 2;
            this.button2.Text = "发送";
            this.button2.Click += new System.EventHandler(this.button2_Click);
            // 
            // button3
            // 
            this.button3.Location = new System.Drawing.Point(144, 24);
            this.button3.Name = "button3";
            this.button3.TabIndex = 3;
            this.button3.Text = "停止";
            this.button3.Click += new System.EventHandler(this.button3_Click);
            // 
            // Form1
            // 
            this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
            this.ClientSize = new System.Drawing.Size(292, 126);
            this.Controls.Add(this.button3);
            this.Controls.Add(this.button2);
            this.Controls.Add(this.textBox1);
            this.Controls.Add(this.button1);
            this.Name = "CForm";
            this.Text = "CForm";
            this.Load += new System.EventHandler(this.CForm_Load);
            this.ResumeLayout(false);

        }
        #endregion

        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        [STAThread]
        static void Main() 
        {
            Application.Run(new CForm());
        }

        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.TextBox textBox1;
        private System.Windows.Forms.Button button2;
        private System.Windows.Forms.Button button3;
        SocketLibrary.Client client;
        private void CForm_Load(object sender, System.EventArgs e)
        {
            client = new SocketLibrary.Client();
            
            SocketLibrary.SocketFactory factory = new SocketLibrary.SocketFactory();
        }

        private void button1_Click(object sender, System.EventArgs e) {
            client.StartClient(System.Net.IPAddress.Parse("192.168.5.9"),8088);//此处输入自己的计算机IP地址,端口需和SocketService.ini中的端口号一样
        }

        private void button2_Click(object sender, System.EventArgs e) {
            SocketLibrary.Message message = new SocketLibrary.Message(SocketLibrary.Message.CommandHeader.SendMessage,1,1,textBox1.Text);
            client.messageQueue.Add(message);
        }

        private void button3_Click(object sender, System.EventArgs e) {
            client.StopClient();
        }
    }
}

6、运行程序,点击CForm中的“连接”按钮,再在TextBox1中输入部分信息,单击“发送”按钮,看看在C:\SocketService.log中是否记录了CForm窗口中TextBox1中的内容。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值