Android使用多线程进行网络聊天室通信
作者:_彼岸雨敲窗_ 发布时间:2022-05-11 18:56:36
TCP/IP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket,从而在通信的两端之间形成网络虚拟链路。一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信了。Java对基于TCP协议的网络通信提供了良好的封装,Java使用Socket对象来代表两端通信接口,并通过Socket产生IO流来进行网络通信。
下面的程序Demo是实现一个简单的C/S聊天室的应用,每个客户端该包含两条线程:一条负责生成主界面,响应用户动作,并将用户输入的数据写入Socket对应的输出流中;另一条负责读取Socket对应的输入流中的数据(从服务器发送过来的数据),并负责将这些数据在程序界面上显示出来。
客户端程序是一个Android应用,因此需要创建一个Android项目,这个Android应用的界面中包含两个文本框:一个用于接收用户的输入;另一个用于显示聊天信息。界面中还有一个按钮,当用户单击该按钮时,程序向服务器发送聊天信息。
layout/activity_main.xml界面布局代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<!-- 定义一个文本框,它用于接收用户的输入 -->
<EditText
android:id="@+id/input"
android:layout_width="280dp"
android:layout_height="wrap_content" />
<Button
android:id="@+id/send"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:text="发送" />
</LinearLayout>
<!-- 定义一个文本框,它用于显示来自服务器的信息 -->
<TextView
android:id="@+id/show"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffff"
android:gravity="top"
android:textColor="#f000"
android:textSize="18sp" />
</LinearLayout>
客户端的Activity负责生成程序界面,并为程序的按钮单击事件绑定事件 * ,当用户单击按钮时向服务器发送信息。
MainActivity.java逻辑代码如下:
package com.fukaimei.multithreadclient;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// 定义界面上的两个文本框
EditText input;
TextView show;
// 定义界面上的一个按钮
Button send;
Handler handler;
// 定义与服务器通信的子线程
ClientThread clientThread;
@Override
public 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);
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 = 0x234;
msg.obj = input.getText().toString();
clientThread.revHandler.sendMessage(msg);
// 清空input文本框
input.setText("");
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
当用户单击该程序界面中的“发送”按钮后,程序将会把input输入框中的内容发送给clientThread的revHandler对象,clientThread负责将用户输入的内容发送给服务器。
ClientThread子线程负责建立与远程服务器的连接,并负责与远程服务器通信,读到数据之后便通过Handler对象发送一条消息;当ClientThread子线程收到UI线程发送过来的消息后,还负责将用户输入的内容发送给远程服务器。
ClientThread.java逻辑代码如下:
package com.fukaimei.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;
public class ClientThread implements Runnable {
private static final String TAG = "ClientThread";
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 {
s = new Socket("172.xx.xx.xxx", 30000);
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);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
// 为当前线程初始化Looper
Looper.prepare();
// 创建revHandler对象
revHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 接收到UI线程中用户输入的数据
if (msg.what == 0x234) {
// 将用户在文本框内输入的内容写入网络
try {
os.write((msg.obj.toString() + "\r\n")
.getBytes("utf-8"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
// 启动Looper
Looper.loop();
} catch (SocketTimeoutException e1) {
Log.d(TAG, "网络连接超时!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
上面线程的功能也非常简单,它只是不断地获取Socket输入流中的内容,当读到Socket输入流中的内容后,便通过Handler对象发送一条消息,消息负责携带读到的数据。除此之外,该子线程还负责读取UI线程发送的消息,接收到消息之后,该子线程负责中携带的数据发送给远程服务器。
服务器端应该包含多条线程,每个Socket对应一条线程,该线程负责读取Socket对应输入流,并将读到的数据向每个Socket输出流发送一遍,因此需要在服务器端使用List来保存所有的Socket。
下面是服务器端的代码。程序为服务器提供了两个类:一个是创建ServerSocket监听的主类;另一个是负责处理每个Socket通信的线程类。
/MultiThreadServer/src/MyServer.java逻辑代码如下:
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
public class MyServer {
// 定义保存所有Socket的ArrayList
public static ArrayList<Socket> socketList = new ArrayList<Socket>();
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(30000);
while (true) {
// 此行代码会阻塞,将一直等待别人的连接
Socket s = ss.accept();
socketList.add(s);
// 每当客户端连接后启动一条ServerThread线程为该客户端服务
new Thread(new ServerThread(s)).start();
}
}
}
上面的程序是服务器端只负责接收客户端Socket的连接请求,每当客户端Socket连接到该ServerSocket之后,程序将对应Socket加入socketList集合中保存,并为该Socket启动一条线程,该程序负责处理该Socket所有的通信任务。服务器端线程类的代码如下。
/MultiThreadServer/src/ServerThread.java逻辑代码如下:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Iterator;
// 负责处理每条线程通信的线程类
public class ServerThread implements Runnable {
// 定义当前线程所处理的Socket
Socket s = null;
// 该线程所处理的Socket所对应的输入流
BufferedReader br = null;
public ServerThread(Socket s) throws IOException {
this.s = s;
// 初始化该Socket对应的输入流
br = new BufferedReader(new InputStreamReader(s.getInputStream(), "utf-8"));
}
@Override
public void run() {
String content = null;
// 采用循环不断从Socket中读取客户端发送过来的数据
while ((content = readFromClient()) != null) {
// 遍历socketList中的每个Socket
// 将读取的内容向每个Socket发送一次
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 (Exception e) {
e.printStackTrace();
// 删除该Socket
it.remove();
System.out.println(MyServer.socketList);
}
}
}
}
// 定义读取客户端数据的方法
private String readFromClient() {
try {
return br.readLine();
} catch (IOException e) { // 如果捕获到异常,表明该Socket对应的客户端已经关闭
e.printStackTrace();
// 删除该Socket
MyServer.socketList.remove(s);
}
return null;
}
}
上面的服务器端线程类不断读取客户端数据,程序使用readFromClient()方法来读取客户端数据,如果在读数据过程中捕获到IOException异常,则表明该Socket对应的客户端Socket出现问题,程序就将该Socket从socketList中删除。
当服务器线程读到客户端数据之后,程序遍历socketList集合,并将该数据向socketList集合中的每个Socket发送一次——该服务器线程将把从Socket中读到的数据向socketList中的每个Socket转发一次。
先运行上面程序的MyServer类,该类运行后只是作为服务器,看不到任何输出。接着可以运行Android客户端——相当于启动聊天界面登录该服务器,接下来在任何一个Android客户端输入一些内容后单击“发送”按钮,将可以看到所有客户端(包含自己)都会收到刚刚输入的内容,这样就简单实现了一个C/S结构的聊天室的功能。
注意:由于该程序需要访问互联网,因此还需要在清单文件AndroidManifest.xml文件中授权访问互联网的权限:
<!-- 授权访问互联网-->
<uses-permission android:name="android.permission.INTERNET" />
Demo程序运行效果界面截图如下:
来源:http://blog.csdn.net/fukaimei/article/details/78158258
猜你喜欢
- 环境准备创建 Maven 项目创建服务器远程连接Tools------Delployment-----Browse Remote Host设
- 因为项目不同,有些公用库而且还是c++的,还有一些带资源的,简单的复制遇到库升级又是一轮配置,编译成aar则解决这些麻烦。但是默认andri
- 一个android文件的Uri地址一般如下: content://media/external/images/media/62026这是一张
- Selenium IDE 是Firefox 浏览器的一个插件, 它会记录你对Firefox的操作,并且可以回放它的操作。 用法简单,不过我觉
- 前言 Gallery的Item使用的是一个ImageView+TextView,并且为其设置了selector,当使用setSe
- Spring Bean的生命周期?首先说一下Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy;Sp
- 工欲善其事,必先利其器,对于想要深入学习Android源码,必须先掌握Android编译命令.一、引言关于Android Build系统,这
- 本文实例为大家分享了java启动线程的方法,供大家参考,具体内容如下1.继承Threadpublic class java_thread e
- 遇到的问题解决方法win7:insert键切回win10:fn+insert切回Mac:fn+i 切回来源:https://blog.csd
- 问题发现今天发生了一件事,令我非常郁闷,就是我在使用一个SDK时,当我调用他的方法时,提示我方法中的参数var1, var2如下:// 方法
- //==============================================// &n
- 一、简介构造函数,基本用法是在类对象声明的时候完成初始化工作。二、实例构造函数1、构造函数的名字与类名相同。2、使用 new 表达式创建类的
- using System;using System.Collections.Generic;using System.Web.Script.
- 今天可是遇到一个很简单的需求,但是却让我蛋疼了半天。滑动屏幕控制物体旋转,但是旋转的角度要在-60到60之间。乍一听这简直是小儿科啊。判断一
- 引言:SpringBoot web项目开发中往往会涉及到一些静态资源的使用,比如说图片,css样式,js等等,今天我们来讲讲这些常见的静态资
- 本文实例讲述了WinForm实现仿视频播放器左下角滚动新闻效果的方法。分享给大家供大家参考。具体实现方法如下:using System;us
- 一、查看线程的运行状态题目线程有以下6种状态:新建、运行、阻塞、等待、计时等待和终止。new新线程时,线程处于新建 状态。调用start()
- 问题描述:java 中inputstream流 转成string,再将String转换会inputStream,下载下来的文件,内容损坏,例
- 我们在j2ee当中,连接数据库的时候经常会用到properties配置文件,我们原来在eclipse或者myeclipse当中会在src文件
- 本文所述为C#实现根据指定容器和控件名字获得控件的方法,在进行C#应用程序设计时有一定的借鉴价值。分享给大家供大家参考借鉴。具体实现方法如下