在线聊天(JavaSE实战项目)学习笔记

09月29日 收藏 0 评论 0 java开发

在线聊天(JavaSE实战项目)学习笔记

转载声明:文章来源https://blog.csdn.net/dingd1234/article/details/80395800

Chat0.1
搭建客户端ChatClient窗口,有两种方式:

一、继承Frame类(比较灵活)
调用Frame类中的setLocation(int x, int y)方法设置窗口位置,setSize(int width, int height)方法设置窗口大小,setVisible(true)方法将窗口显示出来

setSize(int width, int height):其实就是定义控件的大小,有两个参数,分别对应宽度和高度;

setLocation(int x, int y):将组件移到新位置,用x 和 y 参数来指定新位置的左上角

setBounds(int x, int y, int width, int height):四个参数,既定义组件的位置,也定义控件的大小; 其实它就是上面两个函数的功能的组合

import java.awt.Frame;

public class ChatClient extends Frame{

public static void main(String[] args) {

new ChatClient().launchFrame();
}

public void launchFrame()
{
setLocation(400, 300);
setSize(300, 300);
setVisible(true);
}
}

二、直接使用Frame类

Chat0.2
向客户端窗口中添加文本输入框TextField和文本输入区TextArea(可以输入多行文本),使用add方法

import java.awt.*;

public class ChatClient extends Frame{

TextField tfTxt = new TextField();
TextArea taContent = new TextArea();

public static void main(String[] args) {
new ChatClient().launchFrame();
}

public void launchFrame(){
setLocation(400, 300);
setSize(300, 300);
add(tfTxt, BorderLayout.SOUTH);
add(taContent, BorderLayout.NORTH);
pack();//void pack() 使此窗口的大小适合其子组件的首选大小和布局。
setVisible(true);
}

}

Chat0.3
添加客户端窗口关闭功能,使用窗口的 addWindowListener 方法

WindowListener是java中的接口。
主要作用:
用于接收窗口事件的侦听器接口。旨在处理窗口事件的类要么实现此接口(及其包含的所有方法),要么扩展抽象类WindowAdapter(仅重写所需的方法)。然后使用窗口的 addWindowListener 方法将从该类所创建的侦听器对象向该 Window 注册。当通过打开、关闭、激活或停用、图标化或取消图标化而改变了窗口状态时,将调用该侦听器对象中的相关方法,并将 WindowEvent 传递给该方法。

import java.awt.*;
import java.awt.event.*;

public class ChatClient extends Frame{

TextField tfTxt = new TextField();
TextArea taContent = new TextArea();

public static void main(String[] args){
new ChatClient().launchFrame();
}

public void launchFrame() {
setLocation(400, 300);
setSize(300, 300);
add(tfTxt, BorderLayout.SOUTH);
add(taContent, BorderLayout.NORTH);
pack();
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
setVisible(true);
}
}

Chat0.4

向客户端中添加功能:在文本输入框TextFiled中输入内容按回车键后,内容会显示到文本输入区TextArea中

取文本内容getText()方法,设置文本内容setText()方法

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;

public class ChatClient extends Frame{

TextField tfTxt = new TextField();
TextArea taContent = new TextArea();

public static void main(String[] args){
new ChatClient().launchFrame();
}

public void launchFrame() {
setLocation(400, 300);
setSize(300, 300);
add(tfTxt, BorderLayout.SOUTH);
add(taContent, BorderLayout.NORTH);
pack();
addWindowListener(new WindowAdapter() {

@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}

});
tfTxt.addActionListener(new TFListener());
setVisible(true);
}

private class TFListener implements ActionListener{

@Override
public void actionPerformed(ActionEvent e) {
String s = tfTxt.getText().trim();
taContent.setText(s);
tfTxt.setText("");
}

}
}

Chat0.5

创建ChatSever服务端

1、使用ServerSocket创建服务端(ServerSocket对象用于监听来自客户端的Socket连接)
ServerSocket(int port):用指定的端口port来创建一个ServerSocket。该端口应该有一个有效的端口整数值,即0~65535。

2、接收来自客户端Socket的连接请求,使用accept()方法
ServerSocket包含一个监听来自客户端连接请求的方法,即accept()方法

Socket accept():如果接收到一个客户端Socket的连接请求,该方法将返回一个与客户端Socket对应的Socket;否则该方法将一直处于等待状态,线程也被阻塞。

注意:服务端只有一个,客户端有多个,需使用while循环来接收多个客户端

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class ChatServer {
public static void main(String[] args) {
try{
ServerSocket ss = new ServerSocket(8888);
while(true) {//用于接收多个客户端
Socket s = ss.accept();
System.out.println("a client connected");//用于验证客户端是否已连接成功
}
}
catch(IOException e) {
e.printStackTrace();
}
}
}

Chat0.6

ChatClient
1、在显示客户端窗口的同时,将客户端与服务端进行连接,将连接步骤封装成connect()方法

2、connect()方法
(1)创建客户端对象:客户端通常使用Socket的构造器来连接到指定的服务器

Socket(InetAddress, int port):创建连接到指定远程主机、远程端口的Socket,该构造器没有指定本地地址、本地端口,默认使用本地主机的默认IP地址,默认使用系统动态分配的端口。

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.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;

public class ChatClient extends Frame{
Socket s = null;
TextField tfTxt = new TextField();
TextArea taContent = new TextArea();

public static void main(String[] args) {
new ChatClient().launchFrame();
}

public void launchFrame() {
setLocation(400, 300);
setSize(300, 300);
add(tfTxt, BorderLayout.SOUTH);
add(taContent, BorderLayout.NORTH);
pack();
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
tfTxt.addActionListener(new TFListener());
setVisible(true);
connect();
}

public void connect() {
try {
s = new Socket("127.0.0.1", 8888);
System.out.println("connected");
}catch(UnknownHostException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}
}

public class TFListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
String str = tfTxt.getText().trim();
taContent.setText(str);

}
}

}

ChatServer
接收客户端发送过来的数据
当客户端、服务端产生了对应的Socket之后,,程序无须再区分服务器端、客户端,而是通过各自的Socket通信。Socket提供了如下两个方法来获取输入流和输出流。

(1)InputStream getInputStream():返回该Socket对象对应的输入流,让程序通过该输入流从Socket中取出数据。

(2)OutputStream getOutputStream():返回该Socket对象对应的输出流,让程序通过该输出流向Socket中输出数据。

import java.io.DataInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class ChatServer {

public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(8888);
while(true) {
Socket s = ss.accept();
System.out.println("a client connected");
DataInputStream dis = new DataInputStream(s.getInputStream());
String str = dis.readUTF();
System.out.println(str);
dis.close();
}
}catch(IOException e) {
e.printStackTrace();
}
}

}

Chat0.7

ChatClient
将从文本输入框TextField中获取到数据发送到服务端(客户端---->服务端)

OutputStream getOutputStream():返回该Socket对象对应的输出流,让程序通过该输出流向Socket中输出数据。

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.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;


public class ChatClient extends Frame{
Socket s = null;
TextField tfTxt = new TextField();
TextArea taContent = new TextArea();

public static void main(String[] args) {
new ChatClient().launchFrame();
}

public void launchFrame() {
setLocation(400, 300);
setSize(300, 300);
add(tfTxt, BorderLayout.SOUTH);
add(taContent, BorderLayout.NORTH);
pack();
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
tfTxt.addActionListener(new TFListener());
setVisible(true);
connect();
}

public void connect() {
try {
s = new Socket("127.0.0.1", 8888);
System.out.println("connected");
}catch(UnknownHostException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}
}

public class TFListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
String str = tfTxt.getText().trim();
taContent.setText(str);
tfTxt.setText("");

try {
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
dos.writeUTF(str);
dos.flush();
dos.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}

Chat0.8

ChatClient
在关闭窗口的同时关闭输出流,关闭Socket

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.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;


public class ChatClient extends Frame{
Socket s = null;
DataOutputStream dos = null;
TextField tfTxt = new TextField();
TextArea taContent = new TextArea();

public static void main(String[] args) {
new ChatClient().launchFrame();
}

public void launchFrame() {
setLocation(400, 300);
setSize(300, 300);
add(tfTxt, BorderLayout.SOUTH);
add(taContent, BorderLayout.NORTH);
pack();
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
disconnect();
System.exit(0);
}
});
tfTxt.addActionListener(new TFListener());
setVisible(true);
connect();
}

public void connect() {
try {
s = new Socket("127.0.0.1", 8888);
dos = new DataOutputStream(s.getOutputStream());
System.out.println("connected");
}catch(UnknownHostException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}
}

public void disconnect() {
try{
dos.close();
s.close();
}catch(IOException e) {
e.printStackTrace();
}

}
public class TFListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
String str = tfTxt.getText().trim();
taContent.setText(str);
tfTxt.setText("");

try {
dos.writeUTF(str);
dos.flush();
//dos.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}

ChatServer
在之前的版本中服务端不能连续读取客户端发送过来的数据。

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.io.DataInputStream;

public class ChatServer {

public static void main(String[] args) {
boolean started = false;
try{
ServerSocket ss = new ServerSocket(8888);
started = true;
while(started) {//该while循环用于接收多个客户端
boolean bConnected = false;
Socket s = ss.accept();
System.out.println("a client connected");
bConnected = true;
DataInputStream dis = new DataInputStream(s.getInputStream());
while(bConnected) {//该while循环可以连续读取客户端发送过来的信息
String str = dis.readUTF();
System.out.println(str);
}

dis.close();
}
}catch(IOException e) {
e.printStackTrace();
}

}

}

Chat0.9

ChatClient无改动

ChatServer
异常处理:
(1)服务器端启动后,若再次启动服务端,则会产生BindException异常。原因是服务器端启动后,端口已经被占用,无法二次启动。

(2)启动服务器端和客户端后,若关闭客户端则会发生EOFException。

产生原因:服务端读取客户端发过来的数据使用的是readUTF()方法,该方法是阻塞式方法。在客户端关闭后,服务端并不知晓,仍在等待接收数据。


通过这个API,我们可以得出以下信息:
1.这是一个IO异常的子类,名字也是END OF FILE的缩写,当然也表示流的末尾
2.它在表明一个信息,流已经到末尾了,而大部分做法是以特殊值的形式返回给我们,而不是抛异常
(1)也就是说这个异常是被主动抛出来的,而不是底层或者编译器返回给我的,就像NullPointerException或IndexOutOfBoundsException一样。

详细解释见:https://www.cnblogs.com/yiwangzhibujian/p/7107084.html

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.io.DataInputStream;

public class ChatServer {
public static void main(String[] args) {
boolean started = false;
try{
ServerSocket ss = new ServerSocket(8888);
started = true;
while(started) {//该while循环用于接收多个客户端
boolean bConnected = false;
Socket s = ss.accept();
System.out.println("a client connected");
bConnected = true;
DataInputStream dis = new DataInputStream(s.getInputStream());
while(bConnected) {//该while循环可以连续读取客户端发送过来的信息
String str = dis.readUTF();
System.out.println(str);
}
dis.close();
}
}catch(IOException e) {
e.printStackTrace();
}
}
}

Chat1.0

启动服务器端后,若开启多个客户端窗口,则服务器端显示只能连接一个客户端,其他客户端窗口连接不上服务器,向服务器发送数据也无法显示。

原因:服务器端读取客户端发送过来的数据使用的是readUTF()方法,该方法为阻塞式方法,主方法运行后,会卡在该方法处,一直等待接收数据。这也就导致服务器端只能处理一个客户端。

处理方法:开启多线程

import java.io.*;
import java.net.*;

public class ChatServer {
boolean started = false;
ServerSocket ss = null;

public static void main(String[] args) {
new ChatServer().start();
}

public void start() {
try {
ss = new ServerSocket(8888);
} catch(BindException e) {
System.out.println("端口使用中......");
System.out.println("请关掉相关程序,并重新运行!");
System.exit(0);
} catch(IOException e) {
e.printStackTrace();
}

try {
started = true;
while (started) {
Socket s = ss.accept();
Client c = new Client(s);
new Thread(c).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
ss.close();
} catch(IOException e1) {
e1.printStackTrace();
}
}
}
class Client implements Runnable {
private Socket s;
private DataInputStream dis;
private boolean bConnected = false;

public Client(Socket s) {
this.s = s;
try {
dis = new DataInputStream(s.getInputStream());
bConnected = true;
} catch (IOException e) {
e.printStackTrace();
}
}

public void run() {
System.out.println("a client connected");
try {
while (bConnected) {
String str = dis.readUTF();
System.out.println(str);
}
} catch(EOFException e) {
System.out.println("Client closed");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
dis.close();
s.close();
} catch(IOException e1) {
e1.printStackTrace();
}
}
}
}
}

Chat1.1

将从某一客户端接收到的数据转发给其他客户端。

List<Client> clients = new ArrayList<>();//将线程对象存储起来,一个线程对象即代表一个客户端
while (bConnected) {
String str = dis.readUTF();
System.out.println(str);
for(int i = 0; i < clients.size(); i++) {
Client c = clients.get(i);
c.send(str);
}
}

将从客户端读取到的数据,通过对List集合的逐一遍历,发送给每个客户端对象。

import java.io.*;
import java.net.*;
import java.util.*;

public class ChatServer {
ServerSocket ss = null;
boolean started = false;
List<Client> clients = new ArrayList<>();

public static void main(String[] args) {
new ChatServer().start();
}

public void start() {
try {
ss = new ServerSocket(8888);
started = true;
} catch(BindException e) {
System.out.println("端口使用中");
System.out.println("请关闭相关程序重新运行服务器");
System.exit(0);
} catch(IOException e) {
e.printStackTrace();
}

try {
while(started) {
Socket s = ss.accept();
System.out.println("a client connected");
Client c = new Client(s);
new Thread(c).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}

}
}

class Client implements Runnable {
private Socket s;
private DataInputStream dis;
private DataOutputStream dos;
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) {
e.printStackTrace();
}
}

public void send(String str) {
try {
dos.writeUTF(str);
} catch (IOException e) {
e.printStackTrace();
}
}

public void run() {
try {
while (bConnected) {
String str = dis.readUTF();
System.out.println(str);
for(int i = 0; i < clients.size(); i++) {
Client 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(s != null) {
s.close();
}
if(dos != null) {
dos.close();
}
} catch (IOException e) {
e.printStackTrace();
}

}
}
}
}

Chat1.2

客户端接收从服务端发送过来的每个客户端的数据,并将数据显示到文本输入区TextArea中

当我们在向服务器端发送本地客户端的数据时,需要同时接收其他客户端发送过来的数据。因此需要使用多线程。

1.2异常处理:
开启多个客户端,同时进行聊天,当某一个客户端关闭后,会产生SocketException

原因:开启多个客户端后,在关闭其中某一个客户端时(主线程结束),会调用disconnect()方法,将客户端Socket关闭,而此时接收数据的线程中的输入流可能并未关闭,仍会从客户端Socket中读取数据,此时就会产生SocketException异常。

简单处理方式:直接进行捕捉,给出相应提示。

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{
TextField tfTxt = new TextField();
TextArea taContent = new TextArea();
Socket s = null;
DataOutputStream dos = null;
DataInputStream dis = null;
private boolean bConnected = false;

public static void main(String[] args) {
new ChatClient().launchFrame();
}

public void launchFrame() {
setLocation(400, 300);
setSize(300, 300);
add(tfTxt, BorderLayout.SOUTH);
add(taContent, BorderLayout.NORTH);
pack();
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
disconnect();
System.exit(0);
}
});
tfTxt.addActionListener(new TFListener());
setVisible(true);
connect();
new Thread(new RecvThread()).start();
}

public void connect() {
try {
s = new Socket("127.0.0.1", 8888);
dos = new DataOutputStream(s.getOutputStream());
dis = new DataInputStream(s.getInputStream());
System.out.println("connected");
bConnected = true;
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

public void disconnect() {
try {
dos.close();
dis.close();
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}

public 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();
} catch (IOException e1) {
e1.printStackTrace();
}

}

}

private class RecvThread implements Runnable {
public void run() {
try {
while(bConnected) {
String str = dis.readUTF();
taContent.setText(taContent.getText() + str + '\n');
}
} catch(SocketException e) {
System.out.println("退出了,bye!");
} catch(IOException e) {
e.printStackTrace();
}
}
}
}

Chat1.3

异常处理:
(1)同时开启多个客户端进行聊天,当其中某个客户端关闭,继续进行聊天时会产生SocketException。

产生原因:服务器端会通过Socket向每个客户端发送数据,我们将每个客户端的Socket用List集合进行存储,服务器端向每个客户端发送数据时,会通过遍历List集合逐一发送,而此时关闭的客户端的Socket仍在其中,并未从List集合中移除,因此服务器端仍然会像关闭的客户端发送数据,此时便会产生SocketException异常。

处理方式:在发送数据时直接从List集合中移除,并给出提示信息。

import java.io.*;
import java.net.*;
import java.util.*;

public class ChatServer {
ServerSocket ss = null;
boolean started = false;
List<Client> clients = new ArrayList<>();

public static void main(String[] args) {
new ChatServer().start();
}

public void start() {
try {
ss = new ServerSocket(8888);
started = true;
} catch(BindException e) {
System.out.println("端口使用中");
System.out.println("请关闭相关程序重新运行服务器");
System.exit(0);
} catch(IOException e) {
e.printStackTrace();
}

try {
while(started) {
Socket s = ss.accept();
System.out.println("a client connected");
Client c = new Client(s);
new Thread(c).start();
clients.add(c);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}

}
}

class Client implements Runnable {
private Socket s;
private DataInputStream dis;
private DataOutputStream dos;
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) {
e.printStackTrace();
}
}

public void send(String str) {
try {
dos.writeUTF(str);
} catch (SocketException e) {
clients.remove(this);
System.out.println("对方退出了!我从List里面去掉了!");
} catch (IOException e) {
clients.remove(this);
System.out.println("对方退出了");
}
}

public void run() {
try {
while (bConnected) {
String str = dis.readUTF();
System.out.println(str);
for(int i = 0; i < clients.size(); i++) {
Client 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(s != null) {
s.close();
}
if(dos != null) {
dos.close();
}
} catch (IOException e) {
e.printStackTrace();
}

}
}
}
}

注意:EOFException、SocketException均是IOException的子类

C 0条回复 评论

帖子还没人回复快来抢沙发