Android移动开发-使用多线程进行网络聊天室通信的实现

来源:互联网 发布:淘宝客服视频教程 编辑:程序博客网 时间:2024/06/04 21:57

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程序运行效果界面截图如下:

这里写图片描述


Demo程序源码下载地址

阅读全文
1 0
原创粉丝点击