《一》、需求分析
一、前提:同一局域网内
二、功能:
1、注册:userName:name
2、群聊:G:message
3、私聊:P:userName-message
4、退出:byebye
三、基本模型:C/S架构
四、开发环境及工具:JDK 1.8+IDEA开发工具
五、实现原理
服务器端:
1、服务器端实例化一个SeverSocket对象,设置端口号。
2、服务器端的SeverSocket对象调用accept方法,等待客户端连接服务器的端口。
3、获取客户端的输入输出流并向客户端输出。
4、关闭输入输出流,关闭服务器端。
客户端:
1、客户端实例化一个Socket对象并获取服务器域名和端口号。
2、在服务器端中, accept将返回一个Socket对象,该socket连接到客户端的socket。
3、获取服务器端的输入输出流并向服务器端输出。
4、关闭输入输出流,关闭客户端。
六、使用技术:
通过Executors创建线程池实现多线程版的聊天室,使用HashMap存放客户Socket实现多人聊天功能, 在项目中添加了锁
代码块解决注册用户名重复问题。
《二》、代码实现
一、单线程版本:
服务器端:
package com.company.SingleThread;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class Sever {
public static void main(String[] args){
ServerSocket serverSocket;
{
try {
//创建服务器端Socket,端口号为8888;
serverSocket = new ServerSocket( 8888 );
System.out.println("服务器端已就绪,等待客户端连接。。。" );
//等待客户端连接,有客户端连接时返回客户端的Socket对象,否则线程一直处于阻塞状态;
Socket cliet = serverSocket.accept();
System.out.println("有新客户端连接,端口号为:" + cliet.getPort());
//获取客户端的输入流
Scanner clientinput = new Scanner( cliet.getInputStream() );
//useDelimiter(","); 以','为分隔符
//useDelimiter("\n"); “\n”换行符(回车)作为输入的分隔符。
clientinput.useDelimiter( "\n" );
//获取客户端的输出流
PrintStream clientout = new PrintStream( cliet.getOutputStream() );
//读取客户端的输入流
if (clientinput.hasNext()){
System.out.println(cliet.getInetAddress()+"说"+clientinput.next());
}
//向客户端输出
clientout.println( "hello i am Sever " );
//关闭输入输出流,关闭服务器端。
clientinput.close();
clientout.close();
serverSocket.close();
} catch (IOException e) {
System.out.println("服务器端通信出现异常,错误为" + e);
}
}
}
}
客户端:
package com.company.SingleThread;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args){
String severName = "127.0.0.1";
Integer port = 8888;
try {
//创建并获取服务器域名和端口号
Socket client = new Socket( severName,port );
//打印服务器地址
System.out.println("连接上服务器,服务器地址为:"+ client.getInetAddress());
//获取输入输出流
PrintStream out = new PrintStream( client.getOutputStream() );
Scanner in = new Scanner( client.getInputStream() );
//向服务器输出内容
in.useDelimiter( "\n" );
//读取服务器输入
out.println( "hi i am client" );
if (in.hasNext()){
System.out.println("服务器端发来的消息是"+ in.next() );
}
//关闭输入输出流及客户端
in.close();
out.close();
client.close();
} catch (IOException e) {
System.out.println("客户端通信出现异常,错误信息是"+ e );
}
}
}
测试:
二、多线程版本
服务器端:
package com.company.SingleThread;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SeveralThreadServer {
//利用ConcurrentHashMap的高效和安全性来存储所有连接到服务器的客户端信息。
private static Map clientMap = new ConcurrentHashMap( );
private static class ExecuteClient implements Runnable{
private Socket client;
public ExecuteClient (Socket client){
this.client = client;
}
@Override
/*
userName:注册
G:群聊
P:私聊
byebye:用户退出
*/
public void run( ) {
try {
//获取客户端输入流,读取客户端发来的信息
Scanner in = new Scanner( client.getInputStream() );
String strFromClient;
while (true){
if (in.hasNextLine()){
strFromClient = in.nextLine();
//识别Windows下的换行符,将多余的"/r"替换为空
//Windows下换行:/n/r Linux下换行:/n;
Pattern pattern = Pattern.compile( "\r" );
Matcher matcher = pattern.matcher(strFromClient);
strFromClient = matcher.replaceAll( "" );
//注册流程
if (strFromClient.startsWith( "userName" )){
String userName = strFromClient.split( "\\:" )[1];
registerUser(userName,client);
continue;
}
//群聊流程
if (strFromClient.startsWith( "G" )){
String msg = strFromClient.split( "\\:" )[1];
groupChat(msg);
continue;
}
//私聊流程
//P:1-msg
if (strFromClient.startsWith( "P" )){
String userName = strFromClient.split( "\\:" )[1]
.split( "-" )[0];
String msg = strFromClient.split( "\\:" )[1]
.split( "-" )[1];
privateChat(userName,msg);
}
//退出流程
if (strFromClient.contains( "byebye" )){
//遍历Map,获取userName;
String userName = null;
for (String keyName:clientMap.keySet()){
if (clientMap.get( keyName ).equals( client )){
userName = keyName;
}
}
System.out.println("用户"+userName+"下线了" );
clientMap.remove( userName );
continue;
}
}
}
} catch (IOException e) {
System.out.println("服务器通信异常,错误是" + e);
}
}
//注册
private void registerUser(String userName,Socket client){
System.out.println("用户姓名为:"+userName );
System.out.println("用户"+userName+"上线了" );
System.out.println("当前群聊人数为:"+(clientMap.size()+1)+"人" );
clientMap.put( userName,client );
try {
PrintStream out = new PrintStream( client.getOutputStream() );
out.println("用户注册成功" );
} catch (IOException e) {
e.printStackTrace( );
}
}
//群聊
private void groupChat(String msg){
Set> clientSet = clientMap.entrySet();
for (Map.Entryentry:clientSet){
//遍历取出每个Socket
Socket socket = entry.getValue();
PrintStream out = null;
try {
out = new PrintStream( socket.getOutputStream() );
out.println( "群聊信息为"+msg );
} catch (IOException e) {
System.out.println("群聊异常,错误为"+e );
}
}
}
//私聊
private void privateChat(String userName,String msg){
Socket privateSocket = clientMap.get( userName );
try {
PrintStream out = new PrintStream( privateSocket.getOutputStream() );
out.println( "私聊信息为"+msg );
} catch (IOException e) {
System.out.println("私聊异常,错误为"+e );
}
}
}
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 ExecuteClient( client ) );
}
//关闭线程池
executorService.shutdown();
//关闭基站
serverSocket.close();
}
}
客户端:
package com.company.SingleThread;
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
//客户端读取服务器发来的信息线程
class ReadFromSeverThread implements Runnable{
private Socket client;
public ReadFromSeverThread(Socket client){
this.client = client;
}
@Override
public void run( ) {
Scanner in = null;
try {
in = new Scanner( client.getInputStream( ) );
in.useDelimiter( "\n" );
while (true) {
if (in.hasNext( )) {
System.out.println( "从服务器发过来的信息是" + in.next( ) );
}
if (client.isClosed( )) {
System.out.println( "客户端关闭" );
break;
}
}
in.close( );
} catch (IOException e) {
e.printStackTrace( );
}
}
}
//客户端写信息给服务器
class WriteToSeverThread implements Runnable{
private Socket client;
public WriteToSeverThread(Socket client){
this.client = client;
}
public void run( ) {
//获取键盘输入流,读取从键盘发来的信息
Scanner scanner = new Scanner( System.in );
scanner.useDelimiter( "\n" );
try {
//获取客户端输出流,将用户输入的信息发送给服务器
PrintStream out = new PrintStream( client.getOutputStream() );
while (true){
System.out.println( "请输入要发送的信息。。");
String strToSever;
if (scanner.hasNextLine()){
strToSever = scanner.nextLine().trim();
out.println( strToSever );
//退出标志
if (strToSever.contains( "byebye" )){
System.out.println("客户端退出,不聊了" );
scanner.close();
out.close();
client.close();
break;
}
}
}
} catch (IOException e) {
System.out.println("客户端写入信息程序异常,错误为"+e );
}
}
}
public class SeveralThreadClient {
public static void main(String[] args) {
try {
Socket client = new Socket("127.0.0.1",6666 );
Thread readFromSever = new Thread( new ReadFromSeverThread(client) );
Thread writeToSever = new Thread( new WriteToSeverThread(client) );
readFromSever.start();
writeToSever.start();
} catch (IOException e) {
e.printStackTrace( );
}
}
}
测试:
一、注册功能:(userName:name)
二、群聊功能(G:message)
三、私聊功能(P:userName-message)
四、退出(byebye)
以上为聊天室的基础功能实现,有许多功能仍可添加。