【协议分析】rpcx网络协议分析之kcp数据传输

本文涉及的产品
数据传输服务 DTS,数据迁移 small 3个月
推荐场景:
MySQL数据库上云
简介: 【协议分析】rpcx网络协议分析之kcp数据传输

前言:

KCP是一个可靠的协议,它的延迟率比起tcp低很多。在游戏和视频推流领域广泛使用。

在TCP是为流量设计的(每秒内可以传输多少KB的数据),讲究的是充分利用带宽。而 KCP是为流速设计的(单个数据包从一端发送到一端需要多少时间),以10%-20%带宽浪费的代价换取了比 TCP快30%-40%的传输速度。

文章中内容解析了整个kcp的传输数据数据包组成。

系统:mac OS 10.14.3

golang: go1.12 darwin/amd64

抓包工具:tcpdump

kcp协议介绍

KCP是一个快速可靠协议,能以比 TCP浪费10%-20%的带宽的代价,换取平均延迟降低 30%-40%,且最大延迟降低三倍的传输效果。纯算法实现,并不负责底层协议(如UDP)的收发,需要使用者自己定义下层数据包的发送方式,以 callback的方式提供给 KCP。连时钟都需要外部传递进来,内部不会有任何一次系统调用。

(一)rpcx的kcp实现源码

代码可以在github中获取:

https://github.com/rpcx-ecosystem/rpcx-examples3/tree/master/kcp

client端:

package main

import (
  "context"
  "crypto/sha1"
  "flag"
  "fmt"
  "log"
  "net"
  "time"

  example "github.com/rpcx-ecosystem/rpcx-examples3"
  "github.com/smallnest/rpcx/client"
  kcp "github.com/xtaci/kcp-go"
  "golang.org/x/crypto/pbkdf2"
)

var (
  addr = flag.String("addr", "localhost:8972", "server address")
)

const cryptKey = "rpcx-key"
const cryptSalt = "rpcx-salt"

funcmain() {
  flag.Parse()

  pass := pbkdf2.Key([]byte(cryptKey), []byte(cryptSalt), 4096, 32, sha1.New)
  bc, _ := kcp.NewAESBlockCrypt(pass)
  option := client.DefaultOption
  option.Block = bc

  d := client.NewPeer2PeerDiscovery("kcp@"+*addr, "")
  xclient := client.NewXClient("Arith", client.Failtry, client.RoundRobin, d, option)
  defer xclient.Close()

  // plugin
  cs := &ConfigUDPSession{}
  pc := client.NewPluginContainer()
  pc.Add(cs)
  xclient.SetPlugins(pc)

  args := &example.Args{
     A: 10,
     B: 20,
  }

  start := time.Now()
  //for i := 0; i < 10000; i++ {
  // reply := &example.Reply{}
  // err := xclient.Call(context.Background(), "Mul", args, reply)
  // if err != nil {
  //    log.Fatalf("failed to call: %v", err)
  // }
  // //log.Printf("%d * %d = %d", args.A, args.B, reply.C)
  //}
  reply := &example.Reply{}
  err := xclient.Call(context.Background(), "Mul", args, reply)
  if err != nil {
     log.Fatalf("failed to call: %v", err)
  }
  log.Printf("%d * %d = %d", args.A, args.B, reply.C)
  dur := time.Since(start)
  qps := 10000 * 1000 / int(dur/time.Millisecond)
  fmt.Printf("qps: %d call/s", qps)
}

type ConfigUDPSession struct{}

func(p *ConfigUDPSession)ConnCreated(conn net.Conn)(net.Conn, error) {
  session, ok := conn.(*kcp.UDPSession)
  if !ok {
     return conn, nil
  }

  session.SetACKNoDelay(true)
  session.SetStreamMode(true)
  return conn, nil
}

server端:

package main

import (
  "crypto/sha1"
  "flag"
  "net"

  example "github.com/rpcx-ecosystem/rpcx-examples3"
  "github.com/smallnest/rpcx/server"
  kcp "github.com/xtaci/kcp-go"
  "golang.org/x/crypto/pbkdf2"
)

var (
  addr = flag.String("addr", "localhost:8972", "server address")
)

const cryptKey = "rpcx-key"
const cryptSalt = "rpcx-salt"

funcmain() {
  flag.Parse()

  pass := pbkdf2.Key([]byte(cryptKey), []byte(cryptSalt), 4096, 32, sha1.New)
  bc, err := kcp.NewAESBlockCrypt(pass)
  if err != nil {
     panic(err)
  }

  s := server.NewServer(server.WithBlockCrypt(bc))
  s.RegisterName("Arith", new(example.Arith), "")

  cs := &ConfigUDPSession{}
  s.Plugins.Add(cs)

  err = s.Serve("kcp", *addr)
  if err != nil {
     panic(err)
  }
}

type ConfigUDPSession struct{}

func(p *ConfigUDPSession)HandleConnAccept(conn net.Conn)(net.Conn, bool) {
  session, ok := conn.(*kcp.UDPSession)
  if !ok {
     return conn, true
  }

  session.SetACKNoDelay(true)
  session.SetStreamMode(true)
  return conn, true
}

client端和server端使用了AESBlockCrypt加密,密钥对应const cryptKey = “rpcx-key” 和 const cryptSalt = “rpcx-salt”。

kcp支持:Salsa20、sm4、Twofish、Triple_DES、CAST-128、Blowfish、AES、TEA、XTEA等加密方式。

(二)抓包

还是抓回环链路,捕获8972端口数据

tcpdump -i lo0 port 8972 -S -XX

这一块不会抓包的可以看看《【协议分析】rpcx网络协议分析之kcp》 那篇文章。

或者去官网学习下:

www.us.tcpdump.org/manpages/tcpdump.1.html

数据包数据如下:

22:00:28.410752 IP localhost.63488 > localhost.8972: UDP, length 115
0x0000:  0200 0000 4500 008f 3b55 0000 4011 0000  ....E...;U..@...
0x0010:  7f00 0001 7f00 0001 f800 230c 007b fe8e  ..........#..{..
0x0020:  dd9f 8ab0 5984 b839 5bd3 6dbe c94f ac6a  ....Y..9[.m..O.j
0x0030:  8429 1745 cd66 7630 47d7 289d d743 84f7  .).E.fv0G.(..C..
0x0040:  9726 b1f2 d45a 5092 b27f 1135 2b12 4ad9  .&...ZP....5+.J.
0x0050:  cf0b 49e2 f611 3a7a cf06 f328 3ca6 42fc  ..I...:z...(<.B.
0x0060:  b9f2 1930 0dfd 5269 5ef7 b439 2d1e 9a92  ...0..Ri^..9-...
0x0070:  64db 2eaf 95a6 95dc 3af3 eb46 5294 252e  d.......:..FR.%.
0x0080:  1d3c e49c a8f9 e6d1 0bd0 cc58 fe71 6aa5  .<.........X.qj.
0x0090:  24b7 e2                                  $..
22:00:28.411010 IP localhost.8972 > localhost.63488: UDP, length 52
0x0000:  0200 0000 4500 0050 3e9c 0000 4011 0000  ....E..P>...@...
0x0010:  7f00 0001 7f00 0001 230c f800 003c fe4f  ........#....<.O
0x0020:  52fb 130d 850f 90d8 1b04 7a7e 855b e4b1  R.........z~.[..
0x0030:  4173 c282 a462 f211 6aaf 18a2 92e5 5c4e  As...b..j.....\N
0x0040:  a738 c934 c707 71ba c72f 19e1 4caf 56ec  .8.4..q../..L.V.
0x0050:  5b1f e7d4                                [...
22:00:28.411180 IP localhost.8972 > localhost.63488: UDP, length 104
0x0000:  0200 0000 4500 0084 0e44 0000 4011 0000  ....E....D..@...
0x0010:  7f00 0001 7f00 0001 230c f800 0070 fe83  ........#....p..
0x0020:  a99c 6f57 4549 66bb c8b9 5f72 07c7 d697  ..oWEIf..._r....
0x0030:  3656 afd3 2eed 21cd 69b1 321b 85cf 3840  6V....!.i.2...8@
0x0040:  2ec9 9aea 8e3c 4cbe bd2d ed4f b357 2106  ..... 0x0050:  0528 e99f b29d 0e85 3a3d a4e9 b398 e8a2  .(......:=......
0x0060:  18ac 06a1 2672 87e5 72a8 0e54 4055 0980  ....&r..r..T@U..
0x0070:  e3de cc33 788e 41ad e8b7 2289 75da 5e07  ...3x.A...".u.^.
0x0080:  276e 329a 71fa 4f0a                      'n2.q.O.
22:00:28.411261 IP localhost.63488 > localhost.8972: UDP, length 52
0x0000:  0200 0000 4500 0050 06a8 0000 4011 0000  ....E..P....@...
0x0010:  7f00 0001 7f00 0001 f800 230c 003c fe4f  ..........#..<.O
0x0020:  8408 27a6 f032 f202 f717 9f88 8e8e c915  ..'..2..........
0x0030:  f1c5 12c7 0e57 5152 c471 de8c 5a5a f388  .....WQR.q..ZZ..
0x0040:  9391 d2d6 5f9f 95e8 7518 e02e a8f1 7536  ...._...u.....u6
0x0050:  74e4 370b                                t.7.

(三)解析UDP协议包与KCP协议包

3.1 UDP数据解析

解析数据包内容

22:00:28.410752 IP localhost.63488 > localhost.8972: UDP, length 115
0x0000:  0200 0000 4500 008f 3b55 0000 4011 0000  ....E...;U..@...
0x0010:  7f00 0001 7f00 0001 f800 230c 007b fe8e  ..........#..{..
0x0020:  dd9f 8ab0 5984 b839 5bd3 6dbe c94f ac6a  ....Y..9[.m..O.j
0x0030:  8429 1745 cd66 7630 47d7 289d d743 84f7  .).E.fv0G.(..C..
0x0040:  9726 b1f2 d45a 5092 b27f 1135 2b12 4ad9  .&...ZP....5+.J.
0x0050:  cf0b 49e2 f611 3a7a cf06 f328 3ca6 42fc  ..I...:z...(<.B.
0x0060:  b9f2 1930 0dfd 5269 5ef7 b439 2d1e 9a92  ...0..Ri^..9-...
0x0070:  64db 2eaf 95a6 95dc 3af3 eb46 5294 252e  d.......:..FR.%.
0x0080:  1d3c e49c a8f9 e6d1 0bd0 cc58 fe71 6aa5  .<.........X.qj.
0x0090:  24b7 e2                                  $..

网络传输中的字节码都是大端读取的,这个是为了兼容CPU架构。所以在编程中需要转换网络字节码,其实就是小端转成大端。

就像我们的02 00 。

/UDP头定义,共8个字节/

typedefstruct _UDP_HEADER
{

unsignedshort m_usSourPort;       // 源端口号16bit
unsignedshort m_usDestPort;       // 目的端口号16bit
unsignedshort m_usLength;        // 数据包长度16bit
unsignedshort m_usCheckSum;      // 校验和16bit
}__attribute__((packed))UDP_HEADER, *PUDP_HEADER;

3.2 KCP数据解析

根据上面分析我们可以得知,我们的数据部分应该是从第33个字节开始。原因:4字节(回环链路层) + 20字节(IP层) + 8字节(UDP层) = 32字节。

刚好是如下部分:

   0x0020:  dd9f 8ab0 5984 b839 5bd3 6dbe c94f ac6a  ....Y..9[.m..O.j
0x0030:  8429 1745 cd66 7630 47d7 289d d743 84f7  .).E.fv0G.(..C..
0x0040:  9726 b1f2 d45a 5092 b27f 1135 2b12 4ad9  .&...ZP....5+.J.
0x0050:  cf0b 49e2 f611 3a7a cf06 f328 3ca6 42fc  ..I...:z...(<.B.
0x0060:  b9f2 1930 0dfd 5269 5ef7 b439 2d1e 9a92  ...0..Ri^..9-...
0x0070:  64db 2eaf 95a6 95dc 3af3 eb46 5294 252e  d.......:..FR.%.
0x0080:  1d3c e49c a8f9 e6d1 0bd0 cc58 fe71 6aa5  .<.........X.qj.
0x0090:  24b7 e2                                  $..dd9f 8ab0 5984 b839 5bd3 6dbe c94f ac6a

因为我们是针对与传输数据时使用了AESBlockCrypt加密,所以我们可以编写一段go代码对数据包部分做一下解密工作。

代码如下:

package main

import (
  "crypto/sha1"
  "fmt"
  "github.com/xtaci/kcp-go"
  "golang.org/x/crypto/pbkdf2"
)
const cryptKey = "rpcx-key"
const cryptSalt = "rpcx-salt"
funcmain() {
  pass := pbkdf2.Key([]byte(cryptKey), []byte(cryptSalt), 4096, 32, sha1.New) //加密令牌
  bc, err := kcp.NewAESBlockCrypt(pass)
  if err != nil {
     fmt.Println(err)
     return
  }

  //数据包
  var dst []byte = []byte{
        0xdd, 0x9f, 0x8a, 0xb0, 0x59, 0x84, 0xb8, 0x39, 0x5b, 0xd3, 0x6d, 0xbe, 0xc9, 0x4f, 0xac, 0x6a,
        0x84, 0x29, 0x17, 0x45, 0xcd, 0x66, 0x76, 0x30, 0x47, 0xd7, 0x28, 0x9d, 0xd7, 0x43, 0x84, 0xf7,
        0x97, 0x26, 0xb1, 0xf2, 0xd4, 0x5a, 0x50, 0x92, 0xb2, 0x7f, 0x11, 0x35, 0x2b, 0x12, 0x4a, 0xd9,
        0xcf, 0x0b, 0x49, 0xe2, 0xf6, 0x11, 0x3a, 0x7a, 0xcf, 0x06, 0xf3, 0x28, 0x3c, 0xa6, 0x42, 0xfc,
        0xb9, 0xf2, 0x19, 0x30, 0x0d, 0xfd, 0x52, 0x69, 0x5e, 0xf7, 0xb4, 0x39, 0x2d, 0x1e, 0x9a, 0x92,
        0x64, 0xdb, 0x2e, 0xaf, 0x95, 0xa6, 0x95, 0xdc, 0x3a, 0xf3, 0xeb, 0x46, 0x52, 0x94, 0x25, 0x2e,
        0x1d, 0x3c, 0xe4, 0x9c, 0xa8, 0xf9, 0xe6, 0xd1, 0x0b, 0xd0, 0xcc, 0x58, 0xfe, 0x71, 0x6a, 0xa5,
        0x24, 0xb7, 0xe2}


  dec := make([]byte, len(dst))
  fmt.Println("加密数据:")
  for i := 0;i < len(dst) ;i++  {
     fmt.Printf("%02x ", dst[i])
     if (i + 1) % 16 == 0{
        fmt.Printf("\n")
     }
  }
 
  bc.Decrypt(dec,dst) //解密
 
  fmt.Println("\n")
  fmt.Println("解密数据:")
  for i := 0;i < len(dec) ;i++  {
     fmt.Printf("%02x ", dec[i])
     if (i + 1) % 16 == 0{
        fmt.Printf("\n")
     }
  }
  fmt.Println("\n")
}

输入数据如下:

加密数据:
dd 9f 8a b0 59 84 b8 39 5b d3 6d be c9 4f ac 6a
84 29 17 45 cd 66 76 30 47 d7 28 9d d7 43 84 f7
97 26 b1 f2 d4 5a 50 92 b2 7f 11 35 2b 12 4a d9
cf 0b 49 e2 f6 11 3a 7a cf 06 f3 28 3c a6 42 fc
b9 f2 19 30 0d fd 52 69 5e f7 b4 39 2d 1e 9a 92
64 db 2e af 95 a6 95 dc 3a f3 eb 46 52 94 25 2e
1d 3c e4 9c a8 f9 e6 d1 0b d0 cc 58 fe 71 6a a5
24 b7 e2

解密数据:
ac d0 ee 1a 3d 48 23 6c 74 27 e2 a4 73 34 ef 78
b9 f2 0f 28 00 00 00 00 f1 00 59 00 dd 36 c5 20
51 00 20 00 0a 00 00 00 00 00 00 00 00 00 00 00
3f 00 00 00 08 00 00 30 00 00 00 00 00 00 00 00
00 00 00 2f 00 00 00 05 41 72 69 74 68 00 00 00
03 4d 75 6c 00 00 00 00 00 00 00 17 82 a1 41 d3
00 00 00 00 00 00 00 0a a1 42 d3 00 00 00 00 00
00 00 14

KCP数据包组成如下:

图中UDP数据包,则是上面解密程序的解密数据字节。

kcp协议部分。segment 结构体:

type segment struct {
conv uint32
// 发送端与接收端通信时的匹配数字,发送端发送的数据包中此值与接收端的conv值匹配一致时,接收端才会接受此包

cmd uint8
// 改数据包的协议号,协议号有以下枚举:
// IKCP_CMD_PUSH = 81 // cmd: push data,数据包
// IKCP_CMD_ACK = 82 // cmd: ack,确认包,告诉对方收到数据包
// IKCP_CMD_WASK = 83 // cmd: window probe (ask),询问远端滑动窗口的大小
// IKCP_CMD_WINS = 84 // cmd: window size (tell),告知远端滑动窗口的大小

frg uint8
// 分帧号,由于udp传输有数据包大小的限制,因此,应用层一个数据包可能被分为多个udp包

制自己接下来发送数据的大小wnd uint16
// 滑动窗口的大小
// 当Segment做为发送数据时,此wnd为本机滑动窗口大小,用于告诉远端自己窗口剩余多少
// 当Segment做为接收到数据时,此wnd为远端滑动窗口大小,本机知道了远端窗口剩余多少后,可以控

ts uint32
// timestamp , 当前Segment发送时的时间戳

sn uint32
// Sequence Number,Segment数据包的编号

una uint32
// una即unacknowledged,未确认数据包的编号,表示此编号前的所有包都已收到了。

rto uint32
// rto即Retransmission TimeOut,即超时重传时间,在发送出去时根据之前的网络情况进行设置

xmit uint32
// 基本类似于Segment发送的次数,每发送一次会自加一。用于统计该Segment被重传了几次,用于参考,进行调节

resendts uint32
// 即resend timestamp , 指定重发的时间戳,当当前时间超过这个时间时,则再重发一次这个包。

fastack uint32  
// 用于以数据驱动的快速重传机制;
// len uint32 c++版本有数据包的数据长度,go版本无此字段

data []byte// 协议数据的具体内容
}

kcp.go,如下为segment协议包组成源码如下:

func(seg *segment)encode(ptr []byte) []byte {
  ptr = ikcp_encode32u(ptr, seg.conv) //发送端与接收端通信时的匹配数字,进行加密
  ptr = ikcp_encode8u(ptr, seg.cmd)   //数据包的协议号,进行加密
  ptr = ikcp_encode8u(ptr, seg.frg)   //分帧号,进行加密
  ptr = ikcp_encode16u(ptr, seg.wnd)  //数据包窗口大小,进行加密
  ptr = ikcp_encode32u(ptr, seg.ts)   //当前Segment发送时的时间戳,进行加密
  ptr = ikcp_encode32u(ptr, seg.sn)   //Segment数据包的编号,进行加密
  ptr = ikcp_encode32u(ptr, seg.una)  //未确认数据包的编号,进行加密
  ptr = ikcp_encode32u(ptr, uint32(len(seg.data))) //数据长度,进行加密
  atomic.AddUint64(&DefaultSnmp.OutSegs, 1)
  return ptr
}

代码中的的ikcp_encode32u其实是调用了PutUint32进行加密。

sess.go,数据包输入函数:
func(l *Listener)packetInput(data []byte, addr net.Addr) {
  dataValid := false
  if l.block != nil { //判断是否加密
     l.block.Decrypt(data, data) //解密数据包
     data = data[nonceSize:]     //切片16字节,这部分对应的就是图中的nonce。
     checksum := crc32.ChecksumIEEE(data[crcSize:])  //获得crc checksum,data原始地址 + 16的4个字节
     if checksum == binary.LittleEndian.Uint32(data) {
        data = data[crcSize:]   //切片4字节,data原始地址 + 16 + 4
        dataValid = true
     } else {
        atomic.AddUint64(&DefaultSnmp.InCsumErrors, 1)
     }
  } elseif l.block == nil {
     dataValid = true
  }

  if dataValid {
     l.sessionLock.Lock()
     s, ok := l.sessions[addr.String()]
     l.sessionLock.Unlock()

     if !ok { // new address:port
        iflen(l.chAccepts) < cap(l.chAccepts) { // do not let the new sessions overwhelm accept queue
           var conv uint32
           convValid := false
           if l.fecDecoder != nil {  //判断开启FEC
              isfec := binary.LittleEndian.Uint16(data[4:]) //获取2字节FEC type,data原始地址 + 16 + 4 + 4
              if isfec == typeData { //判断是否为Plus2,标志位位0xf1
                 conv = binary.LittleEndian.Uint32(data[fecHeaderSizePlus2:])  //获取conv ,data原始地址 + 16 + 4 + 8
                 convValid = true
              }
           } else {
              conv = binary.LittleEndian.Uint32(data) //没有开启FEC时直接,获取conv = data原始地址 + 4
              convValid = true
           }
           //...省略
     } else {
        s.kcpInput(data)
     }
  }
}

根据以上代码我们可以看到获得数据时起时做了一个解密过程Uint32。

图中为获取conv计算规则

总结:

1.kcp支持加密方式:Salsa20、sm4、Twofish、Triple_DES、CAST-128、Blowfish、AES、TEA、XTEA等加密方式。

2.kcp传输数据是通过udp协议。

3.kcp协议部分的segment采用了PutUint和Uint32进行加解密。

参考资料:

tcpdump命令详解:

www.us.tcpdump.org/manpages/tcpdump.1.html

kcp

https://github.com/skywind3000/kcp

AESBlockCrypt

https://en.wikipedia.org/wiki/Advanced_Encryption_Standard

Salsa20

https://en.wikipedia.org/wiki/Salsa20

sm4

https://github.com/tjfoc/gmsm/tree/master/sm4

Twofish

https://en.wikipedia.org/wiki/Twofish

Triple_DES

https://en.wikipedia.org/wiki/Triple_DES

Blowfish

https://en.wikipedia.org/wiki/Blowfish_(cipher)

TEA

https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm

XTEA

https://en.wikipedia.org/wiki/XTEA

相关实践学习
RocketMQ一站式入门使用
从源码编译、部署broker、部署namesrv,使用java客户端首发消息等一站式入门RocketMQ。
Sqoop 企业级大数据迁移方案实战
Sqoop是一个用于在Hadoop和关系数据库服务器之间传输数据的工具。它用于从关系数据库(如MySQL,Oracle)导入数据到Hadoop HDFS,并从Hadoop文件系统导出到关系数据库。 本课程主要讲解了Sqoop的设计思想及原理、部署安装及配置、详细具体的使用方法技巧与实操案例、企业级任务管理等。结合日常工作实践,培养解决实际问题的能力。本课程由黑马程序员提供。
相关文章
|
2天前
|
缓存 网络协议 网络架构
【计算机网络】第三章 数据链路层(MAC地址 IP地址 ARP协议)
【计算机网络】第三章 数据链路层(MAC地址 IP地址 ARP协议)
|
9天前
|
机器学习/深度学习 数据可视化 测试技术
深度学习:Keras使用神经网络进行简单文本分类分析新闻组数据
深度学习:Keras使用神经网络进行简单文本分类分析新闻组数据
21 0
|
10天前
|
Python 数据可视化 索引
PYTHON用GARCH、离散随机波动率模型DSV模拟估计股票收益时间序列与蒙特卡洛可视化
PYTHON用GARCH、离散随机波动率模型DSV模拟估计股票收益时间序列与蒙特卡洛可视化
24 0
PYTHON用GARCH、离散随机波动率模型DSV模拟估计股票收益时间序列与蒙特卡洛可视化
|
10天前
|
机器学习/深度学习 算法 数据可视化
用SPSS Modeler的Web复杂网络对所有腧穴进行关联规则分析3
用SPSS Modeler的Web复杂网络对所有腧穴进行关联规则分析3
17 0
用SPSS Modeler的Web复杂网络对所有腧穴进行关联规则分析3
|
10天前
|
存储 算法 前端开发
R语言中贝叶斯网络(BN)、动态贝叶斯网络、线性模型分析错颌畸形数据
R语言中贝叶斯网络(BN)、动态贝叶斯网络、线性模型分析错颌畸形数据
32 0
|
10天前
|
网络协议 Java API
深度剖析:Java网络编程中的TCP/IP与HTTP协议实践
【4月更文挑战第17天】Java网络编程重在TCP/IP和HTTP协议的应用。TCP提供可靠数据传输,通过Socket和ServerSocket实现;HTTP用于Web服务,常借助HttpURLConnection或Apache HttpClient。两者结合,构成网络服务基础。Java有多种高级API和框架(如Netty、Spring Boot)简化开发,助力高效、高并发的网络通信。
|
10天前
|
数据可视化 网络可视化
R语言混合图形模型MGM的网络可预测性分析
R语言混合图形模型MGM的网络可预测性分析
10 0
|
11天前
|
算法 定位技术 Windows
R语言最大流最小割定理和最短路径算法分析交通网络流量拥堵问题
R语言最大流最小割定理和最短路径算法分析交通网络流量拥堵问题
16 4
|
11天前
|
存储 网络协议 安全
15.网络协议-Radius协议
15.网络协议-Radius协议
|
11天前
|
存储 网络协议 网络安全
14.网络协议-邮件协议
14.网络协议-邮件协议