1. 程式人生 > >Android網絡應用之Socket(一)

Android網絡應用之Socket(一)

指正 table 可靠 width 好文 [] 發送請求 響應 stub

socket編程是網絡通信的一個基礎應用。不管是手機端還是PC端都須要socket技術來建立網絡通信。

在本章小編主要從下面幾個方面來介紹socket的相關知識:

各自是“什麽是socket?”,“socket有什麽特點?”,“socket與Http以及TCP的差別”。“移動端socket的Demo”。寫的不好的地方請大家批評指正。

一、何為socket?

socket也被稱為“套接字”。它是一種網絡通信的方式,它不是一種協議,而是提供給程序猿實現TCP/IP封裝與傳輸數據的接口。

用於在client和服務端之間建立一個通信管道,使得應用程序/client能夠通過這個通信管道向server/還有一臺主機發送請求,同一時候server也能夠進行對應,建立兩者之間的傳輸數據與交換。

在Android中,我們知道每一個應用程序都能夠使用多線程來進行操作,用於網絡傳輸的app的多個線程能夠都建立一個socket連接。每一個線程維護自己的socket管道,從而實現並發的網絡通信。

socket的組成部分主要包含5個:連接使用的協議(TCP/UDP),clientIP,clientport號,server端IP地址。server端的port號。在Android中,serverSocket用於server端而socket是用於建立網絡連接時用的,在連接成功時兩端都會產生一個socket對象。

二、socket的特點及通信原理

我們能夠看到。一個socket建立時所使用的協議能夠是TCP的。也能夠是UDP的。

TCP是可靠的而UDP是不可靠的,原因就是TCP建立時須要三次握手。兩方建立通信管道才幹夠傳輸數據,且是雙向的。

而UDP在傳輸數據時僅僅是把應用層發來的數據進行打包而且附上目的的地址,不負責對方是否接收到。 一個Socket的通信建立能夠分為client和服務端兩部分: 服務端1、創建server套接字並綁定到自己的一個port上,這個port最好大於1024,由於0~1023port號是被系統預留的,會被一些系統功能所調用。 2、建立套接字的監聽器。監聽是否有別的client程序來請求訪問本port的serverSocket。 3、假設接受到,則接受請求建立連接通信

client

1、創建一個client套接字,綁定要訪問的目標server的IP地址及port號 2、連接服務端(這裏與PC上的socket連接不同。不須要connect(),由於Android上的clientsocket在創建時,假設創建成功會自己主動內部調用連接方法) 3、與服務端進行通信 4、主動關閉clientsocket 了解了client與服務端的通信過程,我們都能了解到:服務端事實上一直保持著監聽client請求的狀態。socket連接僅僅能由發起請求的client主動關閉。兩端在進行通信的時候,數據是通過InputStream和OutputStream來實現的,且首先是服務端收到輸入流。再將結果通過輸出流返回給client。

而client方面是先發送輸出流,再接收到輸入流。

大致模式例如以下: 技術分享 server和client的連接:server監聽------->client請求------->建立連接

三、socket與HTTP、TCP的差別

首先須要知道HTTP是應用層協議,主要解決的是怎樣包裝數據。而TCP是傳輸層協議。主要解決的是怎樣數據傳輸。socket是TCP/IP協議的封裝,本身並非協議。

socket的連接模式與HTTP、TCP的連接模式不一致。

從上節已經了解到socket的連接是從server監聽開始的,而在TCP方面,client與server的連接須要經過三次握手之後才正式開始數據的傳輸,而HTTP則是基於"請求-響應"才幹建立傳輸數據,什麽意思呢?僅僅有先請求,server才幹對外發數據,不能主動建立傳輸。在HTTP經過了0.9、1.0和1.1時代,這樣的連接模式能夠在一次TCP連接建立時一直保持。響應多次請求。

當socket是傳輸層的一個封裝,傳輸層主要由TCP和UDP協議,當socket使用的是TCP協議而不是UDP協議時。一個socket連接事實上就是TCP連接。

四、Demo

這裏我們將創建一個demo,在Android上使用socket。

我們的Demo主要功能是在編輯框中編寫一段文字,然後按發送button。最後內部實現通過socket的通信方式。反饋到textview上進行顯示編輯的內容。裏面的細節重點會在末尾處分析。 以下是xml布局文件:

        android:id="@+id/btn_conn"/>
    <EditText 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:hint="請在這裏編輯要發送的內容"
        android:id="@+id/et_message"/>
    
    <Button 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="發送"
        android:id="@+id/btn_sent"/>
    
    <TextView 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/tv"
        android:hint="接收發來的數據"/>
</LinearLayout>
布局效果例如以下: 技術分享

首先來描寫敘述下我們要實現的功能邏輯: 1、通過點擊連接先建立socket通信管道 2、在編輯框中輸入我們要發送的內容,按下發送button,數據會顯示到文本控件中。 3、在內部自己建立一個線程,不能在UI線程中更新,會造成ANR; 4、本身即作為發送方,又作為接收方,主要過程是把數據打包發送,通過outputstream進行輸出傳輸。創建為消息的內容;接著被handler收到,扔進消息隊列,處理的時候再把數據解壓。進行ui更新。主要功能handler.sentmessage()和handler.handmessage()。還有就是outputstream和inputstream時的數據打包,encoding必須一致。

中文能夠使用GB2312.
代碼例如以下:

package com.example.mysocketdemo;

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.app.ActionBar;
import android.app.Fragment;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.Editable;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import android.os.Build;

public class MainActivity extends Activity {
	private TextView tv;
	private Button btnsent,btnconn;
	private EditText ed_message;
	private OutputStream output;
	private Socket clientSocket;
	private Handler mHandler;
	private MyThread mythread;
	boolean stop = true;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (savedInstanceState == null) {
            getFragmentManager().beginTransaction()
                    .add(R.id.container, new PlaceholderFragment())
                    .commit();
        }
        init();
        //onClickEvent---connect
        btnconn.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				try {
					clientSocket = new Socket("127.0.0.1", 8888);
				} catch (UnknownHostException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				Toast.makeText(getApplicationContext(), "connect ok", Toast.LENGTH_SHORT).show();
				//把socket綁定到自己的線程對象
				try {
					mythread = new MyThread(clientSocket);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				mythread.start();//啟動線程
				
				//更新UI,大部分的數據工作已經交給了mythread對象
				btnsent.setEnabled(true);
				btnconn.setEnabled(false);
				stop = false;
			}
		});
        //sent Message
        btnsent.setOnClickListener(new View.OnClickListener() {
			
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				//當點擊button時,會獲取編輯框中的數據,然後提交給線程
				//先將發送內容進行打包
				byte[]  msgBuffer = null;
				try {
					msgBuffer = ed_message.getText().toString().getBytes("GB2312");
				} catch (UnsupportedEncodingException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				//打包完畢之後,加入socket的輸出流對象
				try {
					output = clientSocket.getOutputStream();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				try {
					//輸出流對象將字節寫入
					//不管是輸出流還是輸入流。操作的都是字節,假設向變成字符串,須要自己構建String對象
					output.write(msgBuffer);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				Toast.makeText(getApplicationContext(), "發送成功", Toast.LENGTH_SHORT).show();
				ed_message.setText("");
			}
		});
        
        //在連接和發送數據之後。接下來就是處理了,發送的數據會通過message的方式傳遞到消息隊列,再由handl進行獲取
        mHandler = new Handler(){
        	public void handleMessage(android.os.Message msg) {
        		tv.setText(msg.obj.toString());
        	};
        };
    }
    public void init()
    {
    	tv = (TextView) findViewById(R.id.tv);
    	btnsent = (Button) findViewById(R.id.btn_sent);
    	ed_message = (EditText) findViewById(R.id.et_message);
    	btnconn = (Button) findViewById(R.id.btn_conn);
    	btnconn.setEnabled(true);
    	btnsent.setEnabled(false);
    }

    //自己定義線程類;
    private class MyThread extends Thread{
    	//構建自己的socket對象,用來在線程內使用
    	private Socket socket;
    	private byte[] buf = null;
    	private InputStream inputstream;
    	String str = null;
    	public MyThread(Socket socket) throws IOException
    	{
    		this.socket = socket;
    		inputstream = this.socket.getInputStream();
    	}
    	@Override
    	public void run() {
    		// TODO Auto-generated method stub
    		while(!stop)
    		{
    			buf = new byte[512];
    			//將inputstream內的數據讀到buf裏
    			try {
					this.inputstream.read(buf);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
    			try {
    				//將buf裏的字符流進行解析,得到
					this.str = new String(buf, "GB2312").trim();
				} catch (UnsupportedEncodingException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
    			//線程內獲取了來自socket的Inputstream字節流,而且轉換成字符串後,線程就獲取了消息的實體內容
    			//此時線程將運行自己的一個使命。就是創建消息,並發送出去
    			Message msg = new Message();
    			msg.obj = this.str;
    			mHandler.sendMessage(msg);
    		}
    	}
    }
    
    @Override
    protected void onDestroy() {
    	// TODO Auto-generated method stub
    	if (mythread!=null) {
			mythread.interrupt();
		}
    	super.onDestroy();
    }



了解代碼的邏輯之後。我再理一下整個思路: 當我們點擊了connnet之後。就會創建連接目標ip地址及port的socket連接,此時,什麽數據都沒有,socket的Outputstream和InputStream裏面都是空的。雖然我們自己定義的線程在連接後就啟動了,而且必須運行run方法,可是此時沒有數據來接收。 當我們編輯好文筆點擊了sentbutton之後。編輯框裏的文本會由字符串向字節的包裝,使用的是String.getbyte()方法。為啥呢,由於outputstream和inputstream僅僅接受字節流的數據。

當socket的getOutputStream獲取了outputstream對象之後,運行了write方法進行數據的寫入,而此時線程依舊在運行,它突然發現連接的socket裏有數據了,就使用getIupteStream獲取了inputstream對象,採用了read方法讀取了字節流,最後打包成字符串。 打包成字符串之後,線程要運行一個自己的使命。就是將其封裝成消息。以消息的形式塞給主線程的消息隊列。此時UI線程的使者Hanlder就完畢了sendmessage的任務。

最後回到主線程,Handler對象使用handlmessage方法將消息的內容顯示在了textView上。


以上就是整個邏輯。



Android網絡應用之Socket(一)