Android Socket编程

一、什么是Socket

什么是socket呢?socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。

二、Socket的连接过程

根据连接启动的方式以及本地Socket要连接的目标,Socket之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。 1、服务器监听:是服务器端Socket并不定位具体的客户端Socket,而是处于等待连接的状态,实时监控网络状态。 2、客户端请求:是指由客户端的Socket提出连接请求,要连接的目标是服务器端的Socket。为此,客户端的Socket必须首先描述它要连接的服务器的Socket,指出服务器端Socket的地址和端口号,然后就向服务器端Socket提出连接请求。 3、连接确认:是指当服务器端Socket监听到或者说接收到客户端Socket的连接请求,它就响应客户端Socket的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端Socket继续处于监听状态,继续接收其他客户端Socket的连接请求。

三、Android socket的基础知识

1.1Accept Timeout

Accept timeout仅对ServerSocket有用。ServerSocket使用accept()方法来监听客户端Socket的连接。默认,ServerSocket.accept()方法会一直阻塞直到有客户端来连接。通常,我们不需要设置accept timeout.

但有时候特殊情况,还是要考虑设置accept timeout.

比如:程序A给程序B发了一个JMS消息,然后程序A启动一个Socket Server,想通过socket等待接收程序B的返回消息。如果不设置accept timeout,并且程序B因为某些原因一直不能连接Socket Server,最终会导致程序A挂起。

Accept Timeout可以这样设置:

ServerSocket serverSocket = new ServerSocket(5555);

serverSocket.setSoTimeout(5000); // in milliseconds

while (true) {

Socket socket = serverSocket.accept();

}

1.2Connect Timeout

当Client端连接Server端的时候,可以指定Connect Timeout

如果没有指定,会使用操作系统的默认值:

OS

Default TCP timeout

BSD

75 seconds

Linux

189 seconds

Solaris

225 seconds

Windows XP

21 seconds

Connect Timeout可以这样设置:

SocketAddress socketAddress = new InetSocketAddress(host, port);

socket = new Socket();

socket.connect(socketAddress, connectTimeout);

1.3Receive Timeout

当socket从另一方接收数据时,可以设置Receive Timeout

默认没有timeout,socket会一直阻塞直到有数据可读取。

Receive Timeout可以这样设置:

Socket socket = new Socket(host, port);

socket.setSoTimeout(timeout);

1.4Send Timeout

Send Timeout是socket给另一方发送数据时使用的。

不过Java里没有办法设置Send Timeout.

当然,socket发送数据的时候,会首先发送到本机OS的一个buffer内。一般只要一次发送的数据不是很大,即使对方挂起或暂时不能接收数据,也不会导致发送方挂起。

2.1Socket ack (acknowledgement)

Socket ack是指当socket接收到数据之后,发送一个ack字符串(比如$ACK)给socket发送方。这样,socket发送方可以根据是否收到了ack判断对方是否收到了数据。

Socket ack是显示的在应用程序中加入的一种通讯协议。如果不使用ack,在socket通讯中,可能会丢失数据。

比如,socket client要连续的给socket server发送100条消息。如果我们在server收到第50条消息的时候,强行kill了server。那么查询client端发送的log,可能client端成功发送了51条。只有当client端发送第52条消息的时候才遇到异常。这样第51条消息就丢失了。

所以为了确保数据传输的准确性,我们可以引入ack协议。有时我们不仅要确保server不但收到了数据,而且还要保证server成功处理了数据。这时,可以等server成功处理完数据之后,再给client发ack。

2.2Socket Keep Alive

Socket连接像数据库连接一样,属于重量型资源。如果我们频繁的创建socket、发送/接收数据、关闭socket,那么会有很大一部分时间浪费在socket的创建和关闭上。

所以,如果我们经常需要与同一个socket地址发送/接收数据时,应该考虑只创建一次socket,然后一直使用这个socket对象发送/接收数据。

2.3Heartbeat

通常,我们会设置socket的receive timeout。这样,如果我们一直打开着socket (keep alive),而很长时间又没有数据通讯,socket接收方就会timeout,最终导致打开的连接坏掉。

如果很长时间没有数据通讯,防火墙或代理服务器也可能会关闭打开的socket连接。

所以为了保证打开的socket连接一直可用,如果一段时间没有数据进行通讯(或指定一个时间间隔),我们可以显示的发送一个heartbeat消息(比如: $HRT)给对方,从而保证连接不会被异常关闭。

2.4Socket Close

每一个socket对象会持有一个socket descriptor (其实就是file descriptor),操作系统对于socket descriptor有一个最大限制。因此当socket不再使用时,一定要记得关闭,即使socket连接失败或出现异常,只要socket对象不为null,一定要记得关闭。

四、socket服务端的编写

服务器端编程步骤:
1: 创建服务器端套接字并绑定到一个端口上(0-1023是系统预留的,最好大约1024以上)
2: 套接字设置监听模式等待连接请求
3: 接受连接请求后进行通信
4: 返回,等待下一个连接请求

import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.IOException;import java.io.InputStreamReader;import java.io.OutputStreamWriter;import java.io.PrintWriter;import java.net.ServerSocket;import java.net.Socket;import java.util.ArrayList;import java.util.List;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class SckServer {private static final int PORT = 9999;// 端口private List<Socket> mList = new ArrayList<Socket>();private ServerSocket server = null;private ExecutorService mExecutorService = null; // thread poolpublic static void main(String[] args) {new SckServer();}public SckServer() {try {// 创建服务器端socket并绑定到一个端口上server = new ServerSocket(PORT);// 使用连接池mExecutorService = Executors.newCachedThreadPool();System.out.print("server start ...");Socket client = null;// 接字设置监听模式等待连接请求while (true) {client = server.accept();mList.add(client);// 接受连接请求后进行通信mExecutorService.execute(new Service(client));}} catch (Exception e) {e.printStackTrace();}}class Service implements Runnable {private Socket socket;private BufferedReader in = null;private String msg = "";public Service(Socket socket) {this.socket = socket;try {in = new BufferedReader(new InputStreamReader(socket.getInputStream()));msg = "user" + this.socket.getInetAddress() + "come toal:"+ mList.size();this.sendmsg();} catch (IOException e) {e.printStackTrace();}}@Overridepublic void run() {// TODO Auto-generated method stubtry {while (true) {if ((msg = in.readLine()) != null) {if (msg.equals("exit")) {System.out.println("ssssssss");mList.remove(socket);in.close();msg = "user:" + socket.getInetAddress()+ "exit total:" + mList.size();socket.close();this.sendmsg();break;} else {msg = socket.getInetAddress() + ":" + msg;this.sendmsg();}}}} catch (Exception e) {e.printStackTrace();}}public void sendmsg() {System.out.println(msg);int num = mList.size();for (int index = 0; index < num; index++) {Socket mSocket = mList.get(index);PrintWriter pout = null;try {pout = new PrintWriter(new BufferedWriter(new OutputStreamWriter(mSocket.getOutputStream())),true);pout.println(msg);} catch (IOException e) {e.printStackTrace();}}}}}


五、Android客户端的编写

客户端编程步骤:
1: 创建客户端套接字(指定服务器端IP地址与端口号)
2: 连接(Android 创建Sockett时会自动连接)
3: 与服务器端进行通信
4: 关闭套接字

package com.lyh.sck;import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.IOException;import java.io.InputStreamReader;import java.io.OutputStreamWriter;import java.io.PrintWriter;import java.net.Socket;import android.app.Activity;import android.app.AlertDialog;import android.content.DialogInterface;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;public class MainActivity extends Activity {private TextView tv_msg = null;private EditText ed_msg = null;private Button btn_send = null;private static final String HOST = "192.168.3.121";private static final int PORT = 9999;private Socket socket = null;private BufferedReader in = null;private PrintWriter out = null;private String content = "";public Handler scHandler;/** Called when the activity is first created. */@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initData();findViews();initSocket();}/** * 初始化控件 * */private void findViews() {tv_msg = (TextView) findViewById(R.id.TextView);ed_msg = (EditText) findViewById(R.id.EditText01);btn_send = (Button) findViewById(R.id.Button02);}/** * 初始化数据 * */private void initData() {scHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {// TODO Auto-generated method stubsuper.handleMessage(msg);if (msg.what == 0) {btn_send.setOnClickListener(new Button.OnClickListener() {@Overridepublic void onClick(View v) {// TODO Auto-generated method stubString msg = ed_msg.getText().toString();if (socket.isConnected()) {if (!socket.isOutputShutdown()) {out.println(msg);}}}});new Thread(runnable).start();} else if (msg.what == 1) {tv_msg.setText(tv_msg.getText().toString() + content);} else if (msg.what == 2) {}}};}/** * 初始化Socket * */public void initSocket() {new Thread() {@Overridepublic void run() {// TODO Auto-generated method stubtry {socket = new Socket(HOST, PORT);//初始化socket对象in = new BufferedReader(new InputStreamReader(socket.getInputStream()));out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);//初始化玩socket,将结果告知scHandlerMessage msg=scHandler.obtainMessage();msg.what=0;scHandler.sendMessage(msg);} catch (IOException ex) {ex.printStackTrace();ShowDialog("login exception" + ex.getMessage());}}}.start();}/*** * 获取数据线程 * */public Runnable runnable = new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubtry {while (true) {if (socket.isConnected()) {if (!socket.isInputShutdown()) {if ((content = in.readLine()) != null) {content += "\n";Message msg=scHandler.obtainMessage();msg.what=1;scHandler.sendMessage(msg);} else {}}}}} catch (Exception e) {e.printStackTrace();}}};  public void ShowDialog(String msg) {new AlertDialog.Builder(this).setTitle("notification").setMessage(msg).setPositiveButton("ok", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// TODO Auto-generated method stub}}).show();}}


备注:Android网络编程的时候,切记不能将耗时的上传下载线程放在UI线程中。

源码下载地址:http://download.csdn.net/detail/stop_pig/7052629

更多相关文章

  1. Android Studio中获取sha1证书指纹数据的方法
  2. android通过webservice连接SQL数据库(一)服务器端
  3. Android开发之数据存储之二:SQLite数据库存储方式【免费提供源码
  4. Android入门篇四:使用全局变量在Activity之间传递数据
  5. (超详细)android中SqLite数据库的使用(一文包懂包会)
  6. Study on Android【二】--ContentProvider数据模型概述
  7. 命令数据库在Android中查看和管理sqlite数据库
  8. Android入门篇四:使用剪切板在Activity之间传递对象数据

随机推荐

  1. [Android]Fragment点击穿透问题
  2. APIDemo动画合集
  3. Unity3D调用Android系统相册
  4. Android通过MCC+MNC实现锁卡
  5. 安卓倒计时
  6. android 多个自定义dialog
  7. 解决Linux下USB连接android手机
  8. Android设备上调用谷歌应用的的Intent 列
  9. NDK_PROJECT_PATH = null问题分析
  10. android sharedpreferences封装简单实用