Golang网络聊天室案例

简介: Golang网络聊天室案例

1.聊天室设计分析

一. 概览

实现 个网络聊天室(群)

功能分析:

  1. 上线下线
  2. 聊天,其他人,自己都可以看到聊天消息
  3. 查询当前聊天室用户名字 who
  4. 可以修改自己名字 rename | Duke
  5. 超时踢出

技术点分析:

1 . sock tcp 编程

2 . map结构 (存储当前用户,map遍历,map删除)

3 . go程,channel

4 . select(超时退出,主动退出)

5 . timer定时器

二、实现基础

第一阶段:

tcp socket,建立多个连接

package main
import (
  "fmt"
  "net"
)
func main(){
  // 创建服务器
  listener,err := net.Listen("tcp",":8088")
  if err != nil{
    fmt.Println("net.Listen err:",err)
    return
  }
  fmt.Println("服务器启动成功,监听中...")
  for {
    fmt.Println("==>主go程监听中......")
    // 监听
    conn,err := listener.Accept()
    if err != nil{
      fmt.Println("listener.Accept err:",err)
      return
    }
    // 建立连接
    fmt.Println("建立连接成功!")
    // 启动处理业务的go程
    go handler(conn)
  }
}
// 处理具体业务
func handler(conn net.Conn){
  for{
    fmt.Println("启动业务...")
    // TODO // 代表这里以后再具体实现
    buf := make([]byte,1024)
    // 读取客户端发送来的数据
    cnt,err := conn.Read(buf)
    if err != nil{
      fmt.Println("listener.Read err:",err)
      return
    }
    fmt.Println("客户端接收客户端发来的数据为:",string(buf[:cnt-1]),",cnt:",cnt)
  }
  
}

go run chatroom.go

启动nc

nc下载地址

2、定义User/map结构

type User struct {
  // 名字
  name string 
  // 唯一 的 id
  id string
  // 管道
  msg chan string
}
// 创建一个全局的map结构,用于保存所有的用户
var allUsers = make(map[string]User)

在Handler中调用

// 处理具体业务
func handler(conn net.Conn){
  for{
    fmt.Println("启动业务...")
//
    // 客户端与服务器建立连接的时候,公有ip和port --> 当成user的id
    clientAddr := conn.RemoteAddr().String()
    fmt.Println("clientAddr:",clientAddr)
    // 创建user
    newUser := User{
      id:clientAddr,// id 我们不会修改,这个作为map中的key
      name:clientAddr,// 可以修改,会提供rename命令修改,建立连接时,初始值与id相同
      msg:make(chan string), // 注意需要make空间,否则无法写入数据
    }
    // 添加user到map结构
    allUsers[newUser.id] = newUser
/
    buf := make([]byte,1024)
    // 读取客户端发送来的数据
    cnt,err := conn.Read(buf)
    if err != nil{
      fmt.Println("listener.Read err:",err)
      return
    }
    fmt.Println("客户端接收客户端发来的数据为:",string(buf[:cnt-1]),",cnt:",cnt)
  }

3.定义message管道

创建监听广播go程函数

// 向所有的用户广播消息,启动一个全局唯一的go程
func broadcast(){
  fmt.Println("广播go程启动成功...")
  // 1. 从message中读取数据
  info := <-message 
  // 2. 将数据写入到每一个用户的msg管道中
  for _,user := range allUsers{
    user.msg <- info 
  }
}

启动,全局唯一

写入上线数据

当前整体源码

package main
import (
  "fmt"
  "net"
)
type User struct {
  // 名字
  name string 
  // 唯一 的 id
  id string
  // 管道
  msg chan string
}
// 创建一个全局的map结构,用于保存所有的用户
var allUsers = make(map[string]User)
// 定义一个message全局通道,用于接收任何人发送过来消息
var message = make(chan string,10)
func main(){
  // 创建服务器
  listener,err := net.Listen("tcp",":8087")
  if err != nil{
    fmt.Println("net.Listen err:",err)
    return
  }
  // 启动全局唯一的go程,负责监听message通道,写给所有的用户
  go broadcast()
  fmt.Println("服务器启动成功,监听中...")
  for {
    fmt.Println("==>主go程监听中......")
    // 监听
    conn,err := listener.Accept()
    if err != nil{
      fmt.Println("listener.Accept err:",err)
      return
    }
    // 建立连接
    fmt.Println("建立连接成功!")
    // 启动处理业务的go程
    go handler(conn)
  }
}
// 处理具体业务
func handler(conn net.Conn){
  for{
    fmt.Println("启动业务...")
    // 客户端与服务器建立连接的时候,公有ip和port --> 当成user的id
    clientAddr := conn.RemoteAddr().String()
    fmt.Println("clientAddr:",clientAddr)
    // 创建user
    newUser := User{
      id:clientAddr,// id 我们不会修改,这个作为map中的key
      name:clientAddr,// 可以修改,会提供rename命令修改,建立连接时,初始值与id相同
      msg:make(chan string,10), // 注意需要make空间,否则无法写入数据
    }
    // 添加user到map结构
    allUsers[newUser.id] = newUser
    // 向message写入数据,当我用户上线的消息,用于通知所有人(广播)
    loginInfo := fmt.Sprintf("[%s][%s] ===> |上线了login!!",newUser.id,newUser.name)
    message <- loginInfo
    buf := make([]byte,1024)
    // 读取客户端发送来的数据
    cnt,err := conn.Read(buf)
    if err != nil{
      fmt.Println("listener.Read err:",err)
      return
    }
    fmt.Println("客户端接收客户端发来的数据为:",string(buf[:cnt-1]),",cnt:",cnt)
  }
  
}
// 向所有的用户广播消息,启动一个全局唯一的go程
func broadcast(){
  fmt.Println("广播go程启动成功...")
  defer fmt.Println("broadcast 程序退出!")
  for {
    // 1. 从message中读取数据
    fmt.Println("broadcast监听message中...")
    info := <-message 
    // 2. 将数据写入到每一个用户的msg管道中
    for _,user := range allUsers{
      // 如果msg是非缓冲,那么会在这里阻塞
      user.msg <- info 
    }
  }
}

4.user监听通道go程

每个用户应该还有一个用来监听自己msg管道的go程,负责将数据返回给客户端

// 每个用户应该还有一个用来监听msg管道的go程,负责将数据返回给客户端
func writeBackToClient(user *User,conn net.Conn){
  fmt.Printf("user:%s 的go程正在监听自己的msg管道:\n",user.name)
  for data := range user.msg{
    fmt.Printf("user:%s 写回给客户端的数据为:%s\n",user.name,data)
    // Write(b []byte)(n int,err error)
    _,_ = conn.Write([]byte(data))
  }
}

当前代码整体为

package main
import (
  "fmt"
  "net"
)
type User struct {
  // 名字
  name string 
  // 唯一 的 id
  id string
  // 管道
  msg chan string
}
// 创建一个全局的map结构,用于保存所有的用户
var allUsers = make(map[string]User)
// 定义一个message全局通道,用于接收任何人发送过来消息
var message = make(chan string,10)
func main(){
  // 创建服务器
  listener,err := net.Listen("tcp",":8087")
  if err != nil{
    fmt.Println("net.Listen err:",err)
    return
  }
  // 启动全局唯一的go程,负责监听message通道,写给所有的用户
  go broadcast()
  fmt.Println("服务器启动成功,监听中...")
  for {
    fmt.Println("==>主go程监听中......")
    // 监听
    conn,err := listener.Accept()
    if err != nil{
      fmt.Println("listener.Accept err:",err)
      return
    }
    // 建立连接
    fmt.Println("建立连接成功!")
    // 启动处理业务的go程
    go handler(conn)
  }
}
// 处理具体业务
func handler(conn net.Conn){
  fmt.Println("启动业务...")
  // 客户端与服务器建立连接的时候,公有ip和port --> 当成user的id
  clientAddr := conn.RemoteAddr().String()
  fmt.Println("clientAddr:",clientAddr)
  // 创建user
  newUser := User{
    id:clientAddr,// id 我们不会修改,这个作为map中的key
    name:clientAddr,// 可以修改,会提供rename命令修改,建立连接时,初始值与id相同
    msg:make(chan string,10), // 注意需要make空间,否则无法写入数据
  }
  // 添加user到map结构
  allUsers[newUser.id] = newUser
  // 启动go程,负责将msg的信息返回给客户端
  go writeBackToClient(&newUser,conn)
  // 向message写入数据,当我用户上线的消息,用于通知所有人(广播)
  loginInfo := fmt.Sprintf("[%s][%s] ===> |上线了login!!",newUser.id,newUser.name)
  message <- loginInfo
  for{
    // 具体业务逻辑
    buf := make([]byte,1024)
    // 读取客户端发送来的数据
    cnt,err := conn.Read(buf)
    if err != nil{
      fmt.Println("listener.Read err:",err)
      return
    }
    fmt.Println("客户端接收客户端发来的数据为:",string(buf[:cnt-1]),",cnt:",cnt)
  }
  
}
// 向所有的用户广播消息,启动一个全局唯一的go程
func broadcast(){
  fmt.Println("广播go程启动成功...")
  defer fmt.Println("broadcast 程序退出!")
  for {
    // 1. 从message中读取数据
    fmt.Println("broadcast监听message中...")
    info := <-message 
    // 2. 将数据写入到每一个用户的msg管道中
    for _,user := range allUsers{
      // 如果msg是非缓冲,那么会在这里阻塞
      user.msg <- info 
    }
  }
}
// 每个用户应该还有一个用来监听msg管道的go程,负责将数据返回给客户端
func writeBackToClient(user *User,conn net.Conn){
  fmt.Printf("user:%s 的go程正在监听自己的msg管道:\n",user.name)
  for data := range user.msg{
    fmt.Printf("user:%s 写回给客户端的数据为:%s\n",user.name,data)
    // Write(b []byte)(n int,err error)
    _,_ = conn.Write([]byte(data))
  }
}

三、增加功能

  1. 查询用户
    查询命令:who==>将当前所有的登录的用户,展示出来,id,name,返回给当前用户
package main
import (
  "fmt"
  "net"
  "strings"
)
type User struct {
  // 名字
  name string 
  // 唯一 的 id
  id string
  // 管道
  msg chan string
}
// 创建一个全局的map结构,用于保存所有的用户
var allUsers = make(map[string]User)
// 定义一个message全局通道,用于接收任何人发送过来消息
var message = make(chan string,10)
func main(){
  // 创建服务器
  listener,err := net.Listen("tcp",":8087")
  if err != nil{
    fmt.Println("net.Listen err:",err)
    return
  }
  // 启动全局唯一的go程,负责监听message通道,写给所有的用户
  go broadcast()
  fmt.Println("服务器启动成功,监听中...")
  for {
    fmt.Println("==>主go程监听中......")
    // 监听
    conn,err := listener.Accept()
    if err != nil{
      fmt.Println("listener.Accept err:",err)
      return
    }
    // 建立连接
    fmt.Println("建立连接成功!")
    // 启动处理业务的go程
    go handler(conn)
  }
}
// 处理具体业务
func handler(conn net.Conn){
  fmt.Println("启动业务...")
  // 客户端与服务器建立连接的时候,公有ip和port --> 当成user的id
  clientAddr := conn.RemoteAddr().String()
  fmt.Println("clientAddr:",clientAddr)
  // 创建user
  newUser := User{
    id:clientAddr,// id 我们不会修改,这个作为map中的key
    name:clientAddr,// 可以修改,会提供rename命令修改,建立连接时,初始值与id相同
    msg:make(chan string,10), // 注意需要make空间,否则无法写入数据
  }
  // 添加user到map结构
  allUsers[newUser.id] = newUser
  // 启动go程,负责将msg的信息返回给客户端
  go writeBackToClient(&newUser,conn)
  // 向message写入数据,当我用户上线的消息,用于通知所有人(广播)
  loginInfo := fmt.Sprintf("[%s][%s] ===> |上线了login!!",newUser.id,newUser.name)
  message <- loginInfo
  for{
    // 具体业务逻辑
    buf := make([]byte,1024)
    // 读取客户端发送来的数据
    cnt,err := conn.Read(buf)
    if err != nil{
      fmt.Println("listener.Read err:",err)
      return
    }
    fmt.Println("客户端接收客户端发来的数据为:",string(buf[:cnt-1]),",cnt:",cnt)
    // -------------业务逻辑处理  开始------------- 
    // 1.查询当前所有的用户 who
    // a. 先判断接收的数据是不是who  ==》 长度&&字符串
    userInput := string(buf[:cnt-1])  // 这是用户输入的数据,最后一个是回车,我们去掉他
    if len(userInput) == 3 && userInput == "who"{
      // b.遍历allUser这个map(key:userid value:user 本身)。将id和name拼接成一个字符,返回给客户端
      fmt.Println("用户即将查询所有用户信息!")
      // 这个切片包含所有的用户信息
      var userInfos []string
      for _,user := range allUsers{
        userInfo := fmt.Sprintf("userid:%s,username:%s",user.id,user.name)
        userInfos = append(userInfos,userInfo)
      }
      // 最终写入到通道中,一定是一个字符串
      r := strings.Join(userInfos,"\n")
      // 将数据返回到客户端
      newUser.msg <- r
    }else{
      // 如果不是用户输入的命令,只是聊天信息,那么只需要写到广播中即可,由其他的go程常规转发
      message <- userInput
    }
    // -------------业务逻辑处理  结束------------- 
  }
  
}
// 向所有的用户广播消息,启动一个全局唯一的go程
func broadcast(){
  fmt.Println("广播go程启动成功...")
  defer fmt.Println("broadcast 程序退出!")
  for {
    // 1. 从message中读取数据
    fmt.Println("broadcast监听message中...")
    info := <-message 
    // 2. 将数据写入到每一个用户的msg管道中
    for _,user := range allUsers{
      // 如果msg是非缓冲,那么会在这里阻塞
      user.msg <- info 
    }
  }
}
// 每个用户应该还有一个用来监听msg管道的go程,负责将数据返回给客户端
func writeBackToClient(user *User,conn net.Conn){
  fmt.Printf("user:%s 的go程正在监听自己的msg管道:\n",user.name)
  for data := range user.msg{
    fmt.Printf("user:%s 写回给客户端的数据为:%s\n",user.name,data)
    // Write(b []byte)(n int,err error)
    _,_ = conn.Write([]byte(data))
  }
}
  1. 重命名
    规则:rename|Duke
    获取数据判断长度7,判断字符是rename
    使用|进行分割,获取|后面的部分,作为名字
    更新用户名字newUser.name = Duke
    通知客户端,更新成功
  2. 主动退出
    每个用户都有自己的watch go程,仅负责监听退出信号
// 启动一个go程,负责监听退出信号,触发后,进行清零工作:delete map,close conn 都在这里处理
func watch(user *User,conn net.Conn,isQuit <-chan bool){
  fmt.Println("启动监听信号退出的go程...")
  defer fmt.Println("watch go程退出!")
  for{
    select{
      case <-isQuit:
        logoutInfo := fmt.Sprintf("%s exit already!",user.name)
        fmt.Println("删除当前用户:",user.name)
        delete(allUsers,user.id)
        message<-logoutInfo
        conn.Close()
        return
    }
  }
}
在handler中启动go watch,同时传入相应信息:
// 定义一个退出信号,用来监听client退出
  var isQuit = make(chan bool)
  // 启动go程,负责监听退出信号
  go watch(&newUser,conn,isQuit)

在read之后,通过cnt判断用户退出,向isQuit写入信号:

测试截图

  1. 超时退出
    使用定时器来进行超时管理
    如果60s没有发送任何数据,那么直接将这个链接关闭
<-time.After(60*time.second)

更新watch函数

// 启动一个go程,负责监听退出信号,触发后,进行清零工作:delete map,close conn 都在这里处理
func watch(user *User,conn net.Conn,isQuit,restTimer <-chan bool){
  fmt.Println("启动监听信号退出的go程...")
  defer fmt.Println("watch go程退出!")
  for{
    select{
      case <-isQuit:
        logoutInfo := fmt.Sprintf("%s exit already!\n",user.name)
        fmt.Println("删除当前用户:",user.name)
        delete(allUsers,user.id)
        message<-logoutInfo
        conn.Close()
        return
      case <-time.After(10*time.Second):
        logoutInfo := fmt.Sprintf("%s timeout exit elready!\n",user.name)
        fmt.Println("删除当前用户:",user.name)
        delete(allUsers,user.id)
        message<-logoutInfo
        conn.Close()
        return 
      case <-restTimer:
        fmt.Printf("连接%s 重置计数器!\n",user.name)
    }
  }
}

创建并传入restTimer管道

效果:

最终代码

package main
import (
  "fmt"
  "net"
  "strings"
  "time"
)
type User struct {
  // 名字
  name string 
  // 唯一 的 id
  id string
  // 管道
  msg chan string
}
// 创建一个全局的map结构,用于保存所有的用户
var allUsers = make(map[string]User)
// 定义一个message全局通道,用于接收任何人发送过来消息
var message = make(chan string,10)
func main(){
  // 创建服务器
  listener,err := net.Listen("tcp",":8087")
  if err != nil{
    fmt.Println("net.Listen err:",err)
    return
  }
  // 启动全局唯一的go程,负责监听message通道,写给所有的用户
  go broadcast()
  fmt.Println("服务器启动成功,监听中...")
  for {
    fmt.Println("==>主go程监听中......")
    // 监听
    conn,err := listener.Accept()
    if err != nil{
      fmt.Println("listener.Accept err:",err)
      return
    }
    // 建立连接
    fmt.Println("建立连接成功!")
    // 启动处理业务的go程
    go handler(conn)
  }
}
// 处理具体业务
func handler(conn net.Conn){
  fmt.Println("启动业务...")
  // 客户端与服务器建立连接的时候,公有ip和port --> 当成user的id
  clientAddr := conn.RemoteAddr().String()
  fmt.Println("clientAddr:",clientAddr)
  // 创建user
  newUser := User{
    id:clientAddr,// id 我们不会修改,这个作为map中的key
    name:clientAddr,// 可以修改,会提供rename命令修改,建立连接时,初始值与id相同
    msg:make(chan string,10), // 注意需要make空间,否则无法写入数据
  }
  // 添加user到map结构
  allUsers[newUser.id] = newUser
  // 定义一个退出信号,用来监听client退出
  var isQuit = make(chan bool)
  // 创建一个用于重置计数器的管道,用于告知watch函数,当前用户正在输入
  var restTimer = make(chan bool)
  // 启动go程,负责监听退出信号
  go watch(&newUser,conn,isQuit,restTimer)
  // 启动go程,负责将msg的信息返回给客户端
  go writeBackToClient(&newUser,conn)
  // 向message写入数据,当我用户上线的消息,用于通知所有人(广播)
  loginInfo := fmt.Sprintf("[%s][%s] ===> |上线了login!!\n",newUser.id,newUser.name)
  message <- loginInfo
  for{
    // 具体业务逻辑
    buf := make([]byte,1024)
    // 读取客户端发送来的数据
    cnt,err := conn.Read(buf)
    if cnt == 0 {
      fmt.Println("客户端主动关闭ctrl+c,准备退出!")
      // map 删除,用户 conn  close掉
      // 服务器还可以主动的退出
      // 在这里不进行真正的退出动作,而是发出一个退出信号,统一做退出处理,可以使用新的管道来做信号传递
      isQuit <- true
    }
    if err != nil{
      fmt.Println("listener.Read err:",err)
      return
    }
    fmt.Println("客户端接收客户端发来的数据为:",string(buf[:cnt-1]),",cnt:",cnt)
    // -------------业务逻辑处理  开始------------- 
    // 1.查询当前所有的用户 who
    // a. 先判断接收的数据是不是who  ==》 长度&&字符串
    userInput := string(buf[:cnt-1])  // 这是用户输入的数据,最后一个是回车,我们去掉他
    if len(userInput) == 3 && userInput == "who"{
      // b.遍历allUser这个map(key:userid value:user 本身)。将id和name拼接成一个字符,返回给客户端
      fmt.Println("用户即将查询所有用户信息!")
      // 这个切片包含所有的用户信息
      var userInfos []string
      for _,user := range allUsers{
        userInfo := fmt.Sprintf("userid:%s,username:%s",user.id,user.name)
        userInfos = append(userInfos,userInfo)
      }
      // 最终写入到通道中,一定是一个字符串
      r := strings.Join(userInfos,"\n")
      // 将数据返回到客户端
      newUser.msg <- r
    }else if len(userInput) > 8 && userInput[:6] == "rename"{
      // 规则:rename|Duke
     //     获取数据判断长度7,判断字符是rename
     //     使用|进行分割,获取|后面的部分,作为名字
     //     更新用户名字newUser.name = Duke
      newUser.name = strings.Split(userInput,"|")[1]
      allUsers[newUser.id] = newUser // 更新map中的user
     //     通知客户端,更新成功
      message <- userInput
    }else{
      // 如果不是用户输入的命令,只是聊天信息,那么只需要写到广播中即可,由其他的go程常规转发
      message <- userInput
    }
    restTimer <- true
    // -------------业务逻辑处理  结束------------- 
  }
  
}
// 向所有的用户广播消息,启动一个全局唯一的go程
func broadcast(){
  fmt.Println("广播go程启动成功...")
  defer fmt.Println("broadcast 程序退出!")
  for {
    // 1. 从message中读取数据
    fmt.Println("broadcast监听message中...")
    info := <-message 
    // 2. 将数据写入到每一个用户的msg管道中
    for _,user := range allUsers{
      // 如果msg是非缓冲,那么会在这里阻塞
      user.msg <- info 
    }
  }
}
// 每个用户应该还有一个用来监听msg管道的go程,负责将数据返回给客户端
func writeBackToClient(user *User,conn net.Conn){
  fmt.Printf("user:%s 的go程正在监听自己的msg管道:\n",user.name)
  for data := range user.msg{
    fmt.Printf("user:%s 写回给客户端的数据为:%s\n",user.name,data)
    // Write(b []byte)(n int,err error)
    _,_ = conn.Write([]byte(data))
  }
}
// 启动一个go程,负责监听退出信号,触发后,进行清零工作:delete map,close conn 都在这里处理
func watch(user *User,conn net.Conn,isQuit,restTimer <-chan bool){
  fmt.Println("启动监听信号退出的go程...")
  defer fmt.Println("watch go程退出!")
  for{
    select{
      case <-isQuit:
        logoutInfo := fmt.Sprintf("%s exit already!\n",user.name)
        fmt.Println("删除当前用户:",user.name)
        delete(allUsers,user.id)
        message<-logoutInfo
        conn.Close()
        return
      case <-time.After(10*time.Second):
        logoutInfo := fmt.Sprintf("%s timeout exit elready!\n",user.name)
        fmt.Println("删除当前用户:",user.name)
        delete(allUsers,user.id)
        message<-logoutInfo
        conn.Close()
        return 
      case <-restTimer:
        fmt.Printf("连接%s 重置计数器!\n",user.name)
    }
  }
}

这里还有问题就是,上锁问题。记得在操作map的时候加上读锁和写锁

案例

package main
import(
  "fmt"
  "sync"
  "time"
)
var idnames = make(map[int]string)
var lock sync.RwMutex
// map不允许同事读写,如果有不同go程同时操作map,需要对map上锁
func main(){
  go func(){
    for{
      lock.lock()
      idnames[0] = "duke"
      lock.Unlock()
    }
  }()
  go func(){
    for{
      lock.Lock()
      name := idnames[0]
      fmt.Println("name:",name)
      lock.Unlock()
    }
  }()
  for{
    fmt.Println("OVER")
    time.Sleep(1*time.Second)
  }
}

感谢大家观看,我们下次见

目录
相关文章
|
7天前
|
安全 算法 网络安全
网络安全与信息安全:构建数字世界的坚固防线在数字化浪潮席卷全球的今天,网络安全与信息安全已成为维系社会秩序、保障个人隐私和企业机密的关键防线。本文旨在深入探讨网络安全漏洞的本质、加密技术的前沿进展以及提升公众安全意识的重要性,通过一系列生动的案例和实用的建议,为读者揭示如何在日益复杂的网络环境中保护自己的数字资产。
本文聚焦于网络安全与信息安全领域的核心议题,包括网络安全漏洞的识别与防御、加密技术的应用与发展,以及公众安全意识的培养策略。通过分析近年来典型的网络安全事件,文章揭示了漏洞产生的深层原因,阐述了加密技术如何作为守护数据安全的利器,并强调了提高全社会网络安全素养的紧迫性。旨在为读者提供一套全面而实用的网络安全知识体系,助力构建更加安全的数字生活环境。
|
29天前
|
Go
Golang生成随机数案例实战
关于如何使用Go语言生成随机数的三个案例教程。
183 91
Golang生成随机数案例实战
|
3月前
|
网络协议
Qt中的网络编程(Tcp和Udp)运用详解以及简单示范案例
Tcp和Udp是我们学习网络编程中经常接触到的两个通讯协议,在Qt也被Qt封装成了自己的库供我们调用,对于需要进行网络交互的项目中无疑是很重要的,希望这篇文章可以帮助到大家。 是关于Qt中TCP和UDP的基本使用和特点:
341 7
|
29天前
|
Go
Golang的time.NewTimer单次定时器使用案例
这篇文章介绍了Go语言中time包的多种定时器使用案例,包括单次定时器的创建、阻塞程序运行的Sleep函数、重置和停止定时器的方法,以及After和AfterFunc函数的使用。
19 5
Golang的time.NewTimer单次定时器使用案例
|
28天前
|
Prometheus Cloud Native Go
Golang语言之Prometheus的日志模块使用案例
这篇文章是关于如何在Golang语言项目中使用Prometheus的日志模块的案例,包括源代码编写、编译和测试步骤。
25 3
Golang语言之Prometheus的日志模块使用案例
|
5月前
|
数据采集 XML 数据格式
Haskell网络爬虫:视频列表获取案例分析
Haskell网络爬虫:视频列表获取案例分析
|
28天前
|
Go
Golang语言基本数据类型默认值及字符串之间互相转换案例
这篇文章讲解了Golang语言中基本数据类型的默认值、类型转换的概述以及整型、浮点型、字符串之间的相互转换案例,包括如何将基本数据类型转换为字符串类型和字符串类型转换为基本数据类型,以及字符串与字节切片之间的转换。
14 2
|
28天前
|
Go
Golang语言数据类型分类及进制转换案例
这篇文章详细介绍了Go语言中数据类型的分类、进制转换的概念和实例,以及数字字面量语法,还涉及了原码、反码和补码的相关知识。
13 0
Golang语言数据类型分类及进制转换案例
|
29天前
|
Go
Golang的time.NewTicker周期性定时器使用案例
这篇文章介绍了Golang中time包的NewTicker函数如何创建周期性定时器,并通过两个示例展示了如何使用定时器以及如何停止定时器。
20 1
|
2月前
|
数据采集 自然语言处理 监控
【优秀python毕设案例】基于python django的新媒体网络舆情数据爬取与分析
本文介绍了一个基于Python Django框架开发的新媒体网络舆情数据爬取与分析系统,该系统利用Scrapy框架抓取微博热搜数据,通过SnowNLP进行情感分析,jieba库进行中文分词处理,并以图表和词云图等形式进行数据可视化展示,以实现对微博热点话题的舆情监控和分析。
【优秀python毕设案例】基于python django的新媒体网络舆情数据爬取与分析