刚开始学java,参考《java从入门到精通》这本书,学到网络程序设计基础这一章节,尤其与其他计算机进行通信,觉得还挺有意思的。所有深入地试试做一个小程序—聊天室程序,在代码中加入我自己的理解和困惑,希望能和大家一起探讨,每行的代码基本都有注释,方便大家理解。
编译环境:eclipse(代码可以直接运行)
以下设计到的关于java的主要知识:Swing、多线程、I/O输入输出、TCP
TCP原理基础
TCP网络程序设计是指利用Socket类编写通信程序。利用TCP协议进行通信的两个应用程序是有主次之分的,一个称为服务器程序,另一个成为客户端程序,两者的功能和编写方法大不一样。
服务器端和客户端的交互过程如下图1所示:
- 服务器程序创建一个ServerSocket(服务器套接字),调用accept()方法等待客户机连接。
- 客户端程序创建一个Socket,请求与服务器建立连接。
- 服务器接受客户机的连接请求,同时创建一个新的Socket与客户建立联系。随后服务器继续等待新的请求。
流程图
服务器段程序
//服务器端程序
import java.awt.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
public class TCPServer extends JFrame {
private static final long serialVersionUID = 1L;
private ServerSocket ss = null; // 创建服务器套接字接口
private boolean bStart = false; // 添加用户标志
private JTextArea taContent = new JTextArea(); // 文本域
private int index = 0; // 用户个数
List<Client> clients = new ArrayList<Client>(); // 泛型--创建List集合(ArrayList是List的子类)--存储客户数
// 窗口布局
public void launchFrame() {
// Container con = this.getContentPane();
taContent.setEditable(false); // 不可编辑
taContent.setBackground(Color.DARK_GRAY); // 背景颜色
taContent.setForeground(Color.YELLOW); // 文字颜色
this.add(taContent); // 容器中添加文本域
this.setSize(300, 350); // 设置大小
this.setLocation(400, 200);
this.setTitle("TCP Server");
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
tcpMonitor();
}
// TCP监听
public void tcpMonitor() {
try {
ss = new ServerSocket(8888); // 服务器套接字8888
bStart = true;
} catch (IOException e) {
e.printStackTrace();
}
try {
while (bStart) {
index++; // index代表人数
Socket s = ss.accept(); // 等待客户端连接
Client c = new Client(s); // 实例化对象--用户
clients.add(c);
// getInetAddress()--返回此服务器套接字的本地地址
// getHostAddress()--获取IP地址
// taContent---文本域
taContent.append(s.getInetAddress().getHostAddress() // 文本域添加字--127.0.0.1 connect 1 clients
+ " connected " + index + " clients\n");
// public Thread(String threadName)-----创建一个名称为threadName的线程对象
new Thread(c).start(); // 启动多线程
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
ss.close(); // 关闭套接字
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String args[]) {
TCPServer ts = new TCPServer(); // 实例化TCP网络程序
ts.launchFrame(); // 实现窗口布局、网络连接
}
private class Client implements Runnable {
DataInputStream dis = null; // 数据输入流
DataOutputStream dos = null; // 数据输出流
Socket s = null; // 服务器接受客户端的连接请求,同时创建一个新的套接字与客户端建立连接
boolean bStart = false; // 添加用户标志
Client(Socket s) {
this.s = s;
try {
dis = new DataInputStream(s.getInputStream()); // 获取输入数据
dos = new DataOutputStream(s.getOutputStream()); // 获取输出数据
} catch (IOException e) {
e.printStackTrace();
}
bStart = true;
}
// 发送数据
public void sendToEveryClient(String str) {
try {
dos.writeUTF(str); // 输出数据----writeUTF()写出一个UTF-8编码的字符串前面会加上2个字节的长度标识
dos.flush(); // 刷新
} catch (IOException e) {
index--; // 用户数减少
clients.remove(this); // 从数组中去除该用户
taContent.append(s.getInetAddress().getHostAddress() // Socket s = null
+ " exited " + index + " clients\n");
System.out.println("对方退出了!我从List里面去掉了!");
}
}
public void run() {
try {
while (bStart) { // 当bStart=true时,读取数据
String str = dis.readUTF(); // 读取数据
System.out.println(str); // 打印信息
// 发送客户的信息
for (int i = 0; i < clients.size(); i++) {
Client c = clients.get(i); // clients数组中的元素都是Client类的
c.sendToEveryClient(str); // 发送信息
}
}
} catch (EOFException e) {
clients.remove(this);
taContent.append(s.getInetAddress().getHostAddress()
+ " exited " + clients.size() + " clients\n");
System.out.println("client closed");
} catch (SocketException e) {
System.out.println("client closed");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (s != null)
s.close();
if (dis != null)
dis.close();
if (dos != null)
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
客户端程序
// 客户端程序
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import javax.swing.*;
public class TCPClient extends JFrame {
private static final long serialVersionUID = 1L;
TextArea taContent = new TextArea(); // 文本域
JTextField tfTxt = new JTextField(20); // 文本框
JButton send = new JButton("发送"); // 按钮
JButton connect = new JButton("连接");
JButton clear = new JButton("清空");
boolean live = false;
JPanel p1 = new JPanel();
JPanel p2 = new JPanel();
Socket s = null; // 创建客户端套接字
DataOutputStream dos = null; // 输出流
DataInputStream dis = null; // 输入流
boolean bConnected = false; // 连接标志
Thread t = new Thread(new RecToServer()); // 多线程--new Runnable()接口
// 窗口布局
public void launchFrame() {
taContent.setEditable(false);
p2.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 5)); // 使用流布局
p2.add(send); // 3个按钮--send、connect、clear
p2.add(connect);
p2.add(clear);
Container con = this.getContentPane(); // 实例化容器
con.add(taContent, "North"); // 容器中添加文本域
con.add(tfTxt, "Center"); // 容器中添加文本框
con.add(p2, "South"); // 容器中添加面板(面板中是两个按钮)
this.setSize(300, 350); // this 指代 JFrame
this.setLocation(400, 200);
this.setTitle("Chat Client");
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 添加按钮监听
connect.addActionListener(new Connect()); // 连接按钮
send.addActionListener(new SendMsg()); // 发送按钮
clear.addActionListener(new ActionListener() { // 清除按钮监听--使文本框中的内容置空
public void actionPerformed(ActionEvent e) {
taContent.setText("");
}
});
}
// 连接服务器
public void connectToServer() {
try {
s = new Socket("127.0.0.1", 8888); // 客户端创建一个Socket套接字
dos = new DataOutputStream(s.getOutputStream()); // 数据输出流
dis = new DataInputStream(s.getInputStream()); // 数据输入流
bConnected = true; // 连接标识符置为true
} catch (BindException e) {
System.out.println("找不到指定的服务器");
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 断开链接
public void disConnect() {
try {
if (s != null) {
s.close(); // Socket s = null
}
if (dos != null) {
dos.close(); // DataOutputStream dos = null
}
if (dis != null) {
dis.close(); // DataInputStream dis = null
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 主程序
public static void main(String args[]) {
TCPClient tc = new TCPClient(); // 实例化TCPlient对象
tc.launchFrame(); // 主程序
}
// 连接按钮----监听
private class Connect implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand() == "连接") { // e.getActionCommand()==获取按钮上的文字(连接)
connectToServer(); // 连接服务器
try {
t.start(); // 启动多线程 Thread t=new Thread(new RecToServer())
} catch (IllegalThreadStateException ex) {
}
connect.setText("断开连接"); // 当连接上服务器以后,连接按钮 变为 断开连接按钮
} else if (e.getActionCommand() == "断开连接") {
disConnect(); // 断开连接
connect.setText("连接");
}
}
}
// 发送按钮--监听
private class SendMsg implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (connect.getActionCommand() == "连接") { // 还没有连接上
/*
* showMessageDialog(Component parentComponent, Object message, String title,int messageType)
* 第一个参数:parentComponent---设置了可以改变窗体的主题信息
* 第二个参数:message---显示的消息内容 第三个参数:title---提示框的标题
* 第四个参数:messageType---设置了信息提示内容的图标,默认值为INFORMATION_MESSAGE或1
*/
JOptionPane.showMessageDialog(TCPClient.this, // 出现提示框
"没有找到指定的服务器", "错误提示", 1);
} else { // 连接上了
String str = tfTxt.getText(); // 获取文本框(发送框)中的内容
tfTxt.setText(""); // 将文本框(发送框)中的内容置零
try {
dos.writeUTF(str); // 将文本框(发送框)中的内容输出
dos.flush(); // 不断刷新
} catch (SocketException ex) { //
System.out.println("没有找到指定的服务器");
JOptionPane.showMessageDialog(TCPClient.this, "没有找到指定的服务器", "错误提示", 1);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
// 多线程接口
private class RecToServer implements Runnable {
public void run() {
try {
while (bConnected) { // 连接标志= true 时
String str = dis.readUTF(); // 读入数据
// System.out.println(str);
taContent.append(str + "\n"); // 在文本域中添加聊天内容str
}
} catch (SocketException e) {
System.out.println("服务器已关闭");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}