鍍金池/ 教程/ Java/ Java TCP Socket 編程
Java TCP Socket 編程
深入剖析 Socket——TCP 套接字的生命周期
基于線程池的 TCP 服務(wù)器
構(gòu)建和解析自定義協(xié)議消息
Socket 通信中由 read 返回值造成的的死鎖問題
基于 NIO 的 TCP 通信
深入剖析 Socket——數(shù)據(jù)傳輸?shù)牡讓訉崿F(xiàn)
Socket 簡介
UDP Socket 編程
Java NIO Socket VS 標準 IO Socket
深入剖析 Socket——TCP 通信中由于底層隊列填滿而造成的死鎖問題
應(yīng)用程序協(xié)議中消息的成幀與解析

Java TCP Socket 編程

TCP 的 Java 支持

協(xié)議相當于相互通信的程序間達成的一種約定,它規(guī)定了分組報文的結(jié)構(gòu)、交換方式、包含的意義以及怎樣對報文所包含的信息進行解析,TCP/IP 協(xié)議族有 IP 協(xié)議、TCP 協(xié)議和 UDP 協(xié)議?,F(xiàn)在 TCP/IP 協(xié)議族中的主要 socket 類型為流套接字(使用 TCP 協(xié)議)和數(shù)據(jù)報套接字(使用 UDP 協(xié)議)。

TCP 協(xié)議提供面向連接的服務(wù),通過它建立的是可靠地連接。Java 為 TCP 協(xié)議提供了兩個類:Socke 類和 ServerSocket 類。一個 Socket 實例代表了 TCP 連接的一個客戶端,而一個 ServerSocket 實例代表了 TCP 連接的一個服務(wù)器端,一般在 TCP Socket 編程中,客戶端有多個,而服務(wù)器端只有一個,客戶端 TCP 向服務(wù)器端 TCP 發(fā)送連接請求,服務(wù)器端的 ServerSocket 實例則監(jiān)聽來自客戶端的 TCP 連接請求,并為每個請求創(chuàng)建新的 Socket 實例,由于服務(wù)端在調(diào)用 accept()等待客戶端的連接請求時會阻塞,直到收到客戶端發(fā)送的連接請求才會繼續(xù)往下執(zhí)行代碼,因此要為每個 Socket 連接開啟一個線程。服務(wù)器端要同時處理 ServerSocket 實例和 Socket 實例,而客戶端只需要使用 Socket 實例。另外,每個 Socket 實例會關(guān)聯(lián)一個 InputStream 和 OutputStream 對象,我們通過將字節(jié)寫入套接字的 OutputStream 來發(fā)送數(shù)據(jù),并通過從 InputStream 來接收數(shù)據(jù)。

TCP 連接的建立步驟

客戶端向服務(wù)器端發(fā)送連接請求后,就被動地等待服務(wù)器的響應(yīng)。典型的 TCP 客戶端要經(jīng)過下面三步操作:

  • 創(chuàng)建一個 Socket 實例:構(gòu)造函數(shù)向指定的遠程主機和端口建立一個 TCP 連接;
  • 通過套接字的 I/O 流與服務(wù)端通信;
  • 使用 Socket 類的 close 方法關(guān)閉連接。

服務(wù)端的工作是建立一個通信終端,并被動地等待客戶端的連接。

典型的 TCP 服務(wù)端執(zhí)行如下兩步操作:

  1. 創(chuàng)建一個 ServerSocket 實例并指定本地端口,用來監(jiān)聽客戶端在該端口發(fā)送的 TCP 連接請求;

  2. 重復(fù)執(zhí)行:

    • 調(diào)用 ServerSocket 的 accept()方法以獲取客戶端連接,并通過其返回值創(chuàng)建一個 Socket 實例;

    • 為返回的 Socket 實例開啟新的線程,并使用返回的 Socket 實例的 I/O 流與客戶端通信; 通信完成后,使用 Socket 類的 close()方法關(guān)閉該客戶端的套接字連接。

TCP Socket Demo

下面給出一個客戶端服務(wù)端 TCP 通信的 Demo,該客戶端在 20006 端口請求與服務(wù)端建立 TCP 連接,客戶端不斷接收鍵盤輸入,并將其發(fā)送到服務(wù)端,服務(wù)端在接收到的數(shù)據(jù)前面加上“echo”字符串,并將組合后的字符串發(fā)回給客戶端,如此循環(huán),直到客戶端接收到鍵盤輸入“bye”為止。

客戶端代碼如下:

package zyb.org.client;  

import java.io.BufferedReader;  
import java.io.IOException;  
import java.io.InputStreamReader;  
import java.io.PrintStream;  
import java.net.Socket;  
import java.net.SocketTimeoutException;  

public class Client1 {  
    public static void main(String[] args) throws IOException {  
        //客戶端請求與本機在20006端口建立TCP連接   
        Socket client = new Socket("127.0.0.1", 20006);  
        client.setSoTimeout(10000);  
        //獲取鍵盤輸入   
        BufferedReader input = new BufferedReader(new InputStreamReader(System.in));  
        //獲取Socket的輸出流,用來發(fā)送數(shù)據(jù)到服務(wù)端    
        PrintStream out = new PrintStream(client.getOutputStream());  
        //獲取Socket的輸入流,用來接收從服務(wù)端發(fā)送過來的數(shù)據(jù)    
        BufferedReader buf =  new BufferedReader(new InputStreamReader(client.getInputStream()));  
        boolean flag = true;  
        while(flag){  
            System.out.print("輸入信息:");  
            String str = input.readLine();  
            //發(fā)送數(shù)據(jù)到服務(wù)端    
            out.println(str);  
            if("bye".equals(str)){  
                flag = false;  
            }else{  
                try{  
                    //從服務(wù)器端接收數(shù)據(jù)有個時間限制(系統(tǒng)自設(shè),也可以自己設(shè)置),超過了這個時間,便會拋出該異常  
                    String echo = buf.readLine();  
                    System.out.println(echo);  
                }catch(SocketTimeoutException e){  
                    System.out.println("Time out, No response");  
                }  
            }  
        }  
        input.close();  
        if(client != null){  
            //如果構(gòu)造函數(shù)建立起了連接,則關(guān)閉套接字,如果沒有建立起連接,自然不用關(guān)閉  
            client.close(); //只關(guān)閉socket,其關(guān)聯(lián)的輸入輸出流也會被關(guān)閉  
        }  
    }  
}  

服務(wù)端需要用到多線程,這里單獨寫了一個多線程類,代碼如下:

package zyb.org.server;  

import java.io.BufferedReader;  
import java.io.InputStreamReader;  
import java.io.PrintStream;  
import java.net.Socket;  

/** 
 * 該類為多線程類,用于服務(wù)端 
 */  
public class ServerThread implements Runnable {  

    private Socket client = null;  
    public ServerThread(Socket client){  
        this.client = client;  
    }  

    @Override  
    public void run() {  
        try{  
            //獲取Socket的輸出流,用來向客戶端發(fā)送數(shù)據(jù)  
            PrintStream out = new PrintStream(client.getOutputStream());  
            //獲取Socket的輸入流,用來接收從客戶端發(fā)送過來的數(shù)據(jù)  
            BufferedReader buf = new BufferedReader(new InputStreamReader(client.getInputStream()));  
            boolean flag =true;  
            while(flag){  
                //接收從客戶端發(fā)送過來的數(shù)據(jù)  
                String str =  buf.readLine();  
                if(str == null || "".equals(str)){  
                    flag = false;  
                }else{  
                    if("bye".equals(str)){  
                        flag = false;  
                    }else{  
                        //將接收到的字符串前面加上echo,發(fā)送到對應(yīng)的客戶端  
                        out.println("echo:" + str);  
                    }  
                }  
            }  
            out.close();  
            client.close();  
        }catch(Exception e){  
            e.printStackTrace();  
        }  
    }  

}  

服務(wù)端處理 TCP 連接請求的代碼如下:

package zyb.org.server;  

import java.net.ServerSocket;  
import java.net.Socket;  

public class Server1 {  
    public static void main(String[] args) throws Exception{  
        //服務(wù)端在20006端口監(jiān)聽客戶端請求的TCP連接  
        ServerSocket server = new ServerSocket(20006);  
        Socket client = null;  
        boolean f = true;  
        while(f){  
            //等待客戶端的連接,如果沒有獲取連接  
            client = server.accept();  
            System.out.println("與客戶端連接成功!");  
            //為每個客戶端連接開啟一個線程  
            new Thread(new ServerThread(client)).start();  
        }  
        server.close();  
    }  
}  

執(zhí)行結(jié)果截圖如下:

http://wiki.jikexueyuan.com/project/java-socket/images/tcpresult.jpg" alt="" />

http://wiki.jikexueyuan.com/project/java-socket/images/tcpresult1.jpg" alt="" />