此聊天程序,可以作为java网络学习的入门例子,程序虽小,五脏俱全,不过很多细节问题需要花时间完善。
1、聊天系统主要有两个模块:Client和Server
2、Client端功能:
①产生一个聊天窗口
②根据服务端的ip+端口后,通过Socket连接到服务端
③向服务端发送消息,并接受服务端的消息
④同时将消息显示在聊天窗口上面
3、Server端功能:
①监听端口
②接受服务端的请求,并与服务端建立连接
③接受消息,转发消息至所有服务端
4、代码ChatClient.java
import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
public class ChatClient extends Frame {
private Socket s = null;
DataOutputStream dos;
DataInputStream dis;
private boolean bConnected = false;
TextField tfTxt;
TextArea taContent;
// 定义接收消息的线程
Thread tRecv = new Thread(new RecvThread());
public static void main(String[] args) {
new ChatClient().lanuchFrame();
}
public void lanuchFrame() {
this.setLocation(400, 300);
this.setSize(300, 300);
tfTxt = new TextField();
taContent = new TextArea();
taContent.setEditable(false);
this.add(tfTxt, BorderLayout.SOUTH);
this.add(taContent, BorderLayout.NORTH);
this.pack();
// 添加匿名监听器
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
disConnect();
System.exit(0);
}
});
tfTxt.addActionListener(new TFListener());
this.setVisible(true);
connect();
tRecv.start();
}
/**
* 连接服务器
*/
public void connect() {
try {
s = new Socket("127.0.0.1", 8888); //这个地方用来添加服务器ip+端口,根据实际改
dos = new DataOutputStream(s.getOutputStream());
dis = new DataInputStream(s.getInputStream());
System.out.println("connected!");
bConnected = true;
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void disConnect() {
/*
try {
// ①让接收消息的线程停下来
bConnected = false;
// ②结束线程
tRecv.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
dos.close();
dis.close();
s.close();
} catch (IOException e) {
// TODO Auto-generated catch
e.printStackTrace();
}
}
*/
try {
// ①让接收消息的线程停下来
bConnected = false;
// ②结束线程
tRecv.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 添加监听器的三种方式:①外部类 ②内部类 ③匿名类
private class TFListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
String str = tfTxt.getText().trim();
// taContent.setText(str);
tfTxt.setText("");
try {
dos.writeUTF(str);
dos.flush();
/*
* //当dis关闭时,里面包装的流都会被关闭, 导致发一次消息就不能再放第二次了,所有要注释掉dos.close();
*/
// dos.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
private class RecvThread implements Runnable {
@Override
public void run() {
try {
while (bConnected) {
String str = dis.readUTF();
// System.out.println(str);
taContent.setText(taContent.getText() + str + "\n");
}
} catch (SocketException e) {
System.out.println("退出了!和你们说88了");
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("退出了!和你们说88了");
}
}
}
}
5、代码ChatServer.java
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
/**
* 设计思路:1 在主线程里面启动一个ServerSocket,不停的监听客户端的请求
* 2:每当接收到了一个客服端,将其Socket包装到一个线程里面,让线程来 处理服务器与客户端的通信
* 3:这样主线程用来接收客服端,子线程用来和每个客户端通信
*
* 注意:内部类的权限和非静态实例的一样,不能在静态方法里面new内部类, 只能用外部实例来new内部类
*
* @author Administrator
*
*/
public class ChatServer {
ServerSocket ss = null;
boolean started = false;
List<Client> clients = new ArrayList<Client>();
public static void main(String[] args) {
new ChatServer().start();
}
public void start() {
// ①建立一个ServerSocket
try {
ss = new ServerSocket(8888);
started = true;
} catch (BindException e1) {
// TODO Auto-generated catch block
System.out.println("端口使用中...");
System.out.println("请关闭相关的程序并重新运行服务器!");
System.exit(0);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
// ②连接客户端
try {
while (started) {
Socket s = ss.accept();
// 服务器接收一个Socket后,启动一个线程来处理客服端的
// 通信
Client c = new Client(s);
System.out.println("a client connected!");
// 使用线程来接收客服端的信息
new Thread(c).start();
clients.add(c);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
ss.close();// 关闭ServerSocket
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/*
* 客户端在服务端的一个包装
*/
class Client implements Runnable {
private Socket s;
private DataInputStream dis = null;
private DataOutputStream dos = null;
private boolean bConnected = false;
public Client(Socket s) {
this.s = s;
try {
dis = new DataInputStream(s.getInputStream());
dos = new DataOutputStream(s.getOutputStream());
bConnected = true;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void send(String str) {
try {
dos.writeUTF(str);
} catch (IOException e) {
clients.remove(this);
System.out.println("对方退出了!我从List里面去掉了");
// e.printStackTrace();
}
}
@Override
public void run() {
Client c=null;
try {
while (bConnected) {
String str = dis.readUTF();
// 每接收一个字符串,就全部转发给其他的客户端
System.out.println(str);
// for (Client c : clients) {
// c.send(str);
// }
for(int i=0;i<clients.size();i++){
c=clients.get(i);
c.send(str);
}
}
} catch (EOFException e) {
System.out.println("Client closed!");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (dis != null)
dis.close();
if (dos != null)
dos.close();
if (s != null) {
s.close();
// s=null;
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
}
}
6、运行:1)运行ChatServer.java:①为其指定监听端口,我这里设置为8888,
2)运行ChatClient.java:①为其指定连接的ip+端口:这里我用的是本地127.0.0.1:8888,这根据ChatServer运行的主机为主
3)效果图: