不要让昨天 占据你的今天 夏午晴天

夏午晴天

Android通过手机搭建服务器,WIFI建立热点实现C/S聊天室通信功能

 

应用效果图:

                                              

                                                                                              客户端                                                                                                          服务器端

 

 先打开手机服务器,使客户端在同一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 + "\n").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 }
View Code
 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 }
View Code

  上面服务器线程类不断读取客户端数据,程序使用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("\n" + 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() + "\r\n").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>
View Code
 

  当用户单击该程序界面中的“发送”按钮后,程序将会把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

posted on 2018-01-01 21:47  夏晴天  阅读(1008)  评论(0编辑  收藏

导航

统计

Live2D