Java4Android之socket網路通訊基礎

來源:互聯網
上載者:User

標籤:java4android   socket   server   client   

本節主要介紹Socket編程,發現Java裡面的socket編程和C語言的還是有一些不一樣,比如TCP socket ,在Java中區分了serverSocket。不過原理都一樣,在流程處理上也非常相似,所以,理解起來並不難。我們會先從基礎說起,從如何建立socket串連,到如何?一個合理的設計例如在android中,我們發送一條訊息,然後監聽一個回複,如何做到不卡死UI,本文將會由淺入深的為大家呈現一個相對完整的android socket編程。

在進入Java 的socket編程之前,我們從原理上切入,然後提及相應的代碼說明。

Socket可以說是一種針對網路的抽象,應用通過它可以來針對網路讀寫資料。就像通過一個檔案的file handler就可以都寫資料到存放裝置上一樣。根據TCP協議和UDP協議的不同,在網路編程方面就有面向兩個協議的不同socket,一個是面向位元組流的一個是面向報文的


TCP

  TCP主要是連線導向的協議,它包含有建立和拆除串連,保證資料流的順序和正確性等功能。每次對TCP中間的資料操作相當於對一個資料流進行訪問。它最典型的特徵就是那三向交握的建立串連過程。TCP的串連建立和撤銷過程如:


Server端Server端所要做的事情主要是建立一個通訊的端點,然後等待用戶端發送的請求。典型的處理步驟如下:
1. 構建一個ServerSocket執行個體,指定本地的連接埠。這個socket就是用來監聽指定連接埠的串連請求的。
2.重複如下幾個步驟:
a. 調用socket的accept()方法來獲得下面用戶端的串連請求。通過accept()方法返回的socket執行個體,建立了一個和用戶端的新串連。
b.通過這個返回的socket執行個體擷取InputStream和OutputStream,可以通過這兩個stream來分別讀和寫資料。
c.結束的時候調用socket執行個體的close()方法關閉socket串連。
 

單線程版本


這個流程的典型範例程式碼如下:

//1. 構造ServerSocket執行個體,指定服務連接埠。ServerSocket servSock = new ServerSocket(servPort);while(true){   // 2.調用accept方法,建立和用戶端的串連           Socket clntSock = servSock.accept();           SocketAddress clientAddress =                    clntSock.getRemoteSocketAddress();           System.out.println("Handling client at " + clientAddress);    // 3. 擷取串連的InputStream,OutputStream來進行資料讀寫            InputStream in = clntSock.getInputStream();            OutputStream out = clntSock.getOutputStream();            while((recvMsgSize = in.read(receiveBuf)) != -1)            {                out.write(receiveBuf, 0, recvMsgSize);            }       // 4.操作結束,關閉socket.            clntSock.close();}  

多線程版本上述是單線程版本的伺服器流程,也可是是多線程的。大體代碼如下:

try   { file://建立伺服器     ServerSocket server = new ServerSocket(9998);     int i=1;     for(;;)     {    <span style="white-space:pre"></span>Socket incoming = server.accept();    <span style="white-space:pre"></span>new ServerThread(incoming,i).start();    //開啟線程來處理用戶端的請求<span style="white-space:pre"></span>i++;     }    }catch (IOException ex){ ex.printStackTrace(); }   

Client端用戶端的請求過程稍微有點不一樣:
1.構建Socket執行個體,通過指定的遠程伺服器位址和連接埠來建立串連。
2.通過Socket執行個體包含的InputStream和OutputStream來進行資料的讀寫。
3.操作結束後調用socket執行個體的close方法,關閉。
範例程式碼如下;

// 1.根據指定的server地址和連接埠,建立socket串連。Socket socket = new Socket(server, servPort);// 2. 根據socket執行個體擷取InputStream, OutputStream進行資料讀寫。InputStream in = socket.getInputStream();OutputStream out = socket.getOutputStream();out.write(data);//3.操作結束,關閉socket.socket.close();


上述代碼就是一個完整的用戶端/伺服器結構的socket通訊代碼。



不卡死UI的socket設計但是,在android中的socket通訊中,我們可以想象一下這樣的情境。我在EditText裡面輸入了一句“你好”,然後我按發送Button按鈕,就把訊息發送給了遠端的伺服器,然後伺服器給我回了一個“你好,歡迎您!”。我們發送完第一條資訊的時候,我們要阻塞在那裡等伺服器的話,會發生什嗎?我們 UI是不是會卡死?所以,很明顯,這樣的設計是不合理的。
用戶端
MyClientActivity.java檔案,主要是定義了一個繼承自Thread類的用於接收資料的類,覆寫了其中的run()方法,在這個函數裡面接收資料,接收到資料後就通過Handler發送訊息,收到訊息後在UI線程裡更新接收到的資料。完整的內容如下:
package com.nan.client;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.io.UnsupportedEncodingException;import java.net.Socket;import java.net.UnknownHostException;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.TextView;import android.widget.Toast;public class MyClientActivity extends Activity {    private EditText mEditText = null;    private Button connectButton = null;    private Button sendButton = null;    private TextView mTextView = null;        private Socket clientSocket = null;    private OutputStream outStream = null;        private Handler mHandler = null;        private ReceiveThread mReceiveThread = null;    private boolean stop = true;        /** Called when the activity is first created. */    @Override    public void onCreate(Bundle savedInstanceState)     {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);                mEditText = (EditText)this.findViewById(R.id.edittext);        mTextView = (TextView)this.findViewById(R.id.retextview);        connectButton = (Button)this.findViewById(R.id.connectbutton);        sendButton = (Button)this.findViewById(R.id.sendbutton);        sendButton.setEnabled(false);                      //連線按鍵監聽        connectButton.setOnClickListener(new View.OnClickListener()         {                        @Override            public void onClick(View v)             {                // TODO Auto-generated method stub                try                 {                    //執行個體化對象並串連到伺服器                    clientSocket = new Socket("113.114.170.246",8888);                }                 catch (UnknownHostException e)                 {                    // TODO Auto-generated catch block                    e.printStackTrace();                }                 catch (IOException e)                 {                    // TODO Auto-generated catch block                    e.printStackTrace();                }                                displayToast("串連成功!");                                        //連線按鍵使能                connectButton.setEnabled(false);                //發送按鈕使能                sendButton.setEnabled(true);                                mReceiveThread = new ReceiveThread(clientSocket);                stop = false;                //開啟線程                mReceiveThread.start();            }        });                //發送資料按鈕監聽        sendButton.setOnClickListener(new View.OnClickListener()         {                        @Override            public void onClick(View v)             {                // TODO Auto-generated method stub                byte[] msgBuffer = null;                //獲得EditTex的內容                String text = mEditText.getText().toString();                try {                    //字元編碼轉換                    msgBuffer = text.getBytes("GB2312");                } catch (UnsupportedEncodingException e1) {                    // TODO Auto-generated catch block                    e1.printStackTrace();                }                                                            try {                    //獲得Socket的輸出資料流                    outStream = clientSocket.getOutputStream();                } catch (IOException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                }                                                                                                    try {                    //發送資料                    outStream.write(msgBuffer);                } catch (IOException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                }                //清空內容                mEditText.setText("");                displayToast("發送成功!");            }        });                      //訊息處理        mHandler = new Handler()        {            @Override            public void handleMessage(Message msg)            {                //顯示接收到的內容                mTextView.setText((msg.obj).toString());            }        };            }        //顯示Toast函數    private void displayToast(String s)    {        Toast.makeText(this, s, Toast.LENGTH_SHORT).show();    }            private class ReceiveThread extends Thread    {        private InputStream inStream = null;                private byte[] buf;          private String str = null;                ReceiveThread(Socket s)        {            try {                //獲得輸入資料流                this.inStream = s.getInputStream();            } catch (IOException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }                      @Override        public void run()        {            while(!stop)            {                this.buf = new byte[512];                                try {                    //讀取輸入資料(阻塞)                    this.inStream.read(this.buf);                } catch (IOException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                }                                 //字元編碼轉換                try {                    this.str = new String(this.buf, "GB2312").trim();                } catch (UnsupportedEncodingException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                }                                Message msg = new Message();                msg.obj = this.str;                //發送訊息                mHandler.sendMessage(msg);                            }        }                    }        @Override    public void onDestroy()    {        super.onDestroy();                if(mReceiveThread != null)        {            stop = true;            mReceiveThread.interrupt();        }    }     }

服務端MyServerActivity.java檔案,定義了兩個Thread子類,一個用於監聽用戶端的串連,一個用於接收資料,其他地方與MyClientActivity.java差不多。完整的內容如下:
package com.nan.server;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.io.UnsupportedEncodingException;import java.net.ServerSocket;import java.net.Socket;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.TextView;import android.widget.Toast;public class MyServerActivity extends Activity {    private TextView ipTextView = null;    private EditText mEditText = null;    private Button sendButton = null;    private TextView mTextView = null;        private OutputStream outStream = null;    private Socket clientSocket = null;    private ServerSocket mServerSocket = null;        private Handler mHandler = null;        private AcceptThread mAcceptThread = null;    private ReceiveThread mReceiveThread = null;    private boolean stop = true;        /** Called when the activity is first created. */    @Override    public void onCreate(Bundle savedInstanceState)     {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);                ipTextView = (TextView)this.findViewById(R.id.iptextview);        mEditText = (EditText)this.findViewById(R.id.sedittext);        sendButton = (Button)this.findViewById(R.id.sendbutton);        sendButton.setEnabled(false);        mTextView = (TextView)this.findViewById(R.id.textview);                //發送資料按鈕監聽        sendButton.setOnClickListener(new View.OnClickListener()         {                        @Override            public void onClick(View v)             {                // TODO Auto-generated method stub                byte[] msgBuffer = null;                //獲得EditTex的內容                String text = mEditText.getText().toString();                try {                    //字元編碼轉換                    msgBuffer = text.getBytes("GB2312");                } catch (UnsupportedEncodingException e1) {                    // TODO Auto-generated catch block                    e1.printStackTrace();                }                                                            try {                    //獲得Socket的輸出資料流                    outStream = clientSocket.getOutputStream();                } catch (IOException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                }                                                                                                    try {                    //發送資料                    outStream.write(msgBuffer);                } catch (IOException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                }                //清空內容                mEditText.setText("");                displayToast("發送成功!");                            }        });        //訊息處理        mHandler = new Handler()        {            @Override            public void handleMessage(Message msg)            {                switch(msg.what)                {                    case 0:                    {                        //顯示用戶端IP                        ipTextView.setText((msg.obj).toString());                        //使能發送按鈕                        sendButton.setEnabled(true);                        break;                    }                    case 1:                    {                        //顯示接收到的資料                        mTextView.setText((msg.obj).toString());                        break;                    }                                 }                                                                       }        };                        mAcceptThread = new AcceptThread();        //開啟監聽線程        mAcceptThread.start();                  }        //顯示Toast函數    private void displayToast(String s)    {        Toast.makeText(this, s, Toast.LENGTH_SHORT).show();    }            private class AcceptThread extends Thread    {        @Override        public void run()        {            try {                //執行個體化ServerSocket對象並設定連接埠號碼為8888                mServerSocket = new ServerSocket(8888);            } catch (IOException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }
            while(1)
{
            try {                //等待用戶端的串連(阻塞)                clientSocket = mServerSocket.accept();            } catch (IOException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }                        mReceiveThread = new ReceiveThread(clientSocket);            stop = false;            //開啟接收線程            mReceiveThread.start();                        Message msg = new Message();            msg.what = 0;            //擷取用戶端IP            msg.obj = clientSocket.getInetAddress().getHostAddress();            //發送訊息            mHandler.sendMessage(msg);  }        }            }            private class ReceiveThread extends Thread    {        private InputStream mInputStream = null;        private byte[] buf ;          private String str = null;                ReceiveThread(Socket s)        {            try {                //獲得輸入資料流                this.mInputStream = s.getInputStream();            } catch (IOException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }                @Override        public void run()        {            while(!stop)            {                this.buf = new byte[512];                                //讀取輸入的資料(阻塞讀)                try {                    this.mInputStream.read(buf);                } catch (IOException e1) {                    // TODO Auto-generated catch block                    e1.printStackTrace();                }                                //字元編碼轉換                try {                    this.str = new String(this.buf, "GB2312").trim();                } catch (UnsupportedEncodingException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                }                                Message msg = new Message();                msg.what = 1;                        msg.obj = this.str;                //發送訊息                mHandler.sendMessage(msg);                            }        }    }              @Override    public void onDestroy()    {        super.onDestroy();                if(mReceiveThread != null)        {            stop = true;            mReceiveThread.interrupt();        }    }        }
上述代碼就是實現了真實環境下android的socket通訊。
UDP

UDP和TCP有兩個典型的區別,一個就是它不需要建立串連,另外就是它在每次收發的報文都保留了訊息的邊界。

server端因為UDP協議不需要建立串連,它的過程如下:
1. 構造DatagramSocket執行個體,指定本地連接埠。
2. 通過DatagramSocket執行個體的receive方法接收DatagramPacket.DatagramPacket中間就包含了通訊的內容。
3. 通過DatagramSocket的send和receive方法來收和發DatagramPacket.
典型的互動流程代碼如下:
// 1. 構建DatagramSocket執行個體,指定本地連接埠。DatagramSocket socket = new DatagramSocket(servPort);// 2. 構建需要收發的DatagramPacket報文DatagramPacket packet = new DatagramPacket(new byte[ECHOMAX], ECHOMAX);while(true){// 3. 收報文socket.receive(packet);System.out.println("Handling client at " + packet.getAddress().getHostAddress()    + " on port " + packet.getPort());// 4. 發報文socket.send(packet);packet.setLength(ECHOMAX);}

client端UDP用戶端的步驟也比較簡單,主要包括下面3步:
1. 構造DatagramSocket執行個體。
2.通過DatagramSocket執行個體的send和receive方法發送DatagramPacket報文。
3.結束後,調用DatagramSocket的close方法關閉。
因為和TCP不同,UDP發送報文的時候可以在同一個本地連接埠隨意發送給不同的伺服器,一般不需要在UDP的DatagramSocket的建構函式中指定目的伺服器的地址。
另外,UDP用戶端還有一個重要的不同就是,TCP用戶端發送echo串連訊息之後會在調用read方法的時候進入阻塞狀態,而UDP這樣卻不行。因為UDP中間是可以允許報文丟失的。如果報文丟失了,進程一直在阻塞或者掛起的狀態,則進程會永遠沒法往下走了。所以會一般設定一個setSoTimeout方法,指定在多久的時間內沒有收到報文就放棄。也可以通過指定一個數字,迴圈指定的次數來讀取報文,讀到就返回,否則就放棄。
 
一個典型的UDP Client程式碼範例如下:

// 1. 構造UDP DatagramSocket對象DatagramSocket socket = new DatagramSocket();// 2。指定timeout時間,防止進入無限等待狀態socket.setSoTimeout(TIMEOUT);// 3. 構造收發的報文對象DatagramPacket sendPacket = new DatagramPacket(bytesToSend,    bytesToSend.length, serverAddress, servPort);DatagramPacket receivePacket =    new DatagramPacket(new byte[bytesToSend.length], bytesToSend.length);// 4.指定嘗試的次數int tries = 0;boolean receivedResponse = false; do{socket.send(sendPacket);try{socket.receive(receivePacket); if(!receivePacket.getAddress().equals(serverAddress)){throw new IOException("Received packet from an unknown source");}receivedResponse = true;}catch(InterruptedIOException e){tries += 1;System.out.println("Timed out, " + (MAXTRIES - tries) + "");}}while((!receivedResponse) && (tries < MAXTRIES));// 根據是否接收到報文進行反饋if(receivedResponse){System.out.println("Received: " + new String(receivePacket.getData()));}else{System.out.println("No response -- giving up.");}// 5. 關閉socketsocket.close();

這次的socket編程就說道這裡,裡面參考和引用轉載了不少別人的東西,mark一下。

Reference:

http://shmilyaw-hotmail-com.iteye.com/blog/1556187

http://www.cnblogs.com/lknlfy/archive/2012/03/04/2379628.html




Java4Android之socket網路通訊基礎

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.