聊天的大致流程图
一、创建服务端
//聊天程序,需要用上线程安全
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;//HashMap查找快
import java.util.concurrent.ConcurrentLinkedDeque;
public class ChatServer {
//1.在类中添加Socket集合和消息队列
/**
* 服务器端口常量
*/
private static final int port = 9999;
/**
* 监听
*/
public void start(){
new Thread ( new SendService () ).start ();
//启动发送线程
ServerSocket serverSocket = null;
Socket client = null;
try {
//申请端口
serverSocket = new ServerSocket ( port );
//监听
while (true) {
//监听客户端连接
System.out.println ("开始监听新的客户端连接...");
client = serverSocket.accept ();
System.out.println ("监听客户端【"+ client.getInetAddress ().getHostAddress ()+":"+
client.getPort ()+"】");
//提供消息服务线程
new ReceiveService ( client ).start ();
//把socket放进客户socket集合,以便发送线程使用
String key = client.getInetAddress ().getHostAddress ()+":"+client.getPort ();
allCustomer.put (key,client );
//监听下一个
}
} catch (IOException e) {
e.printStackTrace ();
}
}
/**
* 所有客户端连接集合
*/
private ConcurrentHashMap<String,Socket> allCustomer = new ConcurrentHashMap <> ( );
/**
* 存放消息的队列
*/
private ConcurrentLinkedDeque<String> messageQueue = new ConcurrentLinkedDeque <> ( );
/**
* 创建发送线程
*/
private class SendService implements Runnable{
@Override
public void run () {
try {
PrintWriter pw = null;
while (true){
//取消息队列中的消息
String mesg = messageQueue.poll ();
synchronized (messageQueue) {
if (mesg != null){
//遍历客户端连接
for (Socket socket : allCustomer.values ()) {
//创建字符输出流半配网络字节流
pw = new PrintWriter ( socket.getOutputStream () );
//向客户端发送消息
pw.println ( mesg );
pw.flush ();
}
}else {
//休息
messageQueue.wait ();
}
}
//到队列里面取下一条消息
}
} catch (Exception e) {
e.printStackTrace ();
}
}
}
//2.创建接受线程,离开ChatServer类没有利用价值,所以写成内部类
private class ReceiveService extends Thread{
// /**
// * 持有消息队列的引用
// * 内部类因为可以直接访问外部类,所以没必要在创建引用
// */
/**
* 3.接受每个线程的客户端服务
*/
//客户端的套接字
private Socket client = null;
public ReceiveService(Socket client){
this.client = client;
}
public void run(){
//因为接收字符所以选择字符流,并且Buffer字符流的readLine()接收
BufferedReader br = null;
try {
//注意socket只能得到字节流,所以把它包装成字符流得用InputStreamReader再包装一下
br = new BufferedReader (
new InputStreamReader ( client.getInputStream () ) );
while (true) {
//接收消息
System.out.println ("等待接收客户端【"+ client.getInetAddress ().getHostAddress ()+"消息");
String mesg = br.readLine ();
System.out.println ("接收客户端【"+ client.getInetAddress ().getHostAddress ()+"消息【"+mesg+"】");
//放入消息队列
synchronized (messageQueue) { //同步监视器
messageQueue.offer ( mesg );
messageQueue.notify ();
}
//放入下一条
}
} catch (IOException e) {
e.printStackTrace ();
}
}
}
}
二、创建客户端
import util.JSONUtil;
import vo.MessageVo;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Date;
import java.util.Scanner;
public class ChatClient {
/**
* 聊天服务器的端口
*/
private String adr = "127.0.0.1";
Socket s = null;
/**
* 聊天服务的端口
*/
private int port = 9999;
public void start(){
try {
//客户知道服务器的地址和端口
while (true) {
s = new Socket (adr,port);
//启动两个地址和端口,直接创建套接字
new ReceiveService ().start ();
new SendService ().start ();
}
} catch (Exception e) {
e.printStackTrace ();
}
}
/**
* 创建键盘监听
*/
public class SendService extends Thread{
PrintWriter pw = null;
public void run(){
while (true) {
Scanner scanner = new Scanner ( System.in );
String mesg = scanner.nextLine ();
MessageVo vo = new MessageVo (mesg,new Date() );
String jsonStr = JSONUtil.obj2json ( vo );
try {
pw = new PrintWriter ( s.getOutputStream ());
} catch (IOException e) {
e.printStackTrace ();
}
pw.println ( mesg );
pw.flush ();
}
}
}
/**
* 创建监听服务消息线程
*/
private class ReceiveService extends Thread{
private BufferedReader br = null;
public void run(){
try {
while (true) {
br = new BufferedReader (
new InputStreamReader ( s.getInputStream() ) );
//监听服务器 发送来的json字符串
String jsonStr = br.readLine ();
// json串转换成对象
MessageVo mvo = JSONUtil.jsonobj ( jsonStr,MessageVo.class );
System.out.println (mvo.getMesg ()+mvo.getDate ());
}
} catch (Exception e) {
e.printStackTrace ();
}
}
}
}
三、创建消息类
import java.util.Date;
public class MessageVo {
private String mesg;
private Date date;
public MessageVo (String mesg, Date date) {
this.mesg = mesg;
this.date = date;
}
public void MessageVo(){
}
public String getMesg () {
return mesg;
}
public void setMesg (String mesg) {
this.mesg = mesg;
}
public Date getDate () {
return date;
}
public void setDate (Date date) {
this.date = date;
}
}
四、创建JsonUtil类
注意:该类的创建实际上是按照net.sf.json.JSONObject包(规范)创建的类
import net.sf.json.JSONObject;
public class JSONUtil {
/**
* 对象转json的方法
* @return
*/
public static String obj2json(Object obj){
JSONObject object = JSONObject.fromObject ( obj );
return object.toString ();
}
/**
* 把json串转成对象的方法
*/
public static <T> T jsonobj(String jsonStr,Class<T> t){
JSONObject object = JSONObject.fromObject ( jsonStr );
return (T) JSONObject.toBean ( object,t );
}
}
五、开启服务端
public class ServerStart {
public static void main (String[] args) {
new ChatServer ().start();
}
}
六、开启客户端
public class ClientStart {
public static void main (String[] args) {
new ChatClient ().start ();
}
}
五、运行结果