您的位置:首页 > 编程语言 > Java开发

Java使用TCP实现聊天室系统

2020-02-07 06:07 190 查看

目标功能:
1.群聊
2.退出
3.私聊
4.查询其他在线玩家
5.管理员登陆
6.踢人
7.禁言
目前已实现四个功能
如何使用?
1 先启动服务端程序,再启动客户端
2 输入昵称后才能连接服务端
3 输入内容后回车即可发送群聊消息
3 输入 ls 查询在线玩家
4 输入 @要私聊对象的名字:要私聊的信息 即可实现私聊
5 输入 exit 即可退出聊天室

服务器

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class Server {
//用该容器存储与用户建立的连接
static CopyOnWriteArrayList<Channel> all = new CopyOnWriteArrayList<>();
List<String> list = new ArrayList();
//以下两个常量用来区分 系统和用户
private final static boolean system = true;
private final static boolean user = false;

public static void main(String[] args) throws IOException {
//创建服务端
ServerSocket server = new ServerSocket(8888);
System.out.println("服务端建立完毕,端口号:" + server.getLocalPort());
//准备连接
Channel c;
//接受客户端
while (true){
//监听到连接后,为该客户端开辟一个线程
Socket client = server.accept();
c = new Channel(client);
new Thread(c).start();
//加入到容器中
all.add(c);
System.out.println("玩家:"+c.name+ " 已连接上...");
}
}
//通信管道,一个用户对应一个管道(内部类),封装了流和用户信息
static class Channel implements Runnable{
private Socket client;
private DataInputStream dis;
private DataOutputStream dos;
private boolean isRunning;

private String name;

//此构造方法用来初始化流和用户昵称
public Channel(Socket client){
this.client = client;
try {
dis = new DataInputStream(client.getInputStream());
dos = new DataOutputStream(client.getOutputStream());
//默认处于连接状态,用于收发信息
isRunning = true;
//初始化姓名
name = dis.readUTF();
//+1为包括自身
this.send("欢迎进入聊天室,当前在线玩家数: "+(all.size()+1)+"\r\n"+
"指令:ls(查询当前在线用户),@XX:YY(向玩家XX发送消息YY),exit(退出聊天室)");
}catch (IOException e) {
System.out.println("初始化失败");
//释放资源
release();
}
}
//run方法循环转发用户消息
@Override
public void run() {
while (isRunning){
String msg = receive(client);
if(!msg.equals("")){
if (msg.equals("exit")){//断开连接
System.out.println("玩家:"+this.name+" 已断开连接");
sendOthers(name+"退出了群聊",system);//告诉所有人 此人离开了
release();
break;
}else if (msg.startsWith("@")){//私聊
int index = msg.indexOf(":");
if (index!= -1){
String targetName = msg.substring(1,index);
sendTarget(msg,targetName);
}else {
sendTarget(msg,"error");
}
}else if (msg.equals("ls")){//查询在线玩家
StringBuilder sb = searchOnline();
if (!sb.equals("]")) {
this.send("当前在线的玩家:"+sb.toString());
}
}
else {
sendOthers(name+"说:"+msg,user);//向所有人广播此消息,除了他自己
}
}
}
}

//接受信息
public String receive(Socket client) {
String msg = "";
try {
//阻塞,只有用户发送了信息,服务器才会接受并广播给其他用户
msg = dis.readUTF();
} catch (IOException e) {
System.out.println("玩家:"+this.name+" 异常中断");
//非输入exit退出,同样需要广播此用户的退出
sendOthers(name+"退出了群聊",system);//告诉所有人 此人离开了
//客户端强制退出时,释放资源
release();
}
return msg;
}

//广播的最底层操作是调用Channel中的send方法
public void send(String msg){
try {
dos.writeUTF(msg);
dos.flush();
}catch (IOException e){
System.out.println("信息传出异常,连接已断开");
release();
}
}

//广播,调用时确认是系统还是用户
public void sendOthers(String msg,boolean who){
if (msg!=""){
for (Channel c:all){
//任何消息不回反馈给发送者本身
if (c!=this) {
if (who == system) {
c.send("***系统消息: " + msg + "***");
} else {//用户
c.send(msg);
}
}
}
}
}

//私聊
public void sendTarget(String msg,String name){
boolean flag = true;
for (Channel c:all){
if (c.name.equals(name)){
c.send(this.name+"悄悄地对你说:"+msg.substring(msg.indexOf(":")+1));
flag = false;
break;
}
}
if (flag){
System.out.println(flag);
if (name.equals("error")){
this.send("指令错误,请输入英文状态下的冒号");
}else {
this.send("***找不到该用户***");
}
}
}

//查询其他在线的玩家
public StringBuilder searchOnline(){
StringBuilder sb = new StringBuilder("[");
for (Channel c:all){
sb.append(c.name+",");
}
sb.setCharAt(sb.length()-1,']');
return sb;
}

//释放资源时从容器中剔除自身的引用(此Channel因各种原因断开),并关闭转发器(停止run方法中的while循环)
private void release(){
this.isRunning = false;
all.remove(this);
System.out.println("剩余玩家"+all.size());
TianUtils.close(dis,dos,client);
}
}
}

客户端

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class Client {
public static void main(String[] args) throws IOException {
//输入昵称后,再向服务器建立连接
System.out.print("请输入昵称:");
String name = new Scanner(System.in).nextLine();
//创建客户端
Socket client = new Socket("localhost",8888);
/**
* 用户应该能同时收发消息,所以需要用到多线程
* Sender为发送者线程
* Receiver为接收者线程
*/
new Thread(new Sender(client,name)).start();
new Thread(new Receiver(client)).start();
}
}

客户端子线程:发送者

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

public class Sender implements Runnable{
private Socket client;
private DataOutputStream dos;
private BufferedReader reader;

//初始化流,并向服务器传递用户昵称,服务器在初始化链接(Channel)时接收昵称
public Sender(Socket client,String name){
this.client = client;
try {
dos = new DataOutputStream(client.getOutputStream());
reader = new BufferedReader(new InputStreamReader(System.in));
dos.writeUTF(name);
} catch (IOException e) {
System.out.println("初始化流失败");
release();
}
}

@Override
public void run() {
while (true){
try {
String msg = reader.readLine();
if (msg.equals("exit")){
dos.writeUTF(msg);//输入exit主动断开连接
dos.flush();
break;
}
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
System.out.println("发送异常");
release();
}
}
}

//释放资源
private void release(){
TianUtils.close(client,dos,reader);
}
}

客户端子线程:接收者

import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;

public class Receiver implements Runnable{
private Socket client;
private DataInputStream dis = null;

//初始化流,用来接收服务器广播的消息
public Receiver(Socket client){
this.client = client;
try {
dis = new DataInputStream(client.getInputStream());
} catch (IOException e) {
System.out.println("初始化失败");
release();
}
}
@Override
public void run() {
while (true){
try {
//当输入exit时,此操作异常,run方法终止
System.out.println(dis.readUTF());
} catch (IOException e) {
System.out.println("与服务器断开连接");
break;
}
}
release();
}

//释放资源
private void release(){
TianUtils.close(client,dis);
}
}
  • 点赞
  • 收藏
  • 分享
  • 文章举报
theTian.cn 发布了16 篇原创文章 · 获赞 10 · 访问量 2332 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: