暑假第四十二天,我们依然很宅的呆在地下室里生活和学习着,最近虽然每天都起得很早,但是效率貌似没有以前高了,学习的劲头没有以前高了,越来越不想吃学校的独家饭了,每天都是馒头(而且还是好硬的那种),不过想想呢,有的吃就不错了,在这就不抱怨了,看看今天实现的android聊天室的聊天功能吧,先说说服务器端的代码及其功能吧
server.java : 负责服务器的界面,以及更服务器主线程ServerThread的启动,产生了BroadCast广播,产生ClientThread线程
ServerThread.java:服务器监听的端口线程,负责创建ServerSocket及监听是否有新的客户端连接,并记录客户端连接及需要发送的信息,产生了BroadCast广播
BroadCast.java: 服务器向客户端广播线程,负责向客户端发送消息,产生ClientThread线程
ClientThread.java:维持服务器和单个客户端的连接线程,负责接受客户端发来是信息
好了接着就看看他们的代码吧!!
1.server.java-------创建ServerThread对象启动run方法
package com.wang;import java.awt.BorderLayout;import javax.swing.*;import java.awt.event.*;public class Server extends JFrame implements ActionListener { /**** *服务器端主程序负责界面,以及服务段主线程ServerThread的启动 * 服务端主线程ServerThread又产生BroadCast及ClientThread线程 建立服务器端主界面中所用到的布局方式 ***/ // 边框容器 BorderLayout borderLayout1 = new BorderLayout(); BorderLayout borderLayout2 = new BorderLayout(); // 创建面板 JPanel jPanel1 = new JPanel(); JPanel jPanel2 = new JPanel(); // 创建按钮 JButton jButton1 = new JButton(); JButton jButton2 = new JButton(); JScrollPane jScrollPane1 = new JScrollPane(); // 创建服务器端接收信息文本框 static JTextArea jTextArea1 = new JTextArea(); boolean bool = false, start = false; // 声明ServerThread线程类对象 ServerThread serverThread; Thread thread; // 构造函数,用于初始化 public Server() { super("Server"); // 设置内容面板布局方式 getContentPane().setLayout(borderLayout1); // 初始化按钮组件 jButton1.setText("启动服务器"); // 按钮的动作设置监听事件 jButton1.addActionListener(this); jButton2.setText("关闭服务器"); // 按钮的动作设置监听事件 jButton2.addActionListener(this); // 初始化jPanel1面板对象,并向其中加入组件,上北 this.getContentPane().add(jPanel1, java.awt.BorderLayout.NORTH); jPanel1.add(jButton1); jPanel1.add(jButton2); // 初始化jPanel2面板对象,并向其中加入组件, jTextArea1.setText(""); jPanel2.setLayout(borderLayout2); jPanel2.add(jScrollPane1, java.awt.BorderLayout.CENTER); jScrollPane1.getViewport().add(jTextArea1); this.getContentPane().add(jPanel2, java.awt.BorderLayout.CENTER); this.setSize(400, 400); this.setVisible(true); } public static void main(String[] args) { Server sever = new Server(); sever.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } // 服务器界面中按钮事件处理 public void actionPerformed(ActionEvent e) { if (e.getSource() == jButton1) { // 声明一个ServerThread对象 serverThread = new ServerThread(); serverThread.start(); } else if (e.getSource() == jButton2) { bool = false; start = false; serverThread.finalize(); this.setVisible(false); } }}
2.ServerThread.java -----创建Broadcast对象,启动该线程,实现run方法后,不断的向客户端发送消息,ServerThread开启后,不断的获取新的客户端并监听是否发送消息
package com.wang;import java.util.*;import java.io.*;import java.net.*;public class ServerThread extends Thread// 服务器监听端口线程{ // 声明ServerSocket类对象 ServerSocket serverSocket; // 指定服务器监听端口常量 public static final int PORT = 80; /** * 创建一个Vector对象,用于存储客户端连接的ClientThread对象 , ClientThread类维持服务器与单个客户端的连接线程 * 负责接收客户端发来的信息,clients负责存储所有与服务器建立连接的客户端 **/ Vector<ClientThread> clients; // 创建一个Vector对象,用于存储客户端发送过来的信息 Vector<Object> messages; // BroadCast类负责服务器向客户端广播消息 BroadCast broadcast; String ip; InetAddress myIPaddress = null; public ServerThread() { /*** * 创建两个Vector数组非常重要 , clients负责存储所有与服务器建立连接的客户端, * messages负责存储服务器接收到的未发送出去的全部客户端的信息 * **/ clients = new Vector<ClientThread>(); messages = new Vector<Object>(); try { // 创建ServerSocket类对象 serverSocket = new ServerSocket(PORT); } catch (IOException E) { } // 获取本地服务器地址信息 try { myIPaddress = InetAddress.getLocalHost(); } catch (UnknownHostException e) { System.out.println(e.toString()); } ip = myIPaddress.getHostAddress(); Server.jTextArea1.append("服务器地址:" + ip + "端口号:" + String.valueOf(serverSocket.getLocalPort()) + "\n"); // 创建广播信息线程并启动 broadcast = new BroadCast(this); broadcast.start(); } /** * 注意:一旦监听到有新的客户端创建即new Socket(ip, PORT)被执行, * 就创建一个ClientThread来维持服务器与这个客户端的连接 **/ public void run() { while (true) { try { // 获取客户端连接,并返回一个新的Socket对象 Socket socket = serverSocket.accept(); System.out.println(socket.getInetAddress().getHostAddress()); // 创建ClientThread线程并启动,可以监听该连接对应的客户端是否发送来消息, 并获取消息 ClientThread clientThread = new ClientThread(socket, this); clientThread.start(); if (socket != null) { synchronized (clients) { // 将客户端连接加入到Vector数组中保存 clients.addElement(clientThread); } } } catch (IOException E) { System.out.println("发生异常:" + E); System.out.println("建立客户端联机失败!"); System.exit(2); } } } public void finalize() { try { // 关闭serverSocket方法 serverSocket.close(); } catch (IOException E) { } serverSocket = null; }}
3.BroadCast.java------
package com.wang;import java.io.*;public class BroadCast extends Thread { // 服务器向客户端广播线程 ClientThread clientThread; // 声明ServerThread对象 ServerThread serverThread; String str; public BroadCast(ServerThread serverThread) { this.serverThread = serverThread; } // 该方法的作用是不停地向所有客户端发送新消息 public void run() { while (true) { try { // 线程休眠200 ms Thread.sleep(200); } catch (InterruptedException E) { } // 同步化serverThread.messages synchronized (serverThread.messages) { // 判断是否有未发的消息 if (serverThread.messages.isEmpty()) { continue; } // 获取服务器端存储的需要发送的第一条数据信息 str = (String) this.serverThread.messages.firstElement(); } // 同步化serverThread.clients synchronized (serverThread.clients) { // 利用循环获取服务器中存储的所有建立的与客户端的连接 for (int i = 0; i < serverThread.clients.size(); i++) { clientThread = (ClientThread) serverThread.clients .elementAt(i); try { // 向记录的每一个客户端发送数据信息 clientThread.out.writeUTF(str); } catch (IOException E) { } } // 从Vector数组中删除已经发送过的那条数据信息 this.serverThread.messages.removeElement(str); } } }}
4.ClientThread.java----获得Socket的输入输出流,向客户端接收或者发送数据
package com.wang;import java.net.*;import java.io.*;public class ClientThread extends Thread { /** * 维持服务器与单个客户端的连接线程,负责接收客户端发来的信息, * 声明一个新的Socket对象, * 用于保存服务器端用accept方法得到的客户端的连接 **/ Socket clientSocket; //声明服务器端中存储的Socket对象的数据输入/输出流 DataInputStream in = null; DataOutputStream out = null; //声明ServerThread对象 ServerThread serverThread; public ClientThread(Socket socket,ServerThread serverThread) { clientSocket=socket; this.serverThread=serverThread; try { //创建服务器端数据输入/输出流 in = new DataInputStream(clientSocket.getInputStream()); out = new DataOutputStream(clientSocket.getOutputStream()); } catch (IOException e2) { System.out.println("发生异常"+e2); System.out.println("建立I/O通道失败!"); System.exit(3); } } //该方法监听该连接对应得客户端是否有消息发送 public void run() { while(true) { try { //读入客户端发送来的信息 String message=in.readUTF(); synchronized(serverThread.messages) { if(message!=null) { //将客户端发送来得信息存于serverThread的messages数组中 serverThread.messages.addElement(message); //在服务器端的文本框中显示新消息 Server.jTextArea1.append(message+'\n'); } } } catch(IOException E){break;} } }}5.接着看看手机客户端的布局main.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <EditText android:id="@+id/username" android:layout_width="270dp" android:layout_height="wrap_content" android:hint="请输入用户名:" android:maxLength="10" > </EditText> <Button android:id="@+id/LoginButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="登陆" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <EditText android:id="@+id/ip" android:layout_width="270dp" android:layout_height="wrap_content" android:digits=".1234567890" android:hint="10.254.1.62" > </EditText> <Button android:id="@+id/LeaveButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="退出" /> </LinearLayout> <EditText android:id="@+id/history" android:layout_width="fill_parent" android:layout_height="280dp" > </EditText> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <EditText android:id="@+id/message" android:layout_width="270dp" android:layout_height="wrap_content" > </EditText> <Button android:id="@+id/SendButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="发送" /> </LinearLayout></LinearLayout>6.接着看看手机客户端的实现ChatClientActivity.java
package com.wang;import android.app.Activity;import android.os.Bundle;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.IOException;import java.net.InetAddress;import java.net.Socket;import java.sql.Date;import java.text.SimpleDateFormat;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.view.View;import android.view.Window;import android.view.WindowManager;import android.widget.Button;import android.widget.EditText;import android.widget.Toast;public class ChatClientActivity extends Activity implements Runnable { private EditText usernameEdit; private EditText ipEdit; private EditText historyEdit; private EditText messageEdit; private Button loginButton; private Button sendButton; private Button leaveButton; /** * 声明字符串,name存储用户名 chat_txt存储发送信息 chat_in存储从服务器接收到的信息 ****/ private String username, ip, chat_txt, chat_in; // 创建Socket通信端口号常量 public static final int PORT = 80; // 声明套接字对象 Socket socket; // 声明线程对象 Thread thread; // 声明客户器端数据输入输出流 DataInputStream dataInputStream; DataOutputStream dataOutputStream; // 是否登录的标记 boolean flag = false; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 取消标题栏 this.requestWindowFeature(Window.FEATURE_NO_TITLE); // 设置全屏 this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.main); // 实例化组件 usernameEdit = (EditText) findViewById(R.id.username); ipEdit = (EditText) findViewById(R.id.ip); historyEdit = (EditText) findViewById(R.id.history); messageEdit = (EditText) findViewById(R.id.message); loginButton = (Button) findViewById(R.id.LoginButton); sendButton = (Button) findViewById(R.id.SendButton); leaveButton = (Button) findViewById(R.id.LeaveButton); // 为三个按钮注册监听器 loginButton.setOnClickListener(listener); sendButton.setOnClickListener(listener); leaveButton.setOnClickListener(listener); } View.OnClickListener listener = new View.OnClickListener() { @Override public void onClick(View v) { switch (v.getId()) { // "进入聊天室"按钮的处理 case R.id.LoginButton: if (flag == true) { Toast.makeText(ChatClientActivity.this, "亲,你已经登陆过啦!!!", Toast.LENGTH_LONG).show(); return; } // 获取用户名 username = usernameEdit.getText().toString(); // 获取服务器ip ip = ipEdit.getText().toString(); // 判断用户名是否有效及ip是否为空 if (username != "" && username != null && username != "用户名输入" && ip != null) { try { // 创建Socket对象 socket = new Socket(ip, PORT); // 创建客户端数据输入/输出流,用于对服务器端发送或接收数据 dataInputStream = new DataInputStream(socket .getInputStream()); dataOutputStream = new DataOutputStream(socket .getOutputStream()); // 得到系统的时间 Date now = new Date(System.currentTimeMillis()); SimpleDateFormat format = new SimpleDateFormat( "hh:mm:ss"); String nowStr = format.format(now); // 输出某某上线啦 dataOutputStream.writeUTF("└(^o^)┘: " + username + " : " + nowStr + " 上线啦!"); } catch (IOException e1) { System.out.println("抱歉连接不成功!!!"); } thread = new Thread(ChatClientActivity.this); // 开启线程,监听服务器段是否有消息 thread.start(); // 说明已经登录成功 flag = true; } break; // "发送"按钮的处理 case R.id.SendButton: if (flag == false) { Toast.makeText(ChatClientActivity.this, "亲,你还没登录,请先登录!", Toast.LENGTH_LONG).show(); return; } // 获取客户端输入的发言内容 chat_txt = messageEdit.getText().toString(); if (chat_txt != null) { // 得到当前时间 Date now = new Date(System.currentTimeMillis()); SimpleDateFormat format = new SimpleDateFormat("hh:mm:ss"); String nowStr = format.format(now); // 发言,向服务器发送发言的信息 try { dataOutputStream.writeUTF("(^_^)∠※--->" + username + " : " + nowStr + " 说\n" + chat_txt); } catch (IOException e2) { } } else { try { dataOutputStream.writeUTF("请发言!!!"); } catch (IOException e3) { } } break; // "退出聊天室"按钮事件的处理 case R.id.LeaveButton: if (flag == true) { if (flag == false) { Toast.makeText(ChatClientActivity.this, "亲,你还没登录,请先登录!", Toast.LENGTH_LONG).show(); return; } try { dataOutputStream.writeUTF("(^_^)/~~ " + username + "下线了!"); // 关闭socket dataOutputStream.close(); dataInputStream.close(); socket.close(); } catch (IOException e4) { } } flag = false; Toast.makeText(ChatClientActivity.this, "已退出!", Toast.LENGTH_LONG).show(); break; } } }; // 客户端线程启动后的动作 @Override public void run() { // 循环执行,作用是一直监听服务器端是否有消息 while (true) { try { // 读取服务器发送来的数据信息 chat_in = dataInputStream.readUTF(); chat_in = chat_in + "\n"; // 发送一个消息,要求刷新界面 mHandler.sendMessage(mHandler.obtainMessage()); } catch (IOException e) { } } } Handler mHandler = new Handler() { public void handleMessage(Message msg) { // 将消息并显示在客户端的对话窗口中 historyEdit.append(chat_in); // 刷新 super.handleMessage(msg); } };}
7,亲,别忘了由于需要网络,需要添加联网的权限哦!!
<uses-permission android:name="android.permission.INTERNET"/>8.如果你完成以上功能,就可以实现android手机客户端上的简单的聊天功能了,运行结果给如下: