1. 程式人生 > >socket程式設計,serverSocket

socket程式設計,serverSocket

     對TCP/IPUDPSocket程式設計這些詞你不會很陌生吧?隨著網路技術的發展,這些詞充斥著我們的耳朵。那麼我想問:

1.         什麼是TCP/IPUDP
2.         Socket在哪裡呢?
3.         Socket

是什麼呢?
4.         你會使用它們嗎?

什麼是TCP/IPUDP

         TCP/IPTransmission Control Protocol/Internet Protocol)即傳輸控制協議/網間協議,是一個工業標準的協議集,它是為廣域網(WANs)設計的。
         UDPUser Data Protocol

,使用者資料報協議)是與TCP相對應的協議。它是屬於TCP/IP協議族中的一種。
        這裡有一張圖,表明了這些協議的關係。

                                                                                

                                                                        圖1

       TCP/IP協議族包括運輸層、網路層、鏈路層。現在你知道TCP/IPUDP的關係了吧。
Socket在哪裡呢?
       
在圖1中,我們沒有看到Socket的影子,那麼它到底在哪裡呢?還是用圖來說話,一目瞭然。



2

       原來Socket在這裡。
Socket是什麼呢?
       Socket是應用層與TCP/IP協議族通訊的中間軟體抽象層,它是一組介面。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket介面後面,對使用者來說,一組簡單的介面就是全部,讓Socket去組織資料,以符合指定的協議。
你會使用它們嗎?
       
前人已經給我們做了好多的事了,網路間的通訊也就簡單了許多,但畢竟還是有挺多工作要做的。以前聽到Socket程式設計,覺得它是比較高深的程式設計知識,但是隻要弄清Socket程式設計的工作原理,神祕的面紗也就揭開了。
       一個生活中的場景。你要打電話給一個朋友,先撥號,朋友聽到電話鈴聲後提起電話,這時你和你的朋友就建立起了連線,就可以講話了。等交流結束,結束通話電話結束此次交談。    生活中的場景就解釋了這工作原理,也許TCP/IP協議族就是誕生於生活中,這也不一定。

      

3

       先從伺服器端說起。伺服器端先初始化Socket,然後與埠繫結(bind),對埠進行監聽(listen),呼叫accept阻塞,等待客戶端連線。在這時如果有個客戶端初始化一個Socket,然後連線伺服器(connect),如果連線成功,這時客戶端與伺服器端的連線就建立了。客戶端傳送資料請求,伺服器端接收請求並處理請求,然後把迴應資料傳送給客戶端,客戶端讀取資料,最後關閉連線,一次互動結束。
       在這裡我就舉個簡單的例子,我們走的是TCP協議這條路(見圖2)。例子用MFC編寫,執行的介面如下:



4



5

       在客戶端輸入伺服器端的IP地址和傳送的資料,然後按傳送按鈕,伺服器端接收到資料,然後迴應客戶端。客戶端讀取回應的資料,顯示在介面上。

       下面是接收資料和傳送資料的函式:



ServerSocket類
   
建立一個ServerSocket類,同時在執行該語句的計算機的指定埠處建立一個監聽服務,如:
    ServerSocket MyListener=new ServerSocket(600);
    這裡指定提供監聽服務的埠是600,一臺計算機可以同時提供多個服務,這些不同的服務之間通過埠號來區別,不同的埠號上提供不同的服務。為了隨時監聽可能的Client請求,執行如下的語句:
    Socket LinkSocket=MyListener.accept();
    該語句呼叫了ServerSocket物件的accept()方法,這個方法的執行將使Server端的程式處於等待狀態,程式將一直阻塞直到捕捉到一個來自Client端的請求,並返回一個用於與該Client通訊的Socket物件Link-Socket。此後Server程式只要向這個Socket物件讀寫資料,就可以實現向遠端的Client讀寫資料。結束監聽時,關閉ServerSocket物件:
    Mylistener.close();

是用作伺服器端程式設計的
    在伺服器端,利用ServerSocket類的建構函式ServerSocket(int port)建立一個ServerSocket類的物件,port引數傳遞埠,這個埠就是伺服器監聽連線請求的埠,如果在這時出現錯誤將丟擲IOException異常物件,否則將建立ServerSocket物件並開始準備接收連線請求。
    服務程式從呼叫ServerSocket的accept()方法開始,直到連線建立。在建立連線後,accept()返回一個最近建立的Socket物件,該Socket物件綁定了客戶程式的IP地址或埠號。

Socket類
    當Client程式需要從Server端獲取資訊及其他服務時,應建立一個Socket物件:
    Socket MySocket=new Socket(“ServerComputerName”,600);
    Socket類的建構函式有兩個引數,第一個引數是欲連線到的Server計算機的主機地址,第二個引數是該Server機上提供服務的埠號。
    Socket物件建立成功之後,就可以在Client和Server之間建立一個連線,並通過這個連線在兩個端點之間傳遞資料。利用Socket類的方法getOutputStream()和getInputStream()分別獲得向Socket讀寫資料的輸入/輸出流,最後將從Server端讀取的資料重新返還到Server端。
    當Server和Client端的通訊結束時,可以呼叫Socket類的close()方法關閉Socket,拆除連線。

ServerSocket 一般僅用於設定埠號和監聽,真正進行通訊的是伺服器端的Socket與客戶端的Socket,在ServerSocket 進行accept之後,就將主動權轉讓了。

客戶端程式設計
    當客戶程式需要與伺服器程式通訊時,需在客戶機建立一個Socket物件。Socket類有建構函式Socket(InetAddress addr,int port)和Socket(String host,intport),兩個建構函式都建立了一個基於Socket的連線伺服器端流套接字的流套接字。對於第一個InetAd-dress子類物件通過addr引數獲得伺服器主機的IP地址,對於第二個函式host引數包被分配到InetAddress物件中,如果沒有IP地址與host引數相一致,那麼將丟擲UnknownHostException異常物件。兩個函式都通過引數port獲得伺服器的埠號。假設已經建立連線了,網路API將在客戶端基於Socket的流套接字中捆綁客戶程式的IP地址和任意一個埠號,否則兩個函式都會丟擲一個IOException物件。
    如果建立了一個Socket物件,那麼它可通過get-InputStream()方法從服務程式獲得輸入流讀傳送來的資訊,也可通過呼叫getOutputStream()方法獲得輸出流來發送訊息。在讀寫活動完成之後,客戶程式呼叫close()方法關閉流和流套接字。


互動過程 Socket與ServerSocket的互動,下面的圖片我覺得已經說的很詳細很清楚了。   Socket 建構函式

Socket()

Socket(InetAddress address, int port)throws UnknownHostException, IOException Socket(InetAddress address, int port, InetAddress localAddress, int localPort)throws IOException Socket(String host, int port)throws UnknownHostException, IOException Socket(String host, int port, InetAddress localAddress, int localPort)throws IOException   除去第一種不帶引數的之外,其它建構函式會嘗試建立與伺服器的連線。如果失敗會丟擲IOException錯誤。如果成功,則返回Socket物件。 InetAddress是一個用於記錄主機的類,其靜態getHostByName(String msg)可以返回一個例項,其靜態方法getLocalHost()也可以獲得當前主機的IP地址,並返回一個例項。Socket(String host, int port, InetAddress localAddress, int localPort)建構函式的引數分別為目標IP、目標埠、繫結本地IP、繫結本地埠。   Socket方法 getInetAddress();      遠端服務端的IP地址 getPort();          遠端服務端的埠 getLocalAddress()      本地客戶端的IP地址 getLocalPort()        本地客戶端的埠 getInputStream();     獲得輸入流 getOutStream();      獲得輸出流 值得注意的是,在這些方法裡面,最重要的就是getInputStream()和getOutputStream()了。   Socket狀態 isClosed();            //連線是否已關閉,若關閉,返回true;否則返回false isConnect();      //如果曾經連線過,返回true;否則返回false isBound();            //如果Socket已經與本地一個埠繫結,返回true;否則返回false 如果要確認Socket的狀態是否處於連線中,下面語句是很好的判斷方式。
boolean isConnection=socket.isConnected() && !socket.isClosed();   //判斷當前是否處於連線

 

半關閉Socket 很多時候,我們並不知道在獲得的輸入流裡面到底讀多長才結束。下面是一些比較普遍的方法:
  • 自定義識別符號(譬如下面的例子,當受到“bye”字串的時候,關閉Socket)
  • 告知讀取長度(有些自定義協議的,固定前幾個位元組表示讀取的長度的)
  • 讀完所有資料
  • 當Socket呼叫close的時候關閉的時候,關閉其輸入輸出流
  ServerSocket 建構函式 ServerSocket()throws IOException ServerSocket(int port)throws IOException ServerSocket(int port, int backlog)throws IOException ServerSocket(int port, int backlog, InetAddress bindAddr)throws IOException   注意點: 1. port服務端要監聽的埠;backlog客戶端連線請求的佇列長度;bindAddr服務端繫結IP 2. 如果埠被佔用或者沒有許可權使用某些埠會丟擲BindException錯誤。譬如1~1023的埠需要管理員才擁有許可權繫結。 3. 如果設定埠為0,則系統會自動為其分配一個埠; 4. bindAddr用於繫結伺服器IP,為什麼會有這樣的設定呢,譬如有些機器有多個網絡卡。 5. ServerSocket一旦綁定了監聽埠,就無法更改。ServerSocket()可以實現在繫結埠前設定其他的引數。 單執行緒的ServerSocket例子 複製程式碼
public void service(){
    while(true){
        Socket socket=null;
        try{
            socket=serverSocket.accept();//從連線佇列中取出一個連線,如果沒有則等待
            System.out.println("新增連線:"+socket.getInetAddress()+":"+socket.getPort());
            ...//接收和傳送資料
        }catch(IOException e){e.printStackTrace();}finally{
            try{
                if(socket!=null) socket.close();//與一個客戶端通訊結束後,要關閉Socket
            }catch(IOException e){e.printStackTrace();}
        }
    }
}
複製程式碼

 

多執行緒的ServerSocket 多執行緒的好處不用多說,而且大多數的場景都是多執行緒的,無論是我們的即時類遊戲還是IM,多執行緒的需求都是必須的。下面說說實現方式:
  • 主執行緒會迴圈執行ServerSocket.accept();
  • 當拿到客戶端連線請求的時候,就會將Socket物件傳遞給多執行緒,讓多執行緒去執行具體的操作;
實現多執行緒的方法要麼繼承Thread類,要麼實現Runnable介面。當然也可以使用執行緒池,但實現的本質都是差不多的。   這裡舉例: 下面程式碼為伺服器的主執行緒。為每個客戶分配一個工作執行緒: 複製程式碼
public void service(){
    while(true){
        Socket socket=null;
        try{
            socket=serverSocket.accept();                        //主執行緒獲取客戶端連線
            Thread workThread=new Thread(new Handler(socket));    //建立執行緒
            workThread.start();                                    //啟動執行緒
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
複製程式碼

 

當然這裡的重點在於如何實現Handler這個類。Handler需要實現Runnable介面: 複製程式碼
class Handler implements Runnable{
    private Socket socket;
    public Handler(Socket socket){
        this.socket=socket;
    }
</span><span style="color:rgb(0,0,255);line-height:1.5 !important;">public</span> <span style="color:rgb(0,0,255);line-height:1.5 !important;">void</span><span style="line-height:1.5 !important;"> run(){
    </span><span style="color:rgb(0,0,255);line-height:1.5 !important;">try</span><span style="line-height:1.5 !important;">{
        System.out.println(</span>"新連線:"+socket.getInetAddress()+":"+<span style="line-height:1.5 !important;">socket.getPort());
        Thread.sleep(</span>10000<span style="line-height:1.5 !important;">);
    }</span><span style="color:rgb(0,0,255);line-height:1.5 !important;">catch</span>(Exception e){e.printStackTrace();}<span style="color:rgb(0,0,255);line-height:1.5 !important;">finally</span><span style="line-height:1.5 !important;">{
        </span><span style="color:rgb(0,0,255);line-height:1.5 !important;">try</span><span style="line-height:1.5 !important;">{
            System.out.println(</span>"關閉連線:"+socket.getInetAddress()+":"+<span style="line-height:1.5 !important;">socket.getPort());
            </span><span style="color:rgb(0,0,255);line-height:1.5 !important;">if</span>(socket!=<span style="color:rgb(0,0,255);line-height:1.5 !important;">null</span><span style="line-height:1.5 !important;">)socket.close();
        }</span><span style="color:rgb(0,0,255);line-height:1.5 !important;">catch</span><span style="line-height:1.5 !important;">(IOException e){
            e.printStackTrace();
        }
    }
}

}

複製程式碼

當然是先多執行緒還有其它的方式,譬如執行緒池,或者JVM自帶的執行緒池都可以。這裡就不說明了。