程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C#高級編程技術復習一,

C#高級編程技術復習一,

編輯:C#入門知識

C#高級編程技術復習一,


從基本的Socket編程進入

(注意:這是轉的一篇2011年的文章,有些知識可能該更新了!)

這一篇文章,我將圖文並茂地介紹Socket編程的基礎知識,我相信,如果你按照步驟做完實驗,一定可以對Socket編程有更好地理解。

本文源代碼,可以通過這裡下載 http://files.cnblogs.com/chenxizhang/SocketWorkshop.rar

 

第一步:創建解決方案

image

第二步:創建服務端程序

這裡可以選擇“Console Application”這個類型,比較方便調試

image

然後編寫如下代碼,實現服務器的基本功能

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//額外導入的兩個命名空間
using System.Net.Sockets;
using System.Net;

namespace SocketServer
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陳希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //創建一個新的Socket,這裡我們使用最常用的基於TCP的Stream Socket(流式套接字)
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //將該socket綁定到主機上面的某個端口
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspx
            socket.Bind(new IPEndPoint(IPAddress.Any, 4530));

            //啟動監聽,並且設置一個最大的隊列長度
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspx
            socket.Listen(4);


            Console.WriteLine("Server is ready!");
            Console.Read();
        }
    }
}

 

現在可以啟動調試一下看看效果如何,正常情況下應該會看到一個提示,因為我們需要在TCP 4530端口進行監聽,所以防火牆會有提示。

image

點擊“Allow access”

image

這樣,我們的服務器就可以開始監聽了。但是這有什麼用呢?是的,沒有什麼用。

我們還需要為服務器添加一些功能,例如接受傳入的請求,給客戶端發送消息,或者從客戶端接收消息等等

第三步:接受傳入的請求

我們需要通過Accept,或者(BeginAccept)來接受傳入的請求,請注意下面代碼中的紅色部分

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//額外導入的兩個命名空間
using System.Net.Sockets;
using System.Net;

namespace SocketServer
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陳希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //創建一個新的Socket,這裡我們使用最常用的基於TCP的Stream Socket(流式套接字)
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //將該socket綁定到主機上面的某個端口
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspx
            socket.Bind(new IPEndPoint(IPAddress.Any, 4530));

            //啟動監聽,並且設置一個最大的隊列長度
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspx
            socket.Listen(4);

            //開始接受客戶端連接請求
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginaccept.aspx
            socket.BeginAccept(new AsyncCallback((ar) =>
            {
                //這就是客戶端的Socket實例,我們後續可以將其保存起來
                var client = socket.EndAccept(ar);

                //給客戶端發送一個歡迎消息
                client.Send(Encoding.Unicode.GetBytes("Hi there, I accept you request at "+DateTime.Now.ToString()));
            }), null);


            Console.WriteLine("Server is ready!");
            Console.Read();
        }
    }
}

 

wow,看起來不錯對吧,我們趕緊做一個客戶端來測試一下吧

 

第四步:創建客戶端

我們還是使用一個Console Application

image

添加如下的代碼,並且創建客戶端連接

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//導入的命名空間
using System.Net.Sockets;

namespace SocketClient
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陳希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //創建一個Socket
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //連接到指定服務器的指定端口
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.connect.aspx
            socket.Connect("localhost", 4530);


            Console.WriteLine("connect to the server");
            Console.Read();

        }
    }
}

 

依次選擇SocketServer和SocketClient這兩個項目,分別將其啟動為調試狀態(右鍵菜單,Debug=>Start new instance)

image

我們看到兩個程序都工作正常。

但是,在客戶端怎麼沒有收到服務器發過來的消息呢?那是因為,我們沒有在客戶端提供這方面的功能。

 

第五步:在客戶端中實現接受消息的方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//導入的命名空間
using System.Net.Sockets;

namespace SocketClient
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陳希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //創建一個Socket
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //連接到指定服務器的指定端口
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.connect.aspx
            socket.Connect("localhost", 4530);

            //實現接受消息的方法

            var buffer = new byte[1024];//設置一個緩沖區,用來保存數據
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginreceive.aspx
            socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback((ar) =>
            {
                //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspx
                var length = socket.EndReceive(ar);
                //讀取出來消息內容
                var message = Encoding.Unicode.GetString(buffer, 0, length);
                //顯示消息
                Console.WriteLine(message);

            }), null);

            Console.WriteLine("connect to the server");
            Console.Read();

        }
    }
}

請注意以上紅色的部分,我們用了BeginReceive方法進行異步的消息偵聽,如果收到了,我們就打印出來

image

看起來已經實現了我們需求了:服務器給客戶端發了一個消息,而客戶端也已經收到了。

但是,這遠遠不夠,因為它們之間的通訊不僅僅是一次性的,那麼如果服務器要不斷地給客戶端發消息,例如每隔兩秒鐘就發送一個消息,如何實現呢?

 

第六步:實現服務器定期向客戶端發消息

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//額外導入的兩個命名空間
using System.Net.Sockets;
using System.Net;

namespace SocketServer
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陳希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //創建一個新的Socket,這裡我們使用最常用的基於TCP的Stream Socket(流式套接字)
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //將該socket綁定到主機上面的某個端口
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspx
            socket.Bind(new IPEndPoint(IPAddress.Any, 4530));

            //啟動監聽,並且設置一個最大的隊列長度
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspx
            socket.Listen(4);

            //開始接受客戶端連接請求
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginaccept.aspx
            socket.BeginAccept(new AsyncCallback((ar) =>
            {
                //這就是客戶端的Socket實例,我們後續可以將其保存起來
                var client = socket.EndAccept(ar);

                //給客戶端發送一個歡迎消息
                client.Send(Encoding.Unicode.GetBytes("Hi there, I accept you request at "+DateTime.Now.ToString()));


                //實現每隔兩秒鐘給服務器發一個消息
                //這裡我們使用了一個定時器
                var timer = new System.Timers.Timer();
                timer.Interval = 2000D;
                timer.Enabled = true;
                timer.Elapsed += (o, a) =>
                {
                    client.Send(Encoding.Unicode.GetBytes("Message from server at " +DateTime.Now.ToString()));
                };
                timer.Start();

            }), null);


            Console.WriteLine("Server is ready!");
            Console.Read();
        }
    }
}

 

我們還要實現在客戶端一直監聽消息的機制,而不是一次性接收.請注意下面紅色的部分

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//導入的命名空間
using System.Net.Sockets;

namespace SocketClient
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陳希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //創建一個Socket
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //連接到指定服務器的指定端口
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.connect.aspx
            socket.Connect("localhost", 4530);
            Console.WriteLine("connect to the server");

            //實現接受消息的方法

            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginreceive.aspx
            socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage),socket);

            Console.Read();

        }


        static byte[] buffer = new byte[1024];

        public static void ReceiveMessage(IAsyncResult ar)
        {
            try
            {
                var socket = ar.AsyncState as Socket;

                //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspx
                var length = socket.EndReceive(ar);
                //讀取出來消息內容
                var message = Encoding.Unicode.GetString(buffer, 0, length);
                //顯示消息
                Console.WriteLine(message);

                //接收下一個消息(因為這是一個遞歸的調用,所以這樣就可以一直接收消息了)
                socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket);
            }
            catch(Exception ex){
                Console.WriteLine(ex.Message);
            }
        }
    }
}

重新調試起來,看起來的效果如下圖所示

image

我們繼續做下面的實驗,一步一步地研究Socket通訊中可能遇到的一些問題

請先關閉掉客戶端這個程序,而不要關閉服務端程序,這時會發現一個錯誤

image

這個錯誤很容易理解,因為客戶端已經關閉,也就是客戶端那個Socket已經不存在了,服務器還繼續向它發送消息當然會出錯。所以,從可靠性方面的考慮,我們必須在發送消息之前檢測Socket的活動狀態

 

第七步:檢測客戶端的活動狀態

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//額外導入的兩個命名空間
using System.Net.Sockets;
using System.Net;

namespace SocketServer
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陳希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //創建一個新的Socket,這裡我們使用最常用的基於TCP的Stream Socket(流式套接字)
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //將該socket綁定到主機上面的某個端口
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspx
            socket.Bind(new IPEndPoint(IPAddress.Any, 4530));

            //啟動監聽,並且設置一個最大的隊列長度
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspx
            socket.Listen(4);

            //開始接受客戶端連接請求
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginaccept.aspx
            socket.BeginAccept(new AsyncCallback((ar) =>
            {
                //這就是客戶端的Socket實例,我們後續可以將其保存起來
                var client = socket.EndAccept(ar);

                //給客戶端發送一個歡迎消息
                client.Send(Encoding.Unicode.GetBytes("Hi there, I accept you request at "+DateTime.Now.ToString()));


                //實現每隔兩秒鐘給服務器發一個消息
                //這裡我們使用了一個定時器
                var timer = new System.Timers.Timer();
                timer.Interval = 2000D;
                timer.Enabled = true;
                timer.Elapsed += (o, a) =>
                {
                    //檢測客戶端Socket的狀態
                    if(client.Connected)
                    {
                        try
                        {
                            client.Send(Encoding.Unicode.GetBytes("Message from server at " + DateTime.Now.ToString()));
                        }
                        catch(SocketException ex)
                        {
                            Console.WriteLine(ex.Message);
                        }
                    }
                    else
                    {
                        timer.Stop();
                        timer.Enabled = false;
                        Console.WriteLine("Client is disconnected, the timer is stop.");
                    }
                };
                timer.Start();

            }), null);


            Console.WriteLine("Server is ready!");
            Console.Read();
        }
    }
}

上面代碼的邏輯很清楚,但有時候還是會觸發那個SocketException。為什麼呢?這是因為我們的Timer是每隔兩秒鐘檢查一次,那麼就很可能有一種情況,我們檢查的時候,它還是連接狀態,消息發出去之後,它斷開了。這種情況肯定是存在的。所以要用Try..catch的結構

 

目前我們實現的場景很簡單,服務器只管發消息,客戶端只管收消息。但實際工作中,可能希望服務器和客戶端都能收發消息。請看下一節

 

第八步:實現雙向收發消息

先看服務端的修改

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//額外導入的兩個命名空間
using System.Net.Sockets;
using System.Net;

namespace SocketServer
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陳希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //創建一個新的Socket,這裡我們使用最常用的基於TCP的Stream Socket(流式套接字)
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //將該socket綁定到主機上面的某個端口
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspx
            socket.Bind(new IPEndPoint(IPAddress.Any, 4530));

            //啟動監聽,並且設置一個最大的隊列長度
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspx
            socket.Listen(4);

            //開始接受客戶端連接請求
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginaccept.aspx
            socket.BeginAccept(new AsyncCallback((ar) =>
            {
                //這就是客戶端的Socket實例,我們後續可以將其保存起來
                var client = socket.EndAccept(ar);

                //給客戶端發送一個歡迎消息
                client.Send(Encoding.Unicode.GetBytes("Hi there, I accept you request at "+DateTime.Now.ToString()));


                //實現每隔兩秒鐘給服務器發一個消息
                //這裡我們使用了一個定時器
                var timer = new System.Timers.Timer();
                timer.Interval = 2000D;
                timer.Enabled = true;
                timer.Elapsed += (o, a) =>
                {
                    //檢測客戶端Socket的狀態
                    if(client.Connected)
                    {
                        try
                        {
                            client.Send(Encoding.Unicode.GetBytes("Message from server at " + DateTime.Now.ToString()));
                        }
                        catch(SocketException ex)
                        {
                            Console.WriteLine(ex.Message);
                        }
                    }
                    else
                    {
                        timer.Stop();
                        timer.Enabled = false;
                        Console.WriteLine("Client is disconnected, the timer is stop.");
                    }
                };
                timer.Start();


                //接收客戶端的消息(這個和在客戶端實現的方式是一樣的)
                client.BeginReceive(buffer,0,buffer.Length,SocketFlags.None,new AsyncCallback(ReceiveMessage),client);

            }), null);


            Console.WriteLine("Server is ready!");
            Console.Read();
        }

        static byte[] buffer = new byte[1024];

        public static void ReceiveMessage(IAsyncResult ar)
        {
            try
            {
                var socket = ar.AsyncState as Socket;

                //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspx
                var length = socket.EndReceive(ar);
                //讀取出來消息內容
                var message = Encoding.Unicode.GetString(buffer, 0, length);
                //顯示消息
                Console.WriteLine(message);

                //接收下一個消息(因為這是一個遞歸的調用,所以這樣就可以一直接收消息了)
                socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket);
            }
            catch(Exception ex){
                Console.WriteLine(ex.Message);
            }
        }
    }
}

可以看出來,為了讓服務器可以接受消息,其實並不需要什麼特別的設計,與客戶端接受消息其實可以是一樣的

 

 

再來看看客戶端的修改

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//導入的命名空間
using System.Net.Sockets;

namespace SocketClient
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陳希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //創建一個Socket
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //連接到指定服務器的指定端口
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.connect.aspx
            socket.Connect("localhost", 4530);
            Console.WriteLine("connect to the server");

            //實現接受消息的方法

            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginreceive.aspx
            socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket);

            //接受用戶輸入,將消息發送給服務器端
            while(true)
            {
                var message = "Message from client : " + Console.ReadLine();
                var outputBuffer = Encoding.Unicode.GetBytes(message);
                socket.BeginSend(outputBuffer, 0, outputBuffer.Length, SocketFlags.None, null, null);
            }

        }


        static byte[] buffer = new byte[1024];

        public static void ReceiveMessage(IAsyncResult ar)
        {
            try
            {
                var socket = ar.AsyncState as Socket;

                //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspx
                var length = socket.EndReceive(ar);
                //讀取出來消息內容
                var message = Encoding.Unicode.GetString(buffer, 0, length);
                //顯示消息
                Console.WriteLine(message);

                //接收下一個消息(因為這是一個遞歸的調用,所以這樣就可以一直接收消息了)
                socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket);
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

我在這裡做了一個死循環,用戶可以不斷地輸入,這些消息會被發送給服務器。如下圖所示

image

【備注】因為服務器每隔兩秒鐘會發送新消息過來,所以在輸入的時候,動作要稍快一點啦

 

本文最後探討一個問題,就是如何讓我們的服務器可以支持多個客戶端

 

第九步:支持多個客戶端

這個步驟只需要修改服務端程序即可

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//額外導入的兩個命名空間
using System.Net.Sockets;
using System.Net;

namespace SocketServer
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陳希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //創建一個新的Socket,這裡我們使用最常用的基於TCP的Stream Socket(流式套接字)
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //將該socket綁定到主機上面的某個端口
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspx
            socket.Bind(new IPEndPoint(IPAddress.Any, 4530));

            //啟動監聽,並且設置一個最大的隊列長度
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspx
            socket.Listen(4);

            //開始接受客戶端連接請求
            //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginaccept.aspx
            socket.BeginAccept(new AsyncCallback(ClientAccepted), socket);


            Console.WriteLine("Server is ready!");
            Console.Read();
        }


        public static void ClientAccepted(IAsyncResult ar)
        {

            var socket = ar.AsyncState as Socket;

            //這就是客戶端的Socket實例,我們後續可以將其保存起來
            var client = socket.EndAccept(ar);

            //給客戶端發送一個歡迎消息
            client.Send(Encoding.Unicode.GetBytes("Hi there, I accept you request at " + DateTime.Now.ToString()));


            //實現每隔兩秒鐘給服務器發一個消息
            //這裡我們使用了一個定時器
            var timer = new System.Timers.Timer();
            timer.Interval = 2000D;
            timer.Enabled = true;
            timer.Elapsed += (o, a) =>
            {
                //檢測客戶端Socket的狀態
                if(client.Connected)
                {
                    try
                    {
                        client.Send(Encoding.Unicode.GetBytes("Message from server at " + DateTime.Now.ToString()));
                    }
                    catch(SocketException ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }
                else
                {
                    timer.Stop();
                    timer.Enabled = false;
                    Console.WriteLine("Client is disconnected, the timer is stop.");
                }
            };
            timer.Start();


            //接收客戶端的消息(這個和在客戶端實現的方式是一樣的)
            client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), client);

            //准備接受下一個客戶端請求
            socket.BeginAccept(new AsyncCallback(ClientAccepted), socket);
        }

        static byte[] buffer = new byte[1024];

        public static void ReceiveMessage(IAsyncResult ar)
        {

            try
            {
                var socket = ar.AsyncState as Socket;

                //方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspx
                var length = socket.EndReceive(ar);
                //讀取出來消息內容
                var message = Encoding.Unicode.GetString(buffer, 0, length);
                //顯示消息
                Console.WriteLine(message);

                //接收下一個消息(因為這是一個遞歸的調用,所以這樣就可以一直接收消息了)
                socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket);
            }
            catch(Exception ex){
                Console.WriteLine(ex.Message);
            }
        }
    }
}

最後調試起來看到的效果如下圖

image

 

本文源代碼,可以通過這裡下載 http://files.cnblogs.com/chenxizhang/SocketWorkshop.rar

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved