欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android使用多线程进行网络聊天室通信

程序员文章站 2023-12-10 13:55:28
tcp/ip通信协议是一种可靠的网络协议,它在通信的两端各建立一个socket,从而在通信的两端之间形成网络虚拟链路。一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路...

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

Android使用多线程进行网络聊天室通信

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。