Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android網絡應用之Socket(一)

Android網絡應用之Socket(一)

編輯:關於Android編程

socket編程是網絡通信的一個基礎應用,無論是手機端還是PC端都需要socket技術來建立網絡通信。在本章小編主要從以下幾個方面來介紹socket的相關知識:

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

一、何為socket?

socket也被稱為“套接字”,它是一種網絡通信的方式,它不是一種協議,而是提供給程序員實現TCP/IP封裝與數據傳輸的接口。用於在客戶端和服務端之間建立一個通信管道,使得應用程序/客戶端可以通過這個通信管道向服務器/另一台主機發送請求,同時服務器也可以進行相應,建立兩者之間的數據傳輸與交換。在Android中,我們知道每個應用程序都可以使用多線程來進行操作,用於網絡傳輸的app的多個線程可以都建立一個socket連接,每個線程維護自己的socket管道,從而實現並發的網絡通信。 socket的組成部分主要包括5個:連接使用的協議(TCP/UDP),客戶端IP,客戶端端口號,服務器端IP地址,服務器端的端口號。在Android中,serverSocket用於服務器端而socket是用於建立網絡連接時用的,在連接成功時兩端都會產生一個socket對象。

二、socket的特點及通信原理

我們可以看到,一個socket建立時所使用的協議可以是TCP的,也可以是UDP的。TCP是可靠的而UDP是不可靠的,原因就是TCP建立時需要三次握手,雙方建立通信管道才可以數據傳輸,且是雙向的。而UDP在數據傳輸時只是把應用層發來的數據進行打包並且附上目的的地址,不負責對方是否接收到。 一個Socket的通信建立可以分為客戶端和服務端兩部分: 服務端:1、創建服務器套接字並綁定到自己的一個端口上,這個端口最好大於1024,因為0~1023端口號是被系統預留的,會被一些系統功能所調用。 2、建立套接字的監聽器,監聽是否有別的客戶端程序來請求訪問本端口的serverSocket。 3、如果接受到,則接受請求建立連接通信

客戶端:1、創建一個客戶端套接字,綁定要訪問的目標服務器的IP地址及端口號 2、連接服務端(這裡與PC上的socket連接不同,不需要connect(),因為Android上的客戶端socket在創建時,如果創建成功會自動內部調用連接方法) 3、與服務端進行通信 4、主動關閉客戶端socket 了解了客戶端與服務端的通信過程,我們都能了解到:服務端其實一直保持著監聽客戶端請求的狀態,socket連接只能由發起請求的客戶端主動關閉。兩端在進行通信的時候,數據是通過InputStream和OutputStream來實現的,且首先是服務端收到輸入流,再將結果通過輸出流返回給客戶端。而客戶端方面是先發送輸出流,再接收到輸入流。 大致模式如下: \ 服務器和客戶端的連接:服務器監聽------->客戶端請求------->建立連接

三、socket與HTTP、TCP的區別

首先需要知道HTTP是應用層協議,主要解決的是如何包裝數據。而TCP是傳輸層協議,主要解決的是如何傳輸數據。socket是TCP/IP協議的封裝,本身並不是協議。 socket的連接模式與HTTP、TCP的連接模式不一致。從上節已經了解到socket的連接是從服務器監聽開始的,而在TCP方面,客戶端與服務器的連接需要經過三次握手之後才正式開始數據的傳輸,而HTTP則是基於請求-響應才能建立數據傳輸,什麼意思呢?只有先請求,服務器才能對外發數據,不能主動建立傳輸。在HTTP經過了0.9、1.0和1.1時代,這種連接模式可以在一次TCP連接建立時一直保持,響應多次請求。 當socket是傳輸層的一個封裝,傳輸層主要由TCP和UDP協議,當socket使用的是TCP協議而不是UDP協議時,一個socket連接其實就是TCP連接。

四、Demo

這裡我們將創建一個demo,在Android上使用socket。我們的Demo主要功能是在編輯框中編寫一段文字,然後按發送按鈕,最後內部實現通過socket的通信方式,反饋到textview上進行顯示編輯的內容。裡面的細節重點會在末尾處分析。 下面是xml布局文件:
        android:id=@+id/btn_conn/>
    
布局效果如下: \

首先來描述下我們要實現的功能邏輯: 1、通過點擊連接先建立socket通信管道 2、在編輯框中輸入我們要發送的內容,按下發送按鈕,數據會顯示到文本控件中; 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
				//當點擊按鈕時,會獲取編輯框中的數據,然後提交給線程
				//先將發送內容進行打包
				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地址及端口的socket連接,此時,什麼數據都沒有,socket的Outputstream和InputStream裡面都是空的。盡管我們自定義的線程在連接後就啟動了,並且必須執行run方法,但是此時沒有數據來接收。 當我們編輯好文筆點擊了sent按鈕之後,編輯框裡的文本會由字符串向字節的包裝,使用的是String.getbyte()方法,為啥呢,因為outputstream和inputstream只接受字節流的數據。當socket的getOutputStream獲取了outputstream對象之後,執行了write方法進行數據的寫入,而此時線程依然在執行,它突然發現連接的socket裡有數據了,就使用getIupteStream獲取了inputstream對象,采用了read方法讀取了字節流,最後打包成字符串。 打包成字符串之後,線程要執行一個自己的使命,就是將其封裝成消息,以消息的形式塞給主線程的消息隊列,此時UI線程的使者Hanlder就完成了sendmessage的任務。 最後回到主線程,Handler對象使用handlmessage方法將消息的內容顯示在了textView上。
以上就是整個邏輯。

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved