首页 技术 正文
技术 2022年11月15日
0 收藏 879 点赞 2,444 浏览 10660 个字

应用效果图:

Android通过手机搭建服务器,WIFI建立热点实现C/S聊天室通信功能                                              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"/>
 package com.example.lifen.serverdemo; import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView; import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collections; public class MainActivity extends AppCompatActivity { private MyServer ms;
public static String ipAddress;
private TextView ip; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); ms = new MyServer();
ms.startAsync();
ip = (TextView) findViewById(R.id.ip);
ipAddress = getLocalIpAddress();
ip.setText(ipAddress);
} @Override
protected void onDestroy() {
super.onDestroy();
ms.stopAsync();
} public String getLocalIpAddress() {
try {
String ipv4;
ArrayList<NetworkInterface> nilist = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface ni: nilist)
{
ArrayList<InetAddress> ialist = Collections.list(ni.getInetAddresses());
for (InetAddress address: ialist){
if (!address.isLoopbackAddress() && !address.isLinkLocalAddress())
{
ipv4=address.getHostAddress();
return ipv4;
}
} } } catch (SocketException ex) {
Log.e("localip", ex.toString());
}
return null;
}
}
 package com.example.lifen.serverdemo; /**
* Created by LiFen on 2018/1/1.
*/ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.Iterator; public class ServerThread implements Runnable {
Socket s = null;
BufferedReader br = null; public ServerThread(Socket s) throws IOException {
this.s = s;
br = new BufferedReader(new InputStreamReader(s.getInputStream(), "utf-8"));
} public void run() {
try {
String content = null;
while ((content = readFromClient()) != null) {
for (Iterator<Socket> it = MyServer.socketList.iterator(); it.hasNext();) {
Socket s = it.next();
try {
OutputStream os = s.getOutputStream();
os.write((content + "\n").getBytes("utf-8"));
} catch (SocketException e) {
e.printStackTrace();
it.remove();
System.out.println(MyServer.socketList);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
} private String readFromClient() {
try {
return br.readLine();
} catch (IOException e) {
e.printStackTrace();
MyServer.socketList.remove(s);
}
return null;
}
}
 package com.example.lifen.serverdemo; import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList; /**
* Created by LiFen on 2018/1/1.
*/ public class MyServer{
public static ArrayList<Socket> socketList = new ArrayList<>();
private boolean isEnable;
private ServerSocket socket; /**
* 启动Server(异步)
*/
public void startAsync(){
isEnable = true;
new Thread(new Runnable() {
@Override
public void run() {
ServerSocket ss = null;
try {
ss = new ServerSocket(30000);
while (true) {
Socket s = ss.accept();
socketList.add(s);
new Thread(new ServerThread(s)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
} /**
* 停止Server(异步)
*/
public void stopAsync() {
if(!isEnable){
return;
}
isEnable = false;
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
socket = null;
}
}

上面服务器线程类不断读取客户端数据,程序使用readFromClient()方法来读取客户数据,如果在读取数据过程中捕获到 Ioexception异常,则表明该 Socket对应的客户端 Socket出现了问题(到底什么问题我们不管,反正不正常),程序就将该 Socket从 socketlist中删除。

当服务器线程读到客户端数据之后,程序遍历 socketlist集合,并将该数据向 socketlist集合中的每个 Socket发送一次该服务器线程将把从 Socket中读到的数据向 socketlist中的每个 Socket转发一次。

二、客户端

 package com.example.lifen.multithreadclient; import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView; import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collections; public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private EditText input;
private Button send;
private TextView show;
Handler handler;
private ClientThread clientThread;
private TextView ip;
public static String ipAddress; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); input = (EditText) findViewById(R.id.input);
send = (Button) findViewById(R.id.send);
show = (TextView) findViewById(R.id.show);
ip = (TextView) findViewById(R.id.ip);
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
if(msg.what == 0x123){
show.append("\n" + msg.obj.toString());
}
}
};
clientThread = new ClientThread(handler);
new Thread(clientThread).start(); ipAddress = getLocalIpAddress(); ip.setText("当前IP: "+ipAddress); send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try{
Message msg = new Message();
msg.what = 0x345;
msg.obj = input.getText().toString();
// Log.i(TAG, "onClick: "+msg.obj);
clientThread.revHandler.sendMessage(msg);
input.setText("");
}catch (Exception e) {
Log.e(TAG, "onClick: ", e);
}
}
});
} public String getLocalIpAddress() {
try {
String ipv4;
ArrayList<NetworkInterface> nilist = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface ni: nilist)
{
ArrayList<InetAddress> ialist = Collections.list(ni.getInetAddresses());
for (InetAddress address: ialist){
if (!address.isLoopbackAddress() && !address.isLinkLocalAddress())
{
ipv4=address.getHostAddress();
return ipv4;
}
} } } catch (SocketException ex) {
Log.e("localip", ex.toString());
}
return null;
} }
 package com.example.lifen.multithreadclient; import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log; import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketTimeoutException; /**
* Created by LiFen on 2017/12/31.
*/ public class ClientThread implements Runnable {
private static final String TAG = "ClientThread";
private Socket s;
//定义向UI线程发送消息的Handler 对象
private Handler handler;
//定义接收UI线程的消息 Handler对象
public Handler revHandler;
private BufferedReader br = null;
private OutputStream os = null; public ClientThread(Handler handler){
Log.d(TAG, "ClientThread() called with: handler = [" + handler + "]");
this.handler = handler;
} @Override
public void run() {
Log.d(TAG, "run() called");
try{
String iptemp = MainActivity.ipAddress;
s = new Socket(iptemp,30000);
br = new BufferedReader(new InputStreamReader(s.getInputStream()));
os = s.getOutputStream();
//启动一条子线程来读取服务器响应的数据
new Thread(){
@Override
public void run() {
String content = null;
try {
while((content = br.readLine()) != null){
Message msg = new Message();
msg.what = 0x123;
msg.obj = content;
handler.sendMessage(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
//为当前线程 初始化Looper
Looper.prepare();
//创建revHandler对象
revHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
if(msg.what == 0x345){
//将用户在文本框内输入网路
try{
os.write((msg.obj.toString() + "\r\n").getBytes("utf-8"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
// 启动Looper
Looper.loop();
}catch (SocketTimeoutException e1){
Log.i(TAG, "run: 网络连接超时");
} catch (Exception e) {
e.printStackTrace();
}
}
}

布局文件:

 <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.lifen.multithreadclient.MainActivity"> <EditText
android:id="@+id/input"
android:layout_width="244dp"
android:layout_height="47dp"
android:ems="10"
android:inputType="textPersonName"
tools:layout_constraintBottom_creator="1"
android:layout_marginStart="45dp"
app:layout_constraintBottom_toBottomOf="parent"
tools:layout_constraintLeft_creator="1"
android:layout_marginBottom="30dp"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginLeft="45dp"
app:layout_constraintRight_toLeftOf="@+id/send"
android:layout_marginRight="8dp"
app:layout_constraintHorizontal_bias="1.0"
android:layout_marginEnd="8dp" /> <Button
android:id="@+id/send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送"
tools:layout_constraintRight_creator="1"
tools:layout_constraintBottom_creator="1"
app:layout_constraintBottom_toBottomOf="@+id/input"
android:layout_marginEnd="16dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginRight="16dp" /> <TextView
android:id="@+id/ip"
android:layout_width="0dp"
android:layout_height="21dp"
android:text="当前IP:"
android:ellipsize="marquee"
android:focusable="true"
android:focusableInTouchMode="true"
android:maxLines="1"
tools:layout_constraintRight_creator="1"
tools:layout_constraintBottom_creator="1"
app:layout_constraintBottom_toTopOf="@+id/send"
android:layout_marginStart="22dp"
android:layout_marginEnd="22dp"
app:layout_constraintRight_toRightOf="parent"
tools:layout_constraintLeft_creator="1"
android:layout_marginBottom="5dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintHorizontal_bias="0.09" /> <ScrollView
android:layout_width="0dp"
android:layout_height="0dp"
tools:layout_constraintTop_creator="1"
tools:layout_constraintRight_creator="1"
tools:layout_constraintBottom_creator="1"
android:layout_marginStart="28dp"
app:layout_constraintBottom_toBottomOf="@+id/ip"
android:layout_marginEnd="28dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginTop="16dp"
tools:layout_constraintLeft_creator="1"
android:layout_marginBottom="21dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"> <TextView
android:id="@+id/show"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.151"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.105" />
</ScrollView> </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学习

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

手机服务器项目代码下载:http://download.csdn.net/download/qq_36726507/10183204

手机客户端项目下载:http://download.csdn.net/download/qq_36726507/10183212

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,497
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,910
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,744
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,498
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:8,135
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:5,298