广告
返回顶部
首页 > 资讯 > 后端开发 > GO >基于Golang如何实现Redis协议解析器
  • 762
分享到

基于Golang如何实现Redis协议解析器

2023-07-05 16:07:12 762人浏览 八月长安
摘要

这篇文章主要介绍了基于golang如何实现Redis协议解析器的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇基于Golang如何实现Redis协议解析器文章都会有所收获,下面我们一起来看看吧。RESP协议RES

这篇文章主要介绍了基于golang如何实现Redis协议解析器的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇基于Golang如何实现Redis协议解析器文章都会有所收获,下面我们一起来看看吧。

RESP协议

RESP是客户端与服务端通信的协议,格式有五种:

正常回复:以“+”开头,以“\r\n”结尾的字符串形式

错误回复:以“-”开头,以“\r\n”结尾的字符串形式

整数:以“:”开头,以“\r\n”结尾的字符串形式

多行字符串:以“$”开头,后跟实际发送字节数,再以“\r\n”开头和结尾

$3\r\nabc\r\n

数组:以“*”开头,后跟成员个数

SET key value
*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n

客户端和服务器发送的命令或数据一律以 \r\n (CRLF)作为换行符。

当我们输入*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n这样一串命令,服务端接收到的是如下的命令:
*3\r\n
$3\r\n
SET\r\n
$3\r\n
key\r\n
$5\r\n
value\r\n

interface/resp/conn.go

type Connection interface {   Write([]byte) error   GetDBIndex() int   SelectDB(int)}interface/resp/reply.gotype Reply interface {ToBytes() []byte}
  • Connection接口:Redis客户端的一个连接

  • Write:给客户端回复消息

  • GetDBIndex:Redis有16个DB

  • Reply接口:响应接口

resp/reply/consts.go

type PongReply struct{}var pongBytes = []byte("+PONG\r\n")func (r *PongReply) ToBytes() []byte {    return pongBytes}var thePongReply = new(PongReply)func MakePongReply() *PongReply {    return thePongReply}type OkReply struct{}var okBytes = []byte("+OK\r\n")func (r *OkReply) ToBytes() []byte {    return okBytes}var theOkReply = new(OkReply)func MakeOkReply() *OkReply {    return theOkReply}var nullBulkBytes = []byte("$-1\r\n")type NullBulkReply struct{}func (r *NullBulkReply) ToBytes() []byte {    return nullBulkBytes}func MakeNullBulkReply() *NullBulkReply {    return &NullBulkReply{}}var emptyMultiBulkBytes = []byte("*0\r\n")type EmptyMultiBulkReply struct{}func (r *EmptyMultiBulkReply) ToBytes() []byte {    return emptyMultiBulkBytes}type NoReply struct{}var noBytes = []byte("")func (r *NoReply) ToBytes() []byte {    return noBytes}

定义五种回复:回复pong,ok,null,空数组,空

resp/reply/reply.go

type ErrorReply interface {   Error() string   ToBytes() []byte}

ErrorReply:定义错误接口

resp/reply/errors.go

type UnknownErrReply struct{}var unknownErrBytes = []byte("-Err unknown\r\n")func (r *UnknownErrReply) ToBytes() []byte {   return unknownErrBytes}func (r *UnknownErrReply) Error() string {   return "Err unknown"}type ArgNumErrReply struct {   Cmd string}func (r *ArgNumErrReply) ToBytes() []byte {   return []byte("-ERR wrong number of arguments for '" + r.Cmd + "' command\r\n")}func (r *ArgNumErrReply) Error() string {   return "ERR wrong number of arguments for '" + r.Cmd + "' command"}func MakeArgNumErrReply(cmd string) *ArgNumErrReply {   return &ArgNumErrReply{      Cmd: cmd,   }}type SyntaxErrReply struct{}var syntaxErrBytes = []byte("-Err syntax error\r\n")var theSyntaxErrReply = &SyntaxErrReply{}func MakeSyntaxErrReply() *SyntaxErrReply {   return theSyntaxErrReply}func (r *SyntaxErrReply) ToBytes() []byte {   return syntaxErrBytes}func (r *SyntaxErrReply) Error() string {   return "Err syntax error"}type WrongTypeErrReply struct{}var wrongTypeErrBytes = []byte("-WRONGTYPE Operation against a key holding the wrong kind of value\r\n")func (r *WrongTypeErrReply) ToBytes() []byte {   return wrongTypeErrBytes}func (r *WrongTypeErrReply) Error() string {   return "WRONGTYPE Operation against a key holding the wrong kind of value"}type ProtocolErrReply struct {   Msg string}func (r *ProtocolErrReply) ToBytes() []byte {   return []byte("-ERR Protocol error: '" + r.Msg + "'\r\n")}func (r *ProtocolErrReply) Error() string {   return "ERR Protocol error: '" + r.Msg}

errors定义5种错误:UnknownErrReply 未知错误,ArgNumErrReply 参数个数错误,SyntaxErrReply 语法错误,WrongTypeErrReply 数据类型错误,ProtocolErrReply 协议错误

resp/reply/reply.go

var (   nullBulkReplyBytes = []byte("$-1")   // 协议的结尾   CRLF = "\r\n")type BulkReply struct {   Arg []byte}func MakeBulkReply(arg []byte) *BulkReply {   return &BulkReply{      Arg: arg,   }}func (r *BulkReply) ToBytes() []byte {   if len(r.Arg) == 0 {      return nullBulkReplyBytes   }   return []byte("$" + strconv.Itoa(len(r.Arg)) + CRLF + string(r.Arg) + CRLF)}type MultiBulkReply struct {   Args [][]byte}func (r *MultiBulkReply) ToBytes() []byte {   argLen := len(r.Args)   var buf bytes.Buffer   buf.WriteString("*" + strconv.Itoa(argLen) + CRLF)   for _, arg := range r.Args {      if arg == nil {         buf.WriteString("$-1" + CRLF)      } else {         buf.WriteString("$" + strconv.Itoa(len(arg)) + CRLF + string(arg) + CRLF)      }   }   return buf.Bytes()}func MakeMultiBulkReply(args [][]byte) *MultiBulkReply {   return &MultiBulkReply{      Args: args,   }}type StatusReply struct {   Status string}func MakeStatusReply(status string) *StatusReply {   return &StatusReply{      Status: status,   }}func (r *StatusReply) ToBytes() []byte {   return []byte("+" + r.Status + CRLF)}type IntReply struct {   Code int64}func MakeIntReply(code int64) *IntReply {   return &IntReply{      Code: code,   }}func (r *IntReply) ToBytes() []byte {   return []byte(":" + strconv.FORMatInt(r.Code, 10) + CRLF)}type StandardErrReply struct {   Status string}func (r *StandardErrReply) ToBytes() []byte {   return []byte("-" + r.Status + CRLF)}func (r *StandardErrReply) Error() string {   return r.Status}func MakeErrReply(status string) *StandardErrReply {   return &StandardErrReply{      Status: status,   }}func IsErrorReply(reply resp.Reply) bool {   return reply.ToBytes()[0] == '-'}
  • BulkReply:回复一个字符串

  • MultiBulkReply:回复字符串数组

  • StatusReply:状态回复

  • IntReply:数字回复

  • StandardErrReply:标准错误回复

  • IsErrorReply:判断是否为错误回复

  • ToBytes:将字符串转成RESP协议规定的格式

resp/parser/parser.go

type Payload struct {   Data resp.Reply   Err  error}type readState struct {   readingMultiLine  bool        expectedArgsCount int        msgType           byte       args              [][]byte    bulkLen           int64    }func (s *readState) finished() bool {   return s.expectedArgsCount > 0 && len(s.args) == s.expectedArgsCount}func ParseStream(reader io.Reader) <-chan *Payload {   ch := make(chan *Payload)   go parse0(reader, ch)   return ch}func parse0(reader io.Reader, ch chan<- *Payload) { ......}

Payload结构体:客服端给我们发的数据

Reply:客户端与服务端互相发的数据都称为Reply

readState结构体:

  • readingMultiLine:解析单行还是多行数据

  • expectedArgsCount:应该读取的参数个数

  • msgType:消息类型

  • args:消息内容

  • bulkLen:数据长度

finished方法:判断解析是否完成

ParseStream方法:异步解析数据后放入管道,返回管道数据

func readLine(bufReader *bufio.Reader, state *readState) ([]byte, bool, error) {   var msg []byte   var err error   if state.bulkLen == 0 {      msg, err = bufReader.ReadBytes('\n')      if err != nil {         return nil, true, err      }      if len(msg) == 0 || msg[len(msg)-2] != '\r' {         return nil, false, errors.New("protocol error: " + string(msg))      }   } else {      msg = make([]byte, state.bulkLen+2)      _, err = io.ReadFull(bufReader, msg)      if err != nil {         return nil, true, err      }      if len(msg) == 0 || msg[len(msg)-2] != '\r' || msg[len(msg)-1] != '\n' {         return nil, false, errors.New("protocol error: " + string(msg))      }      state.bulkLen = 0   }   return msg, false, nil}

readLine:一行一行的读取。读正常的行,以\n分隔。读正文中包含\r\n字符的行时,state.bulkLen加上换行符\r\n(state.bulkLen+2)

func parseMultiBulkHeader(msg []byte, state *readState) error {   var err error   var expectedLine uint64   expectedLine, err = strconv.ParseUint(string(msg[1:len(msg)-2]), 10, 32)   if err != nil {      return errors.New("protocol error: " + string(msg))   }   if expectedLine == 0 {      state.expectedArgsCount = 0      return nil   } else if expectedLine > 0 {      state.msgType = msg[0]      state.readingMultiLine = true      state.expectedArgsCount = int(expectedLine)      state.args = make([][]byte, 0, expectedLine)      return nil   } else {      return errors.New("protocol error: " + string(msg))   }}func parseBulkHeader(msg []byte, state *readState) error {   var err error   state.bulkLen, err = strconv.ParseInt(string(msg[1:len(msg)-2]), 10, 64)   if err != nil {      return errors.New("protocol error: " + string(msg))   }   if state.bulkLen == -1 { // null bulk      return nil   } else if state.bulkLen > 0 {      state.msgType = msg[0]      state.readingMultiLine = true      state.expectedArgsCount = 1      state.args = make([][]byte, 0, 1)      return nil   } else {      return errors.New("protocol error: " + string(msg))   }}

parseMultiBulkHeader:解析数组的头部,设置期望的行数和相关参数。

parseBulkHeader:解析多行字符串的头部。

func parseSingleLineReply(msg []byte) (resp.Reply, error) {   str := strings.TrimSuffix(string(msg), "\r\n")   var result resp.Reply   switch msg[0] {   case '+': // status reply      result = reply.MakeStatusReply(str[1:])   case '-': // err reply      result = reply.MakeErrReply(str[1:])   case ':': // int reply      val, err := strconv.ParseInt(str[1:], 10, 64)      if err != nil {         return nil, errors.New("protocol error: " + string(msg))      }      result = reply.MakeIntReply(val)   }   return result, nil}func readBody(msg []byte, state *readState) error {   line := msg[0 : len(msg)-2]   var err error   if line[0] == '$' {      // bulk reply      state.bulkLen, err = strconv.ParseInt(string(line[1:]), 10, 64)      if err != nil {         return errors.New("protocol error: " + string(msg))      }      if state.bulkLen <= 0 { // null bulk in multi bulks         state.args = append(state.args, []byte{})         state.bulkLen = 0      }   } else {      state.args = append(state.args, line)   }   return nil}

parseSingleLineReply:解析单行命令

readBody:读取多行的命令,如果是$开头,设置bulkLen,读取下一行时根据这个+2,不是$开头则直接添加到args

func parse0(reader io.Reader, ch chan<- *Payload) {    defer func() {       if err := recover(); err != nil {          logger.Error(string(debug.Stack()))      }   }()    bufReader := bufio.NewReader(reader)    var state readState    var err error    var msg []byte    for {       var ioErr bool       msg, ioErr, err = readLine(bufReader, &state)       if err != nil {          if ioErr {             ch <- &Payload{                Err: err,            }             close(ch)             return         }          ch <- &Payload{             Err: err,         }          state = readState{}          continue      }       if !state.readingMultiLine {          if msg[0] == '*' {             // multi bulk reply             err = parseMultiBulkHeader(msg, &state)             if err != nil {                ch <- &Payload{                   Err: errors.New("protocol error: " + string(msg)),               }                state = readState{}                continue            }             if state.expectedArgsCount == 0 {                ch <- &Payload{                   Data: &reply.EmptyMultiBulkReply{},               }                state = readState{}                continue            }         } else if msg[0] == '$' { // bulk reply             err = parseBulkHeader(msg, &state)             if err != nil {                ch <- &Payload{                   Err: errors.New("protocol error: " + string(msg)),               }                state = readState{} // reset state                continue            }             if state.bulkLen == -1 { // null bulk reply                ch <- &Payload{                   Data: &reply.NullBulkReply{},               }                state = readState{} // reset state                continue            }         } else {             // single line reply             result, err := parseSingleLineReply(msg)             ch <- &Payload{                Data: result,                Err:  err,            }             state = readState{} // reset state             continue         }      } else {          // read bulk reply          err = readBody(msg, &state)          if err != nil {             ch <- &Payload{                Err: errors.New("protocol error: " + string(msg)),            }             state = readState{} // reset state             continue         }          // if sending finished          if state.finished() {             var result resp.Reply             if state.msgType == '*' {                result = reply.MakeMultiBulkReply(state.args)            } else if state.msgType == '$' {                result = reply.MakeBulkReply(state.args[0])            }             ch <- &Payload{                Data: result,                Err:  err,            }             state = readState{}         }      }   }}

parse0:解析指令,解析完成后通过channel发出去

resp/connection/conn.go

type Connection struct {    conn net.Conn    waitingReply wait.Wait    mu sync.Mutex // 避免多个协程往客户端中写    selectedDB int}func NewConn(conn net.Conn) *Connection {    return &Connection{        conn: conn,    }}func (c *Connection) RemoteAddr() net.Addr {    return c.conn.RemoteAddr()}func (c *Connection) Close() error {    c.waitingReply.WaitWithTimeout(10 * time.Second)    _ = c.conn.Close()    return nil}func (c *Connection) Write(b []byte) error {    if len(b) == 0 {        return nil    }    c.mu.Lock()    c.waitingReply.Add(1)    defer func() {        c.waitingReply.Done()        c.mu.Unlock()    }()    _, err := c.conn.Write(b)    return err}func (c *Connection) GetDBIndex() int {    return c.selectedDB}func (c *Connection) SelectDB(dbNum int) {    c.selectedDB = dbNum}

之前写的EchoHandler是用户传过来什么,我们传回去什么。现在要写一个RespHandler来代替EchoHandler,让解析器来解析。RespHandler中要有一个管理客户端连接的结构体Connection。

Connection:客户端连接,在协议层的handler中会用到

resp/handler/handler.go

var (   unknownErrReplyBytes = []byte("-ERR unknown\r\n"))type RespHandler struct {   activeConn sync.Map   db         databaseface.Database   closing    atomic.Boolean}func MakeHandler() *RespHandler {   var db databaseface.Database   db = database.NewEchoDatabase()   return &RespHandler{      db: db,   }}func (h *RespHandler) closeClient(client *connection.Connection) {   _ = client.Close()   h.db.AfterClientClose(client)   h.activeConn.Delete(client)}func (h *RespHandler) Handle(ctx context.Context, conn net.Conn) {   if h.closing.Get() {      // closing handler refuse new connection      _ = conn.Close()   }   client := connection.NewConn(conn)   h.activeConn.Store(client, 1)   ch := parser.ParseStream(conn)   for payload := range ch {      if payload.Err != nil {         if payload.Err == io.EOF ||            payload.Err == io.ErrUnexpectedEOF ||            strings.Contains(payload.Err.Error(), "use of closed network connection") {            // connection closed            h.closeClient(client)            logger.Info("connection closed: " + client.RemoteAddr().String())            return         }         // protocol err         errReply := reply.MakeErrReply(payload.Err.Error())         err := client.Write(errReply.ToBytes())         if err != nil {            h.closeClient(client)            logger.Info("connection closed: " + client.RemoteAddr().String())            return         }         continue      }      if payload.Data == nil {         logger.Error("empty payload")         continue      }      r, ok := payload.Data.(*reply.MultiBulkReply)      if !ok {         logger.Error("require multi bulk reply")         continue      }      result := h.db.Exec(client, r.Args)      if result != nil {         _ = client.Write(result.ToBytes())      } else {         _ = client.Write(unknownErrReplyBytes)      }   }}func (h *RespHandler) Close() error {   logger.Info("handler shutting down...")   h.closing.Set(true)   // TODO: concurrent wait   h.activeConn.Range(func(key interface{}, val interface{}) bool {      client := key.(*connection.Connection)      _ = client.Close()      return true   })   h.db.Close()   return nil}

RespHandler:和之前的echo类似,加了核心层的db.exec执行解析的指令

interface/database/database.go

type CmdLine = [][]bytetype Database interface {Exec(client resp.Connection, args [][]byte) resp.ReplyAfterClientClose(c resp.Connection)Close()}type DataEntity struct {Data interface{}}

Exec:核心层的执行

AfterClientClose:关闭之后的善后方法

CmdLine:二维字节数组的指令别名

DataEntity:表示Redis的数据,包括string, list, set等等

database/echo_database.go

type EchoDatabase struct {}func NewEchoDatabase() *EchoDatabase {   return &EchoDatabase{}}func (e EchoDatabase) Exec(client resp.Connection, args [][]byte) resp.Reply {   return reply.MakeMultiBulkReply(args)}func (e EchoDatabase) AfterClientClose(c resp.Connection) {   logger.Info("EchoDatabase AfterClientClose")}func (e EchoDatabase) Close() {   logger.Info("EchoDatabase Close")}

echo_database:测试协议层

Exec:指令解析后,再使用MakeMultiBulkReply包装一下返回去

main.go

err := tcp.ListenAndServeWithSignal(   &tcp.Config{      Address: fmt.Sprintf("%s:%d",         config.Properties.Bind,         config.Properties.Port),   },   handler.MakeHandler())if err != nil {   logger.Error(err)}

main改成刚才写的:handler.MakeHandler()

关于“基于Golang如何实现Redis协议解析器”这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对“基于Golang如何实现Redis协议解析器”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注编程网GO频道。

您可能感兴趣的文档:

--结束END--

本文标题: 基于Golang如何实现Redis协议解析器

本文链接: https://www.lsjlt.com/news/353166.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 基于Golang实现Redis协议解析器
    目录RESP协议本文实现Redis的协议层,协议层负责解析指令,然后将指令交给核心database执行 echo database用来测试协议层的代码 https://github.com/csgopher/go-red...
    99+
    2023-03-24
    Golang编写Redis协议解析器 Golang Redis协议解析器 Golang Redis协议解析 Golang Redis
  • 基于Golang如何实现Redis协议解析器
    这篇文章主要介绍了基于Golang如何实现Redis协议解析器的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇基于Golang如何实现Redis协议解析器文章都会有所收获,下面我们一起来看看吧。RESP协议RES...
    99+
    2023-07-05
  • Golang 实现Redis 协议解析器的解决方案
    本文是 《用 Golang 实现一个 Redis》系列文章第二篇,本文将分别介绍Redis 通信协议 以及 协议解析器 的实现,若您对协议有所了解可以直接阅读协议解析器部分。 Red...
    99+
    2022-11-11
  • Golang 实现Redis 协议解析器的解决方案
    本文是 《用 golang 实现一个 Redis》系列文章第二篇,本文将分别介绍Redis 通信协议 以及 协议解析器 的实现,若您对协议有所了解可以直接阅读协议解析器部分。 Redis 通信协议 Redis 自 2.0...
    99+
    2022-10-25
  • Redis RESP协议如何实现
    本文小编为大家详细介绍“Redis RESP协议如何实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“Redis RESP协议如何实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深...
    99+
    2022-10-19
  • 如何分析基于redis分布式锁实现秒杀
    本篇文章为大家展示了如何分析基于redis分布式锁实现秒杀,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。业务场景所谓秒杀,从业务角度看,是短时间内多个用户“争抢”资源,这里的资源在大部分秒杀场景里是...
    99+
    2023-06-02
  • Java基于quasar如何实现协程池
    这篇文章主要介绍了Java基于quasar如何实现协程池,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。业务场景:golang与swoole都拥抱了协程,在同任务并发数量下,协程可比线程多几倍。所以最近在查询java时了解...
    99+
    2023-07-02
  • Springboot基于Redisson如何实现Redis分布式可重入锁源码解析
    这篇文章主要介绍了Springboot基于Redisson如何实现Redis分布式可重入锁源码解析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。一、前言我们在实现使用Redi...
    99+
    2023-06-29
  • 基于Redis的限流器的实现(示例讲解)
    1 概述 系统中的接口通常都有限流,比如 70次/秒 ,如何保证我们的接口的调用次数在超过第三方接口限流的时候快速失败呢?这时候就需要限流器了。下面是笔者用redis实现限流器的流程图。 2 代码 ...
    99+
    2022-10-18
  • 基于Redis如何实现阻塞队列
    这篇文章主要介绍“基于Redis如何实现阻塞队列”,在日常操作中,相信很多人在基于Redis如何实现阻塞队列问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”基于Redis如何实现阻塞队列”的疑惑有所帮助!接下来...
    99+
    2023-06-22
  • nginx如何搭建基于http协议的视频点播服务器
    本篇文章给大家分享的是有关nginx如何搭建基于http协议的视频点播服务器,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。nginx搭建基于http协议的视频点播服务器1,下载...
    99+
    2023-06-06
  • 如何使用go net实现简单的redis通信协议
    这篇文章主要为大家展示了“如何使用go net实现简单的redis通信协议”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“如何使用go net实现简单的redis通信协议”这篇...
    99+
    2023-06-21
  • Springboot基于Redisson实现Redis分布式可重入锁源码解析
    目录一、前言二、为什么使用Redisson1. 我们打开官网2. 我们可以看到官方让我们去使用其他3. 打开官方推荐4. 找到文档三、Springboot整合Redisson1. 导...
    99+
    2022-11-13
  • 基于Spring Cache如何实现Caffeine+Redis二级缓存
    这篇文章主要为大家展示了“基于Spring Cache如何实现Caffeine+Redis二级缓存”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“基于Spring Cache如...
    99+
    2023-06-29
  • java基于redis有序集合如何实现排行榜
    小编给大家分享一下java基于redis有序集合如何实现排行榜,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!前言排行榜作为互联网...
    99+
    2022-10-18
  • 基于redis分布式锁如何实现秒杀功能
    这篇文章主要介绍了基于redis分布式锁如何实现秒杀功能,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。业务场景所谓秒杀,从业务角度看,是短时...
    99+
    2022-10-18
  • Java容器中如何使用Django框架实现HTTP协议?
    Django是一个基于Python语言的Web框架,它可以帮助开发者快速构建出具有良好可维护性的Web应用程序。而Java容器是指能够运行Java应用程序的环境,如Tomcat、Jetty等。在本文中,我们将探讨如何在Java容器中使用D...
    99+
    2023-06-15
    http django 容器
  • Java如何实现基于用户的协同过滤推荐算法
    小编给大家分享一下Java如何实现基于用户的协同过滤推荐算法,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!协同过滤简单来说是利用某兴趣相投、拥有共同经验之群体的喜好来推荐用户感兴趣的信息,个人通过合作的机制给予信息相当程度...
    99+
    2023-05-30
    java
  • 基于C#如何实现端口扫描器
    这篇文章给大家分享的是有关基于C#如何实现端口扫描器的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一、新建项目并设置界面新建项目:选择Windows窗体项目应用(.Net Framework):设置项目名和路径:...
    99+
    2023-06-21
  • 基于C#如何实现屏幕取色器
    本篇内容主要讲解“基于C#如何实现屏幕取色器”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“基于C#如何实现屏幕取色器”吧!实践过程效果代码public partial cla...
    99+
    2023-07-04
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作