1. 程式人生 > >Netty - 一個簡單的聊天室小專案

Netty - 一個簡單的聊天室小專案

經過一段時間對Netty的學習,我們對Netty各版本以及像ProtocolBuffers等技術應用都有了不少相關的瞭解, 我們就用這段時間學到的只是做一個簡單的聊天室的小專案來練習自己學到的技術。
做這個小專案之前我們先大致瞭解下我們需要用到的技術點,netty3.x/4.x/5.x、swingbuilder、protobuf。這裡我們先以Netty3為例構建專案,各位可以通過git上查詢Netty5以及結合了ProtocolBuffers版本的聊天室專案。
Netty_Demo[git]
我們先稍微比較下Netty幾個版本之間的應用差異

netty3.x netty4.x/5.x
ChannelBuffer ByteBuf
ChannelBuffers PooledByteBufAllocator(要注意使用完後釋放buffer)或UnpooledByteBufAllocator或Unpooled
FrameDecoder ByteToMessageDecoder
OneToOneEncoder MessageToByteEncoder
messageReceive channelRead0(netty5中messageReceive)
Common部分
專案目錄結構

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

首先我們需要匯入依賴,這裡結合spring、mybatis、netty、log4j

pom.xml
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation
="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent> <artifactId>Netty_Demo</artifactId> <groupId>com.ithzk</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>Netty3</artifactId> <name>Netty3</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <!-- spring版本號 --> <spring.version>4.3.13.RELEASE</spring.version> <!-- mybatis版本號 --> <mybatis.version>3.2.6</mybatis.version> <!-- log4j日誌檔案管理包版本 --> <slf4j.version>1.7.7</slf4j.version> <log4j.version>1.2.17</log4j.version> <jedis.version>2.7.3</jedis.version> <druid.version>1.1.6</druid.version> </properties> <dependencies> <!-- https://mvnrepository.com/artifact/io.netty/netty --> <dependency> <groupId>io.netty</groupId> <artifactId>netty</artifactId> <version>3.10.5.Final</version> </dependency> <!-- log start --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>2.4.1</version> </dependency> <!-- mybatis核心包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.2.6</version> </dependency> <!-- mybatis/spring包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.2.2</version> </dependency> <!-- 匯入dbcp的jar包,用來在applicationContext.xml中配置資料庫 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.6</version> </dependency> <!-- spring核心包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-oxm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <!-- 匯入java ee jar 包 --> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> </dependency> <!-- 匯入Mysql資料庫連結jar包 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.30</version> </dependency> </dependencies> </project>

這裡兩個自定義註解主要作用是為了之後Hadnler處理呼叫業務程式碼有序管理使用

SocketCommand.java
package com.chat.common.core.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 請求命令
 * @author hzk
 * @date 2018/10/22
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SocketCommand {

    /**
     * 請求命令號
     */
    short cmd();
}

SocketModule .java
package com.chat.common.core.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 請求模組
 * @author hzk
 * @date 2018/10/22
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SocketModule {

    /**
     * 請求模組號
     */
    short module();
}

這裡請求和響應的自定義編碼器和解碼器和我們之前一起學習時候是大體一致的,基本沒有改變

RequestDecoder.java
package com.chat.common.core.coder;

import com.chat.common.core.constants.Constants;
import com.chat.common.core.model.Request;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.FrameDecoder;

/**
 * 請求解碼器
 * 資料包格式(根據需求定義)
 * 包頭 模組號 命令號 長度 資料 
 * 包頭4位元組
 * 模組號2位元組short
 * 命令號2位元組short
 * 長度4位元組(描述資料部分位元組長度)
 * @author hzk
 * @date 2018/9/29
 */
public class RequestDecoder extends FrameDecoder{

    @Override
    protected Object decode(ChannelHandlerContext channelHandlerContext, Channel channel, ChannelBuffer channelBuffer) throws Exception {
        //可讀長度必須大於基本長度
        if(channelBuffer.readableBytes() >= Constants.AbstractDataStructure.DATA_STRUCTURE_LENGTH){
            //防止socket位元組流攻擊
            if(channelBuffer.readableBytes() > 2048){
                channelBuffer.skipBytes(channelBuffer.readableBytes());
            }

            //記錄包頭開始偏移Index,即第一個可讀資料的起始位置
            int beginIndex;

            while (true){
                beginIndex = channelBuffer.readerIndex();
                //標記讀索引位置
                channelBuffer.markReaderIndex();
                int packHead = channelBuffer.readInt();
                if(Constants.AbstractDataStructure.PACKAGE_HEAD == packHead){
                    break;
                }

                //未讀取到包頭,還原讀索引位置,略過一個位元組
                channelBuffer.resetReaderIndex();
                channelBuffer.readByte();

                if(channelBuffer.readableBytes() < Constants.AbstractDataStructure.DATA_STRUCTURE_LENGTH){
                    //資料包不完整,需要等待後面的資料包
                    return null;
                }
            }
            //模組號
            short module = channelBuffer.readShort();
            //命令號
            short cmd = channelBuffer.readShort();
            //資料長度
            int length = channelBuffer.readInt();
            if(length <0){
                channel.close();
            }

            //判斷請求資料包 資料是否完整
            if(channelBuffer.readableBytes() < length){
                //還原讀指標
                channelBuffer.readerIndex(beginIndex);
                return null;
            }

            //讀取data資料
            byte[] data = new byte[length];
            channelBuffer.readBytes(data);

            //解析出訊息物件,往下傳遞(handler)
            return new Request(module,cmd,data);
        }
        //資料包不完整,需要等待後面的資料包
        return null;
    }
}

RequestEncoder.java
package com.chat.common.core.coder;

import com.chat.common.core.constants.Constants;
import com.chat.common.core.model.Request;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;

/**
 * 請求編碼器
 * 資料包格式(根據需求定義)
 * 包頭 模組號 命令號 長度 資料 
 * 包頭4位元組
 * 模組號2位元組short
 * 命令號2位元組short
 * 長度4位元組(描述資料部分位元組長度)
 * @author hzk
 * @date 2018/9/29
 */
public class RequestEncoder extends OneToOneEncoder{

    @Override
    protected Object encode(ChannelHandlerContext channelHandlerContext, Channel channel, Object rs) throws Exception {
        Request request = (Request) rs;
        ChannelBuffer channelBuffer = ChannelBuffers.dynamicBuffer();
        //包頭
        channelBuffer.writeInt(Constants.AbstractDataStructure.PACKAGE_HEAD);
        //模組Module
        channelBuffer.writeShort(request.getModule());
        //命令號cmd
        channelBuffer.writeShort(request.getCmd());
        //資料長度
        channelBuffer.writeInt(request.getDataLength());
        //資料
        if(null != request.getData()){
            channelBuffer.writeBytes(request.getData());
        }
        return channelBuffer;
    }
}

ResponseDecoder.java
package com.chat.common.core.coder;

import com.chat.common.core.constants.Constants;
import com.chat.common.core.model.Response;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.FrameDecoder;

/**
 * 響應解碼器
 * 資料包格式(根據需求定義)
 * 包頭 模組號 命令號 長度 資料 
 * 包頭4位元組int
 * 模組號2位元組short
 * 命令號2位元組short
 * 響應碼4位元組int
 * 長度4位元組(描述資料部分位元組長度)
 * @author hzk
 * @date 2018/9/29
 */
public class ResponseDecoder extends FrameDecoder{

    @Override
    protected Object decode(ChannelHandlerContext channelHandlerContext, Channel channel, ChannelBuffer channelBuffer) throws Exception {
        //可讀長度必須大於基本長度
        if(channelBuffer.readableBytes() >= Constants.AbstractDataStructure.DATA_STRUCTURE_LENGTH){
            //防止socket位元組流攻擊
            if(channelBuffer.readableBytes() > 2048){
                channelBuffer.skipBytes(channelBuffer.readableBytes());
            }

            //記錄包頭開始偏移Index
            int beginIndex = channelBuffer.readerIndex();

            while (true){
                beginIndex = channelBuffer.readerIndex();
                //標記讀索引位置
                channelBuffer.markReaderIndex
            
           

相關推薦

Netty - 一個簡單聊天專案

經過一段時間對Netty的學習,我們對Netty各版本以及像ProtocolBuffers等技術應用都有了不少相關的瞭解, 我們就用這段時間學到的只是做一個簡單的聊天室的小專案來練習自己學到的技術。 做這個小專案之前我們先大致瞭解下我們需要用到的技術點,netty3.x/4.x/5.

Android多人聊天專案

多人聊天室小專案筆記(APP客戶端) 這兩天自己寫了一個多人聊天室,伺服器是用Java寫的,爛的一批,不想寫了,但是安卓客戶端上遇到了很多問題還是有必要說一下。 1、功能 這個APP客戶端是基於我自己寫的伺服器來執行的,不過有需要的夥伴可以根據自己的需

Netty+Android搭建一個簡易聊天(實現群聊和私聊)

零,前言 JRBM專案中無論是好友私聊,公開聊天室,還是比賽平臺都需要用到長連線,之前沒有接觸過網路通訊等知識,更別說框架了,因此直接上手netty確實有些困難,在前期主要是在b站上看(https://www.bilibili.com/video/av26415011)這個

Netty+Websocket 實現一個簡易聊天

後臺程式碼 /** * 服務端 */ public class ChatServer { public static void main(String[] args) throws Exception { int port=8080; //服務端預設埠 new Ch

C# Redis輔助類封裝與簡單聊天的實現思路說明

執行 sum 頭部 lis 有序 += wait connected 相同 雖然redis api的功能比較齊全,但個人覺得為了更好的方便學習和使用,還是很有必有做一個類似DBHelper的幫助類 輔助類主要功能(代碼會在最後放出來) 1. 事件監聽: 重新配置廣播

python網絡編程基礎--socket的簡介,以及使用socket來搭建一個簡單的udp程序

流程 發送消息 lose 1.10 軟件 搬運 我們 arm 進程間 socket介紹: socket(簡稱套接字),是進程間通訊的一個工具,他能實現把數據從一方傳輸到另一方,完成不同電腦上進程之間的通訊,它好比數據的搬運工。socket應用:不誇張來說,只要跟網絡相關的應

Netty 仿QQ聊天 (實戰二)

Netty 聊天器(百萬級流量實戰二):仿QQ客戶端 瘋狂創客圈 Java 分散式聊天室【 億級流量】實戰系列之15 【部落格園 總入口 】 原始碼IDEA工程獲取連結:Java 聊天室 實戰 原始碼 寫在前面 ​ 大家好,我是作者尼恩。 今天是百萬級流量 Netty 聊天器 打造的系列文章的第二篇,

基於Javasocket NIO的一個CS聊天

不同的SelectableChannel所支援的操作是不同的。例如ServerSocketChannel代表一個ServerSocket,它就只支援OP_ACCEPT操作; 當Selector上註冊的所有Channel都沒有需要處理的IO操作的時候,select方法將會被阻塞,呼叫該方法的執

爬蟲教程」Python做一個簡單爬蟲,白也能看懂的教程

俗話說“巧婦難為無米之炊”,除了傳統的資料來源,如歷史年鑑,實驗資料等,很難有更為簡便快捷的方式獲得資料,在目前網際網路的飛速發展寫,大量的資料可以通過網頁直接採集,“網路爬蟲”應運而生,本篇將會講解簡單的網路爬蟲編寫方法。   開發環境 每個人的開發環境各異,下面上是我的開發

Netty 實現 WebSocket 聊天功能

WebSocket 是 H5 的一種技術,目前有很多語言都有相應的實現,之前有用 WebSocket 實現過 Java 和安卓,IOS 通訊的專案。想來也該分享一下,看過不少專案要實現頁面資料實時更新的操作,有用輪詢有用 Socket 連結的,當然也不排除有很多前端其他技術可以實現,WebSocke

用C語言寫一個簡單的掃雷遊戲

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <windows.h> #include <time.h> /* 用 C 語言寫一個簡單的掃雷遊戲 */ // 1.寫一個遊戲選單 M

Netty多人聊天

在簡單聊天室的程式碼中修改ChatServerHandler類,就可以模擬多人聊天的功能 package com.cppdy.server; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext;

Python做一個簡單爬蟲,白也能看懂的教程

俗話說“巧婦難為無米之炊”,除了傳統的資料來源,如歷史年鑑,實驗資料等,很難有更為簡便快捷的方式獲得資料,在目前網際網路的飛速發展寫,大量的資料可以通過網頁直接採集,“網路爬蟲”應運而生,本篇將會講解簡單的網路爬蟲編寫方法。 開發環境 每個人的開發環境各異,下面上是我的開發環境,對於必須的

Socket.IO實現簡單聊天

Socket.IO介紹 官方文件 https://socket.io/docs/ Socket.io是一個跨瀏覽器支援WebSocket的實時通訊的JS。它不僅簡化了介面,使得操作更容易,而且對於那些不支援WebSocket的瀏覽器,會自動降為Ajax連線,最大限度地保證了相容性。它

SpringBoot 搭建簡單聊天

SpringBoot 搭建簡單聊天室(queue 點對點) 1、引用 SpringBoot 搭建 WebSocket 連結   https://www.cnblogs.com/yi1036943655/p/10089100.html 2、整合Spring Security package com

百度t7 課程, websocket 實現簡單聊天

最簡單的聊天室,我寫了一個小時, 寫了10 分鐘,除錯50分鐘 因為 我是小菜鳥,不過凡事都有過程 index.html <!DOCTYPE html> <html lang="en"> <head> <meta c

分享一個簡單的Django的專案

一個簡單的Django搭建過程前陣子學習Django已經過了一段時間呢,也幾天沒有敲Python程式碼了,所有就從網上找了一個其他人搭建的Django專案來溫故下。分享下這個地址:https://www.jianshu.com/p/267385bc54aa/我本地的環境是用Conda建立的虛擬環境來建立的專案

「爬蟲教程」Python做一個簡單爬蟲,白也能看懂的教程

俗話說“巧婦難為無米之炊”,除了傳統的資料來源,如歷史年鑑,實驗資料等,很難有更為簡便快捷的方式獲得資料,在目前網際網路的飛速發展寫,大量的資料可以通過網頁直接採集,“網路爬蟲”應運而生,本篇將會講解簡單的網路爬蟲編寫方法。   開發環境 每個人的開發環境各異,下面上是我的開發

java socket 基於netty的網路聊天

Netty是一個Java的NIO客戶端服務端框架可以快速的開發網路應用程式,比如客戶端和服務端的協議,大大簡化了網路程式的開發過程。我們知道Netty的整體架構主要由3部分組成:緩衝(buffer)、通道(channel)、事件模型(event model)。所有的

Webpack4 入門到帶你打包一個簡單單頁應用專案

正文前先吐槽下, webpack 對新手入門真的有點不友好,各個版本在配置上都有或多或少的差異,導致在對照各種教程學習的過程中免不了掉進各種坑裡,所以寫這篇文章旨在簡單明瞭的解釋說明 webpack 的各種常用配置,希望能讓新人接觸 webpack 時少走些彎