简易网络聊天器----JavaTcp原理实现
涉及知识
- Java的网络编程tcp(暂仅局域网下实现)
- Java IO操作
- Java 多线程(可实现多人同时在线聊天)
- Java图形化界面(图形化操作)
运行截图
服务器启动界面
客户端启动界面(支持多线程,多人在线)
程序运行截图
代码演示
服务器端代码
package com.tcp;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import java.io.*;
//Java Tcp_app : Server
public class Server {
//主程序开始的地方
public static void main(String[] args) throws Exception{
new Oper(); //通过Oper类的构造方法开始
}
}
//Oper类, SocketServer服务器运行的类
class Oper{
public static boolean flag = false; //定义服务器的启动按钮, 初始化为未启动
public static int count = -1; //定义客户端数量(以数组下标表示个数)
ServerLogin sl; //定义登录窗口对象
ServerChatWindows scw; //定义聊天窗口对象
ServerSocket server; //服务器
Socket client; //客户端
public Oper() throws Exception{ //Oper类构造方法
sl = new ServerLogin(); //创建显示登录窗口,根据用户操作选择是否启动服务器
while(!this.flag) {System.out.println("");} //用户若无操作, 程序进入死循环等待状态
sl.jf.dispose();
scw = new ServerChatWindows(); //关闭登录窗口, 产生聊天窗口
server = new ServerSocket(8090); //建立服务器, 指定端口号为 8888
//进入死循环, 不断接受客户端的连接, 利用多线程实现多个客户端的连接
while(true) {
client = server.accept(); //等待客户端连接
new Thread(new ServerThread(client, scw)).start(); //开辟服务器对此客户端的一个线程,将此客户端和服务器聊天界面传入,并开始运行此线程
count++; //客户端数量累加
}
}
}
//服务器对应客户端的多线程类, 客户端连接的数量就是服务器线程启动的数量
class ServerThread implements Runnable{
Socket client; //定义客户端
ServerChatWindows scw; //服务器聊天窗口
JTextField jtf_news; //定义发送信息的文本框
JTextArea jta_mess; //定义不可编辑的文本区域作为聊天记录界面
JButton jbt_send; //定义发送按钮
PrintStream out; //定义客户端输出流
BufferedReader buf; //定义客户端输入流
public static String str; //定义全局变量 str 为接收客户端发送的信息
//此类的构造方法, 接收客户端 和 服务器的固定聊天界面
public ServerThread(Socket client, ServerChatWindows scw) {
this.client = client;
this.scw = scw;
this.jtf_news = scw.jtf_news;
this.jbt_send = scw.jbt_send;
this.jta_mess = scw.jta_mess; //进行对象的复制传输
}
//线程的run()方法
public void run() {
try {
buf = new BufferedReader(new InputStreamReader(client.getInputStream())); //得到此客户端的输入流
out = new PrintStream(client.getOutputStream()); //得到此客户端的输出流
this.jbt_send.addActionListener(new ServerSendNews(this.out, this.scw)); //增加服务器端信息发送的按钮监听器
this.jtf_news.addKeyListener(new ServerSendNews(this.out, this.scw)); //增加服务器端信息发送的键盘监听器,回车键为发送
//进入死循环, 不断得到客户端发送的信息
while(true) {
this.str = null; //初始化信息为 null
str = buf.readLine(); //得到客户端发送的信息
this.jbt_send.doClick(); //模拟点击服务器信息发送的按钮, 将客户端发送的信息回显给所有客户端
}
}catch(Exception e) { //当客户端断开连接后或其它原因的异常处理提醒
System.out.println("Client isn't connected!");
}
}
}
//服务器启动界面窗口
class ServerLogin{
JFrame jf; //定义窗口
Container con; //存放组件的容器
JPanel jpl_jlb;
JPanel jpl_jbt; //定义两个存放组件的二级容器
JLabel jlb; //定义服务器信息标签
JButton jbt_on;
JButton jbt_ce; //定义两个按钮,作为登录和取消键
//通过构造方法生成聊天界面窗口
public ServerLogin() throws Exception {
jf = new JFrame("Chat-Room Server"); //登录窗口窗口上栏信息
con = jf.getContentPane();
jpl_jlb = new JPanel();
jpl_jbt = new JPanel();
jlb = new JLabel("Chat--Server"); //标签信息
jbt_on = new JButton("On"); //登录按钮
jbt_ce = new JButton("Off"); //取消按钮
//为登录按纽增加事件监听器, 当用户点击 on 时, Oper类全局变量 flag变为 ture, 表示服务器启动, Oper类启动服务器
jbt_on.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
Oper.flag = true;
}
});
//为取消按钮增加事件监听器, 当用户点击 off 时退出程序
jbt_ce.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
//向容器增加组件
jpl_jlb.add(jlb);
jpl_jbt.add(jbt_on);
jpl_jbt.add(jbt_ce);
con.add(jpl_jlb, BorderLayout.NORTH);
con.add(jpl_jbt, BorderLayout.SOUTH);
//设置窗体属性
jf.setSize(300, 120); //设置大小
jf.setLocation(600, 260); //设置位置
jf.setVisible(true); //设置可见
jf.setDefaultCloseOperation(jf.EXIT_ON_CLOSE); //设置可关闭
}
}
//服务器聊天界面窗口类
class ServerChatWindows{
JFrame jf; //定义窗口
Container con; //定义容器
JPanel send;
JPanel mess; //二级容器
JTextField jtf_news;
JTextArea jta_mess;
JScrollPane jsp;
JButton jbt_send; //文本域 , 文本框, 按钮等组件
BufferedReader buf; //定义输入流
PrintStream out; //定义输出流
//通过构造方法生成 服务器聊天界面
public ServerChatWindows() throws Exception{
jf = new JFrame("Server");
con = jf.getContentPane();
jtf_news = new JTextField("Hello, I'm Server!", 14);
jta_mess = new JTextArea(15, 25);
jta_mess.setLineWrap(true);
jsp = new JScrollPane(jta_mess, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
jta_mess.setEditable(false); //多行文本域设置为不可编辑, 用来存放聊天记录
jbt_send = new JButton("send");
mess = new JPanel();
send = new JPanel();
send.add(jtf_news);
send.add(jbt_send);
con.add(jsp, BorderLayout.CENTER);
con.add(send, BorderLayout.SOUTH);
jf.pack();
// jf.setSize(280, 340);
jf.setLocation(500, 260);
jf.setVisible(true);
jf.setDefaultCloseOperation(jf.EXIT_ON_CLOSE); //如上, 设置聊天窗口的属性
}
}
//定义按钮及键盘监听器, 监听服务器发送给的内容
class ServerSendNews implements ActionListener, KeyListener{
private static int count = -1; //定义发送信息的次数
private ServerChatWindows scw; //定义聊天服务器窗口
private JTextArea jta_mess; //定义存放聊天记录的文本框
private JTextField jtf_news; //信息发送的文本框
private JButton jbt_send; //发送按钮
private PrintStream out; //得到向客户端的输出流
private boolean flag; //判断数据流为回显客户端的信息或者为服务器发送的信息
private String str; //存储信息的字符串
//构造方法, 得到客户端的输出流和服务器的聊天窗口
public ServerSendNews(PrintStream out, ServerChatWindows scw) throws Exception{
this.out = out;
this.jtf_news = scw.jtf_news;
this.jta_mess = scw.jta_mess;
this.jbt_send = scw.jbt_send; //对象的复制传输
}
//按钮发生动作需执行的事件
public void actionPerformed(ActionEvent e) {
this.flag = true; //初始化flag为true, 即服务器发送的信息
//若ServerThread类的str属性信息不为空, 则此动作为 模拟点击按钮动作, 即回显客户端信息, flag变为 false, 得到客户端信息
if(ServerThread.str != null){
this.str = ServerThread.str;
this.flag = false;
}
else this.str = "Server: " + jtf_news.getText(); //否则,则为服务器所发送的信息
out.println(this.str); //输出此信息至其它客户端
this.count++; //输出信息次数累加
if((this.count+1) % (Oper.count+1) == 0) { //若输出信息次数可以整除客户端数量, 服务器的信息文本域也将变动, 消息记录也累加
if(this.flag == true) jtf_news.setText(""); //若为服务器发送的信息, 则清空服务器的信息发送文本域
this.jta_mess.append(this.str + "\n"); //累加消息记录
}
}
//增加键盘监听器事件, 当用户输入信息时输入回车键, 执行此事件
//同上, 此为服务器所发送信息, 无需判断信息流
public void keyPressed(KeyEvent arg0) {
if(arg0.getKeyCode() == KeyEvent.VK_ENTER){
this.str = "Server: " + jtf_news.getText();
out.println(this.str);
this.count++;
if((this.count+1) % (Oper.count+1) == 0) {
jtf_news.setText("");
this.jta_mess.append(this.str + "\n");
}
}
}
public void keyReleased(KeyEvent arg0) {}
public void keyTyped(KeyEvent arg0) {}
}
客户端代码
package com.tcp;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import java.net.*;
import java.io.*;
import java.util.*;
//Java Tcp: Client
public class Client {
//程序开始的位置
public static void main(String[] args) throws Exception{
new OperSocket();
}
}
//Tcp Client客户端处理的总工作台
class OperSocket{
public static boolean flag = false; //定义全局变量,客户端启动的总开关, 初始值为false,表示未开
private Socket client; //定义客户端
private BufferedReader buf; //定义客户端输入流
private PrintStream out; //定义客户端输出流
private MyLoginWindows mlw; //定义客户端登录窗口
private ChatWindows cw; //定义客户端聊天窗口
private JTextArea jta_mess; //定义客户端聊天记录文本框
private JButton jbt_send; //定义客户端聊天信息发送按钮
private JTextField jtf_news; //定义客户端聊天信息的文本框
private String str; //定义 str 接收服务器发送的信息
private String name; //定义 name 为客户端输入的 user name
//通过构造方法, 开始运行
public OperSocket() throws Exception{
mlw = new MyLoginWindows(); //产生用户登录窗口
while(!this.flag) {System.out.print("");} //程序进入死循环, 等待用户登录
this.client = new Socket("127.0.0.1", 8090); //若登录成功, 输入连接服务器主机的ip地址(此127.0.0.1为测试ip)
this.name = mlw.jtf_user.getText(); //对象的复制传输
mlw.jf.dispose(); //关闭登录界面窗口
cw = new ChatWindows(this.name); //生成客户端聊天界面窗口
this.jta_mess = cw.jta_mess;
this.jbt_send = cw.jbt_send;
this.jtf_news = cw.jtf_news;
this.buf = new BufferedReader(new InputStreamReader(client.getInputStream())); //获取此客户端的输入流
this.out = new PrintStream(client.getOutputStream()); //得到此客户端的输出流
jbt_send.addActionListener(new SendNews(this.out, this.jtf_news, this.jta_mess, this.name)); //为发送按钮增加监听器
jtf_news.addKeyListener(new SendNews(this.out, this.jtf_news, this.jta_mess, this.name)); //为发送文本框增加键盘监听器
//客户端进入死循环等待服务器发送的信息内容
while(true) {
str = this.buf.readLine(); //获取服务器发送的信息
this.jta_mess.append(str + "\n"); //将信息添加到客户端聊天窗口的消息记录中
}
}
}
//MyLoginWindows 定义客户端登录界面窗口类
//客户端登录界面窗口同服务器近似
class MyLoginWindows{
JFrame jf;
JButton jbt_lg;
JButton jbt_ce;
JLabel jlb_user;
JLabel jlb_pass;
JTextField jtf_user;
JPasswordField jpf_pass;
JPanel user;
JPanel pass;
JPanel button;
Container con;
public MyLoginWindows() {
jf = new JFrame("Login Windows");
jlb_user = new JLabel("User:");
jlb_pass = new JLabel("Pass:");
jtf_user = new JTextField(14);
jtf_user.setText("your name:"); //设置客户端用户姓名提示
jpf_pass = new JPasswordField(14);
jbt_lg = new JButton("Login");
jbt_ce = new JButton("Cancel");
//增加事件监听器, 若用户点击登录, 则进行客户端启动开关为 true, 客户端启动
jbt_lg.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
OperSocket.flag = true;
}
});
//增加事件监听器, 若用户点击取消, 则退出程序
jbt_ce.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
//对登录界面窗口组件布局进行设置
con = jf.getContentPane();
user = new JPanel();
pass = new JPanel();
button = new JPanel();
con.add(user, BorderLayout.NORTH);
con.add(pass, BorderLayout.CENTER);
con.add(button, BorderLayout.SOUTH);
// con.setBackground(Color.RED);
user.add(jlb_user);
user.add(jtf_user);
pass.add(jlb_pass);
pass.add(jpf_pass);
button.add(jbt_lg);
button.add(jbt_ce);
// jf.pack();
jf.setSize(360, 150);
jf.setLocation(600, 280);
jf.setVisible(true);
jf.setDefaultCloseOperation(jf.EXIT_ON_CLOSE);
}
}
//ChatWindows 为客户端 聊天界面窗口
//同服务器聊天界面窗口近似
class ChatWindows{
JFrame jf;
Container con;
JPanel send;
JPanel mess;
JTextField jtf_news;
JTextArea jta_mess;
JScrollPane jsp;
JButton jbt_send;
Socket user;
InetAddress inet;
Scanner sc;
BufferedReader buf;
PrintStream out;
String str;
public ChatWindows(String s) throws Exception{
jf = new JFrame(s);
con = jf.getContentPane();
jtf_news = new JTextField(s + " is Login!", 14);
jta_mess = new JTextArea(15, 25);
jta_mess.setLineWrap(true);
jsp = new JScrollPane(jta_mess, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
jta_mess.setEditable(false);
jbt_send = new JButton("Send");
mess = new JPanel();
send = new JPanel();
send.add(jtf_news);
send.add(jbt_send);
con.add(jsp, BorderLayout.CENTER);
con.add(send, BorderLayout.SOUTH);
jf.pack();
// jf.setSize(280, 340);
jf.setLocation(500, 260);
jf.setVisible(true);
jf.setDefaultCloseOperation(jf.EXIT_ON_CLOSE);
}
}
//定义按钮及键盘监听器类, 对客户端输入信息操作进行监听
class SendNews implements ActionListener, KeyListener{
private JTextArea jta_mess;
private JTextField jtf_news;
private Socket user;
private PrintStream out;
private BufferedReader buf;
private String str;
private String name;
public SendNews(PrintStream out, JTextField jtf_news, JTextArea jta_mess, String name) throws Exception{
this.out = out;
this.jta_mess = jta_mess;
this.jtf_news = jtf_news;
this.name = name;
}
//按钮事件的动作事件操作
public void actionPerformed(ActionEvent e) {
str = jtf_news.getText(); //获取客户端信息流内容
out.println(this.name + ": " + str + ""); //将此信息输出到服务器端
jtf_news.setText(""); //客户端信息文本清空
}
//当用户在信息发送文本域 按回车键 的事件处理
public void keyPressed(KeyEvent arg0) {
if(arg0.getKeyCode() == KeyEvent.VK_ENTER){ //若事件源为键盘的回车
str = jtf_news.getText(); //同上, 进行输出信息操作
out.println(this.name + ": " + str + "");
jtf_news.setText("");
}
}
public void keyReleased(KeyEvent arg0) {}
public void keyTyped(KeyEvent arg0) {}
}