31.发文件与聊天室
2023年1月6日约 1296 字大约 4 分钟
31.发文件与聊天室
1,收发文件
做一个文件上传接收的一个服务。
1,原理
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/39,go语言教程/letsgo/1801-Go语言视频零 基础入门到精通项目实战web编程Golang 2018年新教程/第01套-Go语言快速入门(2018年2 月更新精品,推荐观看)/第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,运行验证
先运行接收方代码,然后在运行发送方进行发送,此处需要留意发送的路径,似乎不能有中文。
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