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 方法
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,我们可以得出以下信息:
也就是说这个异常是被主动抛出来的,而不是底层或者编译器返回给我的,就像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 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 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 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的子类