基本逻辑
- 服务器充当的角色:服务器应能接受客户端的连接;服务器本身一般不产生信息只转发信息。
1.1 连接:服务器应提供IP和端口号;
1.2 连接:允许多个客户端连接一个服务器,使用一个服务器转发信息。需要使用线程,为每个客户端构建一个线程。
1.3 转发:对于每条流入服务器的信息,服务器只做接收和转发。 - 客户端角色:客户端可以连接服务器;可以实现发送信息和接收信息。
2.1 连接:拥有服务器的IP的对应的端口号即可连接
2.2 发送:可以实现向服务器发送信息,然后指定接受信息的客户端,使服务器转发信息给此客户端。发送是独立随机的,应为其建立线程。
2.3 接受:接受服务器转发来的信息。接收是随机的,需要不断的监听接收服务器发送的信息,应为其建立线程。 - 难点:线程的使用。
单人自言自语版
特点
一个客户端可连接到服务器端,客户端可以发送信息给服务器端,也可以接受服务器端发送的信息。但只能发送一条信息,接收一条信息,即发送与接受不独立。
服务器端只能接收一个客户端的接入,然后将该客户端发送的信息在转发给该客户端。
代码
服务器端
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务器:可与一个客户端重复收发消息
* @author dxt
*
*/
public class MultiChat {
public static void main(String[] args) throws IOException{
//1.使用ServerSocket建立服务器端
ServerSocket server = new ServerSocket(8888); //指定端口
//2.使用accept() 接收连入的客户, 程序会一直阻塞在此,直到有客户连入才会向下执行
Socket client = server.accept();
System.out.println("一个客户端已连入");
//3. 获取接入客户的输入输出流
DataInputStream dis = new DataInputStream(client.getInputStream());
DataOutputStream dos = new DataOutputStream(client.getOutputStream());
//4. 循环转发消息
boolean isEnd = false;
while(!isEnd){
//4.1 输入流获取 客户发送的信息
String msg = dis.readUTF();
//4.输出流 转发信息 到客户端
dos.writeUTF(msg);
dos.flush();
}
//5.释放资源
dos.close();
dis.close();
client.close();
server.close();
}
}
客户端
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* 客户端
* 可与服务器重复收发消息
* @author dxt
*
*/
public class MultiClient {
public static void main(String[] args) throws UnknownHostException, IOException{
//1. 建立客户端Socket, 指明IP和端口
Socket client = new Socket("localhost", 8888);
//2. 接收控制台信息
BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
//3. 获取client的输入输出流
DataOutputStream dos = new DataOutputStream(client.getOutputStream());
DataInputStream dis = new DataInputStream(client.getInputStream());
//4.循环接收发送消息
boolean isEnd = false;
while(!isEnd){
//接收控制台输入的信息
String msg = console.readLine();
//4.1 发送信息
dos.writeUTF(msg);
dos.flush();
//4.2 接受信息
msg = dis.readUTF();
System.out.println("接收信息:" + msg);
}
//4.释放资源
dis.close();
dos.close();
client.close();
}
}
多人自言自语版
特点
一个客户端可连接到服务器端,可以独立进行发送和接收信息。
服务器可接受多个客户端的连接,但只能将客户端发送的的消息再转发给它自己。
注意:服务器是如何处理多个客户端的连接的;客户端是如何实现发送与接收信息独立的。
注意:存在很多bug。
代码
服务器端:
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务器:
* 目标:实现 多个客户可以收发信息
* 可实现多人自言自语
* 目标:封装
* @author dxt
*
*/
public class MultiChat {
public static void main(String[] args) throws IOException{
//1.使用ServerSocket建立服务器端
ServerSocket server = new ServerSocket(8888); //指定端口
//2.while(): 不断接收客户端
while(true){
Socket client = server.accept();
System.out.println("一个客户端建立了连接");
//为连入的客户建立线程,不断的接受发送信息
new Thread(new Channel(client)).start();
}
}
/*
* 一个客户代表一个Channel
*/
static class Channel implements Runnable{
private DataOutputStream dos;
private DataInputStream dis;
private Socket client;
private boolean isRunning;
public Channel(){
}
public Channel(Socket client){
this.client = client;
try {
dis = new DataInputStream(client.getInputStream());
dos = new DataOutputStream(client.getOutputStream());
isRunning = true;
} catch (IOException e) {
System.out.println("Client");
release(); //直接释放
}
}
//接收消息
private String receive(){
String msg = "";
try {
msg = dis.readUTF();
} catch (IOException e) {
System.out.println("receive");
release(); //直接释放
}
return msg;
}
//发送消息
private void send(String msg){
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
System.out.println("send");
release(); //直接释放
}
}
//释放资源
private void release(){
this.isRunning = false;
Utils.close(dos, dis, client); //释放资源
}
/**
* 实现run方法
* 在一个线程中要做什么?
* 接收消息->发送消息
*/
public void run(){
while(isRunning){
//接收信息
String msg = receive();
//如果信息不为空,则发送信息
if(!msg.equals("")){
send(msg);
}
}
}
}
}
客户端:
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* 客户端
* 可与服务器重复收发消息
* @author dxt
*
*/
public class MultiClient {
public static void main(String[] args) throws UnknownHostException, IOException{
//1. 建立客户端Socket, 指明IP和端口
Socket client = new Socket("localhost", 8888);
//2. 不断发送消息
//不停 这个属性有Send类中的isRunning来控制
new Thread(new Send(client)).start();
//3. 不断接收消息
new Thread(new Receive(client)).start();
}
}
Send类:
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
/**
* 接收端 发送信息线程
* 给客户端开一个不断接收信息的线程
* @author dxt
*
*/
public class Send implements Runnable {
private Socket client; //此发送线程为那个客户端服务
private BufferedReader console; //从控制台接受信息
private DataOutputStream dos; //客户端的 输出流(发送)
private boolean isRunning;
public Send(Socket client){
super();
this.client = client;
this.console = new BufferedReader(new InputStreamReader(System.in));
try {
this.dos = new DataOutputStream(client.getOutputStream());
} catch (IOException e) {
System.out.println("Send");
this.release(); //释放资源
}
this.isRunning = true;
}
/**
* 资源释放
*/
private void release(){
this.isRunning = false;
Utils.close(dos, client);
}
/**
* 获取控制台输入的信息
* @return
*/
private String getInfo(){
String msg = null;
try {
msg = console.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return msg;
}
private void send(String msg){
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
System.out.println("send");
release();
}
}
/**
* 线程执行操作
*/
public void run(){
while(isRunning){
//1. 从控制台获取信息
String msg = this.getInfo();
//2. 如果信息不为空,则发送
if(!msg.equals("")){
this.send(msg);
}
}
}
}
Receive类
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
/**
* 客户端的 接收消息线程
* @author dxt
*
*/
public class Receive implements Runnable{
private Socket client;
private DataInputStream dis;
private boolean isRunning;
public Receive(Socket client){
this.client = client;
try {
dis = new DataInputStream(client.getInputStream());
this.isRunning = true;
} catch (IOException e) {
System.out.println("Receive");
release();
}
}
/**
* 资源释放
*/
private void release(){
this.isRunning = false;
Utils.close(dis, client);
}
/**
* 客户端接收消息
*/
private String receive(){
String msg = "";
try {
msg = dis.readUTF();
} catch (IOException e) {
System.out.println("receive");
release();
}
return msg;
}
/**
* 线程不断接收信息
*/
public void run(){
while(isRunning){
String msg = receive();
if(!msg.equals("")){
System.out.println("receive:" + msg);
}
}
}
}
Utils类:
import java.io.Closeable;
/**
* 工具类
* 用于释放资源
* @author dxt
*
*/
public class Utils {
public static void close(Closeable... targets){
for(Closeable target : targets){
try{
if(null != target){
target.close();
}
}catch(Exception e){
e.printStackTrace();
}
}
}
}
群聊版
特点
能够实现一个客户端向所有连接到此服务器上的客户发送信息。
代码
服务器端:
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 在线聊天室:服务器
* 目标:加入容器实现群聊
* @author dxt
*
*/
public class Chat {
//添加容器 CopyOnWriteArrayList容器是为了解决并发问题
private static CopyOnWriteArrayList<Channel> all = new CopyOnWriteArrayList<Channel>();
public static void main(String[] args) throws IOException{
//1.使用ServerSocket建立服务器端
ServerSocket server = new ServerSocket(8888); //指定端口
//2.while(): 不断接收客户端
while(true){
Socket client = server.accept();
System.out.println("一个客户端建立了连接");
//为连入的客户建立线程,不断的接受发送信息
Channel c = new Channel(client);
all.add(c);
new Thread(c).start();
//做个统计
System.out.println(c.name + "加入了聊天室");
System.out.println("共有 "+ all.size() + " 人在线。");
}
}
/*
* 一个客户代表一个Channel
*/
static class Channel implements Runnable{
private DataOutputStream dos;
private DataInputStream dis;
private Socket client;
private boolean isRunning;
private String name;
public Channel(){
}
public Channel(Socket client){
this.client = client;
try {
dis = new DataInputStream(client.getInputStream());
dos = new DataOutputStream(client.getOutputStream());
isRunning = true;
name = receive();
} catch (IOException e) {
System.out.println("Client");
release(); //直接释放
}
}
//接收消息
private String receive(){
String msg = "";
try {
msg = dis.readUTF();
} catch (IOException e) {
System.out.println("receive");
release(); //直接释放
}
return msg;
}
//自言自语:自己发送消息给自己
private void send(String msg){
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
System.out.println("send");
release(); //直接释放
}
}
//群聊:发给其他所有人
private void sendOthers(String msg){
for(Channel other : all){
if(other == this){
continue;
}
other.send(this.name+ ": " + msg); //发给其他人
}
}
//释放资源
private void release(){
this.isRunning = false;
Utils.close(dos, dis, client); //释放资源
//退出
all.remove(this);
System.out.println(this.name + "离开了群聊");
}
/**
* 实现run方法
* 在一个线程中要做什么?
* 接收消息->发送消息
*/
public void run(){
while(isRunning){
//接收信息
String msg = receive();
//如果信息不为空,则发送信息
if(!msg.equals("")){
sendOthers(msg); //群聊模式
}
}
}
}
}
客户端:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* 客户端
* 目标:加入容器实现群聊
* @author dxt
*
*/
public class Client {
public static void main(String[] args) throws UnknownHostException, IOException{
System.out.println("------client------");
//1. 建立客户端Socket, 指明IP和端口
Socket client = new Socket("localhost", 8888);
//设置客户端姓名
System.out.println("请输入姓名:");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String name = br.readLine();
//2. 不断发送消息
//不停 这个属性有Send类中的isRunning来控制
new Thread(new Send(client, name)).start();
//3. 不断接收消息
new Thread(new Receive(client)).start();
}
}
Send类:
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
/**
* 接收端 发送信息线程
* 给客户端开一个不断接收信息的线程
* @author dxt
*
*/
public class Send implements Runnable {
private Socket client; //此发送线程为那个客户端服务
private BufferedReader console; //从控制台接受信息
private DataOutputStream dos; //客户端的 输出流(发送)
private boolean isRunning;
private String name;
public Send(Socket client, String name){
super();
this.client = client;
this.console = new BufferedReader(new InputStreamReader(System.in));
this.name = name;
try {
this.dos = new DataOutputStream(client.getOutputStream());
//在初始化时 发送名称
send(name);
} catch (IOException e) {
System.out.println("Send");
this.release(); //释放资源
}
this.isRunning = true;
}
/**
* 资源释放
*/
private void release(){
this.isRunning = false;
Utils.close(dos, client);
}
/**
* 获取控制台输入的信息
* @return
*/
private String getInfo(){
String msg = null;
try {
msg = console.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return msg;
}
private void send(String msg){
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
System.out.println("send");
release();
}
}
/**
* 线程执行操作
*/
public void run(){
while(isRunning){
//1. 从控制台获取信息
String msg = this.getInfo();
//2. 如果信息不为空,则发送
if(!msg.equals("")){
this.send(msg);
}
}
}
}
Receive类:
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
/**
* 客户端的 接收消息线程
* @author dxt
*
*/
public class Receive implements Runnable{
private Socket client;
private DataInputStream dis;
private boolean isRunning;
public Receive(Socket client){
this.client = client;
try {
dis = new DataInputStream(client.getInputStream());
this.isRunning = true;
} catch (IOException e) {
System.out.println("Receive");
release();
}
}
/**
* 资源释放
*/
private void release(){
this.isRunning = false;
Utils.close(dis, client);
}
/**
* 客户端接收消息
*/
private String receive(){
String msg = "";
try {
msg = dis.readUTF();
} catch (IOException e) {
System.out.println("receive");
release();
}
return msg;
}
/**
* 线程不断接收信息
*/
public void run(){
while(isRunning){
String msg = receive();
if(!msg.equals("")){
System.out.println(msg);
}
}
}
}
Utils类:
import java.io.Closeable;
/**
* 工具类
* 用于释放资源
* @author dxt
*
*/
public class Utils {
public static void close(Closeable... targets){
for(Closeable target : targets){
try{
if(null != target){
target.close();
}
}catch(Exception e){
e.printStackTrace();
}
}
}
}
私聊版
特点
在群聊的基础上加入了私聊。如果以固定格式发送信息则会进行私聊,否则会是群聊。
代码在群聊版的基础上只对服务器端做了修改。
代码
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 在线聊天室:服务器
* 目标:实现私聊
* @author dxt
*
*/
public class Chat {
//添加容器 CopyOnWriteArrayList容器是为了解决并发问题
private static CopyOnWriteArrayList<Channel> all = new CopyOnWriteArrayList<Channel>();
public static void main(String[] args) throws IOException{
//1.使用ServerSocket建立服务器端
ServerSocket server = new ServerSocket(8888); //指定端口
//2.while(): 不断接收客户端
while(true){
Socket client = server.accept();
System.out.println("一个客户端建立了连接");
//为连入的客户建立线程,不断的接受发送信息
Channel c = new Channel(client);
all.add(c);
new Thread(c).start();
//做个统计
System.out.println(c.name + "加入了聊天室");
System.out.println("共有 "+ all.size() + " 人在线。");
}
}
/*
* 一个客户代表一个Channel
*/
static class Channel implements Runnable{
private DataOutputStream dos;
private DataInputStream dis;
private Socket client;
private boolean isRunning;
private String name;
public Channel(){
}
public Channel(Socket client){
this.client = client;
try {
dis = new DataInputStream(client.getInputStream());
dos = new DataOutputStream(client.getOutputStream());
isRunning = true;
name = receive();
} catch (IOException e) {
System.out.println("Client");
release(); //直接释放
}
}
//接收消息
private String receive(){
String msg = "";
try {
msg = dis.readUTF();
} catch (IOException e) {
System.out.println("receive");
release(); //直接释放
}
return msg;
}
//发送消息给自己
private void send(String msg){
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
System.out.println("send");
release(); //直接释放
}
}
/**
* 群聊:发给其他所有人
* 私聊:约定数据格式:@xxx:msg
* @param msg
*/
private void sendOthers(String msg){
boolean isPrivate = msg.startsWith("@"); //判断是否是私聊
if(!isPrivate){ //群发模式
for(Channel other : all){
if(other == this){
continue;
}
other.send(this.name+ ": " + msg); //发给其他人
}
}else{ //私聊模式
//获取目标和数据
int index = msg.indexOf(":");
String targetName = msg.substring(1, index); //截取姓名
msg = msg.substring(index+1, msg.length()); //获取信息
//遍历客户端
for(Channel other : all){
if(other.name.equals(targetName)){
other.send(this.name + ":" + msg);
}
}
}
}
//释放资源
private void release(){
this.isRunning = false;
Utils.close(dos, dis, client); //释放资源
//退出
all.remove(this);
System.out.println(this.name + "离开了群聊");
}
/**
* 实现run方法
* 在一个线程中要做什么?
* 接收消息->发送消息
*/
public void run(){
while(isRunning){
//接收信息
String msg = receive();
//如果信息不为空,则发送信息
if(!msg.equals("")){
sendOthers(msg); //群聊模式
}
}
}
}
}