客户端:
package client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TCPClient {
private static Socket socket;
public TCPClient() throws UnknownHostException, IOException {
System.out.println("设置服务器端的地址....");
Scanner scanner = new Scanner(System.in);
String ip = scanner.nextLine();
socket = new Socket(ip,8989);
start();
}
/**
* 启动客户端的套接字
* 同时还是需要线程来代理客户端套接字
* @throws IOException
* @throws UnsupportedEncodingException
*/
private void start() throws UnsupportedEncodingException, IOException{
/**
* 1.连上服务器后需要昵称
* 2.客服端设置昵称,必须更具服务器返回昵称可不可用来设置昵称
* 3.发送聊天的内容,聊天的内容需要线程
*/
Scanner scanner = new Scanner(System.in);
setName(scanner);
//聊天内容需要线程代理,这个线程专门读取群聊或私聊
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new ListServer());
//当看到群里或者私聊消息,然后发消息给对方
PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8"),true);
while(true){
String message= new String(scanner.nextLine().getBytes("gbk"),"utf-8");
pw.println(message);
}
}
private void setName(Scanner scanner){
/**
* 1.输入流:读取服务器返回昵称合法性
* 2.输出流:告诉服务器设置的昵称
* 3.重复检验
*/
String name;
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8"),true);
while(true) {
System.out.println("请输入你的昵称: ");
name= new String(scanner.nextLine().getBytes("gbk"),"utf-8");
if(name.trim().length() == 0) {
System.out.println("昵称不能为空...");
} else {
pw.println(name); // 把昵称发给服务器端, 服务器端getName()来接收你的昵称
String pass = br.readLine(); // 读取服务器端返回昵称是否可以使用, 要么OK,要么FAIL
if(pass != null && (!pass.equals("OK"))) {
System.out.println("昵称已经被占用,请重新输入:");
} else {
System.out.println("昵称 "+ name +" 已设置成功,可以开始聊天了");
break;
}
}
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 专门监听服务器端返回的信息
*
*/
class ListServer implements Runnable{
public void run() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
String message = null;
while((message = br.readLine()) != null) {
System.out.println(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws UnknownHostException, IOException {
new TCPClient();
}
}
服务器端:
package chat;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import jdk.internal.util.xml.impl.Input;
/**
* 网络聊天的服务器套接字
* 1、对客户端监控
* 2.同时暗金恐到客户端需要用线程代理
* 3.信息发转发
* @author Administrator
*
*/
public class TCPServer {
private ServerSocket serverSocket ;//创建服务器端的套接字对象
private ExecutorService executorService;//线程池,代理监控到的客户端套接字对象
private Map<String ,PrintWriter> storeInfo;//存储客户端的信息,出处客户端的名称+IO流
public TCPServer(){
try {
serverSocket = new ServerSocket(8989);
storeInfo = new HashMap<String, PrintWriter>();
executorService = Executors.newCachedThreadPool();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 启动服务器端的套接字
*/
public void start(){
try {
while(true){
System.out.println("等待客户端的连接....");
//监听客户端的套接字
Socket socket = serverSocket.accept();
//获取客户单的IP地址
InetAddress address = socket.getInetAddress();
System.out.println("客户端:"+address.getHostAddress()+"连接成功....");
//线程来代理客户端的套接字的对象
executorService.execute(new listenerClient(socket));//线程代理后启动线程中run方法
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 存储客户端的信息
* @param key
* @param printWriter
*/
private synchronized void putIn(String key,PrintWriter value){
storeInfo.put(key, value);
}
/**
* 群聊:发送消息给所有的客户端
* 服务器遍历所有客户端的流
* 然后发送消息
*/
private synchronized void sendTOAll(String message){
for(PrintWriter pw:storeInfo.values()){
pw.println(message);
}
}
/**
* 私聊:发送消息给指定的客户端
* @param key
* @param message
*/
private synchronized void sendSomene(String name,String message){
PrintWriter pw = storeInfo.get(name);
if(pw != null){
pw.println(message);
}
}
/**
* 删除聊天消息
* 移除客户端信息
* @param name
*/
private synchronized void remove(String name){
storeInfo.remove(name);
System.out.println("当前在线人数:" +storeInfo.size());
}
/**
* 用线程代理客户端的套接字对象
* @author Administrator
*
*/
class listenerClient implements Runnable{
private Socket socket; // 客户端套接字的对象
private String name; // 客户端的昵称
public listenerClient(Socket socket) {
this.socket = socket;
}
/**
* 1. 获取输出流, 发送消息
* 2. 读取客户端的信息, 读取客户端昵称
* 3. 检验是群聊和私聊(客户端如果发送 @吴国友,代表私聊)
* 4. 客户端关闭, 程序还得把客户端套接字关闭
*/
public void run() {
try {
/**
* 一定要注意把客户端套接字的流保存storeInfo里面
* 必须收集(存储)连接上服务端的所有套接字对象
* PrintWriter是一个字符, 而我们套接字只能给出字节流
* 由getName()方法来获取客户端传递过来昵称, 因为服务器端是没有能力给客户端取昵称的
*/
PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true);
name = getName();
putIn(name, pw);
Thread.sleep(100);
sendTOAll("[系统通知]" + name + " 已上线.....");
// 不管是群聊和私聊都得获取输入流
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
// 现在才去读取消息,判断是群聊还是私聊
String message = null;
while((message = br.readLine()) != null) {
// 检验私聊关系,需要字符串的截取
if(message.startsWith("@")) {
int index = message.indexOf(":");
if(index > 0) {
// @对方的名称
String theName = message.substring(1, index);
// @对方的内容
String info = message.substring(index + 1);
// 组合一下消息内容, 让对方看到我的名字和我发送内容,因为我是在私聊
info = name + ":" + info;
// 调用私聊的方法
sendSomene(theName, info);
continue;
}
}
// 群聊
sendTOAll(name +":"+ message);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
remove(name);
sendTOAll("[系统通知]" + name + " 已经下线...");
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 获取客户端昵称的方法
* 1. 客户端连接上服务器
* 2. 提示客户端输入一个昵称
* 3. 昵称一旦输完, 则马上把昵称发送给服务器端
* 4. 服务器拿到昵称, 判断是否有重复昵称,并告知客户端
* @return
*/
private String getName() {
/**
* 要获取客户端的昵称
* 1. 我们要准备两个流
* 2. PrintWriter
* 3. BufferedReader
*/
try {
// 服务器发送消息告诉客户端是否你输入的昵称有重复
PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true);
// 读取客户端输入的昵称
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
// 连续你来我往的一个通信
// 昵称有问题,请客户端重新写一个
// 然后服务器端又重新读取
// 又判断是否重复
while(true) {
String name = br.readLine();
if(name.trim().length() == 0 || storeInfo.containsKey(name)) { // 昵称不可使用或者重复或者你客户端根本就没有输入
pw.println("FAIL");
} else { // 代表昵称可以使用
pw.println("OK");
return name;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
public static void main(String[] args) {
TCPServer server = new TCPServer();
server.start();
}
}