版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_40301026/article/details/88728215
首先实现的功能:
1.群聊。一个服务器端可以承载多个客户端(用户)来请求访问。服务器端对其请求做出处理,并转发给其他的客户端(用户)。
2.私聊。因为是控制台输入输出,所以私聊格式为:@xxx: 。而且自己给私发服务器端不响应。
思路:1.用ServerSocket模拟服务器端,并且开启多线程的调用accept()等待客户端连接。
2.Socket模拟客户端。使用多线程达到一边发一边收。
3.数据的传输用到的IO流DataInputStream(client.getInputStream())和DataOutputStream(client.getOutputStream())
4.TCP底层原理不再阐述。不懂:https://blog.csdn.net/qq_40301026/article/details/88623353
5.群聊功能:每来一个客户端,将其姓名作为key,客户端的对象地址作为value,,存在ConcurrentHashMap(线程安全)容器里面。当一个客户端发送消息时,服务器端作为中转站,从容器中拿到其他客户端,转发此消息。
6.私聊:对接收到的客户端消息进行检查,如果符合私聊格式就从容器中拿到私聊对象,转发给对方。
先来看看实现的效果????
*启动一个服务器端(IP地址本机,端口号:8848)和三个客户端。
*三个客户端加入聊天室
* 群聊
*私聊:
*离开:
代码:
服务器端:
package cn.liu.chat03;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.concurrent.ConcurrentHashMap;
/**
* 服务器端:利用多线程和多个客户端能收发多条消息
* 再进行封装
* 实现群聊功能
* @author Administrator
*
*/
public class Server {
private ConcurrentHashMap<String,Work> holder;//用来存储客户端
private ServerSocket server;//服务器端
private int serverPort;//服务器的端口号
//启动一个服务端
public Server(int serverPort) {
super();
this.serverPort = serverPort;
this.holder = new ConcurrentHashMap<>();//创建容器
try {
this.server = new ServerSocket(serverPort);//创建一个服务器端
} catch (IOException e) {
e.printStackTrace();
}
}
//服务端响应,工作。
public void run() {
System.out.println("----------ChatServer----------");
//响应多个客户端
while(true) {
new Thread(new Work(server,holder)).start();
}
}
}
package cn.liu.chat03;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;
/**
* //将服务器响应一个客户端的功能进行封装
*
* @author Administrator
*
*/
public class Work implements Runnable{
private Socket client;//拿到客户端套接字
private ServerSocket server;//服务器端
private DataInputStream is;//IO流通道
private DataOutputStream os;//IO流通道
private boolean flg;//控制多次收发
private ConcurrentHashMap<String,Work> holder;//用来存储客户端
private boolean sign ;//true是客户端消息,false是服务器消息
//初始化,1.等待连接。2.并且将输入输出流通道搭建好
public Work(ServerSocket server,ConcurrentHashMap<String,Work> holder) {
super();
try {
this.client = server.accept();
//将输入输出流通道搭建好
this.is = new DataInputStream(client.getInputStream());
this.os = new DataOutputStream(client.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
this.holder = holder;
this.flg = true;
this.sign = true;
}
//执行的线程体
@Override
public void run() {
System.out.println("一个客户端已建立连接。");
String name = judge();//得到此客户端名字
judgeFirst(name);
//当关闭此客户端,程序跳出judgeFirst()中while循环,服务器给其他客户端发送离开消息
responseOther(name+":离开聊天室!");
release();//释放相关资源
}
//判断客户端和其他客户端是不是重名,不重名则返回,并加入容器, 重名则重新接收。
private String judge() {
response("欢迎到来!");
response("起一个好听的昵称吧!");
while(true){
String name = receive();
if(!holder.containsKey(name)) {
holder.put(name,this);//加入此客户端存起来
return name;
}else {
String unname = "姓名重名了!请重新起一个!";
response(unname);
}
}
}
//判断客户端是不是第一次发送消息,是则发送欢迎,不是则进行聊天功能(私聊OR群聊)。
private void judgeFirst(String name) {
while(flg) {
if(sign) {//第一次给其他客户端转发此消息
responseOther("欢迎"+name+"加入群聊!");
sign = false;
}else {
String str = this.receive();
if(str.equals(""))//关闭客户端,receive()收到"",则退出聊天室
{
break;
}else {
survey(name,str);
}
}
}
}
//群聊
private void groupChat(String name,String message) {
String datas = name+":"+message;
responseOther(datas);
}
//私聊,系统默认识别:“@xxx:”为私聊,之后的话为私发内容
private void privateChat(String name,String str) {
//对数据进行分离
int index = 0;
while(str.charAt(index)!=':') {
index++;
}
String nameOther = str.substring(1,index);//得到名字
str = name+str.substring(index);//处理信息
//向此人转发信息
for(ConcurrentHashMap.Entry<String,Work> entry: holder.entrySet()) {
if(!entry.getValue().equals(this)) {//排除和自己私聊
if(entry.getKey().equals(nameOther)) {//找到此客户端
entry.getValue().response(str);//服务器转发此消息
}
}
}
}
//检测是群聊还是私聊,检测后并调用相应功能
private void survey(String name,String messager) {//有@即视为想私聊
if(messager.charAt(0)=='@') {
privateChat(name,messager);
}else {
groupChat(name,messager);
}
}
//拿到客户数据
private String receive() {
//拿到客户端数据
String str = "";
try {
str = is.readUTF();
} catch (IOException e) {
release();
}
return str;
}
//给客户端做出响应
private void response(String str) {//m表示是不是系统消息
try {
os.writeUTF(str);
os.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//给本线程外的其他客户端做出响应
private void responseOther(String str) {
for(Work entry: holder.values()) {
if(!entry.equals(this)) {//给其他客户端转发此消息
entry.response(str);
}
}
}
//释放相关资源
private void release() {
this.flg = false;
ChatUtils.close(is,os,client);
//一个客户端退出
removeClient();
}
//一个客户端退出
private void removeClient() {
String name ="";
for(ConcurrentHashMap.Entry<String,Work> entry: holder.entrySet()) {
if(entry.getValue().equals(this)) {
name = entry.getKey();
break;
}
}
holder.remove(name);
}
}
客户端:
package cn.liu.chat03;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* 对客户端再进行二次封装
* Send类实现发数据
* Receive类拿到数据
* @author Administrator
*
*/
public class Client {
private Socket client;//客户端
private String ip;//服务器ip地址
private int serverPort;//服务器端口
public Client(String ip, int serverPort) {
this.ip = ip;
this.serverPort = serverPort;
try {
this.client = new Socket(ip,serverPort);//创建一个客户端
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//和服务器通信
public void run() {
System.out.println("--------------ChatClient-------------");
new Thread(new Send(client)).start();
new Thread(new Receive(client)).start();
}
}
package cn.liu.chat03;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
/**
* 1.收到服务器的响应
* 2.释放资源
* @author Administrator
*
*/
public class Receive implements Runnable{
private DataInputStream is;
private Socket client;
private boolean flg;
public Receive(Socket client) {
super();
this.client = client;
try {
this.is = new DataInputStream(client.getInputStream());
} catch (IOException e) {
e.printStackTrace();
}
this.flg = true;
}
@Override
public void run() {
while(flg) {
receiveData();
}
release();
}
private void receiveData(){
try {
String str = is.readUTF();
System.out.println(str);
} catch (IOException e) {
e.printStackTrace();
}
}
private void release() {
this.flg = false;
ChatUtils.close(is,client);
}
}
package cn.liu.chat03;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;
/**
* 1.从控制台接收消息
* 2.把消息发给服务器端
* 3.释放资源
* @author Administrator
*
*/
public class Send implements Runnable{
private DataOutputStream os;
private Socket client;
private boolean flg;
public Send(Socket client) {
super();
this.client = client;
try {
this.os = new DataOutputStream(client.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
this.flg = true;
}
@Override
public void run() {
while(flg) {
//从控制台获取消息,发送数据到服务器端
sendData(input());
}
release();
}
//从控制台获取消息
private String input() {
Scanner ss = new Scanner(System.in);
String str = ss.nextLine();
return str;
}
//发送数据到服务器端
private void sendData(String str) {
try {
os.writeUTF(str);
os.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
//释放资源
private void release() {
this.flg = false;
ChatUtils.close(os,client);
}
}
工具类:
package cn.liu.chat03;
import java.io.Closeable;
import java.io.IOException;
/**
* 工具类
* @author Administrator
*
*/
public class ChatUtils {
/**
* 释放资源
*/
public static void close(Closeable... resource) {
for(Closeable a:resource) {
try {
if(null!=a)
a.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
测试类:
package cn.liu.chat03;
public class ClientTest {
public static void main(String[] args) {
Client ss= new Client("Dick",8848);
ss.run();
}
}
package cn.liu.chat03;
public class TestServer {
public static void main(String[] args) {
Server ss = new Server(8848);
ss.run();
}
}