Android网络应用之Socket

Posted zhchoutai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android网络应用之Socket相关的知识,希望对你有一定的参考价值。

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、假设接受到,则接受请求建立连接通信


client1、创建一个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的主要内容,如果未能解决你的问题,请参考以下文章

Android网络通信之Socket

我的Android进阶之旅NDK开发之在C++代码中使用Android Log打印日志,打印出C++的函数耗时以及代码片段耗时详情

Android应用开发之使用Socket进行大文件断点上传续传

android开发系列之socket编程

一起Talk Android吧(第三百二十九回:Android中网络通信之UPD Socket)

Android Framework实战开发视频--跨进程通信之Socket通信