@@ -1,58 +0,0 @@ 31.发文件与聊天室 | 凤凰涅槃进阶之路

31.发文件与聊天室

Abel sun2023年1月6日约 1296 字大约 4 分钟

31.发文件与聊天室

1,收发文件

做一个文件上传接收的一个服务。

1,原理

HTB1lQAydG5s3KVjSZFN763D3FXaC

2,os.stat的使用

通过这个函数可以获取到文件对应的一些状态信息,如下举例:

package main

import (
 "fmt"
 "os"
)

func main() {
 list := os.Args
 if len(list) != 2 {
  fmt.Println("usage: xxx file")
  return
 }
 filename := list[1]
 info, err := os.Stat(filename)
 if err != nil {
  fmt.Println("err = ", err)
  return
 }
 fmt.Println("file name is ", info.Name())
 fmt.Println("file size is ", info.Size())

}

运行结果如下:

$ go run 04_os.stat的使用.go '/d/all-in/39go语言教程/letsgo/1801-Go语言视频零 基础入门到精通项目实战web编程Golang 2018年新教程/01-Go语言快速入门(20182 月更新精品,推荐观看)/7天视频/01_昨日回顾.mp4'
file name is  01_昨日回顾.mp4
file size is  67010611

3,发送方

先来写一个发送端代码:

package main

import (
 "fmt"
 "io"
 "net"
 "os"
)

//发送文件内容
func SendFile(path string, conn net.Conn) {
 //以只读方式打开文件
 f, err := os.Open(path)
 if err != nil {
  fmt.Println("os.Open err = ", err)
  return
 }
 defer f.Close()

 buf := make([]byte, 1024*4)
 //读多少内容,发送多少
 for {
  n, err := f.Read(buf) //从文件读取内容
  if err != nil {
   if err == io.EOF {
    fmt.Println("文件发送完毕")
   } else {
    fmt.Println("f.Read err = ", err)
   }
   return
  }
  //发送内容
  conn.Write(buf[:n]) //给服务器发送内容
 }

}
func main() {
 //提示输入文件
 fmt.Println("请输入需要传输的文件:")
 var path string
 fmt.Scan(&path)

 //获取文件名 info.name()
 info, err := os.Stat(path)
 if err != nil {
  fmt.Println("os.Stat err = ", err)
  return
 }

 //主动连接服务器
 conn, err := net.Dial("tcp", "127.0.0.1:8000")
 if err != nil {
  fmt.Println("net.Dial err = ", err)
  return
 }
 defer conn.Close()

 //先给接收方发送文件名
 _, err = conn.Write([]byte(info.Name()))
 if err != nil {
  fmt.Println("conn.Write err = ", err)
  return
 }

 //判断接收方的回复,如果回复"ok",说明对方准备好,可以发送文件
 var n int
 buf := make([]byte, 1024)
 n, err = conn.Read(buf)
 if err != nil {
  fmt.Println("conn.Read err = ", err)
  return
 }
 if "ok" == string(buf[:n]) {
  //发送文件内容
  SendFile(path, conn)
 }

}

4,接收方

然后是接收方:

package main

import (
 "fmt"
 "io"
 "net"
 "os"
)

//接受文件内容
func RecvFile(FileName string, conn net.Conn) {
 //新建文件
 f, err := os.Create(FileName)
 if err != nil {
  fmt.Println("os.Create err = ", err)
  return
 }
 buf := make([]byte, 1024*4)
 //接受多少,写入多少,一点不差
 for {
  n, err := conn.Read(buf)
  if err != nil {
   if err == io.EOF {
    fmt.Println("文件接收完毕")
   } else {
    fmt.Println("conn.Read err = ", err)

   }
   return
  }
  f.Write(buf[:n])
 }
}
func main() {
 //监听
 listenner, err := net.Listen("tcp", "127.0.0.1:8000")
 if err != nil {
  fmt.Println("net.Listen err = ", err)
  return
 }
 defer listenner.Close()

 //阻塞等待用户连接
 conn, err := listenner.Accept()
 if err != nil {
  fmt.Println("listenner.Accept err = ", err)
  return
 }
 defer conn.Close()
 buf := make([]byte, 1024)
 var n int
 n, err = conn.Read(buf)
 if err != nil {
  fmt.Println("conn.Read err = ", err)
  return
 }
 FileName := string(buf[:n])

 //回复"ok"
 conn.Write([]byte("ok"))
 //接受文件内容
 RecvFile(FileName, conn)

}

5,运行验证

先运行接收方代码,然后在运行发送方进行发送,此处需要留意发送的路径,似乎不能有中文。

HTB1TFovdRGw3KVjSZFw762Q2FXag

2,并发聊天室

代码内容:

package main

import (
 "fmt"
 "net"
 "strings"
 "time"
)

type Client struct {
 C    chan string //用户发送数据的管道
 Name string      //用户名
 Addr string      //网络地址
}

//保存在线用户,使用map,cliAddr ===> Client
var onlineMap map[string]Client

var message = make(chan string)

//新开一个协程,转发消息,只要有消息来了,就遍历map,给map中每个成员都发送此消息
func Manager() {
 //给map分配空间
 onlineMap = make(map[string]Client)
 for {
  msg := <-message //没有消息前,阻塞等待
  //遍历map,给map中每个成员都发送此消息
  for _, cli := range onlineMap {
   cli.C <- msg
  }
 }
}

//给用户发信息
func WriteMsgToClient(cli Client, conn net.Conn) {
 for msg := range cli.C {
  conn.Write([]byte(msg + "\n"))
 }
}

func MakeMsg(cli Client, msg string) (buf string) {
 buf = "[" + cli.Addr + "]" + cli.Name + ": " + msg
 return
}

func HandleConn(conn net.Conn) {
 //获取客户端的网络地址
 cliAddr := conn.RemoteAddr().String()

 //创建一个结构体,默认情况下,用户名和网络地址一样
 cli := Client{make(chan string), cliAddr, cliAddr}
 //把结构体添加到map
 onlineMap[cliAddr] = cli
 //新开一个协程,专门给客户端发送信息
 go WriteMsgToClient(cli, conn)

 //广播用户在线
 //message<-"[" + cli.Addr + "]" + cli.Name + ": login"
 message <- MakeMsg(cli, "login")
 //提示我是谁
 cli.C <- MakeMsg(cli, "I m here")

 isQuit := make(chan bool)//对方是否主动退出
 hasData := make(chan bool)//对方是否有数据
 //新建一个协程,接收用户发送过来的数据
 go func() {
  buf := make([]byte, 2048)
  for {
   n, err := conn.Read(buf)
   if n == 0 { //对方断开,或者出问题
    isQuit <- true
    fmt.Println("conn.Read err = ", err)
    return
   }
   msg := string(buf[:n-1])
   if len(msg) == 3 && msg == "who" {
    //遍历map,给当前用户发送所有在线用户
    conn.Write([]byte("user list:\n"))
    for _, tmp := range onlineMap {
     msg = tmp.Addr + ":" + tmp.Name + "\n"
     conn.Write([]byte(msg))
    }
   } else if len(msg) >= 8 && msg[:6] == "rename" {
    //rename|mike
    name := strings.Split(msg, "|")[1]
    cli.Name = name
    onlineMap[cliAddr] = cli
    conn.Write([]byte("rename success\n"))
   } else { //转发此内容
    message <- MakeMsg(cli, msg)
   }
   hasData <- true //代表有数据
  }
 }()
 for {
  //通过select检测channel的流动
  select {
  case <-isQuit:
   delete(onlineMap, cliAddr)//当前用户从map中删除
   message <- MakeMsg(cli, "login out")//广播谁下线了
   return
   
  case <-hasData:

  case <-time.After(10 * time.Second): //60s后退出
   delete(onlineMap, cliAddr)
   message <- MakeMsg(cli, "time out leave out")
   return
  }
 }

}
func main() {
 //监听
 listener, err := net.Listen("tcp", ":8000")
 if err != nil {
  fmt.Println("net.Listen err = ", err)
  return
 }
 defer listener.Close()

 //新开一个协程,转发消息,只要有消息来了,就遍历map,给map中每个成员都发送此消息
 go Manager()

 //主协程,阻塞循环等待用户连接
 for {
  conn, err := listener.Accept()
  if err != nil {
   fmt.Println("listener.Accept err = ", err)
   continue
  }
  go HandleConn(conn) //处理用户连接
 }

}
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.9.1