功能
1.登录和注册功能,客户端保存用户信息
2.具有私聊功能
3.具有一个聊天室功能
4.在服务端保存登录用户信息,总登录人数,私聊信息,群聊信息
项目整体思路
客户端有一个主线程与一个子线程。主线程用来向服务端传递数据,子线程用来接收服务端传来的数据。
服务端有一个主线程与两个子线程。一个子线程专门处理群聊功能,一个子线程专门处理私聊功能。主线程通过接收客户端传来的数据,为客户端提供相应的服务端。
具体目录如下
所用到的一些模块知识
IO流 数据流/序列化流 文件操作 等
多线程 多线程代码的调试 等
网络通信 此处所用TCP通信 Socket类 等
ip地址、端口的认识
客户端
主线程
public class Client {
private boolean IsRoom;
private String userName;
private String userPassWord;
public String getUserPassWord() {
return userPassWord;
}
public void setUserPassWord(String userPassWord) {
this.userPassWord = userPassWord;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public boolean getIsRoom() {
return IsRoom;
}
public void setIsRoom(boolean room) {
IsRoom = room;
}
public static void main(String[] args) {
Client client = new Client();
Scanner scanner = new Scanner(System.in);
try(
Writer Infowriter = new FileWriter("Room4\\src\\ClientAccountInfo.txt",true);
)
{
System.out.println("请输入您的账号名:");
client.setUserName(scanner.nextLine());
Infowriter.write ("账号名:"+ client.getUserName()+" ");
System.out.println("请输入您的密码:");
client.setUserPassWord(scanner.nextLine());
Infowriter.write("密码:"+ client.getUserPassWord()+"\r\n");
// 刷新字符流的缓冲区
Infowriter.flush();
// 注意这里的8888是对应服务端的端口
Socket socket = new Socket("10.36.222.54",8888);
OutputStream socketOs = socket.getOutputStream();
DataOutputStream DataSocketOs = new DataOutputStream(socketOs);
// 将用户姓名传给服务端
DataSocketOs.writeUTF(client.getUserName());
// 将用户信息传给服务端,让服务端保存
DataSocketOs.writeUTF(client.getUserName()+" "+ client.getUserPassWord());
System.out.println("登录成功,请做出您的选择:");
System.out.println("1.进行私聊");
System.out.println("2.加入群聊");
int choice = scanner.nextInt();
scanner.nextLine();
new Thread(new ClientThread(socket)).start();
switch (choice){
case 1:
DataSocketOs.writeBoolean(false);
while (true) {
String targetUserName = scanner.nextLine();
DataSocketOs.writeUTF(targetUserName);
if (targetUserName.equals("exit")){
continue;
}
while (true) {
System.out.println("对Ta说点什么吧:");
String whatISaid = scanner.nextLine();
DataSocketOs.writeUTF(whatISaid);
if (whatISaid.equals("exit")){
break;
}
}
}
case 2:
System.out.println("加入群聊成功!");
DataSocketOs.writeBoolean(true);
while(true){
System.out.println("说点什么吧:");
String whatISaid = scanner.nextLine();
DataSocketOs.writeUTF(whatISaid);
}
}
} catch (Exception e) {
// 在Java中,如果客户端断开与服务端的连接,服务端会报异常。
// 当客户端主动断开连接时,服务端在尝试读取或写入数据时会抛出异常
throw new RuntimeException(e);
}
}
}
子线程
public class ClientThread implements Runnable{
Socket socket;
public ClientThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try(
InputStream is = socket.getInputStream();
DataInputStream DataIs = new DataInputStream(is);
)
{
while (true){
String msg = DataIs.readUTF();
System.out.println(msg);
}
} catch (Exception e) {
System.out.println("Ta挂了");
throw new RuntimeException(e);
}
}
}
客户端注意事项
主线程一定只能用来给服务端发送数据,子线程只能用来接收服务端传来的数据。
自己在写项目时,因为在客户端主线程接收了服务端传来的数据,导致原本的子线程无法接收数据,进而导致项目逻辑出错。
教训:客户端一定有且仅有一条用来接受数据的管道,万不可多开。
服务端
主线程
public class Servant {
public static List<Boolean> isRoom = new ArrayList<>();
public static Map<String,Socket> OnlineUsers = new HashMap<>();
public static List<Socket> RoomSockets = new ArrayList<>();
public static void main(String[] args) {
try(
Writer ServantUsersOs = new FileWriter("Room4\\src\\ServantUsers.txt",true);
)
{
System.out.println("服务器启动成功");
ServerSocket serverSocket = new ServerSocket(8888);
int count = 0;
while (true){
count++;
Socket socket = serverSocket.accept();
InputStream socketIs = socket.getInputStream();
DataInputStream DataSocketIS = new DataInputStream(socketIs);
String userName= DataSocketIS.readUTF();
// 接收用户账号信息,并输出到文件中
String OneUserInfo = DataSocketIS.readUTF();
ServantUsersOs.write(OneUserInfo + "\r\n");
ServantUsersOs.flush();
Servant.OnlineUsers.put(userName,socket);
// Servant.sockets.add(socket);
Boolean tmp = DataSocketIS.readBoolean();
Servant.isRoom.add(tmp);
if (Servant.isRoom.get(count - 1)){
Servant.RoomSockets.add(socket);
new Thread(new RoomServantThread(userName,socket,count - 1)).start();
}
else {
new Thread(new PrivateChatThread(socket,userName)).start();
}
String nums = String.valueOf(Servant.OnlineUsers.size());
Writer allwriter = new FileWriter("Room4\\src\\Allpeople.txt");
allwriter.write("当前总登录人数:"+ nums);
allwriter.flush();
allwriter.close();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
主线程记录总登录人数注意事项
Writer allwriter = new FileWriter("Room4\\src\\Allpeople.txt");
allwriter.write("当前总登录人数:"+ nums);
allwriter.flush();
allwriter.close();
文件要实现文件覆盖功能,需要在每次写完文件,并flush后,将文件流关闭。否则缓冲区中的数据将不会被加载进文件中。
服务群聊的子线程
public class RoomServantThread implements Runnable{
public String userName ;
public int Index;
public Socket socket ;
public RoomServantThread(String userName, Socket socket,int Index){
this.userName = userName;
this.socket = socket;
this.Index = Index;
}
@Override
public void run() {
try (
InputStream is = socket.getInputStream();
DataInputStream DataIs1 = new DataInputStream(is);
Writer pubWriter = new FileWriter("Room4\\src\\publicChat.txt",true);
)
{
while (true){
String someOneSaid = DataIs1.readUTF();
for (Socket roomSocket : Servant.RoomSockets) {
OutputStream os = roomSocket.getOutputStream();
DataOutputStream DataOs2 = new DataOutputStream(os);
DataOs2.writeUTF(userName + " say:" +someOneSaid);
pubWriter.write(userName + " say:" + someOneSaid + "\r\n");
pubWriter.flush();
}
}
} catch (Exception e) {
Servant.isRoom.remove(Index);
Servant.RoomSockets.remove(socket);
Servant.OnlineUsers.remove(userName,socket);
}
}
}
服务私聊的子线程
public class PrivateChatThread implements Runnable{
public Socket socket;
public String username;
public PrivateChatThread(Socket socket,String username){
this.socket = socket;
this.username = username;
}
@Override
public void run() {
try (
DataOutputStream DataSocketOs = new
DataOutputStream(socket.getOutputStream());
DataInputStream DataSocketIs = new
DataInputStream(socket.getInputStream());
Writer priWriter = new FileWriter("Room4\\src\\priChat.txt",true);
)
{
while (true) {
String tmp = "请选择您的聊天对象: ";
Set<String> allTheOnlineUsers = Servant.OnlineUsers.keySet();
for (String onlineUser : allTheOnlineUsers) {
tmp += onlineUser +" ";
}
DataSocketOs.writeUTF(tmp);
boolean flag = false;
String name = DataSocketIs.readUTF();
if (name.equals("exit")){
continue;
}
for (String onlineUser : allTheOnlineUsers) {
if (onlineUser.equals(name)){
Socket beTalked = Servant.OnlineUsers.get(onlineUser);
while (true) {
String whatTalkedSaid = DataSocketIs.readUTF();
if (whatTalkedSaid.equals("exit")){
flag = true;
break;
}
DataOutputStream p = new
DataOutputStream(beTalked.getOutputStream());
p.writeUTF(username + " say:" +whatTalkedSaid);
priWriter.write(username+" say:" + whatTalkedSaid
+"\r\n");
priWriter.flush();
}
}
if (flag)
break;
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}