websocket协议以及在gin中的应用

目录

  • websocket协议简介
    • WebSocket 协议的来源
      • 短轮询
        • 本质
        • 实现
        • 应用场景
        • 优缺点
      • 长轮询
        • 本质
        • 实现
        • 应用场景
        • 优缺点
      • WebSocket协议
        • websocket定义及与HTPP的关系
          • 相对于http,websocket的优点
        • [http 101 状态码](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Protocol_upgrade_mechanism#Common_uses_for_this_mechanism)(告诉服务器我要升级请求协议)
        • 实现原理
        • 应用场景
        • websocket的消息格式
          • 关闭状态码
          • 关闭帧
        • 为什么不直接用tcp
  • go使用websocket
    • 包的选择
    • 在线调试网站
    • 应用
      • 简单实现websocket发送消息
      • 简单聊天室
  • Q&A?
    • 在Golang中存储WebSocket连接的最佳方法是什么?
      • 使用map存储
      • 使用redis存储(待补充)
      • 不存储
    • 处理离线信息
      • Redis的Sorted Set存储
      • 使用消息队列(Message Queue)来处理离线消息
      • 能否用redis的发布订阅实现?

websocket协议简介

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。WebSocket 通信协议于 2011 年被 IETF 定为标准 RFC6455,并由 RFC7936 补充规范。(RFC 是一系列以编号排定的文件,它由一系列草案和标准组成。几乎所有互联网通信协议均记录在 RFC 中,例如 HTTP 协议标准、 WebSocket 协议标准、Base64 编码规范等。除此之外,RFC 还加入了许多论题。)

WebSocket 协议的来源

在 WebSocket 协议出现以前,网站通常使用轮询来实现类似“数据实时更新”这样的效果。要注意的是,这里的“数据实时更新”是带有引号的,这表示并不是真正意义上的数据实时更新。轮询指的是在特定的时间间隔内,由客户端主动向服务端发起 HTTP 请求,以确认是否有新数据的行为。下图描述了轮询的过程:

在这里插入图片描述
这种一问一答的方式有着明显的缺点,即浏览器需要不断的向服务器发出请求。由于 HTTP 请求包含较长的头部信息(例如 User-Agent、Referer 和 Host 等),其中真正有效的数据可能只是很小的一部分,所以这样会浪费很多的带宽资源。

短轮询

本质

浏览器发送HTTP request从服务器获取最新的数据.

实现

特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP request,服务器不管有没有最新数据都直接响应并关闭连接。

应用场景

传统的web通信模式。后台处理数据,需要一定时间,前端想要知道后端的处理结果,就要不定时的向后端发出请求以获得最新情况。

优缺点

优点:

与普通HTTP请求无异,前后端程序编写较为容易

缺点:

  1. 请求中有大半是无用,难于维护,浪费带宽和服务器资源;

  2. 响应的结果没有顺序(因为是异步请求,当发送的请求没有返回结果的时候,后面的请求又被发送。而此时如果后面的请求比前面的请 求要先返回结果,那么当前面的请求返回结果数据时已经是过时无效的数据了).

长轮询

本质

浏览器发送HTTP request从服务器获取最新的数据.

实现

客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。

应用场景

聊天网页,WebQQ,FacebookIM等.

优缺点

优点:

在无新数据的情况下不会频繁的请求,耗费资源小。

缺点:

服务器阻塞连接会消耗资源,返回数据顺序无保证,难于管理维护。

WebSocket协议

HTML5规范在传统的web交互基础上为我们带来了众多的新特性,随着web技术被广泛 用于web APP的开发,这些新特性得以推广和使用,而websocket作为一种新的web通信,技术具有巨大意义。

在这里插入图片描述

websocket定义及与HTPP的关系

客户端与服务端连接成功之前,使用的通信协议是 HTTP。连接成功后,使用的才是 WebSocket 协议。

在这里插入图片描述

  1. WebSocket与Http协议一样都是基于TCP的应用层协议。
  2. 首先,客户端向服务端发出一个 HTTP 请求,请求中携带了服务端规定的信息,并在信息中表明希望将协议升级为 WebSocket。这个请求被称为升级请求,双端升级协议的整个过程叫做握手。然后服务端验证客户端发送的信息,如果符合规范则将协议替换成 WebSocket,并将升级成功的信息响应给客户端。最后,双方就可以基于 WebSocket 协议互相推送信息了。
相对于http,websocket的优点

相对于 HTTP 协议来说,WebSocket 具有开销少、实时性高、支持二进制消息传输、支持扩展和更好的压缩等优点。这些优点如下所述:

  1. 较少的开销:WebSocket 只需要一次握手,在每次传输数据时只传输数据帧即可。而 HTTP 协议下,每次请求都需要携带完整的请求头信息,例如 User-Agent、Referer 和 Host 等。所以 WebSocket 的开销相对于 HTTP 来说会少很多。

  2. 更强的实时性:由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于一问一答的 HTTP 来说,WebSocket 协议下的数据传输的延迟明显更少。

  3. 支持二进制消息传输:WebSocket 定义了二进制帧,可以更轻松地处理二进制内容。

  4. 支持扩展:开发者可以扩展协议,或者实现部分自定义的子协议。

  5. 更好的压缩:Websocket 在适当的扩展支持下,可以沿用之前内容的上下文。这样在传递类似结构的数据时,可以显著地提高压缩率。

http 101 状态码(告诉服务器我要升级请求协议)

在这里插入图片描述

101状态代码被发送作为对包括一个请求的响应"Upgrade"报头信号,该请求的接收方愿意升级到所期望的协议之一。如果"101 Switching Protocols"返回了状态码,则标头还必须包含Connection和Upgrade标头,以描述所选协议。

在这里插入图片描述

说明:

  1. WebSocket未加密连接为ws://端口80,加密后的协议为wss://端口 443.

  2. WebSocket是类似Socket的TCP长连接,一旦WebSocket连接建立后,服务器和浏览器之间可以自由的进行数据传递.

  3. WebSocket协议中浏览器与服务器是完全平等的,可以相互发送请求亦可以主动断开连接.

实现原理

请求头中重要的字段:
在这里插入图片描述

响应头中重要的字段:
在这里插入图片描述
参数说明:
在这里插入图片描述
过程:

客户端带上一段随机生成的base64码(Sec-WebSocket-Key),发给服务器。

如果服务器正好支持升级成websocket协议。就会走websocket握手流程,同时根据客户端生成的base64码,用某个公开的算法变成另一段字符串,放在HTTP响应的 Sec-WebSocket-Accept 头里,同时带上101状态码,发回给浏览器。

之后,浏览器也用同样的公开算法将base64码转成另一段字符串,如果这段字符串跟服务器传回来的字符串一致,那验证通过。
在这里插入图片描述
就这样经历了一来一回两次HTTP握手,websocket就建立完成了,后续双方就可以使用webscoket的数据格式进行通信了。

应用场景

· 即时聊天通信

· 多玩家游戏

· 在线协同编辑/编辑

· 实时数据流的拉取与推送

· 体育/游戏实况(自动寻路)

· 实时地图位置

websocket的消息格式

数据包在websocket中被叫做帧。
在这里插入图片描述

  1. opcode字段:这个是用来标志这是个什么类型的数据帧。比如:

    1. 等于1时是指text类型(string)的数据包
    2. 等于2是二进制数据类型([]byte)的数据包
    3. 等于8是关闭连接的信号
  2. payload字段:存放的是真正想要传输的数据的长度,单位是字节。比如发送的数据是字符串"111",那它的长度就是3。

    1. 如果最开始的7bit的值是 0~125,那么它就表示了 payload 全部长度,只读最开始的7个bit就完事了。
      在这里插入图片描述
    2. 如果是126(0x7E)。那它表示payload的长度范围在 126~65535 之间,接下来还需要再读16bit。这16bit会包含payload的真实长度。
      在这里插入图片描述
    3. 如果是127(0x7F)。那它表示payload的长度范围>=65536,接下来还需要再读64bit。这64bit会包含payload的长度。这能放2的64次方byte的数据,换算一下好多个TB,肯定够用了。
      在这里插入图片描述
  3. payload data字段:这里存放的就是真正要传输的数据,在知道了上面的payload长度后,就可以根据这个值去截取对应的数据。

关闭状态码

这些状态码是WebSocket协议中定义的关闭代码,用于指示连接关闭的原因。根据RFC 6455(WebSocket协议规范)第11.7节的定义,以下是一些常见的关闭代码及其含义:

  • CloseNormalClosure (1000): 正常关闭连接。
  • CloseGoingAway (1001): 终端离开,表示终端正在关闭连接,例如浏览器标签被关闭或导航离开当前页面。
  • CloseProtocolError (1002): 协议错误,表示由于协议错误导致连接关闭。
  • CloseUnsupportedData (1003): 不支持的数据,表示接收到了不支持的数据类型或格式。
  • CloseNoStatusReceived (1005): 未接收到状态码,表示连接关闭时未接收到预期的状态码。
  • CloseAbnormalClosure (1006): 异常关闭,表示连接意外关闭,无法确定具体原因。
  • CloseInvalidFramePayloadData (1007): 无效的帧载荷数据,表示接收到的帧载荷数据不符合协议规范。
  • ClosePolicyViolation (1008): 协议违规,表示违反了协议的约束或策略。
  • CloseMessageTooBig (1009): 消息过大,表示接收到的消息超过了允许的最大大小。
  • CloseMandatoryExtension (1010): 必需扩展,表示服务器要求使用一个或多个扩展,但客户端未提供。
  • CloseInternalServerErr (1011): 内部服务器错误,表示服务器在处理连接时遇到了内部错误。
  • CloseServiceRestart (1012): 服务重启,表示服务器正在重新启动。
  • CloseTryAgainLater (1013): 请稍后重试,表示服务器暂时无法处理连接,请求客户端稍后再试。
  • CloseTLSHandshake (1015): TLS握手错误,表示TLS握手过程中发生错误。

这些关闭代码可以用于指示连接关闭的原因,以便在处理WebSocket连接时做出适当的处理。

关闭帧

WebSocket的关闭状态码是通过特定的控制帧(Control Frames)在WebSocket报文中传输的。在关闭连接时,发送方会发送一个关闭帧(Close Frame),其中包含状态码。

关闭帧的结构如下:

 0                   1                   2                   30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+---------------------------------------------------------------+

关闭帧的Payload Data字段中包含了两个字节的状态码。状态码位于Payload Data字段的开始位置,紧随着Masking-key字段(如果设置了掩码)。

要读取或处理状态码,您需要解析WebSocket报文并提取关闭帧中的Payload Data字段,并从中读取两个字节的状态码。请注意,Payload Data字段中的数据是以网络字节顺序(Big Endian)表示的。

需要注意的是,关闭帧的状态码是由发送方发送给接收方的,并且接收方可以根据状态码执行适当的操作或响应。

为什么不直接用tcp

TCP协议本身就是全双工,但直接使用纯裸TCP去传输数据,会有粘包的"问题"。为了解决这个问题,上层协议一般会用消息头+消息体的格式去重新包装要发的数据。

而消息头里一般含有消息体的长度,通过这个长度可以去截取真正的消息体。

HTTP协议和大部分RPC协议,以及websocket协议,都是这样设计的。

websocket完美继承了TCP协议的全双工能力,并且还贴心的提供了解决粘包的方案。它适用于需要服务器和客户端(浏览器)频繁交互的大部分场景。比如网页/小程序游戏,网页聊天室,以及一些类似飞书这样的网页协同办公软件。

回到文章开头的问题,在使用websocket协议的网页游戏里,怪物移动以及攻击玩家的行为是服务器逻辑产生的,对玩家产生的伤害等数据,都需要由服务器主动发送给客户端,客户端获得数据后展示对应的效果。

go使用websocket

包的选择

官方包:golang.org/x/net/websocket
三方包:github.com/gorilla/websocket

其中gorilla/websocket更常用些,go的另外一个web框架iris中,就使用的是gorilla/websocket库。

下载:go get github.com/gorilla/websocket

在线调试网站

http://www.jsons.cn/websocket/
在这里插入图片描述

应用

简单实现websocket发送消息

package mainimport ("github.com/gin-gonic/gin""github.com/gorilla/websocket""log""net/http""time"
)var connectPool = make([]*websocket.Conn, 0)func main() {engine := gin.Default()upGrader := websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {return true},}engine.GET("/ws", func(context *gin.Context) {conn, err := upGrader.Upgrade(context.Writer, context.Request, nil)if err != nil {log.Println(err)http.NotFound(context.Writer, context.Request)return}log.Println("连接建立成功", conn.RemoteAddr())for {_ = conn.WriteMessage(websocket.TextMessage, []byte("hello world"))time.Sleep(time.Second)}})log.Fatalln(engine.Run("127.0.0.1:8080"))
}

简单聊天室

  1. 当有人上线时,加入UID-Socket映射
  2. 通过解析websocket的data获取用户行为,如:
    1. 当to=="0"时,代表着广播消息,遍历映射给所有人发消息
    2. 当to=="xx"时,代表着私发消息,找到对应的socket给其发消息。

演示:

  1. Generalzy3广播大家好
    在这里插入图片描述
    其他两人均收到了,
    在这里插入图片描述
    在这里插入图片描述
  2. Generalzy2对Generalzy3私发雪豹闭嘴,只有Generalzy3收到了
package mainimport ("encoding/json""fmt""github.com/gin-gonic/gin""github.com/gorilla/websocket""log""net/http"
)var ConnUIDMapping = make(map[string]*websocket.Conn)func ParseInfo(msg []byte) (map[string]string, error) {result := make(map[string]string)err := json.Unmarshal(msg, &result)if err != nil {return nil, err}return result, nil
}func main() {engine := gin.Default()upGrader := websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {return true},}engine.GET("/Online", func(context *gin.Context) {username := context.Query("username")conn, err := upGrader.Upgrade(context.Writer, context.Request, nil)if err != nil {log.Println(err)http.NotFound(context.Writer, context.Request)return}ConnUIDMapping[username] = connfor {fmt.Println(ConnUIDMapping)msgType, content, err := conn.ReadMessage()if err != nil {log.Println(err)break} else {if msgType == websocket.CloseMessage {log.Println("client closed!")break} else {res, err := ParseInfo(content)if err != nil {log.Println(err)break} else {to, msg := res["to"], res["msg"]switch to {case "0":for _, c := range ConnUIDMapping {_ = c.WriteMessage(websocket.TextMessage, []byte(msg))}default:if c, ok := ConnUIDMapping[to]; ok {_ = c.WriteMessage(websocket.TextMessage, []byte(msg))} else {_ = conn.WriteMessage(websocket.TextMessage, []byte(to+"不存在!"))}}}}}}_ = conn.WriteMessage(websocket.CloseMessage, []byte("closed"))delete(ConnUIDMapping, username)_ = conn.Close()})log.Fatalln(engine.Run("127.0.0.1:8080"))
}

Q&A?

在Golang中存储WebSocket连接的最佳方法是什么?

使用map存储

针对在线用户,使用map存储,onlineUser = map[int] *websocket.Conn

var userConnMap = make(map[string]*websocket.Conn)// 存储用户 Conn
func storeUserConn(userID string, conn *websocket.Conn) {userConnMap[userID] = conn
}// 获取用户 Conn
func getUserConn(userID string) *websocket.Conn {return userConnMap[userID]
}// 删除用户 Conn
func deleteUserConn(userID string) {delete(userConnMap, userID)
}

在使用 Map 存储 Conn 时,需要考虑并发安全问题。可以使用锁或者通道等机制来保证并发安全。另外,如果在线用户较多,存储在内存中的 Map 可能会占用过多的内存,这时可以考虑使用 Redis 等外部存储来存储在线用户的 Conn。

使用redis存储(待补充)

  1. chatgpt给出的代码全部都是错误的根本不可能使用,且,socket这样的对象序列化了再反序列化回来,也不是原来的那个对象了

不存储

以redis的发布订阅为例,当用户登录且想与某个人发消息时,可以以对方ID为key建立一个topic,然后本用户publish userId msg发送消息,这样对方只要在线且监听自己的topic就可以接收到消息,但注意一点,由于redis不支持消费离线数据,所以可以用专用的mq去代替或者使用redis的Sorted Set存储消息,然后等用户上线了从zset中读取消息,发送给自己。

var upGrader = websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {return true
}}func ChatWithPerson(ctx *gin.Context) {uInterface, _ := ctx.Get("user")u := uInterface.(*models.User)conn, err := upGrader.Upgrade(ctx.Writer, ctx.Request, nil)if err != nil {ctx.Error(err)return}defer func() {err = conn.Close()if err != nil {ctx.Error(err)}}()wg := &sync.WaitGroup{}wg.Add(2)// writego func(wg *sync.WaitGroup, conn *websocket.Conn, ctx *gin.Context) {defer wg.Done()for {msg := new(models.Message)if err := conn.ReadJSON(msg); err != nil {global.GlobalLogger.Error(err.Error())break}msg.UserID = u.IDmsg.MsgType = models.PrivateMsg// 入库if err = global.GlobalMysqlClient.Create(msg).Error; err != nil {global.GlobalLogger.Error(err.Error())break}// {"dst":"02195049","from":"02195048","msgType":1,"msg":xxx"}// 序列化结构体msgBinary, err := json.Marshal(*msg)if err != nil {global.GlobalLogger.Error(err.Error())break}// 给对方频道发送dstChan := strconv.FormatUint(uint64(msg.Target), 10)if err = global.GlobalRedisClient.Publish(context.TODO(), dstChan, string(msgBinary)).Err(); err != nil {global.GlobalLogger.Error(err.Error())break}// 写入前端if err = conn.WriteMessage(websocket.BinaryMessage, msgBinary); err != nil {global.GlobalLogger.Error(err.Error())break}}global.GlobalLogger.Info("write exit(0)")}(wg, conn, ctx)// readgo func(wg *sync.WaitGroup, conn *websocket.Conn, ctx *gin.Context) {defer wg.Done()// 监听自己ownChan := strconv.FormatUint(uint64(u.ID), 10)sub := global.GlobalRedisClient.Subscribe(context.TODO(), ownChan)for {if msg, err := sub.ReceiveMessage(context.TODO()); err != nil {global.GlobalLogger.Error(err.Error())break} else {msgStruct := new(models.Message)err := json.Unmarshal([]byte(msg.Payload), msgStruct)if err != nil {global.GlobalLogger.Error(err.Error())break}err = conn.WriteMessage(websocket.TextMessage, []byte(msg.Payload))if err != nil {global.GlobalLogger.Error(err.Error())break}}}global.GlobalLogger.Info("read exit(0)")}(wg, conn, ctx)wg.Wait()
}

处理离线信息

对于离线用户,您可以考虑使用离线消息推送(Offline Message Push)的方式。离线消息推送是指在用户不在线时,服务器将消息缓存起来,并在用户再次上线时将消息推送给用户。

Redis的Sorted Set存储

实现离线消息推送的一种方式是使用Redis的Sorted Set数据结构,其中以用户ID为Key,消息发送时间为Score,消息内容为Value存储离线消息。具体实现步骤如下:

在用户离线时,将消息缓存到Redis的Sorted Set中:

score := time.Now().Unix()
err := rdb.ZAdd(ctx, userId, &redis.Z{Score:  float64(score),Member: message,
}).Err()
if err != nil {// 处理错误
}

在用户上线时,从Redis的Sorted Set中取出离线消息推送给用户:

start := "-inf"
end := strconv.FormatInt(time.Now().Unix(), 10)
res, err := rdb.ZRangeByScore(ctx, userId, &redis.ZRangeBy{Min: start,Max: end,
}).Result()
if err != nil {// 处理错误
}for _, msg := range res {// 发送消息给客户端err = conn.WriteMessage(websocket.TextMessage, []byte(msg))if err != nil {// 处理错误}
}// 删除已推送的消息
err = rdb.ZRemRangeByScore(ctx, userId, start, end).Err()
if err != nil {// 处理错误
}

在第1步中,我们将消息存储到以用户ID为Key的Sorted Set中,以消息发送时间为Score,消息内容为Value。在第2步中,我们从Sorted Set中取出离线消息并推送给用户,然后将已经推送的消息从Sorted Set中删除。

需要注意的是,由于离线消息是存储在Redis中的,因此需要保证Redis服务的可用性和稳定性,避免数据丢失或者消息推送失败。此外,离线消息的大小和数量也需要进行控制,避免对系统性能造成过大的影响。

使用消息队列(Message Queue)来处理离线消息

除了使用Redis的Sorted Set数据结构来实现离线消息推送外,还可以考虑使用消息队列(Message Queue)来处理离线消息。相比Redis的Sorted Set,消息队列的可靠性更高,支持更复杂的消息处理逻辑,同时也更容易进行水平扩展。

以下是使用消息队列实现离线消息推送的一种方案:

用户发送消息时,将消息发送到消息队列中:

message := fmt.Sprintf("%s:%s", senderId, content)
err := mq.SendMessage(message)
if err != nil {// 处理错误
}

在用户上线时,从消息队列中取出离线消息推送给用户:

for {message, err := mq.ReceiveMessage()if err != nil {// 处理错误}if message == nil {break}parts := strings.SplitN(message.Body, ":", 2)senderId := parts[0]content := parts[1]if senderId == userId {continue}// 发送消息给客户端err = conn.WriteMessage(websocket.TextMessage, []byte(content))if err != nil {// 处理错误}
}

在第1步中,我们将消息发送到消息队列中。在第2步中,我们从消息队列中取出离线消息并推送给用户。需要注意的是,为了避免将消息推送给发送消息的用户,我们在推送消息之前先判断消息的发送者ID和当前用户ID是否相同。

使用消息队列实现离线消息推送需要考虑消息的持久化、消息的去重、消息的过期等问题,因此需要选择适合业务需求的消息队列系统,并进行相应的配置和优化。

能否用redis的发布订阅实现?

Redis的发布订阅机制(Pub/Sub)可以实现简单的消息通信,但并不适合作为离线消息推送的实现方式。原因如下:

Redis的发布订阅机制只支持最新消息的消费,不支持消费历史消息。如果使用Redis的发布订阅机制实现离线消息推送,就需要在订阅者断线时将消息持久化存储,然后在订阅者重新连接时读取历史消息。这样会增加实现复杂度,并且需要考虑历史消息的存储和清理问题。

Redis的发布订阅机制在分布式环境下需要考虑订阅者的负载均衡问题。如果有多个订阅者,需要保证消息能够均匀地分配给各个订阅者,避免某个订阅者的负载过高。

相比之下,专门设计的消息队列系统通常支持持久化存储、消息去重、消息过期等功能,并且能够支持分布式集群,能够更好地满足离线消息推送的需求。因此,如果需要实现离线消息推送,建议选择专门的消息队列系统,而不是使用Redis的发布订阅机制。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/61238.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Django利用Channels+websocket开发聊天室

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 一、什么是Websocket? 2.Python-Django ASGI 3,Django开发聊天室或信息推送 前言 数据库系统课程设计要求,要开发一个B2B的…

《3D打印:正在到来的工业革命(第2版)》——2.2节材料挤出技术

本节书摘来自异步社区《3D打印:正在到来的工业革命(第2版)》一书中的第2章,第2.2节材料挤出技术,作者 【英】Christopher Barnatt(克里斯多夫),更多章节内容可以访问云栖社区“异步社…

【每日新闻】9家虚拟运营商与中国移动签约首批牌照有望近期下发 | 澳大利亚正考虑禁止采购华为5G设备...

点击关注中国软件网 最新鲜的企业级干货聚集地 趋势洞察 坚持是种信念,努力是种精神! 2018中国软件生态大会 趋势洞察 工信部王卫明:人工智能正在成为推进供给侧结构性改革的新动能 工业和信息化部科技司副司长王卫明表示:人工智…

大咖云集,EI稳定检索,第14届机器学习与计算国际会议(ICMLC 2022)

14th - ICMLC 2022 第14届机器学习与计算国际会议 2月18-21日 | 中国广州 关于我们 机器学习是人工智能及模式识别领域的共同研究热点,其理论和方法已被广泛应用于解决工程应用和科学领域的复杂问题。为了给机器学习与计算研究领域的专家学者提供一个交流相关领域…

艾伦·麦席森·图灵

艾伦麦席森图灵 艾伦麦席森图灵(英语:Alan Mathison Turing,1912年6月23日—1954年6月7日),英国数学家、逻辑学家,被称为计算机科学之父,人工智能之父。1931年图灵进入剑桥大学国王学院&#x…

人机交互-1-人机交互概述

Lec1-人机交互概述 1. 相关术语 Man-Machine Interaction (MMI) / Human-Machine Interaction (HMI) “Man-Machine Interaction” politically incorrectStudy of the ways how humans use machines Man-Computer Interaction (MCI) / Human-Computer Interaction (HCI)Comp…

学习A-level课程能申请哪些国家

在许多学生和学生家长的感觉中A-level课程内容是外国的考試,只可以申请办理英国的大学。实际上这一看法是不规范的,A-level是一个十分全球化的课程内容,被全世界许多我国的顶级高校接收和认同。 要想考A-Level,先明确A-Level适不适宜自身。A-…

世界环境日 | 始祖数字化可持续发展峰会就在6.5!

2022年世界环境日始祖数字化可持续发展峰会将于6月5日北京时间14:00全球线上举行,将邀请来自工业、数字化科技、环境建筑、时尚等领域的嘉宾共同探讨相关议题,推动数字化生态互联,助力可持续发展。 我们诚挚地邀请大家和我们共聚在下午的峰会…

【Energy Reports期刊发表】2022年能源与环境工程国际会议(CFEEE 2022)

【Energy Reports】 【广西大学主办】 2022年能源与环境工程国际会议(CFEEE 2022) 重要信息 会议网址:www.cfeee.org 会议时间:2022年12月16-18日 召开地点:广西-北海 截稿时间:2022年11月10日 录用…

Neurology:肚子越大,脑子越小-肚子胖与大脑萎缩相关

医林研究院-让医学更简单! 肥胖,既是一种特征,也是一种疾病,肥胖是世界卫生组织确定的十大慢性疾病之一,肥胖者更易患代谢性疾病和心脑血管疾病和慢性肾脏病等。 全世界有近40%的人超重,13%的人肥胖&#x…

交中IB课程中心2022届早申阶段录取成果汇总

2022届早申阶段录取成果汇总截至2022年2月19日 最是一年春好处,交中IB录取的佳音随着春风次第而来。在刚刚过去的海外大学早申请阶段,交中IB学子凭借超群实力,斩获了多所世界顶尖名校的录取。正是对知识的渴求、对梦想的执着、对未来的坚定&…

【会议分享】2022年智能车国际会议(ICoIV 2022)

2022年智能车国际会议(ICoIV 2022) 重要信息 会议网址:www.icoiv.org 会议时间:2022年10月14-16日 召开地点:中国成都 截稿时间:2022年8月30日 录用通知:投稿后2周内 收录检索:EI,Scopus 会议简介 ★…

CSP-J第一轮常考知识点

一、OSI模型 (1)中文名 ​ 开放式通信系统互联参考模型 (2)层次 ​ 物理层->数据链路层->网络层->传输层->会话层->表示层->应用层 (3)介绍 ​ 物理层: 将数据转换为可通过物理介质传送的电子信号 相当于邮局中的搬运工人。 ​ 数据链路层: 决定访问网…

【Energy Reports期刊发表】 2022年能源与环境工程国际会议(CFEEE 2022)

【Energy Reports期刊发表】 2022年能源与环境工程国际会议(CFEEE 2022) 重要信息 会议网址:www.cfeee.org 会议时间:2022年12月16-18日 召开地点:广西-北海 截稿时间:2022年11月1日 录用通知&#x…

世界上最健康的程序员作息表「值得一看」

昨晚看了一篇“传说中”的“世界上最健康的作息时间表”,开始纠结自己还要不要5点半起床。 都说程序员这一行,猝死概率极高,究其原因还是加班太狠、作息不规律、缺乏运动.... 今天和大家分享一下这篇文章,还是非常值得参考的&…

周五了,世界上ZUI健康的工程师作息表!

关注、星标公众号,直达精彩内容 链接:https://sleepyocean.github.io/pages/55d148/ 996已经为很多行业的常态,近些年来,经常会看到新闻报道年轻人猝死的事情,究其原因还是很难有很好的作息规律。 今天给大家带来“传说…

世界上最健康的程序员作息表!

都说程序员这一行,猝死概率极高,究其原因还是很难有很好的作息规律。 今天给大家带来“传说中”的“世界上最健康的作息时间表”(仅供参考),随时提醒自己吧,毕竟身体可是自己的哦。 原文链接:ht…

超高频RFID无人机仓库库存管理

1.项目背景 Intelligent Energy做为行业的供应链库存解决方案提供商,签署了为无人机提供燃料电池系统的交易。 拉夫堡 英国 3月23日Intelligent Energy跟PINC公司做了一个交易,为它们提供无人机的冷却燃料电池系统,这是Intelligent Energy初…

卡迪夫大学计算机硕士专业怎么样,卡迪夫大学计算机硕士专业

卡迪夫大学计算机硕士课程的学生会得到一个第1手的了解软件解决跨学科问题的机会,以及计算系统可以实现什么。学生将学习、实践和演示软件工程师所需的专业技能,单独或作为一个团队的一部分开发一个软件的方案。 卡迪夫大学计算机硕士专业课程 核心课程 Web Application Develo…

机器学习32道选择题

1. 丰巢柜中有A、B、C、D、E、F六个人的快递,每个人各有两个快递。现在随机取出5个快递,那么5个快递中至少有2个快递属于同一个人的概率是多少?(0.757575757575758) 2. 假设有三个人同时参加这场笔试,假设满…