最近准备开始做一个解决方案项目,在这里做了一个前期准备工作。希望也可以为别人所用。废话不多说,开始: http://414543604.blog.163.com/blog/static/129404346201361371635990/
1.简单思路:
(1)我们需要编写一个遥控器的服务器端。为了实现这个服务器端,我们可以HTTP协议的Socket/InetSocketAddress,来设置手机和服务端(电视)的IP 和端口。
客户端将事件封装(JSON格式),写入流中,事件分别有MotionEvent,KeyEvent,SensorEvent。
(2)服务端接收并解析流,然后还原成事件。完成遥控器的响应。
2.下面有一个简单实例:
客户机端实现:
package com.test.remote;
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
public class ClientActivity extends Activity implements On
private EditText mServerIp;
private EditText mServerPort;
private EventSender mEventSender;
@Override
protected void on
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // 这里可以替换为detectAll() 就包括了磁盘读写和网络I/O
.penaltyLog() //打印logcat,当然也可以定位到dropbox,通过文件保存相应的log
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects() //探测SQLite数据库操作
.penaltyLog() //打印logcat
.penaltyDeath()
.build());
super.on
setContentView(R.layout.client_layout);
mServerIp = (EditText) findViewById(R.id.cli_adr_edi);
mServerPort = (EditText) findViewById(R.id.cli_port_edi);
findViewById(R.id.cli_connect_btn).setOnClickListener(this);
findViewById(R.id.num_1).setOnClickListener(this);
findViewById(R.id.num_2).setOnClickListener(this);
findViewById(R.id.num_3).setOnClickListener(this);
findViewById(R.id.num_4).setOnClickListener(this);
findViewById(R.id.num_5).setOnClickListener(this);
findViewById(R.id.num_6).setOnClickListener(this);
findViewById(R.id.num_7).setOnClickListener(this);
findViewById(R.id.num_8).setOnClickListener(this);
findViewById(R.id.num_9).setOnClickListener(this);
}
@Override
public void on
int co
switch (v.getId()) {
case R.id.cli_connect_btn:
if(mEventSender != null) mEventSender.close();
mEventSender = new EventSender(mServerIp.getText().toString(), Integer.valueOf(mServerPort.getText().toString()));
mEventSender.connect();
return;
case R.id.num_1:
co
break;
case R.id.num_2:
co
break;
case R.id.num_3:
co
break;
case R.id.num_4:
co
break;
case R.id.num_5:
co
break;
case R.id.num_6:
co
break;
case R.id.num_7:
co
break;
case R.id.num_8:
co
break;
case R.id.num_9:
co
break;
}
if(mEventSender == null) return;
mEventSender.println(createKeyEvent(new KeyEvent(KeyEvent.ACT
mEventSender.println(createKeyEvent(new KeyEvent(KeyEvent.ACT
}
private String createKeyEvent(KeyEvent event) {
JSONObject json = new JSONObject();
try {
json.put("Event", event);
} catch (JSONException e) {
e.printStackTrace();
}
return json.toString();
}
public class EventSender {
private static final String TAG = "EventSender";
private Socket mSocket;
private String mDstAddress;
private int mDstPort;
private PrintWriter mPrintWriter;
private EventSender(String dstAddress, int dstPort){
Log.d(TAG, "EventSender");
mDstAddress = dstAddress;
mDstPort = dstPort;
mSocket = new Socket();
}
public void println(String str){
mPrintWriter.println(str);
}
public boolean close(){
Log.d(TAG, "close()");
if(mSocket != null){
try {
mSocket.close();
mSocket = null;
return true;
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
public boolean connect(){
Log.d(TAG, "connect()");
try {
if (mSocket != null && !mSocket.isConnected()) {
Log.d(TAG, "new InetSocketAddress()");
mSocket.connect(new InetSocketAddress(mDstAddress, mDstPort));
mPrintWriter = new PrintWriter(mSocket.getOutputStream(), true);
mSocket.setKeepAlive(true);
}
return true;
}catch (IOException e) {
e.printStackTrace();
}
return false;
}
}
}
服务器端实现:
package com.test.remote;
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
public class ServerActivity extends Activity implements On
private static final String TAG = "ServerActivity";
private RemoteServer mRemoteServer;
private EditText mServerPort;
private TextView mServerRecEvent;
@Override
protected void on
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // 这里可以替换为detectAll() 就包括了磁盘读写和网络I/O
.penaltyLog() //打印logcat,当然也可以定位到dropbox,通过文件保存相应的log
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects() //探测SQLite数据库操作
.penaltyLog() //打印logcat
.penaltyDeath()
.build());
super.on
setContentView(R.layout.server_layout);
findViewById(R.id.ser_port_btn).setOnClickListener(this);
mServerPort = (EditText) findViewById(R.id.ser_port_edi);
mServerRecEvent = (TextView) findViewById(R.id.ser_recev_event);
}
@Override
public void on
openServer(Integer.valueOf(mServerPort.getText().toString()));
}
public void openServer(int dstPort){
Log.d(TAG, "openServer() - dstPort:" + dstPort);
mServerRecEvent.setText("");
if (mRemoteServer == null) {
mRemoteServer = new RemoteServer(dstPort);
if(mRemoteServer.openServer()) {
mServerRecEvent.setText("服务启动成功!!!!/r/n");
}
Log.d(TAG, "openServer() - mRemoteServer:" + mRemoteServer);
}
}
private void on
Log.d(TAG, "line: " + line);
Message msg = Message.obtain();
msg.what = 0;
msg.obj = line;
mHandler.sendMessage(msg);
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
String text = mServerRecEvent.getText().toString();
mServerRecEvent.setText(text + String.valueOf(msg.obj));
}
};
class RemoteServer implements Runnable{
private static final String TAG = "RemoteServer";
private int mPort;
private ServerSocket mServerSocket;
private boolean mIsClose = false;
public boolean isClose(){
return mIsClose;
}
public boolean close(){
try {
mServerSocket.close();
mServerSocket = null;
mIsClose = true;
} catch (IOException e) {
e.printStackTrace();
}
return mIsClose;
}
public RemoteServer(int port){
Log.d(TAG, "RemoteServer()");
mPort = port;
}
public boolean openServer(){
try {
mServerSocket = new ServerSocket(mPort);
new Thread(this).start();
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
public void run() {
try {
while (!mIsClose) {
Socket socket = mServerSocket.accept();
new ServerThread(socket);
}
} catch (IOException e) {
}
}
private class ServerThread extends Thread {
private Socket client;
private BufferedReader in;
public ServerThread(Socket s) throws IOException {
client = s;
in = new BufferedReader(new InputStreamReader(client.getInputStream()));
start();
}
public void run() {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);
try {
String line = in.readLine();
while (!mIsClose) {
if(line != null){
on
}
line = in.readLine();
}
Log.d(TAG, "--- See you, bye! ---");
client.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
主类:
package com.test.remote;
imp
imp
imp
imp
imp
public class MainActivity extends Activity implements On
@Override
protected void on
super.on
setContentView(R.layout.activity_main);
findViewById(R.id.btn_client).setOnClickListener(this);
findViewById(R.id.btn_server).setOnClickListener(this);
}
@Override
public void on
switch (v.getId()) {
case R.id.btn_client:
startActivity(new Intent(this, ClientActivity.class));
break;
case R.id.btn_server:
startActivity(new Intent(this, ServerActivity.class));
break;
}
}
}
布局文件:
主类的布局:
<?xml version="1.0"?>
-<LinearLayout android:layout_gravity="center" android:orientation="vertical" android:layout_height="match_parent" android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android"> <Button android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="@string/btn_client" android:id="@+id/btn_client"/> <Button android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="@string/btn_server" android:id="@+id/btn_server"/> </LinearLayout>
客户端的布局:
<?xml version="1.0"?>
-<LinearLayout android:orientation="vertical" android:layout_height="match_parent" android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android"> -<LinearLayout android:orientation="horizontal" android:layout_height="wrap_content" android:layout_width="match_parent"> <EditText android:layout_height="wrap_content" android:layout_width="0dip" android:layout_weight="3" android:id="@+id/cli_adr_edi"/> <EditText android:layout_height="wrap_content" android:layout_width="0dip" android:layout_weight="1" android:id="@+id/cli_port_edi"/> <Button android:layout_height="wrap_content" android:layout_width="wrap_content" android:id="@+id/cli_connect_btn" android:text="@string/cli_connect_server"/> </LinearLayout> -<LinearLayout android:layout_height="wrap_content" android:layout_width="fill_parent"> <Button android:layout_height="wrap_content" android:layout_width="0dip" android:layout_weight="1" android:id="@+id/num_1" android:text="1"/> <Button android:layout_height="wrap_content" android:layout_width="0dip" android:layout_weight="1" android:id="@+id/num_2" android:text="2"/> <Button android:layout_height="wrap_content" android:layout_width="0dip" android:layout_weight="1" android:id="@+id/num_3" android:text="3"/> </LinearLayout> -<LinearLayout android:layout_height="wrap_content" android:layout_width="fill_parent"> <Button android:layout_height="wrap_content" android:layout_width="0dip" android:layout_weight="1" android:id="@+id/num_4" android:text="4"/> <Button android:layout_height="wrap_content" android:layout_width="0dip" android:layout_weight="1" android:id="@+id/num_5" android:text="5"/> <Button android:layout_height="wrap_content" android:layout_width="0dip" android:layout_weight="1" android:id="@+id/num_6" android:text="6"/> </LinearLayout> -<LinearLayout android:layout_height="wrap_content" android:layout_width="fill_parent"> <Button android:layout_height="wrap_content" android:layout_width="0dip" android:layout_weight="1" android:id="@+id/num_7" android:text="7"/> <Button android:layout_height="wrap_content" android:layout_width="0dip" android:layout_weight="1" android:id="@+id/num_8" android:text="8"/> <Button android:layout_height="wrap_content" android:layout_width="0dip" android:layout_weight="1" android:id="@+id/num_9" android:text="9"/> </LinearLayout> </LinearLayout>
服务端的布局:
<?xml version="1.0"?>
-<LinearLayout android:orientation="vertical" android:layout_height="match_parent" android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android"> -<LinearLayout android:orientation="horizontal" android:layout_height="wrap_content" android:layout_width="match_parent"> <EditText android:layout_height="wrap_content" android:layout_width="0dip" android:layout_weight="1" android:id="@+id/ser_port_edi"/> <Button android:layout_height="wrap_content" android:layout_width="wrap_content" android:id="@+id/ser_port_btn" android:text="@string/btn_server"/> </LinearLayout> <TextView android:layout_height="wrap_content" android:layout_width="match_parent" android:id="@+id/ser_recev_event"/> </LinearLayout>
用到的字符串:
<?xml version="1.0" encoding="UTF-8"?>
-<resources> <string name="app_name">Remote</string> <string name="btn_client">客户端</string> <string name="btn_server">服务端</string> <string name="btn_start_server">启动服务</string> <string name="cli_connect_server">连接服务</string> </resources>
3.附HTTP协议的讲解文章:
【Android】HTTP和Socket连接的区别
(2010-12-23 16:59:29)转载▼
标签: android | 分类: Android |
相信不少初学手机联网开发的朋友都想知道Http与Socket连接究竟有什么区别,希望通过自己的浅显理解能对初学者有所帮助。
1、TCP连接
要想明白Socket连接,先要明白TCP连接。手机能够使用联网功能是因为手机底层实现了TCP/IP协议,可以使手机终端通过无线网络建立TCP连接。TCP协议可以对上层网络提供接口,使上层网络数据的传输建立在“无差别”的网络之上。
建立起一个TCP连接需要经过“三次握手”:
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”(过程就不细写了,就是服务器和客户端交互,最终确定断开)
2、HTTP连接
HTTP协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。
HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。
1)在HTTP 1.0中,客户端的每次请求都要求建立一次单独的连接,在处理完本次请求后,就自动释放连接。
2)在HTTP 1.1中则可以在一次连接中处理多个请求,并且多个请求可以重叠进行,不需要等待一个请求结束后再发送下一个请求。
由于HTTP在每次请求结束后都会主动释放连接,因此HTTP连接是一种“短连接”,要保持客户端程序的在线状态,需要不断地向服务器发起连接请求。通常的做法是即使不需要获得任何数据,客户端也保持每隔一段固定的时间向服务器发送一次“保持连接”的请求,服务器在收到该请求后对客户端进行回复,表明知道客户端“在线”。若服务器长时间无法收到客户端的请求,则认为客户端“下线”,若客户端长时间无法收到服务器的回复,则认为网络已经断开。
3、SOCKET原理
3.1套接字(socket)概念
套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。
应用层通过传输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个 TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。
3.2 建立socket连接
建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。
套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
4、SOCKET连接与TCP连接
创建Socket连接时,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。
5、Socket连接与HTTP连接
由于通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。但在实际网络应用中,客户端到服务器之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。
而HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。
很多情况下,需要服务器端主动向客户端推送数据,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数据传送给客户端;若双方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端,因此,客户端定时向服务器端发送连接请求,不仅可以保持在线,同时也是在“询问”服务器是否有新的数据,如果有就将数据传给客户端。
作者:* M
2013-07-13