文章目录
- 最终版本
第9个版本:这个版本的主要作用解决只能发一次消息的问题;上一个版本中我们将客户端输入的内容发送到服务器,但是运行后发现只能发送一次消息,后续消息就发不出去了,这次我们就是解决这个问题。
解决这个问题的思路是1.在服务端我们得一直不停的接受数据,所以得写个循环 2.是在客户端屏蔽掉写完数据就关闭输出流的语句,但这样会导致新的问题,所以我们还在客户端增加了两步:1,是增加了关闭窗口前的清理工作 2. 连接开始后的初始化工作。
//server.java
package Chat9;
import java.io.*;
import java.io.IOException;
import java.net.*;
public class Server {
public static void main(String[] args) {
boolean started = false;
try {
ServerSocket sc = new ServerSocket(8088);
started = true;//一旦连接就位true
//接受客户端连接,不一定是一个连接,就用死循环
while(started) {
boolean bConnect = false;
Socket s = sc.accept();
System.out.println("a client connected!");
bConnect = true;//accept后变为true
DataInputStream dis = new DataInputStream(s.getInputStream());
//收也不能收只收一次,建个循环
while(bConnect) {
String str = dis.readUTF();//readUTF是转化成utf-8编码格式的
System.out.println(str);
}
dis.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//client.java
package Chat9;
import java.io.*;
import java.net.*;
import java.awt.*;//Frame
import java.awt.event.*;//WindowAdapter,WindowEvent
import java.io.IOException;
//解决只能发一次消息的问题
public class Client extends Frame {// 继承框架
Socket s = null;
DataOutputStream dos = null;//每次发送都要调用,不如做成全局变量
TextField tfText = new TextField();// 全局变量,方便后面使用
TextArea taContext = new TextArea();
public static void main(String[] args) {
new Client().launchFrame();
}
//窗口
public void launchFrame() {
setLocation(800, 300);// 位置
this.setSize(400, 400);// 大小
add(tfText, BorderLayout.SOUTH);// 显示框加到南边
add(taContext, BorderLayout.NORTH);// 输入框加到北边
pack();// 处理中间大部分空白
this.addWindowListener(new WindowAdapter() {//WindowAdapter是个抽象类,里面对接口WindowListener的方法都进行了空实现。 这样编程时不需要自己再去实现全部接口WindowListener里的方法。只需覆写自己需要的方法,其他的方法 WindowAdapter都实现了
//重写windowsClosing窗口关闭
public void windowClosing(WindowEvent arg0) {
disconnect();
System.exit(0);//正常退出就行
}
});
tfText.addActionListener(new TFListener());
setVisible(true);
connect();//发完信息后直接连接
}
//Connet
public void connect() {
try {
s = new Socket("localhost",8088);//127.0.0.1
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//把tfText里面的内容通过Enter进入到taContext
private class TFListener implements ActionListener{
//处理enter以后的动作,我们现在想enter后把输入的东西发过去,得在这里进行处理。
public void actionPerformed(ActionEvent e) {
String str = tfText.getText().trim();//trim去掉空格
//把输入的展示到展示框中。
taContext.setText(str);
tfText.setText("");//处理enter后的tfText,使其为空。
//把输入的内容发送到Server中
SendMessage(str);
}
}
//把输入的内容发送到Server中
public void SendMessage(String str) {
try {
// DataOutputStream dos = new DataOutputStream(s.getOutputStream());//DataOutputStream:数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后应用程序可以使用数据输入流将数据
dos.writeUTF(str);//发送消息
dos.flush();//你向输出流写入东西之后,执行flush(),目的是把缓冲区里的东西强行写入输出流.因为有些带缓冲区的输出流要缓冲区满的时候才输出.
//dos.close();//关掉了以后就只能发送一次内容了,再发就close掉了
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
第10个版本:这个版本的主要作用是 解决关闭窗口后,Server报错的情况,所以改变的只有服务器。
package Chat10;
import java.io.*;
import java.io.IOException;
import java.net.*;
public class Server {
public static void main(String[] args) {
boolean started = false;
ServerSocket sc = null;
Socket s = null;
DataInputStream dis = null;
try {
sc = new ServerSocket(8088);
}catch(BindException e) {
System.out.println("端口使用中");
System.out.println("请关掉相关程序,重新启动");
System.exit(0);//为的是启动两次服务器带来的错误
}catch(IOException e){
e.printStackTrace();
//System.out.println("连接失败!");
}
try {
started = true;//一旦连接就位true
//接受客户端连接,不一定是一个连接,就用死循环
while(started) {
boolean bConnect = false;
s = sc.accept();
System.out.println("a client connected!");
bConnect = true;//accept后变为true
dis = new DataInputStream(s.getInputStream());
//收也不能收只收一次,建个循环
while(bConnect) {
String str = dis.readUTF();//readUTF是转化成utf-8编码格式的,是个阻塞的方法,会一直等待,当client窗口关闭的时候,会报错
System.out.println(str);
}
//dis.close();
}
}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();//再关闭掉Socket
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
第11个版本:这个版本的主要作用是 实现多个Client聊天,实现思路是在Server里面创建多线程,主线程只负责连接,其他线程负责处理之后的问题,所以改变的也只有服务端
package Chat11;
import java.io.*;
import java.io.IOException;
import java.net.*;
public class Server {
boolean started = false;
ServerSocket sc = null;
public static void main(String[] args) {
new Server().start();
}
//接受客户端连接后起一个线程,进行处理
public void start() {
try {
sc = new ServerSocket(8088);
started = true;
}catch(BindException e) {
System.out.println("端口使用中");
System.out.println("请关掉相关程序,重新启动");
System.exit(0);//为的是启动两次服务器带来的错误
}catch(IOException e){
e.printStackTrace();
}
try {
while(started) {
Socket s = sc.accept();
Client c = new Client(s);
System.out.println("a client connected!");
new Thread(c).start();//新建Thread并启动
}
}catch (IOException e) {
e.printStackTrace();
}finally {
try {
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//线程类(内部类还是外部类)
class Client implements Runnable{//客户端那边在这边的一个包装
private Socket s;
private DataInputStream dis = null;
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() {
try {
//收也不能收只收一次,建个循环
while(bConnected) {
String str = dis.readUTF();//readUTF是转化成utf-8编码格式的,是个阻塞的方法,会一直等待,当client窗口关闭的时候,会报错
System.out.println(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();//再关闭掉Socket
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
}
第12个版本:这个版本的主要作用是 实现不同客户端之间的通讯,此时我们的服务器相当于消息中转站
//client.java
package Chat12;
import java.io.*;
import java.net.*;
import java.awt.*;//Frame
import java.awt.event.*;//WindowAdapter,WindowEvent
import java.io.IOException;
//Client接受其他客户端发送的内容
public class Client extends Frame {// 继承框架
Socket s = null;
DataOutputStream dos = null;//每次发送都要调用,不如做成全局变量
DataInputStream dis = null;
private boolean bConnected = false;
TextField tfText = new TextField();// 全局变量,方便后面使用
TextArea taContext = new TextArea();
Thread tRecv = new Thread(new RecvThread());
public static void main(String[] args) {
new Client().launchFrame();
}
//窗口
public void launchFrame() {
setLocation(800, 300);// 位置
this.setSize(400, 400);// 大小
add(tfText, BorderLayout.SOUTH);// 显示框加到南边
add(taContext, BorderLayout.NORTH);// 输入框加到北边
pack();// 处理中间大部分空白
//关闭窗口的操作
this.addWindowListener(new WindowAdapter() {
//重写windowsClosing窗口关闭
public void windowClosing(WindowEvent arg0) {
disconnect();
System.exit(0);//正常退出就行
}
});
tfText.addActionListener(new TFListener());
setVisible(true);
connect();//发完信息后直接连接
//new Thread(new RecvThread()).start();
tRecv.start();//将Thread提升到全局变量等级,让其他地方能访问到他
}
//Connect
public void connect() {
try {
s = new Socket("localhost",8088);//127.0.0.1
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();
}
//这里又有个问题,方法阻塞的话,窗口又管不了,嗯,报错就打印就打印,不管了
/*try {
bConnected = false;//线程结束
tRecv.join();//合并到一个线程
}catch (InterruptedException e) {//catch join的错误
e.printStackTrace();
}finally {
try {
dos.close();//先清理资源
dis.close();
s.close();//再关闭连接
} catch (IOException e) {
e.printStackTrace();
}
}*/
}
//把tfText里面的内容通过Enter进入到taContext
private class TFListener implements ActionListener{
//处理enter以后的动作,我们现在想enter后把输入的东西发过去,得在这里进行处理。
public void actionPerformed(ActionEvent e) {
String str = tfText.getText().trim();//trim去掉空格
//taContext.setText(str);//把输入的展示到展示框中。为了窗口好看,删了这句话
tfText.setText("");//处理enter后的tfText,使其为空。
//把输入的内容发送到Server中
SendMessage(str);
}
}
//新建一个内部类,里面新启一个线程,负责接收其他客户端发过来的消息
private class RecvThread implements Runnable{
public void run() {
try {
while(bConnected) {
String str = dis.readUTF();//这里因为是阻塞的,问题有点大,嗯。怎么改呢。。。。。。。气死我
//System.out.println(str);
//taContext.setText(str);//这样写会冲掉之前写的
taContext.setText(taContext.getText() + str + '\n');
}
}catch(SocketException e) {
System.out.println("退出了,拜");
}catch(IOException e) {
e.printStackTrace();
}
}
}
//把输入的内容发送到Server中
public void SendMessage(String str) {
try {
// DataOutputStream dos = new DataOutputStream(s.getOutputStream());//DataOutputStream:数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后应用程序可以使用数据输入流将数据
dos.writeUTF(str);//发送消息
dos.flush();//你向输出流写入东西之后,执行flush(),目的是把缓冲区里的东西强行写入输出流.因为有些带缓冲区的输出流要缓冲区满的时候才输出.
//dos.close();//关掉了以后就只能发送一次内容了,再发就close掉了
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
//server.java
package Chat12;
import java.io.*;
import java.io.IOException;
import java.net.*;
import java.util.*;
public class Server {
boolean started = false;
ServerSocket sc = null;
List<Client> clients = new ArrayList<Client>();//新建一个集合装不同的客户端Socket,但是这里得装整个类Client
public static void main(String[] args) {
new Server().start();
}
//接受客户端连接后起一个线程,进行处理
public void start() {
try {
sc = new ServerSocket(8088);
started = true;
}catch(BindException e) {
System.out.println("端口使用中");
System.out.println("请关掉相关程序,重新启动");
System.exit(0);//为的是启动两次服务器带来的错误
}catch(IOException e){
e.printStackTrace();
}
try {
while(started) {
Socket s = sc.accept();
Client c = new Client(s);
System.out.println("a client connected!");
new Thread(c).start();//新建Thread并启动
clients.add(c);//新建一个Client,得加到clients集合中
}
}catch (IOException e) {
e.printStackTrace();
}finally {
try {
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//线程类(内部类还是外部类)
class Client implements Runnable{//客户端那边在这边的一个包装
private Socket s;//每一个客户端单独的连接
private DataInputStream dis = null; //从Socket里面单独的管道连接
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) {
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(dos != null) dos.close();
if(s != null)
s.close();//再关闭掉Socket
// s = null;//方便垃圾回收器回收
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
}
最终版本
这个版本的主要作用是 上一个版本中当关掉一个窗口的时候,输入会报错。要解决这个问题。
client.java
//client.java
package Chat14;
import java.io.*;
import java.net.*;
import java.awt.*;//Frame
import java.awt.event.*;//WindowAdapter,WindowEvent
import java.io.IOException;
//13版本的错误在于当关掉一个窗口的时候,在输入会报错。要解决这个
public class Client extends Frame {// 继承框架
Socket s = null;
DataOutputStream dos = null;//每次发送都要调用,不如做成全局变量
DataInputStream dis = null;
private boolean bConnected = false;
TextField tfText = new TextField();// 全局变量,方便后面使用
TextArea taContext = new TextArea();
Thread tRecv = new Thread(new RecvThread());
public static void main(String[] args) {
new Client().launchFrame();
}
//窗口
public void launchFrame() {
setLocation(800, 300);// 位置
this.setSize(400, 400);// 大小
add(tfText, BorderLayout.SOUTH);// 显示框加到南边
add(taContext, BorderLayout.NORTH);// 输入框加到北边
pack();// 处理中间大部分空白
//关闭窗口的操作
this.addWindowListener(new WindowAdapter() {
//重写windowsClosing窗口关闭
public void windowClosing(WindowEvent arg0) {
disconnect();
System.exit(0);//正常退出就行
}
});
tfText.addActionListener(new TFListener());
setVisible(true);
connect();//发完信息后直接连接
//new Thread(new RecvThread()).start();
tRecv.start();//将Thread提升到全局变量等级,让其他地方能访问到他
}
//Connect
public void connect() {
try {
s = new Socket("localhost",8088);//127.0.0.1
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();
}
}
//把tfText里面的内容通过Enter进入到taContext
private class TFListener implements ActionListener{
//处理enter以后的动作,我们现在想enter后把输入的东西发过去,得在这里进行处理。
public void actionPerformed(ActionEvent e) {
String str = tfText.getText().trim();//trim去掉空格
tfText.setText("");//处理enter后的tfText,使其为空。
//把输入的内容发送到Server中
SendMessage(str);
}
}
//新建一个内部类,里面新启一个线程,负责接收其他客户端发过来的消息
private class RecvThread implements Runnable{
public void run() {
try {
while(bConnected) {
String str = dis.readUTF();//这里因为是阻塞的,问题有点大,嗯。怎么改呢,怎么改,怎么改,怎么改
taContext.setText(taContext.getText() + str + '\n');
}
}catch(SocketException e) {
System.out.println("退出了,拜");
}catch(EOFException e) {
System.out.println("退出了,bye");
}catch(IOException e) {
e.printStackTrace();
}
}
}
//把输入的内容发送到Server中
public void SendMessage(String str) {
try {
// DataOutputStream dos = new DataOutputStream(s.getOutputStream());//DataOutputStream:数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后应用程序可以使用数据输入流将数据
dos.writeUTF(str);//发送消息
dos.flush();//你向输出流写入东西之后,执行flush(),目的是把缓冲区里的东西强行写入输出流.因为有些带缓冲区的输出流要缓冲区满的时候才输出.
//dos.close();//关掉了以后就只能发送一次内容了,再发就close掉了
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
server.java
//server.java
package Chat14;
import java.io.*;
import java.io.IOException;
import java.net.*;
import java.util.*;
public class Server {
boolean started = false;
ServerSocket sc = null;
List<Client> clients = new ArrayList<Client>();//新建一个集合装不同的客户端Socket,但是这里得装整个类Client
public static void main(String[] args) {
new Server().start();
}
//接受客户端连接后起一个线程,进行处理
public void start() {
try {
sc = new ServerSocket(8088);
started = true;
}catch(BindException e) {
System.out.println("端口使用中");
System.out.println("请关掉相关程序,重新启动");
System.exit(0);//为的是启动两次服务器带来的错误
}catch(IOException e){
e.printStackTrace();
}
try {
while(started) {
Socket s = sc.accept();
Client c = new Client(s);
System.out.println("a client connected!");
new Thread(c).start();//新建Thread并启动
clients.add(c);//新建一个Client,得加到clients集合中
}
}catch (IOException e) {
e.printStackTrace();
}finally {
try {
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//线程类(内部类还是外部类)
class Client implements Runnable{//客户端那边在这边的一个包装
private Socket s;//每一个客户端单独的连接
private DataInputStream dis = null; //从Socket里面单独的管道连接
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) {
e.printStackTrace();
}
}
//加一个方法,主要负责发东西
public void send(String str) {
try {
dos.writeUTF(str);//当一个窗口关闭的时候,另一个窗口再输入消息的话就会报错,原因就在这里
} catch (IOException e) {
clients.remove(this);
System.out.println("对方退出了,无法再进行聊天");
//e.printStackTrace();
}
}
//线程里的东西
public void run() {
// Client c = null;
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(dos != null) dos.close();
if(s != null) s.close();//再关闭掉Socket
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
}