Android Socket编程android端服务器和客户端的实现
其实和java实现的差不多,或本质是用java实现的,但由于android有自身的独特性,所以还是有一些要注意的点:
我这个Demo是以服务器开启,然后客户端连上服务器后就可与服务器进行交互,客户端每向服务器发送一条信息,服务器就向客户端返回相应的信息,两端都在android端实现(网上有很多客户端用android实现的,没找到服务器也在android端实现的,另外网上的服务器基本都有开始没有关闭,我这里也添加了关闭,其中涉及多线程,希望能给大家一点启发,当然不足之处还望告知,我也是初学的学生),其中客户端基本借鉴《疯狂Android讲义》。
这个Demo的功能简单但扩展性很大,以这个为核心可以扩展出很多应用或功能(例如即时聊天,微型QQ等);
先上图:
服务器:
客户端:
服务器端代码(有注释不说了):
package com.example.multithreadserver;
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.InetAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.TextView;
/*
* @Author mowen
* @Time 2013/6/9
*/
public class MainActivity extends Activity {
private static final int PORT = 9999;
private List<Socket> mList = new ArrayList<Socket>();
private volatile ServerSocket server=null;
private ExecutorService mExecutorService = null; //线程池
private String hostip;//本机IP
private TextView mText1;
private TextView mText2;
private Button mBut1=null;
private Handler myHandler=null;
private volatile boolean flag= true;//线程标志位
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);
hostip = getLocalIpAddress(); //获取本机IP
mText1=(TextView) findViewById(R.id.textView1);
mText1.setText(hostip);
mText1.setEnabled(false);
mText2=(TextView) findViewById(R.id.textView2);
mBut1=(Button) findViewById(R.id.but1);
mBut1.setOnClickListener(new Button1ClickListener());
//取得非UI线程传来的msg,以改变界面
myHandler =new Handler(){
@SuppressLint("HandlerLeak")
public void handleMessage(Message msg){
if(msg.what==0x1234){
mText2.append("\n" + msg.obj.toString());
}
}
};
}
//对button1的监听事件
private final class Button1ClickListener implements View.OnClickListener{
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
//如果是“启动”,证明服务器是关闭状态,可以开启服务器
if(mBut1.getText().toString().equals("启动")){
System.out.println("flag:"+flag);
ServerThread serverThread=new ServerThread();
flag=true;
serverThread.start();
mBut1.setText("关闭");
}else{
try {
flag=false;
server.close();
for(int p=0;p<mList.size();p++){
Socket s=mList.get(p);
s.close();
}
mExecutorService.shutdownNow();
mBut1.setText("启动");
System.out.println("服务器已关闭");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//Server端的主线程
class ServerThread extends Thread {
public void stopServer(){
try {
if(server!=null){
server.close();
System.out.println("close task successed");
}
} catch (IOException e) {
System.out.println("close task failded");
}
}
public void run() {
try {
server = new ServerSocket(PORT);
} catch (IOException e1) {
// TODO Auto-generated catch block
System.out.println("S2: Error");
e1.printStackTrace();
}
mExecutorService = Executors.newCachedThreadPool(); //创建一个线程池
System.out.println("服务器已启动...");
Socket client = null;
while(flag) {
try {
System.out.println("S3: Error");
client = server.accept();
System.out.println("S4: Error");
//把客户端放入客户端集合中
mList.add(client);
mExecutorService.execute(new Service(client)); //启动一个新的线程来处理连接
}catch ( IOException e) {
System.out.println("S1: Error");
e.printStackTrace();
}
}
}
}
//获取IPv6的IP地址
/*public String getLocalIpAddress() {
try {
for (Enumeration<NetworkInterface> en = NetworkInterface
.getNetworkInterfaces(); en.hasMoreElements();) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf
.getInetAddresses(); enumIpAddr.hasMoreElements();) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress()) {
return inetAddress.getHostAddress().toString();
}
}
}
} catch (SocketException ex) {
Log.e("WifiPreference IpAddress", ex.toString());
}
return null;
} */
//获取本地IP
public static String getLocalIpAddress() {
try {
for (Enumeration<NetworkInterface> en = NetworkInterface
.getNetworkInterfaces(); en.hasMoreElements();) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf
.getInetAddresses(); enumIpAddr.hasMoreElements();) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress() && !inetAddress.isLinkLocalAddress()) {
return inetAddress.getHostAddress().toString();
}
}
}
} catch (SocketException ex) {
Log.e("WifiPreference IpAddress", ex.toString());
}
return null;
}
//处理与client对话的线程
class Service implements Runnable {
private volatile boolean kk=true;
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="OK";
this.sendmsg(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
public void run() {
while(kk) {
try {
if((msg = in.readLine())!= null) {
//当客户端发送的信息为:exit时,关闭连接
if(msg.equals("exit")) {
mList.remove(socket);
//in.close();
//socket.close();
break;
//接收客户端发过来的信息msg,然后发送给客户端。
} else {
Message msgLocal = new Message();
msgLocal.what = 0x1234;
msgLocal.obj =msg+" (客户端发送)" ;
System.out.println(msgLocal.obj.toString());
System.out.println(msg);
myHandler.sendMessage(msgLocal);
msg = socket.getInetAddress() + ":" + msg+"(服务器发送)";
this.sendmsg(msg);
}
}
} catch (IOException e) {
System.out.println("close");
kk=false;
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//向客户端发送信息
public void sendmsg(String msg) {
System.out.println(msg);
PrintWriter pout = null;
try {
pout = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())),true);
pout.println(msg);
}catch (IOException e) {
e.printStackTrace();
}
}
}
}
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<Button
android:id="@+id/but1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/textView1"
android:layout_alignParentRight="true"
android:layout_marginRight="60dp"
android:text="启动" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/textView1"
android:layout_below="@+id/textView1"
android:layout_marginTop="44dp"
android:text="内容" />
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="15dp"
android:layout_marginTop="48dp"
android:text="IP" />
</RelativeLayout>
客户端主要借鉴《疯狂Android讲义》:
UI端文件
package org.crazyit.net;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
/**
*/
public class MultiThreadClient extends Activity
{
// 定义界面上的两个文本框
EditText input;
TextView show;
// 定义界面上的一个按钮
Button send;
Handler handler;
// 定义与服务器通信的子线程
ClientThread clientThread;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
input = (EditText) findViewById(R.id.input);
send = (Button) findViewById(R.id.send);
show = (TextView) findViewById(R.id.show);
handler = new Handler() //①
{
@Override
public void handleMessage(Message msg)
{
// 如果消息来自于子线程
if (msg.what == 0x123)
{
// 将读取的内容追加显示在文本框中
show.append("\n" + msg.obj.toString());
}
}
};
clientThread = new ClientThread(handler);
// 客户端启动ClientThread线程创建网络连接、读取来自服务器的数据
new Thread(clientThread).start(); //①
send.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
try
{
// 当用户按下发送按钮后,将用户输入的数据封装成Message,
// 然后发送给子线程的Handler
Message msg = new Message();
msg.what = 0x345;
msg.obj = input.getText().toString();
clientThread.revHandler.sendMessage(msg);
// 清空input文本框
input.setText("");
}
catch (Exception e)
{
e.printStackTrace();
}
}
});
}
}
功能线程类:
/**
*
*/
package org.crazyit.net;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketTimeoutException;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
/**
*/
public class ClientThread implements Runnable
{
private Socket s;
// 定义向UI线程发送消息的Handler对象
private Handler handler;
// 定义接收UI线程的消息的Handler对象
public Handler revHandler;
// 该线程所处理的Socket所对应的输入流
BufferedReader br = null;
OutputStream os = null;
public ClientThread(Handler handler)
{
this.handler = handler;
}
public void run()
{
try
{
System.out.println("T1");
s = new Socket("10.0.2.15", 9999);
System.out.println("T2");
br = new BufferedReader(new InputStreamReader(
s.getInputStream()));
os = s.getOutputStream();
// 启动一条子线程来读取服务器响应的数据
new Thread()
{
@Override
public void run()
{
String content = null;
// 不断读取Socket输入流中的内容。
try
{
while ((content = br.readLine()) != null)
{
// 每当读到来自服务器的数据之后,发送消息通知程序界面显示该数据
Message msg = new Message();
msg.what = 0x123;
msg.obj = content;
handler.sendMessage(msg);
System.out.println(msg.obj.toString());
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
}.start();
// 为当前线程初始化Looper
Looper.prepare();
// 创建revHandler对象
revHandler = new Handler()
{
@Override
public void handleMessage(Message msg)
{
// 接收到UI线程中用户输入的数据
if (msg.what == 0x345)
{
// 将用户在文本框内输入的内容写入网络
try
{
System.out.println("HHHHHHH"+msg.obj.toString());
os.write((msg.obj.toString() + "\r\n")
.getBytes("utf-8"));
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
};
// 启动Looper
Looper.loop();
}
catch (SocketTimeoutException e1)
{
System.out.println("网络连接超时!!");
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
布局文件很简单:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<!-- 定义一个文本框,它用于接受用户的输入 -->
<EditText
android:id="@+id/input"
android:layout_width="240dp"
android:layout_height="wrap_content"
/>
<Button
android:id="@+id/send"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="8px"
android:text="@string/send"
/>
</LinearLayout>
<TextView
android:id="@+id/show"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="top"
android:background="#ffff"
android:textSize="14dp"
android:textColor="#f000"
/>
</LinearLayout>
声明:真机上只需把客户端IP改成真机IP,即服务器上显示的IP即可通信
虚拟机上同一个虚拟机可通信
不同虚拟机由于Android虚拟机设计的原因,需要端口重定向等操作才能实现通信附带两个工程的源码:http://download.youkuaiyun.com/detail/mowen1111/5553829