聊天室实现是通过C/S架构实现,既要有服务器端,也要有客户端。
实现原理:
(1)服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。
(2)服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。
(3)服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。
(4)Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。
(5)在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。
(6)连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。
客户端和服务器端要实现的类和方法:
C:客户端
Socket类:
public Socket(String host,int port):绑定指定域名,端口号的服务器。
获取客户端输入流,读取服务器端发来的消息:
public InputStream getInputStream();
获取客户端输出流,向服务器端发送消息:
public OutputStream getOutputStream();
S:服务器端
ServerSocket类:
建立起基站
public ServerSocket():无参构造,默认绑定本地IP。
public ServerSocket(int port):默认绑定本地IP127.0.0.1以及制定端口号。
等待连接:
public Socket accept():等待客户端连接,线程阻塞,当有客户端连接时,返回当有客户端socket。
当服务器与客户端建立起连接后,通过输入输出流(客户端的socket)来通信。
Socket提供的方法:
获取客户端输入流,读取客户端发来的消息:
public InputStream getInputStream();
获取客户端输出流,向客户端发送消息:
public OutputStream getOutputStream();
一.单线程版本聊天室:
服务器端:
public class SimpleServer {
public static void main(String[] args) {
try {
//建立服务端ServerSocket并绑定本地端口号6666
ServerSocket serverSocket=new ServerSocket(6666);
//等待客户端连接
System.out.println("等待客户端连接中");
//服务器线程一直阻塞,直到有客户端连接,返回客户端连接Socket
Socket client=serverSocket.accept();
System.out.println("有客户端连接,客户端端口号为:"+client.getPort());
//获取客户端输出流,向客户端输出信息:true:自动刷新
PrintStream out=new PrintStream(client.getOutputStream(),true,"UTF-8");
//获取客户端输入流,读取客户端信息
Scanner in=new Scanner(client.getInputStream());
if(in.hasNextLine())
{
System.out.println("客户端发来的信息"+in.nextLine());
}
out.println("holle I am Server");
//关闭输入,输出流
in.close();
out.close();
serverSocket.close();
} catch (IOException e) {
System.out.println("服务器端建立连接失败,异常为:"+e);
}
}
}
客户端:
public class SimpleCilent {
public static void main(String[] args) {
try {
//建立客户端Socket并绑定服务器
Socket client=new Socket("127.0.0.1",6666);
//获取输出流向,服务器发送信息
PrintStream out=new PrintStream(client.getOutputStream(),true,"UTF-8");
out.println("Hi,I am client");
//获取输入流,读取服务器发来的消息
Scanner in=new Scanner(client.getInputStream());
if(in.hasNextLine())
{
System.out.println("服务器发来的信息为:"+in.nextLine());
}
//关闭输入输出流
in.close();
out.close();
client.close();
} catch (IOException e) {
System.out.println("客户端出现通信异常,异常为:"+e);
}
}
}
二.多线程版本的聊天室:
服务器端:
/*
多线程版本服务器端
*/
public class MultiThreadServer {
//采用ConcurrentHashMap存储所有连接到服务器客户端信息,避免线程安全问题,效率高
private static Map<String,Socket> clientMap=new ConcurrentHashMap<>();
//采用内部类具体处理每个客户端请求,并且内部类可以访问外部类私有属性
private static class ExeccuteRealClient implements Runnable
{
/*
服务器端功能:
注册用户:userName:
群聊:G:
私聊:P:用户-
用户退出:byebye
*/
private Socket client;
private ExeccuteRealClient(Socket client) {
this.client = client;
}
@Override
public void run() {
try {
//获取客户端输入流,读取用户发来的信息
Scanner in=new Scanner(client.getInputStream());
String str="";
while(true)
{
if(in.hasNextLine())
{
str=in.nextLine();
//识别Windows下的换行符,将/r换成空字符串
Pattern pattern=Pattern.compile("\r");
Matcher matcher=pattern.matcher(str);
matcher.replaceAll("");
//用户注册
if(str.startsWith("userName"))
{
//userName:test
String userName=str.split("\\:")[1];
userRegister(userName,client);
continue;
}
//群聊
if(str.startsWith("G"))
{
//G:123
String msg=str.split("\\:")[1];
GroupChat(msg);
continue;
}
//私聊
if(str.startsWith("P"))
{
//P:test-123
String userName=str.split("\\:")[1].split("\\-")[0];
String msg=str.split("\\:")[1].split("\\-")[1];
privateChat(userName,msg);
continue;
}
//退出
if(str.contains("byebye"))
{
//遍历Map,获取userName,根据V找K
String userName="";
for (String key:clientMap.keySet()) {
if(clientMap.get(key).equals(client)){
userName=key;
}
}
System.out.println("用户"+userName+"下线了");
clientMap.remove(userName);
System.out.println("当前聊天室一共还有"+clientMap.size()+"人");
continue;
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//用户注册方法
private static void userRegister(String userName,Socket socket)
{
System.out.println("用户"+userName+"上线了");
//将用户保存在map中
clientMap.put(userName,socket);
System.out.println("当前聊天室一共有"+clientMap.size()+"人");
try {
//告知用户注册成功
PrintStream out=new PrintStream(socket.getOutputStream());
out.println("用户注册成功");
} catch (IOException e) {
e.printStackTrace();
}
}
//群聊
private static void GroupChat(String msg)
{
Set<Map.Entry<String,Socket>> clientSet=clientMap.entrySet();
for (Map.Entry<String,Socket> entry:clientSet) {
//遍历取出每个Socket
Socket socket=entry.getValue();
try {
//获取每个Socket的输出流
PrintStream out=new PrintStream(socket.getOutputStream());
out.println("群聊消息为:"+msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
//私聊
private static void privateChat(String userName,String msg)
{
//根据用户名获取指定Socket
Socket socket=clientMap.get(userName);
try {
//获取指定socketde 输出流
PrintStream out=new PrintStream(socket.getOutputStream());
out.println("私聊消息:"+msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
//建立大小为20的线程:通过线程池
ExecutorService executorService= Executors.newFixedThreadPool(20);
//建立基站
ServerSocket serverSocket=new ServerSocket(6666);
for (int i = 0; i <20 ; i++) {
System.out.println("等待客户端连接");
Socket client=serverSocket.accept();
System.out.println("有新的客户端连接,端口号为:"+client.getPort());
//每当有用户连接,新建线程处理
executorService.submit(new ExeccuteRealClient(client));
}
executorService.shutdown();
serverSocket.close();
}
}
客户端:
/*
客户端读取服务器发来信息线程
*/
class ReadFromServer implements Runnable
{
private Socket client;
public ReadFromServer(Socket client) {
this.client = client;
}
@Override
public void run() {
try {
Scanner in=new Scanner(client.getInputStream());
while(true)
{
if(in.hasNextLine())
{
System.out.println("从服务器发来的消息为:"+in.nextLine());
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
向服务器发送信息
*/
class WriteToServer implements Runnable
{
private Socket client;
public WriteToServer(Socket client) {
this.client = client;
}
@Override
public void run() {
try {
//获取键盘输入流,读取用户从键盘发来的消息
Scanner scanner=new Scanner(System.in);
String string="";
//获取客户端输出流,将用户键盘输入的消息发送给服务器
PrintStream out=new PrintStream(client.getOutputStream(),true,"UTF-8");
while(true)
{
System.out.println("请输入向服务器发送的信息:");
if(scanner.hasNextLine())
{
string=scanner.nextLine();
out.println(string);
}
//设置退出标致
if(string.contains("beybey"))
{
System.out.println("客户端退出,不聊了");
scanner.close();
out.close();
client.close();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
多线程版本客户端
*/
public class MultiThreadClient {
public static void main(String[] args) {
try {
Socket client=new Socket("127.0.0.1",6666);
Thread readThread=new Thread(new ReadFromServer(client));
Thread writeThread=new Thread(new WriteToServer(client));
readThread.start();
writeThread.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}