《一》、需求分析
一、前提:同一區域網路内
二、功能:
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<String, Socket> clientMap = new ConcurrentHashMap<String, Socket>( );
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<Map.Entry<String,Socket>> clientSet = clientMap.entrySet();
for (Map.Entry<String,Socket>entry: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)
以上為聊天室的基礎功能實作,有許多功能仍可添加。