1. 程式人生 > >專案總結——深入淺出socket網路程式設計

專案總結——深入淺出socket網路程式設計

前言:

為什麼會有如題的概念呢,我想對於沒有主動聽說過socket網路程式設計的人來說讀到題目可能就已經矇頭了,為了很好的讓大家進入場景,首先說一下一個需要用到這點東西的業務需求。

首先大家應該明確的是socket網路程式設計是以CS的模式下才有的,比如機房收費系統,在機房收費系統中可能會遇到不同的機房使用的收費系統是使用同一個資料庫的。但是對於一些顯示的資訊,只有每次查詢資料庫的時候才能更新到窗體中,拿最簡單的主介面的當前上機的人數來說,有機房A和機房B兩個機房,並且他們是使用同一個資料庫的,然後兩個值班教師在同一時間工作,但機房A增加或減少上機的人數,機房B的值班教師是不能實時的接到訊息並更新自己的窗體的。這就會導致窗體顯示的資訊暫時性的錯誤。這時就用到了我們的

socket網路程式設計。

一、官方的解釋socket機制:

在網路程式設計中最常用的方案便是Client/Server(客戶機/伺服器)模型。在這種方案中客戶應用程式向伺服器程式請求服務。一個服務程式通常在一個眾所周知的地址監聽對服務的請求,也就是說,服務程序一 直處於休眠狀態,直到一個客戶向這個服務的地址提出了連線請求。在這個時刻,服務程式被"驚醒"並且為客戶提供服務-對客戶的請求作出適當的反應。

為了方便這種Client/Server模型的網路程式設計,90年代初,由Microsoft聯合了其他幾家公司共同制定了一套WINDOWS下的網路程式設計介面,即WindowsSockets規範,它不是一種網路協議,而是一套開放的、支援多種協議的Windows下的網路程式設計介面。現在的Winsock已經基本上實現了與協議無關,你可以使用Winsock來呼叫多種協議的功能,但較常使用的是TCP/IP協議。Socket實際在計算機中提供了一個通訊埠,可以通過這個埠與任何一個具有Socket介面的計算機通訊。應用程式在網路上傳輸,接收的資訊都通過這個Socket介面來實現。

二、通俗的理解socket

我們可以簡單的把Socket理解為一個可以連通網路上不同計算機程式之間的管道,把一堆資料從管道的A端扔進去,則會從管道的B端(也許同時還可以從C、D、E、F……端冒出來)。管道的埠由兩個因素來唯一確認,即機器的IP地址和程式所使用的埠號。IP地址的含義所有人都知道,所謂埠號就是程式設計師指定的一個數字,許多著名的木馬程式成天在網路上掃描不同的埠號就是為了獲取一個可以連通的埠從而進行破壞。比較著名的埠號有http的80埠,當然,建議大家自己寫程式不要使用太小的埠號,它們一般被系統佔用了,也不要使用一些著名的埠,一般來說使用1000~5000之內的埠比較好。

Socket可以支援資料的傳送和接收,它會定義一種稱為套接字的變數,傳送資料時首先建立套接字,然後使用該套接字的send等方法對準某個IP/埠進行資料傳送;接收端也首先建立套接字,然後將該套接字繫結到一個IP/埠上,所有發向此埠的資料會被該套接字的recv等函式讀出。如同讀出檔案中的資料一樣。

三、例子:

說了這麼多我想大家對於socket 有了簡單的認識,當然還是要在程式碼中深入的理解。

在看程式碼之前,還要做最後的解釋,socket程式設計一定使用客戶端和伺服器的,雖然接下來的例子中只是一對一的通訊,但其實和一對多的通訊是一個原理的。

1.伺服器程式碼

首先看伺服器程式碼:

/*********************************************************
 * 開發人員:韓義
 * 建立時間:2013/9/27 13:51:48
 * 描述說明:socket網路變成例項伺服器
 * 版本:1.0
 *  版權所有:資訊科技提高班        
 * *******************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;

namespace Server
{
    public partial class server : Form
    {

        Socket s = null;
        IPEndPoint iep = null;
        byte[] buf = new byte[1024];
        Socket worker = null;

        public server()
        {
            InitializeComponent();
            Control.CheckForIllegalCrossThreadCalls = false;
        }
        //啟動伺服器。--韓義
        private void button1_Click_1(object sender, EventArgs e)
        {
            //建立一個通道
            s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //建立一個偵聽點
            iep = new IPEndPoint(IPAddress.Any, 20000);

            //繫結到通道上
            s.Bind(iep);

            //偵聽
            s.Listen(6);
            //通過非同步來處理,開啟監聽連線,並交給Accept函式處理連線
            s.BeginAccept(new AsyncCallback(Accept), s);
            this.button1.Visible = false;
        }

        //接收到連線的動作--韓義
        void Accept(IAsyncResult ia)
        {
            //獲取使用者定義的物件,它限定或包含關於非同步操作的資訊。
            s = ia.AsyncState as Socket;//此屬性返回一個物件,該物件是啟動非同步操作的方法的最後一個引數。
            worker = s.EndAccept(ia);//返回一個 Socket,它處理與遠端主機的通訊。
            s.BeginAccept(new AsyncCallback(Accept), s);//重新接收連結,並制定回掉函式

            try
            {
                //開始接受資料
                worker.BeginReceive(buf, 0, buf.Length, SocketFlags.None, new AsyncCallback(Receive), worker);
            }
            catch
            { throw; }
        }

        //收到訊息的動作--韓義
        void Receive(IAsyncResult ia)
        {
            //獲取使用者定義的物件,它限定或包含關於非同步操作的資訊。
            worker = ia.AsyncState as Socket;
            //獲取資料長度
            int count = worker.EndReceive(ia);
            //自己開始接收資料。
            worker.BeginReceive(buf, 0, buf.Length, SocketFlags.None, new AsyncCallback(Receive), worker);
            //按GB2312的標準取出資料
            string context = Encoding.GetEncoding("gb2312").GetString(buf, 0, count);
            this.textBox1.Text += Environment.NewLine;
            this.textBox1.Text += context;
        }
        //傳送訊息--韓義
        private void button2_Click_1(object sender, EventArgs e)
        {
            string context = "管理員:" + this.textBox2.Text.Trim();

            if (context != "")
            {
                this.textBox1.Text += Environment.NewLine;//換行
                this.textBox1.Text += context;
                this.textBox2.Text = "";
                worker.Send(Encoding.GetEncoding("gb2312").GetBytes(context));
            }
        }
    }
}

2.伺服器介面:


3.客戶端程式碼

/*********************************************************
 * 開發人員:韓義
 * 建立時間:2013/9/27 13:51:48
 * 描述說明:socket網路變成例項客戶端
 * 版本:1.0
 *  版權所有:資訊科技提高班        
 * *******************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;

namespace Client
{
    public partial class client : Form
    {
        Socket s = null;
        IPEndPoint iep = null;
        byte[] buf = new byte[1024];

        public client()
        {
            InitializeComponent();
            Control.CheckForIllegalCrossThreadCalls = false;
        }
        //連線伺服器--韓義
        private void button1_Click_1(object sender, EventArgs e)
        {
            s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            iep = new IPEndPoint(IPAddress.Parse("127.0.0.1"),20000);
            try
            {
                s.Connect(iep);   //建立與遠端主機的連線
                this.label1.Text = "連線成功";//連線成功提示
                this.button1.Visible  = false;//隱藏連線按鈕
            }
            catch
            { throw; }
            try
            {
                //開始接收連線。
                s.BeginReceive(buf, 0, buf.Length, SocketFlags.None, new AsyncCallback(Receive), s);
            }
            catch
            { throw; }
        }
        //傳送訊息--韓義
        private void button2_Click_1(object sender, EventArgs e)
        {            
            string context = iep.ToString() + ":" + this.textBox2.Text.Trim();
            if (context != "")
            {
                this.textBox1.Text += Environment.NewLine;
                this.textBox1.Text += context;
                this.textBox2.Text = "";
                //傳送訊息
                s.Send(Encoding.GetEncoding("gb2312").GetBytes(context));
            }
        }

        //收到訊息的動作--韓義
        void Receive(IAsyncResult ia)
        {
            s = ia.AsyncState as Socket;
            int count = s.EndReceive(ia);
            //自己開始接收資料
            s.BeginReceive(buf, 0, buf.Length, SocketFlags.None, new AsyncCallback(Receive), s);
            string context = Encoding.GetEncoding("gb2312").GetString(buf, 0, count);
            this.textBox1.Text += Environment.NewLine;
            this.textBox1.Text += context;
        }
    }
}

4.客戶端介面:


當然大家會發現其實這就是一個聊天工具,和一個好友聊天就是一對一,群聊天就是一對多。這又進一步的幫助理解網路程式設計的含義。

四、總結:

通過以上的程式碼,我們可以大概的總結一下socket程式設計的幾個步驟:

  1. 建立Socket

在伺服器端建立一個監聽的Socket,為此可以呼叫Socket()函式用來建立這個監聽的Socket,並定義此Socket所使用的通訊協議。此函式呼叫成功返回Socket物件,失敗則返回INVALID_SOCKET

初始化呼叫的socket的建構函式,MSDN上共有三種建構函式過載,這裡我們用的是第三種

  1. 繫結埠

接下來要為伺服器端定義的這個監聽的Socket指定一個地址及埠(Port),這樣客戶端才知道待會要連線哪一個地址的哪個埠,為此我們要呼叫bind()函式,該函式呼叫成功返回0,否則返回SOCKET_ERROR。

  1. 監聽

當伺服器端的Socket物件繫結完成之後,伺服器端必須建立一個監聽的佇列來接收客戶端的連線請求。listen()函式使伺服器端的Socket進入監聽狀態,並設定可以建立的最大連線數(目前最大值限制為 6, 最小值為1)。該函式呼叫成功返回0,否則返回SOCKET_ERROR。

  1. 伺服器端接受客戶端的連線請求

當Client提出連線請求時,Server端監聽視窗會收到客戶端送來我們自定義的一個訊息,這時,我們可以分析lParam,然後呼叫相關的函式來處理此事件。為了使伺服器端接受客戶端的連線請求,就要使用accept()函式,該函式新建一Socket與客戶端的Socket相通,原先監聽之Socket繼續進入監聽狀態,等待他人的連線要求。該函式呼叫成功返回一個新產生的Socket物件,否則返回INVALID_SOCKET。

以上可以說是socket網路程式設計的核心內容。其實真的沒有那麼難理解

特別說明:

  1. 本例項只是一對一的通訊,一對多的通訊或多對多的通訊只需在此基礎上略加改動。
  2. 本例項只是很簡單的小例子,有需要複用的程式碼請注意命名規範。