应用效果图:
客户端 服务器端
先打开手机服务器,使客户端在同一ip下即可完成wifi热点下通信
一、服务器端
服务器端是用Socket 实现,Socket基础可参考我的上一篇博文《手机服务器微架构设计与实现 之 http server》代码如下:
所需权限:
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
1 package com.example.lifen.serverdemo; 2 3 import android.os.Bundle; 4 import android.support.v7.app.AppCompatActivity; 5 import android.util.Log; 6 import android.widget.TextView; 7 8 import java.net.InetAddress; 9 import java.net.NetworkInterface; 10 import java.net.SocketException; 11 import java.util.ArrayList; 12 import java.util.Collections; 13 14 public class MainActivity extends AppCompatActivity { 15 16 private MyServer ms; 17 public static String ipAddress; 18 private TextView ip; 19 20 @Override 21 protected void onCreate(Bundle savedInstanceState) { 22 super.onCreate(savedInstanceState); 23 setContentView(R.layout.activity_main); 24 25 ms = new MyServer(); 26 ms.startAsync(); 27 ip = (TextView) findViewById(R.id.ip); 28 ipAddress = getLocalIpAddress(); 29 ip.setText(ipAddress); 30 } 31 32 @Override 33 protected void onDestroy() { 34 super.onDestroy(); 35 ms.stopAsync(); 36 } 37 38 public String getLocalIpAddress() { 39 try { 40 String ipv4; 41 ArrayList<NetworkInterface> nilist = Collections.list(NetworkInterface.getNetworkInterfaces()); 42 for (NetworkInterface ni: nilist) 43 { 44 ArrayList<InetAddress> ialist = Collections.list(ni.getInetAddresses()); 45 for (InetAddress address: ialist){ 46 if (!address.isLoopbackAddress() && !address.isLinkLocalAddress()) 47 { 48 ipv4=address.getHostAddress(); 49 return ipv4; 50 } 51 } 52 53 } 54 55 } catch (SocketException ex) { 56 Log.e("localip", ex.toString()); 57 } 58 return null; 59 } 60 }
1 package com.example.lifen.serverdemo; 2 3 /** 4 * Created by LiFen on 2018/1/1. 5 */ 6 7 import java.io.BufferedReader; 8 import java.io.IOException; 9 import java.io.InputStreamReader; 10 import java.io.OutputStream; 11 import java.net.Socket; 12 import java.net.SocketException; 13 import java.util.Iterator; 14 15 public class ServerThread implements Runnable { 16 Socket s = null; 17 BufferedReader br = null; 18 19 public ServerThread(Socket s) throws IOException { 20 this.s = s; 21 br = new BufferedReader(new InputStreamReader(s.getInputStream(), "utf-8")); 22 } 23 24 public void run() { 25 try { 26 String content = null; 27 while ((content = readFromClient()) != null) { 28 for (Iterator<Socket> it = MyServer.socketList.iterator(); it.hasNext();) { 29 Socket s = it.next(); 30 try { 31 OutputStream os = s.getOutputStream(); 32 os.write((content + " ").getBytes("utf-8")); 33 } catch (SocketException e) { 34 e.printStackTrace(); 35 it.remove(); 36 System.out.println(MyServer.socketList); 37 } 38 } 39 } 40 } catch (IOException e) { 41 e.printStackTrace(); 42 } 43 } 44 45 private String readFromClient() { 46 try { 47 return br.readLine(); 48 } catch (IOException e) { 49 e.printStackTrace(); 50 MyServer.socketList.remove(s); 51 } 52 return null; 53 } 54 }
1 package com.example.lifen.serverdemo; 2 3 import java.io.IOException; 4 import java.net.ServerSocket; 5 import java.net.Socket; 6 import java.util.ArrayList; 7 8 /** 9 * Created by LiFen on 2018/1/1. 10 */ 11 12 public class MyServer{ 13 public static ArrayList<Socket> socketList = new ArrayList<>(); 14 private boolean isEnable; 15 private ServerSocket socket; 16 17 /** 18 * 启动Server(异步) 19 */ 20 public void startAsync(){ 21 isEnable = true; 22 new Thread(new Runnable() { 23 @Override 24 public void run() { 25 ServerSocket ss = null; 26 try { 27 ss = new ServerSocket(30000); 28 while (true) { 29 Socket s = ss.accept(); 30 socketList.add(s); 31 new Thread(new ServerThread(s)).start(); 32 } 33 } catch (IOException e) { 34 e.printStackTrace(); 35 } 36 } 37 }).start(); 38 } 39 40 /** 41 * 停止Server(异步) 42 */ 43 public void stopAsync() { 44 if(!isEnable){ 45 return; 46 } 47 isEnable = false; 48 try { 49 socket.close(); 50 } catch (IOException e) { 51 e.printStackTrace(); 52 } 53 socket = null; 54 } 55 }
上面服务器线程类不断读取客户端数据,程序使用readFromClient()方法来读取客户数据,如果在读取数据过程中捕获到 Ioexception异常,则表明该 Socket对应的客户端 Socket出现了问题(到底什么问题我们不管,反正不正常),程序就将该 Socket从 socketlist中删除。
当服务器线程读到客户端数据之后,程序遍历 socketlist集合,并将该数据向 socketlist集合中的每个 Socket发送一次该服务器线程将把从 Socket中读到的数据向 socketlist中的每个 Socket转发一次。
二、客户端
1 package com.example.lifen.multithreadclient; 2 3 import android.os.Bundle; 4 import android.os.Handler; 5 import android.os.Message; 6 import android.support.v7.app.AppCompatActivity; 7 import android.util.Log; 8 import android.view.View; 9 import android.widget.Button; 10 import android.widget.EditText; 11 import android.widget.TextView; 12 13 import java.net.InetAddress; 14 import java.net.NetworkInterface; 15 import java.net.SocketException; 16 import java.util.ArrayList; 17 import java.util.Collections; 18 19 public class MainActivity extends AppCompatActivity { 20 private static final String TAG = "MainActivity"; 21 private EditText input; 22 private Button send; 23 private TextView show; 24 Handler handler; 25 private ClientThread clientThread; 26 private TextView ip; 27 public static String ipAddress; 28 29 @Override 30 protected void onCreate(Bundle savedInstanceState) { 31 super.onCreate(savedInstanceState); 32 setContentView(R.layout.activity_main); 33 34 input = (EditText) findViewById(R.id.input); 35 send = (Button) findViewById(R.id.send); 36 show = (TextView) findViewById(R.id.show); 37 ip = (TextView) findViewById(R.id.ip); 38 handler = new Handler(){ 39 @Override 40 public void handleMessage(Message msg) { 41 if(msg.what == 0x123){ 42 show.append(" " + msg.obj.toString()); 43 } 44 } 45 }; 46 clientThread = new ClientThread(handler); 47 new Thread(clientThread).start(); 48 49 ipAddress = getLocalIpAddress(); 50 51 ip.setText("当前IP: "+ipAddress); 52 53 send.setOnClickListener(new View.OnClickListener() { 54 @Override 55 public void onClick(View v) { 56 try{ 57 Message msg = new Message(); 58 msg.what = 0x345; 59 msg.obj = input.getText().toString(); 60 // Log.i(TAG, "onClick: "+msg.obj); 61 clientThread.revHandler.sendMessage(msg); 62 input.setText(""); 63 }catch (Exception e) { 64 Log.e(TAG, "onClick: ", e); 65 } 66 } 67 }); 68 } 69 70 public String getLocalIpAddress() { 71 try { 72 String ipv4; 73 ArrayList<NetworkInterface> nilist = Collections.list(NetworkInterface.getNetworkInterfaces()); 74 for (NetworkInterface ni: nilist) 75 { 76 ArrayList<InetAddress> ialist = Collections.list(ni.getInetAddresses()); 77 for (InetAddress address: ialist){ 78 if (!address.isLoopbackAddress() && !address.isLinkLocalAddress()) 79 { 80 ipv4=address.getHostAddress(); 81 return ipv4; 82 } 83 } 84 85 } 86 87 } catch (SocketException ex) { 88 Log.e("localip", ex.toString()); 89 } 90 return null; 91 } 92 93 }
1 package com.example.lifen.multithreadclient; 2 3 import android.os.Handler; 4 import android.os.Looper; 5 import android.os.Message; 6 import android.util.Log; 7 8 import java.io.BufferedReader; 9 import java.io.IOException; 10 import java.io.InputStreamReader; 11 import java.io.OutputStream; 12 import java.net.Socket; 13 import java.net.SocketTimeoutException; 14 15 /** 16 * Created by LiFen on 2017/12/31. 17 */ 18 19 public class ClientThread implements Runnable { 20 private static final String TAG = "ClientThread"; 21 private Socket s; 22 //定义向UI线程发送消息的Handler 对象 23 private Handler handler; 24 //定义接收UI线程的消息 Handler对象 25 public Handler revHandler; 26 private BufferedReader br = null; 27 private OutputStream os = null; 28 29 public ClientThread(Handler handler){ 30 Log.d(TAG, "ClientThread() called with: handler = [" + handler + "]"); 31 this.handler = handler; 32 } 33 34 @Override 35 public void run() { 36 Log.d(TAG, "run() called"); 37 try{ 38 String iptemp = MainActivity.ipAddress; 39 s = new Socket(iptemp,30000); 40 br = new BufferedReader(new InputStreamReader(s.getInputStream())); 41 os = s.getOutputStream(); 42 //启动一条子线程来读取服务器响应的数据 43 new Thread(){ 44 @Override 45 public void run() { 46 String content = null; 47 try { 48 while((content = br.readLine()) != null){ 49 Message msg = new Message(); 50 msg.what = 0x123; 51 msg.obj = content; 52 handler.sendMessage(msg); 53 } 54 } catch (IOException e) { 55 e.printStackTrace(); 56 } 57 } 58 }.start(); 59 //为当前线程 初始化Looper 60 Looper.prepare(); 61 //创建revHandler对象 62 revHandler = new Handler(){ 63 @Override 64 public void handleMessage(Message msg) { 65 if(msg.what == 0x345){ 66 //将用户在文本框内输入网路 67 try{ 68 os.write((msg.obj.toString() + " ").getBytes("utf-8")); 69 } catch (IOException e) { 70 e.printStackTrace(); 71 } 72 } 73 } 74 }; 75 // 启动Looper 76 Looper.loop(); 77 }catch (SocketTimeoutException e1){ 78 Log.i(TAG, "run: 网络连接超时"); 79 } catch (Exception e) { 80 e.printStackTrace(); 81 } 82 } 83 }
布局文件:
1 <?xml version="1.0" encoding="utf-8"?> 2 <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:tools="http://schemas.android.com/tools" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent" 7 tools:context="com.example.lifen.multithreadclient.MainActivity"> 8 9 <EditText 10 android:id="@+id/input" 11 android:layout_width="244dp" 12 android:layout_height="47dp" 13 android:ems="10" 14 android:inputType="textPersonName" 15 tools:layout_constraintBottom_creator="1" 16 android:layout_marginStart="45dp" 17 app:layout_constraintBottom_toBottomOf="parent" 18 tools:layout_constraintLeft_creator="1" 19 android:layout_marginBottom="30dp" 20 app:layout_constraintLeft_toLeftOf="parent" 21 android:layout_marginLeft="45dp" 22 app:layout_constraintRight_toLeftOf="@+id/send" 23 android:layout_marginRight="8dp" 24 app:layout_constraintHorizontal_bias="1.0" 25 android:layout_marginEnd="8dp" /> 26 27 <Button 28 android:id="@+id/send" 29 android:layout_width="wrap_content" 30 android:layout_height="wrap_content" 31 android:text="发送" 32 tools:layout_constraintRight_creator="1" 33 tools:layout_constraintBottom_creator="1" 34 app:layout_constraintBottom_toBottomOf="@+id/input" 35 android:layout_marginEnd="16dp" 36 app:layout_constraintRight_toRightOf="parent" 37 android:layout_marginRight="16dp" /> 38 39 <TextView 40 android:id="@+id/ip" 41 android:layout_width="0dp" 42 android:layout_height="21dp" 43 android:text="当前IP:" 44 android:ellipsize="marquee" 45 android:focusable="true" 46 android:focusableInTouchMode="true" 47 android:maxLines="1" 48 tools:layout_constraintRight_creator="1" 49 tools:layout_constraintBottom_creator="1" 50 app:layout_constraintBottom_toTopOf="@+id/send" 51 android:layout_marginStart="22dp" 52 android:layout_marginEnd="22dp" 53 app:layout_constraintRight_toRightOf="parent" 54 tools:layout_constraintLeft_creator="1" 55 android:layout_marginBottom="5dp" 56 app:layout_constraintLeft_toLeftOf="parent" 57 app:layout_constraintHorizontal_bias="0.09" /> 58 59 <ScrollView 60 android:layout_width="0dp" 61 android:layout_height="0dp" 62 tools:layout_constraintTop_creator="1" 63 tools:layout_constraintRight_creator="1" 64 tools:layout_constraintBottom_creator="1" 65 android:layout_marginStart="28dp" 66 app:layout_constraintBottom_toBottomOf="@+id/ip" 67 android:layout_marginEnd="28dp" 68 app:layout_constraintRight_toRightOf="parent" 69 android:layout_marginTop="16dp" 70 tools:layout_constraintLeft_creator="1" 71 android:layout_marginBottom="21dp" 72 app:layout_constraintLeft_toLeftOf="parent" 73 app:layout_constraintTop_toTopOf="parent"> 74 75 <TextView 76 android:id="@+id/show" 77 android:layout_width="wrap_content" 78 android:layout_height="wrap_content" 79 app:layout_constraintBottom_toBottomOf="parent" 80 app:layout_constraintHorizontal_bias="0.151" 81 app:layout_constraintLeft_toLeftOf="parent" 82 app:layout_constraintRight_toRightOf="parent" 83 app:layout_constraintTop_toTopOf="parent" 84 app:layout_constraintVertical_bias="0.105" /> 85 </ScrollView> 86 87 </android.support.constraint.ConstraintLayout>
当用户单击该程序界面中的“发送”按钮后,程序将会把nput输入框中的内容发送给clientthread的 rehandle对象, clientThread负责将用户输入的内容发送给服务器。
为了避免UI线程被阻塞,该程序将建立网络连接、与网络服务器通信等工作都交给Client thread线程完成,代码47行处启动 ClientThread线程。
由于 Android不允许子线程访问界面组件,因此上面的程序定义了一个 Handler来处理来自线程的消息,如程序中38~45行代码所示。
Client thread子线程负责建立与远程服务器的连接,并负责与远程服务器通信,读到数据之后便通过 Handler对象发送一条消息;当 Client thread子线程收到UI线程发送过来的消息(消息携带了用户输入的内容)后,还负责将用户输入的内容发送给远程服务器。该子线程的代码如上ClientThread代码。
ClientThread线程的功能是不断的获取Soket输出流中的内容,当读到Socket输入流中内容后,便通过Handler对象发送一条消息,消息负责携带读到的数据。该线程还负责读取UI线程发送的消息,接收消息后,该子线程负责将消息中携带的数据发送到远程服务器。
ClientThread子线程中Looper学习
手机服务器项目代码下载:http://download.csdn.net/download/qq_36726507/10183204
手机客户端项目下载:http://download.csdn.net/download/qq_36726507/10183212