1. 程式人生 > >TCP/IP簡介和Android中Socket及http的使用

TCP/IP簡介和Android中Socket及http的使用

計算機網路及其協議

概述

OSI/RM

在1978年國際標準化組織(ISO)提出了“開放系統互連參考模型”,即著名的OSI/RM 模型(Open System Interconnection/Reference Model)。此後,不同廠家生產的計算機便能相互通訊,建立起了計算機網路。OSI/RM將計算機網路體系結構的通訊協議劃分為七層,自下而上依次為:物理層、資料鏈路層、網路層、傳輸層、會話層、表示層、應用層。

IPTCP UDP 協議

由於OSI/RM 模型過於複雜也難以實現,現實中廣泛應用的是TCP/IP 模型。它是由ARPA 於1977 年到1979 年推出的一種網路體系結構和協議規範。TCP/IP 是一個協議集,而非指TCP 和 IP 兩種協議。在很多情況下,它是利用IP 進行通訊時所必須用到的協議群的統稱。具體來說,IP 或 ICMP、TCP 或 UDP、TELNET 或 FTP、以及 HTTP 等都屬於 TCP/IP 協議。他們與 TCP 或 IP 的關係緊密,是網際網路必不可少的組成部分。TCP/IP 一詞泛指這些協議,因此,有時也稱 TCP/IP 為網際協議群。網際網路進行通訊時,需要相應的網路協議,TCP/IP 原本就是為使用網際網路而開發制定的協議族。因此,網際網路的協議就是 TCP/IP,TCP/IP 就是網際網路的協議。

TCP/IP 模型也是分層模型,分為4 層。OSI/RM 模型與TCP/IP 模型的參考層次如圖所示:

應用層:是大多數普通與網路相關的程式為了通過網路與其他程式通訊所使用的層。在應用層中,資料以應用內部使用的格式進行傳送,然後被編碼成標準協議的格式。如HTTP 協議、FTP 協議、接收電子郵件的POP3 和IMAP 協議、傳送郵件的SMTP 協議,以及遠端登入使用的SSH 和Telnet 等。所以使用者通常是與應用層進行互動。

傳輸層:提供兩臺主機之間透明的資料傳輸,通常用於端到端連線、流量控制或錯誤恢復。這一層的兩個最重要的協議是TCP(Transmission Control Protocol,傳輸控制協議)和UDP(User Datagram Protocol,使用者資料報協議)。

網路層:提供端到端的資料包交付,它負責資料包從源傳送到目的地,任務包括網路路由、差錯控制和IP 編址等。這一層包括的重要協議有IP(版本4 和版本6)、ICMP(Internet Control Message Protocol,Internet 控制報文協議)和IPSec(Internet Protocol Security,Internet 協議安全)。

網路介面層:負責通過網路傳送和接收IP 資料報;允許主機連入網路時使用多種現成的與流行的技術,如乙太網、令牌網、幀中繼、ATM、X.25、DDN、SDH、WDM 等。

一個應用層應用一般都會使用到兩個傳輸層協議之一:面向連線的

TCP 傳輸控制協議 面向無連線的UDP 使用者資料報協議。下面分析TCP/IP 協議棧中常用的IP、TCP 和UDP 協議。

IP 協議

網際網路協議(Internet Protocol,IP)是用於報文交換網路的一種面向資料的協議。IP是在TCP/IP 協議中網路層的主要協議,任務是根據源主機和目的主機的地址傳送資料。為達到此目的,IP 定義了定址方法和資料報的封裝結構。第一個架構的主要版本,現在稱為IPv4,仍然是最主要的網際網路協議。當前世界各地正在積極部署IPv6。

TCP 協議

傳輸控制協議(Transmission Control Protocol,TCP)是一種面向連線的、可靠的、基於位元組流的傳輸層通訊協議。流就是指不間斷的資料結構,當應用程式採用 TCP 傳送訊息時,雖然可以保證傳送的順序,但還是猶如沒有任何間隔的資料流傳送給接收端。TCP 為提供可靠性傳輸,可以進行丟包時的重發控制,還可以對次序亂掉的分包進行順序控制的機制。此外,因為TCP 作為一種面向有連線的協議,只有在確認通訊對端存在時才會傳送資料,從而還具備“流量控制”、“擁塞控制”、提高網路利用率等眾多功能。著名的三次握手就是指建立一個 TCP 連線時需要客戶端和伺服器端總共傳送三個包以確認連線的建立,而終止TCP連線就是四次揮手,需要客戶端和服務端總共傳送4個包以確認連線的斷開。

UDP 協議

使用者資料報協議(User Datagram Protocol ,UDP)是TCP/IP 模型中一種面向無連線的傳輸層協議,提供面向事務的簡單不可靠資訊傳送服務。UDP 協議基本上是IP 協議與上層協議的介面。UDP 協議適用於埠分別執行在同一臺裝置上的多個應用程式中。與TCP 不同,UDP 並不提供對IP 協議的可靠機制、流控制以及錯誤恢復功能等,在資料傳輸之前不需要建立連線。由於UDP 比較簡單,UDP 頭包含很少的位元組,所以比TCP負載消耗少。UDP 適用於不需要TCP 可靠機制的情形,比如,當高層協議或應用程式提供錯誤和流控制功能的時候。UDP 服務於很多知名應用層協議,包括網路檔案系統(Network File System,NFS)、簡單網路管理協議(Simple Network Management Protocol,SNMP)、域名系統(DomainName System,DNS)以及簡單檔案傳輸系統(Trivial File Transfer Protocol,TFTP)。

在Android 中使用TCP、UDP 協議

Socket字面翻譯是“插座”,通常也稱作“套接字”,是對TCP/IP的封裝的程式設計介面。Socket把複雜的TCP/IP 協議族隱藏在Socket 介面後面。Socket 用於描述IP地址和埠,是一個通訊鏈的控制代碼。應用程式通常通過Socket向網路發出請求或者應答網路請求。就像一臺伺服器可能會提供很多服務,每種服務對應一個Socket,並繫結到一個埠上,不同的埠對應於不同的服務,或者比喻成每個服務就是一個Socket插座,客戶端若是需要哪種服務,就將它的Socket插頭插到相應的插座上面。有一個比喻:HTTP是轎車,提供了封裝或者顯示資料的具體形式;Socket是發動機,提供了網路通訊的能力。

Socket 的基本操作包括:

連線遠端機器

傳送資料

接收資料

關閉連線

繫結埠

監聽到達資料

在繫結的埠上接受來自遠端機器的連線

 

伺服器要和客戶端通訊,兩者都要例項化一個Socket:

客戶端(java.net. Socket)可以實現連線遠端機器、傳送資料、接收資料、關閉連線等

伺服器(java.net. ServerSocket)還需要實現繫結埠,監聽到達的資料,接受來自遠端機器的連線。

 

Socket 一般有兩種型別:TCP 套接字和UDP 套接字。兩者都接收傳輸協議資料包並將其內容向前傳送到應用層。

TCP 把訊息分解成資料包(資料報,datagrams)並在接收端以正確的順序把它們重新裝配起來,TCP 還處理對遺失資料包的重傳請求,位於上層的應用層要處理的事情就少多了。

UDP 不提供裝配和重傳請求這些功能,它只是向前傳送資訊包。位於上層的應用層必須確保訊息是完整的,並且是以正確的順序裝配的。

使用TCP 通訊

TCP 建立連線之後,通訊雙方都同時可以進行資料的傳輸;在保證可靠性上,採用超時重傳和捎帶確認機制;在流量控制上,採用滑動視窗協議,協議中規定,對於視窗內未經確認的分組需要重傳;在擁塞控制上,採用慢啟動演算法

TCP 伺服器端工作的主要步驟如下:

步驟1 呼叫ServerSocket(int port)建立一個ServerSocket,並繫結到指定埠上。

步驟2 呼叫accept(),監聽連線請求,如果客戶端請求連線,則接受連線,返回通訊套接字。

步驟3 呼叫Socket 類的getOutputStream() 和getInputStream() 獲取輸出和輸入流,開始網路資料的傳送和接收。

步驟4 關閉通訊套接字。

示例程式碼如下所示:

private int TCP_SERVER_PORT = 8080;
private void serverTCPFunction() {
    ServerSocket serverSocket = null;
    try {
        // TCP_SERVER_PORT 為指定的繫結埠,為int 型別
        serverSocket = new ServerSocket(TCP_SERVER_PORT);
        // 監聽連線請求
        Socket socket = serverSocket.accept();
        // 獲取輸入流 並 寫入讀Buffer 中
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        // 獲取輸出流 並 放到寫Buffer 中
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        // 讀取接收資訊,轉換為字串
        String incomingMsg = in.readLine() + System.getProperty("line.separator");
        // 生成傳送字串
        String outgoingMsg = "goodbye from port " + TCP_SERVER_PORT +  System.getProperty("line.separator");
        // 將傳送字串寫入上面定義的BufferedWriter 中
        out.write(outgoingMsg);
        // 重新整理,傳送
        out.flush();
        // 關閉
        socket.close();
    } catch (InterruptedIOException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 判定是否初始化ServerSocket 物件,如果初始化則關閉serverSocket
        if (serverSocket != null) {
            try {
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

TCP 客戶端工作的主要步驟如下:

步驟1 呼叫Socket() 建立一個流套接字,並連線到伺服器端。

步驟2 呼叫Socket 類的getOutputStream() 和getInputStream() 方法獲取輸出和輸入流,開始網路資料的傳送和接收。

步驟3 關閉通訊套接字。

編寫TCP 客戶端程式碼如下所示:

private void clientTCPFunction() {
    try {
        // 初始化Socket,TCP_SERVER_PORT 為指定的埠,int 型別
        Socket socket = new Socket("localhost", TCP_SERVER_PORT);
        // 獲取輸入流
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        // 生成輸出流
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        // 生成輸出內容
        String outMsg = "TCP connecting to " + TCP_SERVER_PORT + System.getProperty("line.separator");
        // 寫入
        out.write(outMsg);
        // 重新整理,傳送
        out.flush();
        // 獲取輸入流
        String inMsg = in.readLine() + System.getProperty("line.separator");
        // 關閉連線
        socket.close();
    } catch (UnknownHostException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

使用UDP 通訊

UDP 有不提供資料報分組、組裝和不能對資料包排序的缺點,也就是說,當報文傳送之後,是無法得知其是否安全完整到達的。UDP 用來支援那些需要在計算機之間傳輸資料的網路應用,包括網路視訊會議系統在內的眾多的客戶端/ 伺服器模式的網路應用都需要使用UDP 協議。UDP 協議的主要作用是將網路資料流量壓縮成資料報的形式。一個典型的資料報就是一個二進位制資料的傳輸單位。

UDP 伺服器端工作的主要步驟如下:

步驟1 呼叫DatagramSocket(int port) 建立一個數據報套接字,並繫結到指定埠上。

步驟2 呼叫DatagramPacket(byte[]buf,int length),建立一個位元組陣列以接收UDP 包。

步驟3 呼叫DatagramSocket 類的receive(),接受UDP 包。

步驟4 關閉資料報套接字。

示例程式碼如下所示:

private int UDP_SERVER_PORT = 9090;
private int MAX_UDP_DATAGRAM_LEN = 1024;

private void serverDUPFunction() {
    // 接收的位元組大小,客戶端傳送的資料不能超過MAX_UDP_DATAGRAM_LEN
    byte[] lMsg = new byte[MAX_UDP_DATAGRAM_LEN];

    // 新建一個DatagramSocket 類
    DatagramSocket ds = null;
    try {
        // UDP 伺服器監聽的埠
        ds = new DatagramSocket(UDP_SERVER_PORT);
        // 例項化一個DatagramPacket 類
        DatagramPacket dp = new DatagramPacket(lMsg, lMsg.length);
        // 準備接收資料
        ds.receive(dp);
    } catch (SocketException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (ds != null) {
            ds.close();
        }
    }
}

UDP 客戶端工作的主要步驟如下:

步驟1 呼叫DatagramSocket() 建立一個數據包套接字。

步驟2 呼叫DatagramPacket(byte[]buf,int offset,int length,InetAddress address,int port),建立要傳送的UDP 包。

步驟3 呼叫DatagramSocket 類的send() 傳送UDP 包。

步驟4 關閉資料報套接字。

示例程式碼如下所示:

private void clientDUPFunction() {
    // 定義需要傳送的資訊
    String udpMsg = "hello world from UDP client " + UDP_SERVER_PORT;
    // 新建一個DatagramSocket 物件
    DatagramSocket ds = null;
    try {
        // 初始化DatagramSocket 物件
        ds = new DatagramSocket();
        // 初始化InetAddress 物件
        InetAddress serverAddr = InetAddress.getByName("127.0.0.1");
        // 初始化DatagramPacket 物件
        DatagramPacket dp = new DatagramPacket(udpMsg.getBytes(), udpMsg.length(), serverAddr, UDP_SERVER_PORT);
        // 傳送
        ds.send(dp);
    }
    catch (SocketException e) {
        e.printStackTrace();
    } catch (UnknownHostException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (ds != null) {
            ds.close();
        }
    }
}

經典的Socket 聊天室示例

服務端TCPServerService.java程式碼:

public class TCPServerService extends Service {

    public final static int SERVER_PORT = 1234;

    private boolean mIsDestoryed = false;
    private ServerSocket mServerSocket;

    @Override
    public void onCreate() {
        super.onCreate();

        new Thread() {
            @Override
            public void run() {
                initTcpServer();
            }
        }.start();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        mIsDestoryed = true;
        if (mServerSocket != null) {
            try {
                mServerSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void initTcpServer() {
        // 初始化ServerSocket
        try {
            mServerSocket = new ServerSocket(SERVER_PORT);
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }

        // 一直處於檢測客戶端連線,可連線多個客戶端
        while (!mIsDestoryed) {
            try {
                // 接受客戶端請求,若無客戶端請求則堵塞
                final Socket client = mServerSocket.accept();

                new Thread() {
                    @Override
                    public void run() {
                        try {
                            responseClient(client);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    ;
                }.start();

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void responseClient(Socket socket) throws IOException {
        // 用於接收客戶端訊息
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        // 用於向客戶端傳送訊息
        PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
        out.println("已經跟客戶端連線上了");
        while (!mIsDestoryed) {
            // 讀取客戶端訊息,若無訊息,則阻塞住
            String str = in.readLine();
            if (str == null) {
                break;
            }
            String msg = "我收到你訊息了";
            out.println(msg);
//            out.write(msg);
//            out.flush();
        }
        // 關閉流
        if (out != null) {
            out.close();
        }
        if (in != null) {
            in.close();
        }
        if (socket != null) {
            socket.close();
        }
    }
}

客戶端MainActivity.java程式碼:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private Button mSendButton;

    private PrintWriter mPrintWriter;
    private Socket mClientSocket;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mSendButton = findViewById(R.id.send);
        mSendButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mPrintWriter == null) {
                    return;
                }
                String msg = "傳送訊息";
                mPrintWriter.println(msg);
                Log.d(TAG, "Client :" + msg);
            }
        });

        startService();
        connectTCPServer();
    }

    private void startService() {
        Intent service = new Intent(this, TCPServerService.class);
        startService(service);
    }

    private void connectTCPServer() {
        new Thread() {
            @Override
            public void run() {
                onConnectTCPServer();
            }
        }.start();
    }

    private void onConnectTCPServer() {
        while (mClientSocket == null) {
            try {
                mClientSocket = new Socket("localhost", TCPServerService.SERVER_PORT);
                mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(mClientSocket.getOutputStream())), true);

            } catch (IOException e) {
                // 若連線失敗,則每2秒後重試
                SystemClock.sleep(2000);
            }
        }

        try {
            // 接收伺服器端的訊息
            BufferedReader br = new BufferedReader(new InputStreamReader(mClientSocket.getInputStream()));
            while (!MainActivity.this.isFinishing()) {
                // 讀取服務端訊息,若無訊息,則阻塞住
                String msg = br.readLine();
                Log.d(TAG, "Server :" + msg);
            }

            if (br != null) {
                br.close();
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (mClientSocket != null) {
                try {
                    mClientSocket.shutdownInput();
                    mClientSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                if (mPrintWriter != null) {
                    mPrintWriter.close();
                }
            }
        }
    }
}

程式執行後,便會在onCreate中連線服務,日誌:

MainActivity: Server :已經跟客戶端連線上了

當按下按鈕後,便會向服務端傳送一條資訊,此時服務端收到資訊也回覆了一條資訊,日誌:

MainActivity: Client :傳送訊息

MainActivity: Server :我收到你訊息了

Android 中的HTTP 程式設計

我們曾經有過文章《Android Volley的使用(一)基本網路請求》介紹過在Android程式開發中使用網路請求的情況,使用Volley框架固然是一個很不錯的選擇。但是如果你的程式正在面臨著size變大的問題,剛好就只需要一個簡單的這方面功能時,你就不得不要放棄使用第三方框架,改為SDK自帶的來自己編寫。Android本身提供了httpClient和HttpURLConnection來進行網路請求通訊。

HttpClient

的優勢在於處理一些可能需要使用者登入而且具有相應的許可權才可訪問該頁面。例如需要涉及Session、Cookie的處理時,就要使用HttpClient。但是,在Android 6.0 SDK版本已經將HttpClient類庫移除了,如果想繼續使用它,可以在相應的module下的build.gradle中加入:

 android {
       useLibrary 'org.apache.http.legacy'
 }

使用示例

    /**
     * HttpClient的Get請求
     * @param url 請求的URL
     * @return
     */
    public static String httpClientGet(String url) {
        InputStream inStream = null;
        HttpClient httpclient = null;
        try {
            httpclient = new DefaultHttpClient();
            HttpResponse response = httpclient.execute(new HttpGet(url));
            inStream = response.getEntity().getContent();

            String result = inputStreamToString(inStream);
            return result;

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (inStream != null) {
                    inStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            httpclient.getConnectionManager().shutdown();
        }
        return null;
    }

    /**
     * HttpClient的Post請求
     * @param url 請求的URL
     * @param nameValuePairs 請求引數,值的新增如:nameValuePairs.add(new BasicNameValuePair("id", "12345"));
     * @return
     */
    public static String httpClientPost(String url, List<NameValuePair> nameValuePairs) {
        InputStream inStream = null;
        HttpClient httpclient = null;

        try {
            httpclient = new DefaultHttpClient();

            HttpPost httppost = new HttpPost(url);
            httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs ,"UTF-8"));
            HttpResponse response = httpclient.execute(httppost);
            inStream = response.getEntity().getContent();

            String result = inputStreamToString(inStream);
            return result;

        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (inStream != null) {
                    inStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            httpclient.getConnectionManager().shutdown();
        }
        return null;
    }

    /**
     * 將InputStream 格式轉化為StringBuilder 格式
     * @param is
     * @return
     */
    private static String inputStreamToString(InputStream is) {
        String line = "";
        StringBuilder sb = new StringBuilder();
        try {
            BufferedReader rd = new BufferedReader(new InputStreamReader(is));
            while ((line = rd.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sb.toString();
// 或
//        Scanner scanner = new Scanner(is).useDelimiter("\\A");
//        return scanner.hasNext() ? scanner.next() : "";
    }

HttpURLConnection

如果在一般的網路請求情況下,只是需要簡單的網路請求或表單提交請求,HttpURLConnection則是最佳的選擇。它的API簡單,體積較小。壓縮和快取機制可以有效地減少網路訪問的流量,在提升速度和省電方面也起到了較大的作用。正如在Android 6.0版本後官方將HttpClient移除也是建議我們在開中發使用HttpURLConnection。

使用示例

     /**
     * HttpURLConnection的Get請求
     * @param urlStr
     * @return
     */
    public static String httpURLConnectionGet(String urlStr) {
        InputStream inStream = null;

        HttpURLConnection conn = null;
        try {
            URL url = new URL(urlStr);
            conn = (HttpURLConnection)url.openConnection();
//            conn.setConnectTimeout(timeOut);
//            conn.setReadTimeout(readTimeout);
            conn.setRequestMethod("GET");

            if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
                return null;
            }
            inStream = conn.getInputStream();

            String result = inputStreamToString(inStream);
            return result;

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (inStream != null) {
                    inStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (conn != null) {
                conn.disconnect();
            }
        }
        return null;
    }

    public static String HttpURLConnectionPost(String urlStr, String params) {
        HttpURLConnection conn = null;
        InputStream inStream = null;
        PrintWriter printWriter = null;
        try {
            URL url = new URL(urlStr);
            conn = (HttpURLConnection) url.openConnection();
            conn.setDoInput(true);
            conn.setDoOutput(true);                 // 是否輸入引數
            //conn.setConnectTimeout(timeOut);      // 連線超時 單位毫秒
            //conn.setReadTimeout(readTimeout);     // 讀取超時 單位毫秒
            conn.setUseCaches(false);               // Post 請求不能使用快取

            // 第1種方式:使用直接輸出流的方式(已知輸出流的長度):
            // conn.setFixedLengthStreamingMode(輸出流的固定長度);

            // 第2種方式:使用直接輸出流的方式(未知輸出流的長度):
            conn.setChunkedStreamingMode(5);        // 5 為塊的大小

            // 第3種方式:本地快取後一次性輸出:
            // 兩個函式都不指定

            conn.setRequestMethod("POST");

            // application/x-javascript text/xml     ->xml資料
            // application/x-javascript              ->json物件
            // application/x-www-form-urlencoded     ->表單資料
            // 在給HttpURLConnection 設定request屬性的時候,Android4.0+自動對屬性的內容中的空格進行了轉義替換‘\s’。
            // 但是,Android2.0+ 沒有做這個處理,所以有些網路訪問一直Bad!
            conn.setRequestProperty("Content-Type", ("application/x-www-form-urlencoded; charset=utf-8").replaceAll("\\s", ""));
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("Content-Length", String.valueOf(params.length()));

            //PrintWriter 是文字。也就是unicode編碼格式的,當然也包括漢字。
            printWriter = new PrintWriter(conn.getOutputStream());
            printWriter.write(params);
            printWriter.flush();

            // OutputStream 接受的是bytes
            // byte[] data = params.getBytes("UTF-8");
            // OutputStream outputStream = conn.getOutputStream();
            // outputStream.write(data);
            // outputStream.flush();

            //DataOutputStream 是OutputStream的一個實用類,寫byte,寫String都可以
            // DataOutputStream dataOutputStream = new DataOutputStream(conn.getOutputStream());
            // dataOutputStream.writeBytes(params);
            // dataOutputStream.flush();

            if(conn.getResponseCode() == HttpURLConnection.HTTP_OK){
                inStream = new BufferedInputStream(conn.getInputStream());
            } else {
                inStream = new BufferedInputStream(conn.getErrorStream());
            }

            String result = inputStreamToString(inStream);
            return result;

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
            if (printWriter != null) {
                printWriter.close();
            }
            try {
                if (inStream != null) {
                    inStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

下載檔案的示例:

public static boolean downloadZip(Context ctx, String urlStr) {
    InputStream is = null;
    FileOutputStream fos = null;
    boolean bRet = false;
    HttpURLConnection conn = null;
    try {
        File zipFile = getDownloadFile(ctx);  // 例如:/data/data/com.zyx.demo/mydownload/xx.zip
        URL url = new URL(urlStr);
        conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setReadTimeout(5000);
        conn.setConnectTimeout(5000);

        if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
            return false;
        }

        is = conn.getInputStream();
        fos = new FileOutputStream(zipFile);

        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = is.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
        }
        bRet = true;
    } catch (Exception e) {
    } finally {
        try {
            if (is != null) {
                is.close();
            }
            if (fos != null) {
                fos.close();
            }
            if (conn != null) {
                conn.disconnect();
            }
        } catch (Exception e) {
        }
    }
    return bRet;
}

public static File getDownloadFile(Context ctx) {
    try {
        String dexDir = ctx.getDir("mydownload", Context.MODE_PRIVATE).getAbsolutePath();
        String path = dexDir + File.separator + "xx.zip";
        return new File(path);
    } catch (Exception e) {
    }
    return null;
}