1. 程式人生 > >使用Java Socket模擬實現聊天室

使用Java Socket模擬實現聊天室

        使用Java Socket模擬實現了一個聊天室,實現了基本的私聊以及群聊。分為伺服器端和客戶端,下面我來介紹一下實現的步驟。

伺服器端

        伺服器端是聊天室的核心所在,主要用來處理客戶端的請求,先來看一下伺服器端的主方法:

    public static void main(String[] args) {
        try {
            ExecutorService executorService = Executors.newFixedThreadPool(100);//最多容納100個客戶端聊天
            ServerSocket serverSocket = new ServerSocket(6655);//監聽6655號埠
            for (int i = 0; i < 100; i++) {
                Socket client = serverSocket.accept();
                System.out.println("有新的使用者連線 " + client.getInetAddress() +
                        client.getPort());
                executorService.execute(new ExecuteClientThread(client));
            }
            executorService.shutdown();
            serverSocket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

        首先我建立了一個固定大小為100的執行緒池,這個聊天室的實現是一個伺服器執行緒對應一個客戶端執行緒的,就是說執行緒池的大小就是最大的同時聊天的人數。伺服器的執行順序是這樣的:

        1:監聽埠,等待客戶端連線

        2:如果有客戶端連線到監聽的埠,那麼通過accept()方法返回該客戶端的Socket,並且線上程池中啟動一個新的伺服器執行緒用來與剛剛連線的客戶端"溝通"。

        3:把接收到的客戶端的Socket構造注入新啟動的伺服器執行緒中,這樣伺服器執行緒就可以獲取到客戶端對應的流。

        到這裡,伺服器已經和客戶端連線成功了,我們現在來看一下伺服器執行緒是如何處理客戶端的請求的,先上一段伺服器程式碼

    private static Map<String, Socket> clientMap = new ConcurrentHashMap<>();//儲存所有的使用者資訊

    static class ExecuteClientThread implements Runnable {
        private Socket client;//每一個伺服器執行緒對應一個客戶端執行緒
        ExecuteClientThread(Socket client) {
            this.client = client;
        }
......

        程式碼的第一行,建立了一個ConcurrentHashmap,這個map不是某個執行緒中的,而是伺服器的static屬性,用來儲存所有客戶端的資訊。因為客戶端是有姓名,有Socket的,所以採用K-value的模式來儲存,使用者名稱作為Key。考慮到執行緒安全的原因,採用了ConcurrentHashmap,保證了執行緒安全。

        接下來就是剛剛構造注入的、連線的客戶端的Socket了,我們可以通過這個Socket獲取到輸入和輸出流。

        然後就是伺服器的執行緒執行的run方法了,具體的就直接看程式碼把。都有註釋,就不一一解釋了,以下是所有伺服器端的程式碼

import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
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 Main {


    private static Map<String, Socket> clientMap = new ConcurrentHashMap<>();//儲存所有的使用者資訊

    static class ExecuteClientThread implements Runnable {
        private Socket client;//每一個伺服器執行緒對應一個客戶端執行緒
        ExecuteClientThread(Socket client) {
            this.client = client;
        }

        @Override
        public void run() {
            boolean Flag = true;//防止一個客戶端多次註冊所做的標記位置
            try {
                PrintStream PrintToCilent = new PrintStream(client.getOutputStream());//伺服器向用戶輸出一些提示資訊


                Scanner scanner = new Scanner(client.getInputStream());
                String str = null;//使用者外部的輸入資訊
                while (true) {
                    if (scanner.hasNext()) {
                        str = scanner.next();//外部的使用者輸出

                        Pattern pattern = Pattern.compile("\r");//排除特殊符號
                        Matcher matcher = pattern.matcher(str);
                        str = matcher.replaceAll("");

                        if (str.startsWith("userName")) {
                            String userName = str.split(":")[1];
                            userRegist(userName, client, Flag);
                            Flag = false;
                        }
                        // 群聊流程
                        else if (str.startsWith("G:")) {
                            PrintToCilent.println("已進入群聊模式!");
                            groupChat(scanner,client);
                        }
                        // 私聊流程
                        else if (str.startsWith("P")) {//模式
                            String userName = str.split("-")[1];
                            PrintToCilent.println("已經進入與"+userName+"的私聊");

                            privateChat(scanner,userName);
                        }
                        // 使用者退出
                        else if (str.contains("byebye")) {
                            String userName = null;
                            for (String getKey:clientMap.keySet()) {
                                if (clientMap.get(getKey).equals(client)) {
                                    userName = getKey;
                                }
                            }

                            System.out.println("使用者"+userName+"下線了..");
                            clientMap.remove(userName);//將此例項從map中移除
                        }
                    }
                }

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
       
        private void userRegist(String userName, Socket client, boolean Flag) throws IOException {
            PrintStream PrintToCilent = new PrintStream(client.getOutputStream());//伺服器向用戶輸出一些提示資訊
            if(Flag) {
                System.out.println("使用者" + userName + "上線了!");

                clientMap.put(userName, client);//把使用者加入儲存map
                System.out.println("當前群聊人數為" + (clientMap.size()) + "人");
                PrintToCilent.println("註冊成功!");
            }else {
                PrintToCilent.println("警告:一個客戶端只能註冊一個使用者!");
            }
        }
     
        private void groupChat(Scanner scanner,Socket client) throws IOException {
            // 取出clientMap中所有客戶端Socket,然後遍歷一遍
            // 分別取得每個Socket的輸出流向每個客戶端輸出
            PrintStream PrintToClient = new PrintStream(client.getOutputStream());//在群聊的時候伺服器向客戶端傳送資料
            boolean ExitFlag = false;

            Set<Map.Entry<String, Socket>> entrySet =
                    clientMap.entrySet();

            String userName = null;
            for (Map.Entry<String, Socket> socketEntry : entrySet) {//獲得:是哪個使用者說的話
                if (socketEntry.getValue() == client) {
                    userName = socketEntry.getKey();//發出資訊的使用者
                }
            }
            String msg = null;

            while (true) {
                if (scanner.hasNext()) {
                    msg = scanner.next();
                    if("exit".equals(msg)){//如果使用者退出了
                        for(Map.Entry<String,Socket> stringSocketEntry : entrySet){
                            new PrintStream(stringSocketEntry.getValue().getOutputStream(),true).println("使用者"+userName+"剛剛退出了群聊!!");//給所有人發退出群聊的訊息
                        }
                        return;
                    }


                    for (Map.Entry<String, Socket> stringSocketEntry : entrySet) {//遍歷使用者的map,獲取所有使用者的Socket
                        try {
                            Socket socket = stringSocketEntry.getValue();
                            PrintStream ps = new PrintStream(socket.getOutputStream(), true);

                            ps.println("群聊:使用者" + userName + "說: " + msg);//給每個使用者發訊息
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                }
            }

        }
        private void privateChat(Scanner scanner, String privatepeopleName) throws IOException {

            Socket privateUser = clientMap.get(privatepeopleName);
            PrintStream ps = new PrintStream(privateUser.getOutputStream());//拿到私聊物件的輸出流
            PrintStream PrintToClient = new PrintStream(client.getOutputStream());//拿到當前客戶端的輸出流
            String Message = null;
            String MyName = null;
            Set<Map.Entry<String,Socket>> set = clientMap.entrySet();
            for(Map.Entry<String,Socket> value : set){
                if(value.getValue() == client){
                    MyName = value.getKey();
                    break;
                }
            }

            while (true) {
                if(scanner.hasNext()) {
                    Message = scanner.next();
                    if ("exit".equals(Message)){//如果使用者輸入了退出
                        PrintToClient.println("已退出和"+privatepeopleName+"的私聊");
                        ps.println("對方已經退出了私聊");
                        break;
                    }
                    ps.println(MyName+"說"+Message);//如果使用者沒有退出,向私聊物件傳送訊息
                }
            }


        }


    }

    public static void main(String[] args) {
        try {
            ExecutorService executorService = Executors.newFixedThreadPool(100);//最多容納100個客戶端聊天
            ServerSocket serverSocket = new ServerSocket(6655);
            for (int i = 0; i < 100; i++) {
                Socket client = serverSocket.accept();
                System.out.println("有新的使用者連線 " + client.getInetAddress() +
                        client.getPort());
                executorService.execute(new ExecuteClientThread(client));
            }
            executorService.shutdown();
            serverSocket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

        然後是客戶端的程式碼,客戶端的程式碼比較簡單:分為兩個執行緒,一個執行緒用於接收伺服器的資料,一個執行緒用於向伺服器傳送資料。我就直接上程式碼了,裡面有註釋的。

import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;


class ExcuteServerInPut implements Runnable{//接收伺服器的資料
    private Socket ToServer;

    ExcuteServerInPut(Socket ToServer){
        this.ToServer = ToServer;
    }

    @Override
    public void run() {
        try {
            Scanner scanner = new Scanner(ToServer.getInputStream());
               while (scanner.hasNext()){
                System.out.println(scanner.nextLine());
            }
            scanner.close();
            ToServer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class ExcuteServerOutPut implements Runnable{//向伺服器傳送資料

    private Socket Socket;
    ExcuteServerOutPut(Socket Socket){
        this.Socket = Socket;
    }

    @Override
    public void run() {
        try {
            PrintStream printStream = new PrintStream(Socket.getOutputStream());
            Scanner scanner = new Scanner(System.in);
            scanner.useDelimiter("\n");
            System.out.println("*****************************************");
            System.out.println("***使用者註冊:useerName:同戶名(僅限一次)***");
            System.out.println("***進入群聊:G:           退出群聊:exit***");
            System.out.println("***私聊:P-使用者名稱         退出私聊:exit***");
            System.out.println("***********退出聊天室:byebye*************");
            while (true){
                if(scanner.hasNext()) {
                    String string = scanner.next();
                    printStream.println(string);
                    if ("byebye".equals(string)) {
                        System.out.println("退出!");
                        printStream.close();
                        scanner.close();
                        break;
                    }
                }

            }

            Socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


public class Main {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 6655);
        ExcuteServerInPut excuteServerInPut = new ExcuteServerInPut(socket);
        ExcuteServerOutPut excuteServerOutPut = new ExcuteServerOutPut(socket);
        new Thread(excuteServerInPut).start();
        new Thread(excuteServerOutPut).start();
        }
}
        後續我會做一些改進,希望可以對大家有所幫助


相關推薦

使用Java Socket模擬實現聊天

        使用Java Socket模擬實現了一個聊天室,實現了基本的私聊以及群聊。分為伺服器端和客戶端,下面我來介紹一下實現的步驟。伺服器端        伺服器端是聊天室的核心所在,主要用來處理客戶端的請求,先來看一下伺服器端的主方法: public stat

Node.Js+Redis+Socket.IO 實現 聊天或推送訊息

基於Redis 推送,向Node.Js 推送訊息,Node.Js 把 訊息推送給 Socket.IO(可以是WebSocket,也可以是long-Polling,由Socket.IO 封裝) 需要 Redis服務端,Node.Js需要 redis模組和Socket.IO模組

java socket多執行緒實現聊天

java socket多執行緒實現聊天室 程式碼放在我的GitHub上點選檢視 版本1,一對一,收發有順序 /** * @Author: Hzw * @Time: 2018/4/19 11:38 * @Description: 聊天室客戶端V1.0,雙向通訊,客戶端傳

websocket+php socket實現聊天

原文地址:http://www.cnblogs.com/nickbai/articles/6169745.html  這兩天用了點時間,研究了一下,用php socket+ websocket實現了一個小型的聊天室。我採用的是 select/poll 的同步模型,雖然扛不住很大的併發,

java基於redis訂閱/釋出訊息實現聊天功能

一、引言 趁著國慶節把redis高階應用都寫完吧,其實都很簡單。 redis高階應用:安全性、事務處理、持久化操作,訂閱/釋出、虛擬記憶體 安全性其實就是在連線redis時,需要一個密碼認證,可以

Java——實現聊天

學習Java的每一個人都知道,聊天室是每一個程式設計師都要過手的專案,根據要求的不同,聊天室的實現可易可難。我今天的聊天室程式主要實現的功能是:1、私聊功能2、群聊功能3、檢視成員列表功能4、退出聊天室功能5、傳送檔案功能內容比較簡單,是學完JavaSE的一次知識總結,沒有用

Socket —— 通過多執行緒簡單模擬聊天

/** * Created by Liwei on 2016/7/17. * 模擬一個簡單的聊天室程式 * 通過多執行緒改進剛開的聊天程式,這樣我們就可以實現在一個視窗傳送和接收資料了 */ p

使用 Socket.IO 開發聊天

對象 ima -1 hello 前言 text 而後 一定的 發布 前言 Socket.IO 是一個用來實現實時雙向通信的框架,其本質是基於 WebSocket 技術。 我們首先來聊聊 WebSocket 技術,先設想這麽一個場景: · 用戶小A,打開了某個網站的充值界面

Python Socket 編程——聊天演示樣例程序

str easy bin star ddl wid cep gif div 上一篇 我們學習了簡單的 Python TCP Socket 編程,通過分別寫服務端和client的代碼了解主要的 Python Socket 編程模型。本文再通過一個樣例來加強一下對

1025_(即時通訊)使用 Socket.IO 開發聊天

使用 Socket.IO 開發聊天室 前言 Socket.IO 是一個用來實現實時雙向通訊的框架,其本質是基於 WebSocket 技術。 我們首先來聊聊 WebSocket 技術,先設想這麼一個場景: · 使用者小A,打開了某個網站的充值介面,該介面上有一個付款的二維碼。 ·

ajax實現聊天功能

需求如下: 先死後活。  需求分析,分析思路如圖所示: 1.建立資料庫 create database chat; create table messages( id int unsigned primary key auto_increment, sender va

使用java nio 編寫簡易聊天

伺服器端:相當於是一個接收客戶端訊息的分發器,為了簡單,直接在接收到客戶端的訊息後,                  直接傳送給所有的客戶端 package chatroom.chatser

go語言實現聊天

go語言實現聊天室 聊天室分為服務端和客戶端。第一部分為服務端程式碼,第二部分為客戶端程式碼。 一、服務端程式碼 //chatroom server package main import ( "fmt" "net" ) //定義函式checkError,用來錯誤處理 fu

利用netty簡單實現聊天

1.匯入依賴包 <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId>

Java Socket通訊實現私聊、群聊 WebSocket+Java 私聊、群聊例項

   前言   閒言少敘,上程式碼!     程式碼編寫    server服務端 /** * 服務端 */ public class Server { private static ServerSocket server = null;

Android端使用Netty+Protocol Buffer實現聊天

之前寫過一篇Protocol Buffer使用轉換工具將proto檔案轉換成Java檔案流程及使用,就是在這篇的基礎上,將客戶端與伺服器規定好的協議ChatServer.proto轉換成ChatServerProto.java檔案。 一、實現步驟: 0、應用目錄

Python網路程式設計---實現聊天

寫一個聊天室 要求: 1.進入聊天室需要輸入姓名,姓名不能重複 2.有人進入聊天室會想其他人傳送通知,xxx 進入了聊天室 3.一個人發訊息,其他人會收到訊息 xxx 說:xxxx 4.某人退出聊天室,其他人也會收到通知 xxx 退出了聊天室 5.管理員喊話功能 -基於u

Python Socket 程式設計:聊天示例程式

上一篇 我們學習了簡單的 Python TCP Socket 程式設計,通過分別寫服務端和客戶端的程式碼瞭解基本的 Python Socket 程式設計模型。本文再通過一個例子來加強一下對 Socket 程式設計的理解。 聊天室程式需求 我們要實現的是簡單的聊天室的例子,就是允

Java簡易多人聊天(cmd下執行)

思路: 1.首先建立伺服器監聽 ServerSocket server = new ServerSocket(5000); 監聽某個指定埠號是否有連線請求 2.客戶端發起請求,也就是new了一個Socket socket = new Socket(host,port); 想某個指定埠號發起請

Java基於UDP通訊聊天程式碼

UDP就是將目的地和資料封裝在一個包中,然後傳送給相應的目的地,包的大小最大為64K,安全性低,效率高 在建立之前我們餘姚知道一些類和方法的用法 比如:InetAdress類 //得到名為zhang的主機物件 InetAddress iAddress=InetAddress.getByN