1. 程式人生 > >從socket開始學習網路程式設計

從socket開始學習網路程式設計

1 URL快速下載(上傳)。使用WebClinet對URL進行瀏覽並下載,可以說程式碼清晰、支援豐富。包括編碼格式下載格式非同步下載Form上傳引數拼接等等各種。

 WebClient mywebclient = new WebClient();
 //下載為字串
var websiteString = mywebclient.DownloadString("123.com");
//下載為二進位制
var websitedata = mywebclient.DownloadData("123.com");
//非同步下載並監聽完成
 mywebclient.DownloadStringCompleted += (Object Sender,DownloadStringCompletedEventArgs e) =>{
 Console.Write("");
};

HTTP請求構造。在很多場景中,需要偽造Referer、UserAgent、ContentType等等,從一個語言的HTTP庫對HTTP協議的支援細膩程度可以看出其是否親爬蟲,幸運的是,HttpWebRequest確實足夠全面,能夠滿足所有的自定義需求。

 var myhttpWebRequset = (HttpWebRequest)WebRequest.Create(new Uri(@"htttp://www.baidu.com"));
            myhttpWebRequset.Method = "post";
            myhttpWebRequset.ContentType = "application/x-www-form-urlencoded";
            myhttpWebRequset.ContentLength = 100;
            myhttpWebRequset.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322)";

Cookies處理。雖然Cookies已經逐漸淡出歷史的舞臺,但依然有大量的Web開發框架是以Cookie為支撐做Session體系的,所以Cookie的靈活操作也非常重要。

  var myhttpWebRequset = (HttpWebRequest)WebRequest.Create(new Uri(@"htttp://www.baidu.com"));
            myhttpWebRequset.Referer = @"http://www.baidu.com";
  var cookieContainer = new CookieContainer();
            cookieContainer.Add(new Uri(@"http://baidu.com"), new Cookie("key", "1"));
            myhttpWebRequset.CookieContainer = cookieContainer;

代理服務。有時候目標伺服器會對IP訪問做限制,這時候使用代理伺服器以及不停的更換代理伺服器就非常重要了,如下處理也很簡潔

 var mywebProxy = new WebProxy(new Uri(@"127.0.0.1:8080"));
            myhttpWebRequset.Proxy = mywebProxy;

socket概念

Socket的中文釋義稱為套接字,是支撐TCP/IP通訊最基本的操作單元。可以將Socket看做不同主機之間的程序進行雙向通訊的端點,在一個雙方都可以通訊的Socket例項中,既儲存了對方的IP地址和埠,也保持了雙方通訊採用的協議等資訊。

 

①.流套接字:實現面向連線的TCP通訊

②.資料報套接字:實現無連線的UDP通訊

③.原始套接字:實現IP資料包的通訊(這裡不做討論)

網路協議中使用埠號識別主機上不同的程序。

Tcp

TCP是一種面向連線的、可靠的,基於位元組流的傳輸層通訊協議。

TCP的工作過程

TCP是面向連線的協議,TCP協議通過三個報文段完成類似電話呼叫的連線建立過程,這個過程稱為三次握手

第一次握手:建立連線時,客戶端傳送SYN包(SEQ=x)到伺服器,並進入SYN_SEND狀態,等待伺服器確認。

第二次握手:伺服器收到SYN包,必須確認客戶的SYN(ACK=x+1),同時自己也傳送一個SYN包(SEQ=y),即SYN+ACK包,此時伺服器進入SYN_RECV狀態。

第三次握手:客戶端收到伺服器的SYN+ACK包,向伺服器傳送確認包ACK(ACK=y+1),此包傳送完畢,客戶端和伺服器進入Established狀態,完成三次握手。

傳輸資料

一旦通訊雙方建立了TCP連線,連線中的任何一方都能向對方傳送資料和接收對方發來的資料。TCP協議負責把使用者資料(位元組流)按一定的格式和長度組成多個數據報進行傳送,並在接收到資料報之後按分解順序重新組裝和恢復使用者資料。
利用TCP傳輸資料時,資料是以位元組流的形式進行傳輸的。

tcp協議負責把資料按格式和長度傳送並接受後組裝。

連線的終止

建立一個連線需要三次握手,而終止一個連線要經過四次握手,這是由TCP的半關閉(half-close)造成的。 

TCP最主要的特點如下。
(1) 是面向連線的協議。
(2) 端到端的通訊。每個TCP連線只能有兩個端點,而且只能一對一通訊,不能一點對多點直接通訊。
(3) 高可靠性。通過TCP連線傳送的資料,能保證資料無差錯、不丟失、不重複地準確到達接收方,並且保證各資料到達的順序與其發出的順序相同。
(4) 全雙工方式傳輸。
(5) 資料以位元組流的方式傳輸。
(6) 傳輸的資料無訊息邊界。

 

 同步與非同步

同步工作方式是指利用TCP編寫的程式執行到監聽或接收語句時,在未完成工作(偵聽到連線請求或收到對方發來的資料)前不再繼續往下執行,執行緒處於阻塞狀態,直到該語句完成相應的工作後才繼續執行下一條語句。
非同步工作方式是指程式執行到監聽或接收語句時,不論工作是否完成,都會繼續往下執行。

 

UDP是一種簡單的、面向資料報的無連線的協議,提供的是不一定可靠的傳輸服務。所謂“無連線”是指在正式通訊前不必與對方先建立連線,不管對方狀態如何都直接傳送過去。這與發手機簡訊非常相似,只要知道對方的手機號就可以了,不要考慮對方手機處於什麼狀態。UDP雖然不能保證資料傳輸的可靠性,但資料傳輸的效率較高。

 UDP與TCP的區別
(1) UDP可靠性不如TCP
TCP包含了專門的傳遞保證機制,當資料接收方收到傳送方傳來的資訊時,會自動向傳送方發出確認訊息;傳送方只有在接收到該確認訊息之後才繼續傳送其他資訊,否則將一直等待直到收到確認資訊為止。與TCP不同,UDP並不提供資料傳送的保證機制。如果在從傳送方到接收方的傳遞過程中出現數據報的丟失,協議本身並不能做出任何檢測或提示。因此,通常人們把UDP稱為不可靠的傳輸協議。
(2) UDP不能保證有序傳輸
UDP不能確保資料的傳送和接收順序。對於突發性的資料報,有可能會亂序。

UDP的優勢

  1. UDP速度比TCP快
    由於UDP不需要先與對方建立連線,也不需要傳輸確認,因此其資料傳輸速度比TCP快得多。對於強調傳輸效能而不是傳輸完整性的應用(比如網路音訊播放、視訊點播和網路會議等),使用UDP比較合適,因為它的傳輸速度快,使通過網路播放的視訊音質好、畫面清晰。
    (2) UDP有訊息邊界
    傳送方UDP對應用程式交下來的報文,在新增首部後就向下直接交付給IP層。既不拆分,也不合並,而是保留這些報文的邊界。使用UDP不需要考慮訊息邊界問題,這樣使得UDP程式設計相比TCP,在對接收到的資料的處理方面要方便的多。在程式設計師看來,UDP套接字使用比TCP簡單。UDP的這一特徵也說明了它是一種面向報文的傳輸協議。
    (3) UDP可以一對多傳輸
    由於傳輸資料不建立連線,也就不需要維護連線狀態(包括收發狀態等),因此一臺伺服器可以同時向多個客戶端傳輸相同的訊息。利用UDP可以使用廣播或組播的方式同時向子網上的所有客戶程序傳送訊息,這一點也比TCP方便。
    其中,速度快是UDP的首要優勢
    由於TCP協議中植入了各種安全保障功能,在實際執行的過程中會佔用大量的系統開銷,無疑使速度受到嚴重影響。反觀UDP,由於拋棄了資訊可靠傳輸機制,將安全和排序等功能移交給上層應用完成,極大地降低了執行時間,使速度得到了保證。簡而言之,UDP的“理念”就是“不顧一切,只為更快地傳送資料”。

根據socket通訊基本流程圖,總結通訊的基本步驟:

伺服器端:

第一步:建立一個用於監聽連線的Socket對像;

第二步:用指定的埠號和伺服器的ip建立一個EndPoint對像;

第三步:用socket對像的Bind()方法繫結EndPoint;

第四步:用socket對像的Listen()方法開始監聽;

第五步:接收到客戶端的連線,用socket對像的Accept()方法建立一個新的用於和客戶端進行通訊的socket對像;

第六步:通訊結束後一定記得關閉socket;

客戶端:

第一步:建立一個Socket對像;

第二步:用指定的埠號和伺服器的ip建立一個EndPoint對像;

第三步:用socket對像的Connect()方法以上面建立的EndPoint對像做為引數,向伺服器發出連線請求;

第四步:如果連線成功,就用socket對像的Send()方法向伺服器傳送資訊;

第五步:用socket對像的Receive()方法接受伺服器發來的資訊 ;

第六步:通訊結束後一定記得關閉socket;

在服務端

 

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
           //定義回撥:解決跨執行緒訪問問題
          private delegate void SetTextValueCallBack(string strValue);
        //定義接收客戶端傳送訊息的回撥
        private delegate void ReceiveMsgCallBack(string strReceive);
        //宣告回撥
        private SetTextValueCallBack setCallBack;
        //宣告
        private ReceiveMsgCallBack receiveCallBack;
        //定義回撥:給ComboBox控制元件新增元素
        private delegate void SetCmbCallBack(string strItem);
        //宣告
        private SetCmbCallBack setCmbCallBack;
        //定義傳送檔案的回撥
        private delegate void SendFileCallBack(byte[] bf);
        //宣告
        private SendFileCallBack sendCallBack;
        //用於通訊的Socket
        Socket socketSend;
        //用於監聽的SOCKET
        Socket socketWatch;

        //將遠端連線的客戶端的IP地址和Socket存入集合中
        Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();

        //建立監聽連線的執行緒
        Thread AcceptSocketThread;

        //接收客戶端傳送訊息的執行緒
        Thread threadReceive;

        private void btn_Start_Click(object sender, EventArgs e)
        {
            //當點選開始監聽的時候 在伺服器端建立一個負責監聽IP地址和埠號的Socket
            socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //獲取ip地址
            IPAddress ip = IPAddress.Parse(this.txt_IP.Text.Trim());
            //建立埠號
            IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(this.txt_Port.Text.Trim()));
            //繫結IP地址和埠號
            socketWatch.Bind(point);
            this.txt_Log.AppendText("監聽成功" + " \r \n");
            //開始監聽:設定最大可以同時連線多少個請求
            socketWatch.Listen(10);
            setCallBack = new SetTextValueCallBack(SetTextValue);
            receiveCallBack = new ReceiveMsgCallBack(ReceiveMsg);
            setCmbCallBack = new SetCmbCallBack(AddCmbItem);
            sendCallBack = new SendFileCallBack(SendFile);

            //建立執行緒
            AcceptSocketThread = new Thread(new ParameterizedThreadStart(StartListen));



        } 
             /// <summary>
         /// 等待客戶端的連線,並且建立與之通訊用的Socket
         /// </summary>
         /// <param name="obj"></param>
         private void StartListen(object obj)
         {
             Socket socketWatch = obj as Socket;
              while (true)
              {               
                //等待客戶端的連線,並且建立一個用於通訊的Socket
                  socketSend = socketWatch.Accept();
                  //獲取遠端主機的ip地址和埠號
                   string strIp = socketSend.RemoteEndPoint.ToString();
                   dicSocket.Add(strIp, socketSend);
                   this.cmb_Socket.Invoke(setCmbCallBack, strIp);
                   string strMsg = "遠端主機:" + socketSend.RemoteEndPoint + "連線成功";
                  //使用回撥
                     txt_Log.Invoke(setCallBack, strMsg);
 
                  //定義接收客戶端訊息的執行緒
                  Thread threadReceive = new Thread(new ParameterizedThreadStart(Receive));
                 threadReceive.IsBackground = true;
                threadReceive.Start(socketSend);
  
            }
        }


                  /// <summary>
          /// 伺服器端不停的接收客戶端傳送的訊息
          /// </summary>
          /// <param name="obj"></param>
          private void Receive(object obj)
          {
              Socket socketSend = obj as Socket;
              while (true)
              {
                //客戶端連線成功後,伺服器接收客戶端傳送的訊息
                  byte[] buffer = new byte[2048];
                  //實際接收到的有效位元組數
                  int count = socketSend.Receive(buffer);
                  if (count == 0)//count 表示客戶端關閉,要退出迴圈
                  {
                      break;
                  }
                  else
                  {
                      string str = Encoding.Default.GetString(buffer, 0, count);
                      string strReceiveMsg = "接收:" + socketSend.RemoteEndPoint + "傳送的訊息:" + str;
                      txt_Log.Invoke(receiveCallBack, strReceiveMsg);
                  }
              }
          }
 

        /// <summary>
        /// 回撥委託需要執行的方法
        /// </summary>
        /// <param name="strValue"></param>
        private void SetTextValue(string strValue)
         {
              this.txt_Log.AppendText(strValue + " \r \n");
          }

    private void txt_Port_TextChanged(object sender, EventArgs e)
        {

        } 
          private void ReceiveMsg(string strMsg)
         {
             this.txt_Log.AppendText(strMsg + " \r \n");
         }
  
         private void AddCmbItem(string strItem)
         {
             this.cmb_Socket.Items.Add(strItem);
        }

         private void SendFile(byte[] sendBuffer)
         {
 
             try
              {
                 dicSocket[cmb_Socket.SelectedItem.ToString()].Send(sendBuffer, SocketFlags.None);
             }
           catch (Exception ex)
             {
                  MessageBox.Show("傳送檔案出錯:"+ex.Message);
             }
         }
        /// 伺服器給客戶端傳送訊息
        private void btn_Send_Click_Click(object sender, EventArgs e)
        {
            try
            {
                string strMsg = this.txt_Msg.Text.Trim();
                byte[] buffer = Encoding.Default.GetBytes(strMsg);
                List<byte> list = new List<byte>();
                list.Add(0);
                list.AddRange(buffer);
                //將泛型集合轉換為陣列
                byte[] newBuffer = list.ToArray();
                //獲得使用者選擇的IP地址
                string ip = this.cmb_Socket.SelectedItem.ToString();
                dicSocket[ip].Send(newBuffer);
            }
            catch (Exception ex)
            {

            }

        }

 
        private void btn_StopListen_Click_1(object sender, EventArgs e)
        {
            socketWatch.Close();
            socketSend.Close();
            //終止執行緒
            AcceptSocketThread.Abort();
            threadReceive.Abort();
        }
    }

2 在客戶端

 public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        //建立連線的Socket
        Socket socketSend;
        //建立接收客戶端傳送訊息的執行緒
        Thread threadReceive; 

        /// <summary>
        /// 回撥委託需要執行的方法
        /// </summary>
        /// <param name="strValue"></param>
        private void SetTextValue(string strValue)
        {
            this.txt_Log.AppendText(strValue + " \r \n");
        }

 
        private void ReceiveMsg(string strMsg)
        {
            this.txt_Log.AppendText(strMsg + " \r \n");
        } 
       //連線
        private void btn_Connect_Click(object sender, EventArgs e)
        {
            socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress ip = IPAddress.Parse(this.txt_IP.Text.Trim());
            socketSend.Connect(ip, Convert.ToInt32(this.txt_Port.Text.Trim()));
            this.txt_Log.Invoke(new MethodInvoker(()=> { SetTextValue("連線成功"); }));
            //開啟一個新的執行緒不停的接收伺服器傳送訊息的執行緒
            threadReceive = new Thread(new ThreadStart(Receive));
            //設定為後臺執行緒
            threadReceive.IsBackground = true;
            threadReceive.Start();
        }

        /// 介面伺服器傳送的訊息
        private void Receive()
        {
            while (true) {
                byte[] buffer = new byte[2048];
                //實際接收到的位元組數
                int r = socketSend.Receive(buffer);
                if (r == 0)
                {
                    break;
                }
                else
                {
                    if (buffer[0] == 0)//表示傳送的是文字訊息
                    {
                        string str = Encoding.Default.GetString(buffer, 1, r - 1);
                        this.txt_Log.Invoke(new MethodInvoker(() => { SetTextValue("接收遠端伺服器:" + socketSend.RemoteEndPoint + "傳送的訊息:" + str); }));
                      

                    }
                }
            }

        }

        private void btn_StopListen_Click_1(object sender, EventArgs e)
        {
            socketSend.Close(); 
            //終止執行緒
            threadReceive.Abort(); 
        }

        private void btn_Send_Click(object sender, EventArgs e)
        {
            string strMsg = this.txt_Msg.Text.Trim();
            byte[] buffer = new byte[2048];
            buffer = Encoding.Default.GetBytes(strMsg);
            int receive = socketSend.Send(buffer);
        }
    }

  我們可以通過TcpClient物件的GetStream()方法獲取該物件傳送和接收資料的 NetworkStream 物件:

            TcpClient client = new TcpClient();
            client.Connect("www.baidu.com", 8099);
            NetworkStream nStream = client.GetStream();

也可以通過使用Socket來獲取 NetworkStream 物件:

NetworkStream myNetworkStream = new NetworkStream(mySocket);