目录
具体效果
1、创建聊天消息的区域(不是发送消息的区域)
private JTextArea messageArea;//多行纯文本区域
messageArea = new JTextArea();//对象构建
/*
消息接收发送区域
*/
JPanel messagePanel = new JPanel(new BorderLayout());//缓冲区 BorderLayout边界布局容器
messageArea.setFont(new Font("微软雅黑", Font.ITALIC,20));//设置输入字体大小
messagePanel.add(new JScrollPane(messageArea),BorderLayout.CENTER);//JScrollPane轻量的可滚动视图 设置在组件中间
2、发送消息的区域
private JTextField messageFile;//消息发送区域
messageFile = new JTextField();//添加发送消息区域
/*
发送消息区域
*/
messageFile.setFont(new Font("微软雅黑" ,Font.PLAIN,12));//在此处设定字体并不能在输入的框内看见设定的效果
messagePanel.add(new JScrollPane(messageFile),BorderLayout.SOUTH);//设置在组件下方(南方)
3、文本发送
-
发送按钮
private JButton sendButton;//发送消息按钮
sendButton = new JButton("发送");//消息发送按钮
/*
添加按钮组件
*/
JPanel buttonPanel = new JPanel(new FlowLayout());//FlowLayout 组件管理器
buttonPanel.add(sendButton);
-
按钮监听器
sendButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String message = messageFile.getText(); messageArea.append("\n" + "Me: " + message ); messageFile.setText(""); for (Client client: clients) { client.sendMessage(message);//遍历 并加入写好的类进行信息处理 } } });
4、建立主存储容器,存储以上容器
/*
主界面组件控制分支组件显示
*/
JPanel mainPanel = new JPanel(new BorderLayout());//BorderLayout 边界布局器
mainPanel.add(messagePanel, BorderLayout.CENTER);//聊天消息区域添加到主组件中央
mainPanel.add(buttonPanel,BorderLayout.SOUTH);//发送按钮设定到主组件的下方
add(mainPanel);
值得一提的是:
add(mainPanel);这行代码把主界面的东西加入到哪里了呢
因为在写窗口类的过程中,主类是继承JFrame类,所以本类是具备JFrame中的特点的,最后把主界面的组件加入到的是本类对象当中。
5、好友列表
-
列表中的内容
private JList<String> friendList;//好友列表
/*
写入好友列表的名字 静态
*/
friendList = new JList<>(new String[]{"friend one"
,"friend two"
,"friend three"
});
-
列表组件加入主组件中
-
以及设定显示好友列表的字体
/*
设定显示好友图像的字体
添加组件到主组件内
*/
friendList.setFont(new Font("微软雅黑",Font.PLAIN,20));
JScrollPane friendScroll = new JScrollPane(friendList);
friendScroll.setPreferredSize(new Dimension(150,0));
//设置 friendScroll 组件的首选大小 new Dimension(100,0) 表示组件期望的宽度和高度 100像素 0表时高度没确定
mainPanel.add(friendScroll,BorderLayout.WEST);//把组件设置在西方
-
监听器部分
friendList.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
System.out.println(e.getValueIsAdjusting());
if (!e.getValueIsAdjusting()) {
String selectValue = friendList.getSelectedValue();
chatName = selectValue;
System.out.println(selectValue);
//设置窗体的标题
setTitle("Chat With: " + selectValue);
messageArea.setText("------" + selectValue + "-------");
setVisible(true);
}
}
});
6、群组列表
-
群组名字创建
groupList = new JList<>(new String[]{
"group one",
"group two",
"group three"
});
-
字体
-
组件在主组件位置
groupList.setFont(new Font("微软雅黑",Font.PLAIN,20));
JScrollPane groupJScroll = new JScrollPane(groupList);//把群组组件添加到上下可移动组件中
groupJScroll.setPreferredSize(new Dimension(150,0));
mainPanel.add(groupJScroll,BorderLayout.EAST);//添加到主组件东方
-
监听器
groupList.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
System.out.println(e.getValueIsAdjusting());
if (!e.getValueIsAdjusting()){
String selectValue = groupList.getSelectedValue();
System.out.println(selectValue);
//创建消息区域
setTitle("Chat With:" +selectValue);
chatName = selectValue;
messageArea.setText("--------" + selectValue +"-------");
setVisible(true);
}
}
});
7、服务端
private class ServerSocketTask implements Runnable{
//内部类
@Override
public void run() {
try {
ServerSocket serverSocket = new ServerSocket(789);
while(true){
/*
创建服务端和客户端对象
*/
Socket socket = serverSocket.accept();//while持续监听
Client client = new Client(socket);
clients.add(client);//放入创建的客户端存储容器种
Thread clientThread = new Thread(client);//客户端加入线程
clientThread.start();//启动线程
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
8、客户端
private class Client implements Runnable{
private Socket socket;//创建客户端套接字
private String name;
public Client(Socket socket) {
this.socket = socket;
this.name = socket.getRemoteSocketAddress().toString();
//将当前对象(即该类的一个实例)的name属性设置为客户端(socket)
// 的远程 IP 地址和端口号,以字符串的形式表示。
}
@Override
public void run() {
while (true){
try {
byte[] bytes = new byte[1024];//设定一个1024bit的存储空间 1KB
int len = socket.getInputStream().read(bytes);
//把客户端的输入的内容读取到byte里,并返回实际读取的字节数
String msg = new String(bytes,0,len);
messageArea.append("\n" + name + ": " + msg);
} catch (IOException e) {
clients.remove(this);//删除当前连接客户端
//将当前对象(即该类的一个实例)
// 从存储客户端连接信息的容器中删除。
try {
socket.close();//关闭客户端
} catch (IOException ioException) {
ioException.printStackTrace();
}
e.printStackTrace();
}
}
}
有一个有趣的问题:为什么要在客户端出现异常的情况下把当前客户端关闭呢?这里会出现什么问题呢?
会出现以下问题
- 资源泄露:如果是在输入输出流上出现异常,在资源没有得到回收的情况下,会出现资源泄露的问题。
- 死锁:异常在I/O操作当中,可能会造成死锁,而线程会等待I/O完成,而I/O可能会阻塞,导致没法退出,一直等待,导致死锁出现。
- 资源争夺:在不断开客户端的情况下,服务端会在异常的客户端上一直工作,会占用资源,从而影响服务器性能
-
消息发送方法
public void sendMessage(String message){
try {
socket.getOutputStream().write(message.getBytes());
// 将 message 内容转换成字节数组,然后通过 socket 的客户端输出流发送到服务端。
socket.getOutputStream().flush();//刷新管道
} catch (IOException e) {
e.printStackTrace();
}
}
源码链接🔗:
链接:https://pan.baidu.com/s/1puiRWIEVrJ7zf3OxMwjR3w
提取码:fri0