测试驱动开发
1,开发服务端
(1),创建一个服务端程序,开启监听端口
(2),开始不断等待客户端的请求,一旦来一个客户端请求,就开启一个线程为其服务
(3),服务端线程-run
------接收客户端注册的用户名,并且回馈“欢迎×××进入聊天室”
------改进代码更简洁
------后续客户端给我发什么,我就给他回什么
------客户端A给我发什么,我就给其他的客户端(B,C)发送“A说:××××”
------新增一个集合来存储我们客户端的信息(记录哪些客户端连接着服务端)
2,开发客户端
(1),创建一个跟服务端的连接
------测试目标:测服务端是否可以处理多个客户端请求
(2),向服务端注册用户信息
------“请输入用户名:”
------方式一:服务端收到客户端连接请求后,将这个信息发给客户端
------方式二:这个信息在客户端实现
(3),成功接收服务端的消息
(4),引入多线程来改进,一个负责读,一个负责写
------ReadThread,WriteThread 划分好工作范围
------把通用对象的创建移到构造方法里面,run就更加可以专注自己的业务
3,私聊 @姓名#内容
分析:
1.用tcp编程:客户端和服务器端
2.服务器来了个客户就开启一个线程与客户聊天,将多个客户的线程存在集合中
3.客户端先发用户名,再才能发消息聊天
4.一个客户端向服务器发送消息,服务要将这个消息发给其他所有客户
5.客户要用两个线程,一个读的线程,一个写的线程
6.整个项目功能:群聊和私聊
群聊:客户端->发消息->服务器将客户端的消息转发->给所有其他客户端,线程集合
私聊:客户端->发消息->服务器将客户端的消息转发->指定私聊的用户
package com.qf.day35.zy1;
import java.io.IOException;
import java.net.Socket;
/**
* @Author LXM
* @Date 2020/3/27 0027
*/
public class ChatClient {
public static void main(String[] args) throws IOException {
//创建客户端socket,第一个参数服务端ip,第二个参数服务端的端口号
Socket socket1=new Socket("127.0.0.1",9999);
//创建写入线程对象和读取的线程对象与服务器间进行通信
WriteThread wt=new WriteThread(socket1);
ReadThread rt=new ReadThread(socket1);
//启动线程
wt.start();
rt.start();
}
}
package com.qf.day35.zy1;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;
/**
* @Author LXM
* @Date 2020/3/27 0027
*/
public class ReadThread extends Thread {
/**
*声明一个读取的网络字符输入流
*/
BufferedReader r;
/**
*通过构造方法将用户socket对象伟过来,用socket创建网络读取流,来读取服务器发送消息
*/
public ReadThread(Socket socket1) {
try {
r=new BufferedReader(new InputStreamReader(socket1.getInputStream()));
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void run() {
try {
while (true){
//读取服务器发送的消息
String receiveMess=r.readLine();
//将接收消息输出
System.out.println(receiveMess);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
package com.qf.day35.zy1;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Scanner;
/**
* @Author LXM
* @Date 2020/3/27 0027
*/
public class WriteThread extends Thread {
/**
*声明一个网络字符输出流,向服务器发消息
*/
BufferedWriter w;
/**
*通过构造方法将当前用户socket对象传过来,用socket对象创建网络字符输入流,向服务器发消息
*@param socket1
*/
public WriteThread(Socket socket1) {
try {
w=new BufferedWriter(new OutputStreamWriter(socket1.getOutputStream()));
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void run() {
Scanner input=new Scanner(System.in);
try {
//1.第一次要将客户端用户名发送服务器
System.out.println("请输入用户名:");
String cname=input.nextLine();
//将用户名发送给服务器
w.write(cname);
//换行
w.newLine();
//刷新
w.flush();
//2.循环向服务器发消息
while (true) {
//从控制台接收要发送的消息
String sendMess = input.nextLine();
//将消息写入网络中,通过socket发给服务器
w.write(sendMess);
//换行
w.newLine();
//刷新
w.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.qf.day35.zy1;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/**
* @Author LXM
* @Date 2020/3/27 0027
*/
public class ChatServer {
/**
*声明一个集合存所有客户端对应线程对象
*/
public static List<ServerThread> clientThreads=new ArrayList();
public static void main(String[] args) throws IOException {
//创建服务端socket
ServerSocket ss=new ServerSocket(9999);
System.out.println("客户端与服务器正在连接中...");
while (true){
//监听客户端,并接收客户端socket
Socket socket1=ss.accept();
System.out.println("来了一个客户");
//来了一个客户端,开启一个服务端线程,专门用来与当前客户端聊天
ServerThread st=new ServerThread(socket1);
//将当前客户对应线程对象存在集合中
clientThreads.add(st);
//启动线程
st.start();
}
}
}
package com.qf.day35.zy1;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.SocketException;
/**
* @Author LXM
* @Date 2020/3/27 0027
*/
public class ServerThread extends Thread {
/**
*声明读客户端消息的网络字符输入流
*/
BufferedReader r;
/**
*声明写客户端消息的网络字符输出流
*/
BufferedWriter w;
/**
*通过构造方法将当前服务端线程对应客户端的socket传过来,用socket创建网络读写流与对应客户端聊天
*@param socket1
*/
public ServerThread(Socket socket1) {
try {
r=new BufferedReader(new InputStreamReader(socket1.getInputStream()));
w=new BufferedWriter(new OutputStreamWriter(socket1.getOutputStream()));
}catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
/*1.欢迎信息*/
//接收客户端的用户名
String cname=r.readLine();
//准备欢迎信息
String welcomeMess="欢迎"+cname+"进入聊天室";
//将客户端姓名存在当前客户端对应服务器的线程中,作为服务端线程名称
Thread.currentThread().setName(cname);
//遍历所有客户端对应线程集合,将当前用户的欢迎信息转发给所有用户
for(ServerThread t1:ChatServer.clientThreads){
//将消息写入当前t1线程对应客户端
t1.w.write(welcomeMess);
//换行
t1.w.newLine();
//刷新
t1.w.flush();
}
//群聊,循环聊天
while (true){
/*1.接收当前客户端发的消息*/
String recieveMess=r.readLine();
//判断客户是群聊还是私聊
if (recieveMess.startsWith("@")){//私聊,@姓名#内容
//将私聊的内容进行拆分
String[] s=recieveMess.substring(1).split("#");
//获得拆分后私聊对象姓名
String receiveName=s[0];
//获得私聊的内容
String receiveWord=s[1];
//处理私聊内容
String word2=Thread.currentThread().getName()+"悄悄对你说:"+receiveWord;
//遍历线程集合,找到私聊的用户,将私聊的内容转发给指定用户
for (ServerThread t1:ChatServer.clientThreads){
if (t1.getName().equals(receiveName)){
//将消息写入当前t1线程对应客户端
t1.w.write(word2);
//换行
t1.w.newLine();
//刷新
t1.w.flush();
break;
}
}
}else{//群聊
//将当前客户端的消息进行处理
String word=Thread.currentThread().getName()+"说:"+recieveMess;
/*2.再将当前客户端发的消息转发给其他所有客户端*/
//遍历所有客户端对应线程集合来将消息转发给每个客户
for (ServerThread t1:ChatServer.clientThreads){
if (!t1.getName().equals(Thread.currentThread().getName())){
//将消息写入当前t1线程对应客户端
t1.w.write(word);
//换行
t1.w.newLine();
//刷新
t1.w.flush();
}
}
}
}
} catch (SocketException e1){//有用户退出了就会抛出这个异常
//1.获得退出的用户信息
String exitMess=Thread.currentThread().getName()+"退出了聊天室!";
/*2.将退出的用户所对应服务器端的线程从线程集合中删除*/
//遍历当前线程集合,找到当前要退出线程对象,然后再删除
for (int i=0;i<ChatServer.clientThreads.size();i++){
if (ChatServer.clientThreads.get(i).getName().equals(Thread.currentThread().getName())){
//根据找到要删除的线程对象的索引,将当前线程对象从集合中删除
ChatServer.clientThreads.remove(i);
break;
}
}
/*3.将当前用户退出的消息转发给所有其他用户*/
//遍历集合,将线程退出消息发给所有用户
try {
for (ServerThread t1 : ChatServer.clientThreads) {
//将消息写入当前t1线程对应客户端
t1.w.write(exitMess);
//换行
t1.w.newLine();
//刷新
t1.w.flush();
}
}catch (Exception e){
e.printStackTrace();
}
}catch (Exception e) {
e.printStackTrace();
}
}
}