Day07 网络编程


01上节回顾

本节主要内容:网络编程
上节回顾: 接口、 包管理(package)

接口回顾

接口本质上是一个父类,实现类是一个子类

定义接口(父类)

// Pay 定义接口(父类)
type Pay interface {
   Pay() string
}

实现了方法pay(),就是实现类。

type AliPay struct {
   
}

func (a AliPay)Pay(){
   fmt.Println("阿里支付")
}

包管理回顾

GOPATH 源码放在$GOPATH/src
package api 什么意思? 指定包名
指定包名,如果和路径不一致。import导入路径,使用时用api这个指定的包名

执行顺序:

当import时:加载对应包里的文件,
        建议顺序:标准库,系统库,第三方库,本项目库,不同分组使用空行分割开。

再执行main函数之前,执行加载import的文件。

别名: F fmt

包名为main的包为应用程序的入口包,编译不包含main包的源码文件时不会得到可执行文件。

编译 一定是编译的内容是 package main的包

go语言:用一个文件夹,组织代码的。都标识到api

这两个文件内不能用放同名的函数,变量名等

关键字洁癖: 能不用关键字就不用关键字

通过首字母大写,代表Pabulic

环境变量 GO111MODULE=off

go get 拉取到GOPATH/src

包的导入格式

省略引用格式 . 作用

go mod 回顾

1、GO111MDULE参数改成on

声明和go.mod内容

go mod init xxx //xxx即声明的模块名

会在当前目录下生成go.mod文件

module xxx

go 1.17

go mod init xxx只是扫描下内容,写在go.mod
go mod tidy 会添加缺失的模块,以及移除不需要的模块
go mod verify 校验下载的包是否做修改了
go mod vender 虚拟环境
开发时,多个版本,依赖不同,可能导致冲突。
再执行时,就会修改vendor中的内容,就不会再修改GOPATH/pkg/mod内容
vendor目录在项目目录下

导入时:

以项目xxx所在开始导,跟xxx在哪里 无所谓
xxx 就代表了mysite的路径位置

执行go mod vendor 在项目目录mysite下生成vendor

02 网络三要素

之前写的程序是单机的。
涉及到通信
        服务http协议
        微服务rpc协议

网络编程印象。 socket理解,偏底层,重点在应用

借助语言,实现通信的过程,是网络编程。 

计算机与计算机发消息是:通信的主体是程序app,不是计算机

程序:一堆代码
进程:程序正在运行的状态

进程A-->进程B 发消息

三层:程序  操作系统  硬件

Open()是向操作系统发送调用

通信:
    1 通过ip找到计算机
    2 找到进程:端口号
    3 通信协议(两边需要一致的协议) 英语 汉语
        TCP、UDP
TCP:安全可靠,降低效率
        三次握手,四次断开
UDP:存在丢失,快

通信三要素:

  IP 端口 协议

03 TCP三次握手

端口号:

  操作系统默认,注意不要冲突了。

数据传输方式

  常用的有两种 SOCK_STREAM(流套接字)SOCK_DGRAM(数据报套接字)

OS模型

打印hello world
经历转化的过程,了解

TCP报文格式

三次握手

请求
响应收到,请求
响应收到

 

源端口 发送放端口
序号seq:
确认号ack

6个标志位:共占6bit 分别是

URG 紧急指针有效
ACK 确认序号有效
PSH 接收方应该尽快吧这个报文交给应用层

RST 重置连接
SYN 建立一个新连接
FIN 断开一个连接  
SYN、SYN+ACK
序列号和应答号关系。 ACK序列号+1

三次握手的过程,在连接的过程中,底层已经实现好了
connect已经实现

04 socket套接字对象

开发更关于与业务,不可能把底层都实现,所以应用了封装的概念。把下四层进行封装,全部抽象成了一个socket层。

05 聊天案例

架构模式:
    CS架构
    搭建服务端 1对多

步骤1 建立连接conn

服务端

程序会卡在listen.Accept(),等待用户连接
package main

import (
   "fmt"
   "net"
)

//服务端
func main() {
   // (1)确定网络三要素,建立服务
   listen,_:=net.Listen("tcp","0.0.0.0:8888")
   fmt.Println("listen:",listen) //listen &{0xc00007aa00 { 0}}
   // (2)等待用户连接,没有连接的客户端保持阻塞
   fmt.Println("server is waiting...")
   conn,_:=listen.Accept()
   fmt.Println("conn:",conn)
}

客户端

package client

import (
   "fmt"
   "net"
)

func main() {
   // (1)确定网络三要素,建立服务
   conn,_:=net.Dial("tcp","127.0.0.1:8888")
   fmt.Println("conn",conn)
}

步骤2 conn发信息

数据核心二进制,索引conn.Write()得发字节

客户端发起Write

package main

import (
   "fmt"
   "net"
)

//客户端
func main() {
   // (1)确定网络三要素
   conn,_:=net.Dial("tcp","127.0.0.1:7777")
   fmt.Println("conn",conn) //conn.Write() conn.Read()
   content:="nihao!"
   n,_:=conn.Write([]byte(content))
   fmt.Println("n:",n)
}

服务端接收Read

接收多大?
n是什么?n是数据长度

package main

import (
   "fmt"
   "net"
)

// 服务端
func main() {
   // (1)确定网络三要素,建立服务
   listen,_:=net.Listen("tcp","0.0.0.0:7777")

   // (2)等待用户连接,没有连接的客户端保持阻塞
   fmt.Println("server is waiting...")
   conn,_:=listen.Accept()
   fmt.Println("conn",conn) //conn.Write() conn.Read()
   data:=make([]byte,1024)
   n,_:=conn.Read(data)
   fmt.Println("n",n)

   fmt.Println(data)
}

查看下ndata都是什么

转字符串

var s1=make([]byte,1024)
   n,_=conn.Read(s1)
   fmt.Println(string(s1[:n]))
}

打印结果
server is waiting...
conn &{{0xc00007ac80}}
n 6
nihao!

步骤3 回复消息

服务端

package main

import (
   "fmt"
   "net"
)

// 服务端
func main() {
   // (1)确定网络三要素,建立服务
   listen,_:=net.Listen("tcp","0.0.0.0:7777")

   // (2)等待用户连接,没有连接的客户端保持阻塞
   fmt.Println("server is waiting...")
   conn,_:=listen.Accept()
   fmt.Println("conn",conn) //conn.Write() conn.Read()

   // 发送一个消息
   data:=make([]byte,1024)
   n,_:=conn.Read(data)
   fmt.Println("n",n)
   fmt.Println("接收客户端信息:",string(data[:n]))

   // 回复一个消息
   res:=string(data[:n])+"too!"
   //res:=strings.ToUpper(string(data[:n]))
   n2,_:=conn.Write([]byte(res))
   fmt.Println("n",n2)
}

打印结果
server is waiting...
conn &{{0xc00007ac80}}
n 6
接收客户端信息: nihao!
n 10

客户端

package main

import (
   "fmt"
   "net"
)

//客户端
func main() {
   // (1)确定网络三要素
   conn,_:=net.Dial("tcp","127.0.0.1:7777")
   fmt.Println("conn",conn) //conn.Write() conn.Read()
   content:="nihao!"
   n,_:=conn.Write([]byte(content))
   fmt.Println("n:",n)

   //接收服务端的响应
   var data=make([]byte,1024)
   n2,_:=conn.Read(data)
   fmt.Println("n",n2)
   fmt.Println("服务端回复的信息:",string(data[:n2]))
}

打印结果
conn &{{0xc00007aa00}}
n: 6
n 10
服务端回复的信息: nihao!too!

小问题处理

注意修改切片长度,否则截取1024再拼接

res:=string(data)+"too!"
和
res:=string(data[:n])+"too!"

步骤4 添加引导输入

package main

import (
   "fmt"
   "net"
)

//客户端
func main() {
   // (1)确定网络三要素
   conn,_:=net.Dial("tcp","127.0.0.1:7777")
   fmt.Println("conn",conn) //conn.Write() conn.Read()
   //content:="nihao!"
   var content string
   fmt.Print(">>>")
   fmt.Scanln(&content)
   n,_:=conn.Write([]byte(content))
   fmt.Println("n:",n)

   //接收服务端的响应
   var data=make([]byte,1024)
   n2,_:=conn.Read(data)
   fmt.Println("n",n2)
   fmt.Println("服务端回复的信息:",string(data[:n2]))
}

输入hello

练习题:

1、客户端,小写转大写返回

服务端代码

package main

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

func main() {

   listen,_:=net.Listen("tcp","0.0.0.0:8000")
   conn,_:=listen.Accept()

   //服务端接收打印
   var data=make([]byte,1024)
   n,_:=conn.Read(data)
   fmt.Println(n)
   fmt.Println("data:",string(data[:n]))

   //服务端发送
   dataSli:=data[:n]
   res:=strings.ToUpper(string(dataSli))
   n2,_:=conn.Write([]byte(res))
   fmt.Println(n2)
}

客户端代码

package main

import (
   "fmt"
   "net"
)

func main() {
   conn,_:=net.Dial("tcp","0.0.0.0:8000")

   //客户端发起写入
   content:="client send..."
   n,_:=conn.Write([]byte(content))
   fmt.Println(n)

   //接收服务器端发来的消息
   var res=make([]byte,1024)
   n2,_:=conn.Read(res)
   resSli:=res[:n2]
   fmt.Println(string(resSli))
}

2 循环

服务端代码

package main

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

func main() {

   listen,_:=net.Listen("tcp","0.0.0.0:8000")
   conn,_:=listen.Accept()

   for {
      //服务端接收打印
      var data=make([]byte,1024)
      n,_:=conn.Read(data)
      //fmt.Println(n)
      fmt.Println("data:",string(data[:n]))

      //服务端发送
      dataSli:=data[:n]
      res:=strings.ToUpper(string(dataSli))
      //n2,_:=conn.Write([]byte(res))
      //fmt.Println(n2)
      conn.Write([]byte(res))
   }
}

客户端代码

package main

import (
   "fmt"
   "net"
)

func main() {
   conn,_:=net.Dial("tcp","0.0.0.0:8000")

   for {
      //客户端发起写入
      //content:="client send..."
      var content string
      fmt.Print("请输入:")
      fmt.Scanln(&content)
      //n,_:=conn.Write([]byte(content))
      //fmt.Println(n)
      conn.Write([]byte(content))

      //接收服务器端发来的消息
      var res=make([]byte,1024)
      n2,_:=conn.Read(res)
      resSli:=res[:n2]
      fmt.Println(string(resSli))
   }
}

此处有问题:

1、客户端退出,服务端不停循环

解决:增加退出机制,if n==0 {判断处理

    for {
      // 发送一个消息
      data:=make([]byte,1024)
      n,_:=conn.Read(data)
      fmt.Println("n",n)
      fmt.Println("接收客户端信息:",string(data[:n]))
      if n==0 {
         break
      }

      // 回复一个消息
      res:=string(data[:n])+"too!"
      //res:=strings.ToUpper(string(data[:n]))
      n2,_:=conn.Write([]byte(res))
      fmt.Println("n",n2)
   }
}

2、输入 ni hao 有空格时,断裂

解决:使用NewReader

//content:="nihao!"
//var content string
//fmt.Print(">>>")
//fmt.Scanln(&content)
reader:=bufio.NewReader(os.Stdin)
fmt.Print(">>>")
content,_:=reader.ReadString('\n') //读到换行
content=strings.TrimSpace(content)

n,_:=conn.Write([]byte(content))
fmt.Println("n:",n)

3 退出机制

服务器端代码

注意:这样做不好,应该是:客户端断开就退出,而不是把数据返回客户端再退出。

package main

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

func main() {

   listen,_:=net.Listen("tcp","0.0.0.0:8000")
   conn,_:=listen.Accept()

   flag := true
   for flag {
      //服务端接收打印
      var data=make([]byte,1024)
      n,_:=conn.Read(data)
      //fmt.Println(n)
      fmt.Println("data:",string(data[:n]))

      //服务端发送
      dataSli:=data[:n]
      res:=strings.ToUpper(string(dataSli))
      //n2,_:=conn.Write([]byte(res))
      //fmt.Println(n2)
      conn.Write([]byte(res))
      if string(dataSli) =="exit" {
         flag=false
      }
   }
}

客户端代码

package main

import (
   "fmt"
   "net"
)

func main() {
   conn,_:=net.Dial("tcp","0.0.0.0:8000")

   flag:=true
   for flag {
      //客户端发起写入
      //content:="client send..."
      var content string
      fmt.Print("请输入:")
      fmt.Scanln(&content)
      //n,_:=conn.Write([]byte(content))
      //fmt.Println(n)
      conn.Write([]byte(content))

      //接收服务器端发来的消息
      var res=make([]byte,1024)
      n2,_:=conn.Read(res)
      resSli:=res[:n2]
      fmt.Println(string(resSli))
      if content=="exit" {
         flag=false
      }
   }
}

此处问题:

当客户端退出时,服务端也退出,再使用for循环,使程序卡在accept的位置

4思考:(服务器端是否应该退出?)

完善为服务端不退出等待接收

package main

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

func main() {

   listen,_:=net.Listen("tcp","0.0.0.0:8000")
   for {
      conn,_:=listen.Accept()

      flag := true
      for  flag {
         //服务端接收打印
         var data=make([]byte,1024)
         n,_:=conn.Read(data)
         //fmt.Println(n)
         fmt.Println("data:",string(data[:n]))

         //服务端发送
         dataSli:=data[:n]
         res:=strings.ToUpper(string(dataSli))
         //n2,_:=conn.Write([]byte(res))
         //fmt.Println(n2)
         conn.Write([]byte(res))
         if string(dataSli) =="exit" {
            flag=false
         }
      }
   }
}

注意点,客户端直接关闭

最开始想法,此处continue,但是仍然会陷入死循环

      n,err:=conn.Read(data) //
         if err!=nil{
            fmt.Println("err:",err)
            continue
         }
         //fmt.Println(n)

需要处理下err的问题,否则客户端直接关闭,服务端死循环

package main

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

func main() {
   listen,_:=net.Listen("tcp","0.0.0.0:8000")
   for {
      conn,_:=listen.Accept()
      flag := true
      for  flag {
         //服务端接收打印
         var data=make([]byte,1024)
         n,err:=conn.Read(data) //注意此处根据n==0判断终端退出

         if n==0 {

            break
         }
         //fmt.Println(n)
         fmt.Println("data:",string(data[:n]))

         //服务端发送
         dataSli:=data[:n]
         res:=strings.ToUpper(string(dataSli))
         //n2,_:=conn.Write([]byte(res))
         //fmt.Println(n2)
         conn.Write([]byte(res))
         if string(dataSli) =="exit" {
            flag=false
         }
      }
   }
}

下午06 聊天案例扩展

客户端停了 服务器端不应该停
服务端加上for循环,阻塞,等待新连接
for true {
   // (2)等待用户连接,没有连接的客户端保持阻塞
   fmt.Println("server is waiting...")
   conn,_:=listen.Accept()
   fmt.Println("conn",conn) //conn.Write() conn.Read()

里边for循环,处理数据使用
外边for循环,处理等待连接使用

最终代码

客户端

客户端正常退出,没有发送

package main

import (
   "bufio"
   "fmt"
   "net"
   "os"
   "strings"
)

//客户端
func main() {
   // (1)通过网络三要素连接服务器
   conn,_:=net.Dial("tcp","127.0.0.1:8000")
   fmt.Println("conn",conn) //conn.Write() conn.Read()

   for {
      // (2)向服务器发送内容
      reader:=bufio.NewReader(os.Stdin)
      fmt.Print(">>>")
      content,_:=reader.ReadString('\n') //读到换行
      content=strings.TrimSpace(content)

      //正常退出
      if content == "exit" {
         break
      }

      conn.Write([]byte(content))
      //n,_:=conn.Write([]byte(content))
      //fmt.Println("n:",n)


      //(3)接收服务端的响应
      var data=make([]byte,1024)
      n2,_:=conn.Read(data)
      fmt.Println("服务端回复的信息:",string(data[:n2]))
   }
}

服务端

服务端n==0退出

package main

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

// 服务端
func main() {
   // (1)通过网络三要素连接服务器
   listen,_:=net.Listen("tcp","0.0.0.0:8080")
   for true {
      // (2)等待用户连接,没有连接的客户端保持阻塞
      fmt.Println("server is waiting...")
      conn,_:=listen.Accept()
      fmt.Println("conn",conn) //conn.Write() conn.Read()

      for true {
         //(3)接收客户端的数据
         data:=make([]byte,1024)
         n,_:=conn.Read(data) //默认阻塞
         fmt.Println("n",n)
         fmt.Println("接收客户端信息:",string(data[:n]))
         //客户端强制退出
         if n==0 {
            break
         }

         //(4)回复一个消息
         res:=strings.ToUpper(string(data[:n]))
         n2,_:=conn.Write([]byte(res))
         fmt.Println("n",n2)
      }
   }
}

报错原因:

服务器没启动,或连接的端口有问题。
panic: runtime error: invalid memory address or nil pointer dereference
29行代码 conn.Write([]byte(content))

不完美的地方:

1、使用bufio.NewReader(os.Stdin)时,直接按回车,处理问题。

  直接回车content没有数据,终端一直卡住,只能强制退出

reader:=bufio.NewReader(os.Stdin)
fmt.Print(">>>")
content,_:=reader.ReadString('\n') //读到换行
content=strings.TrimSpace(content)

2、错误处理机制不好,用exit退出。如果功能想输入exit也转大写呢?

  后续再思考如何解决

下午07单用户处理分析

关闭套接字对象

conn 文件描述符
将内层for循环,处理读写的部分抽取成函数,使用关闭套接字。
defer conn.Close()

默认goland再次执行会提示终止当前会话,增加允许开多个终端运行

或者使用命令行,再启动客户端

1、拨号成功了
2、客户端2 执行到conn.Write()
此时服务器在执行第一个客户端给的代码
    服务器端的conn是第1个客户端的conn。
    服务器不知道有没有其他的客户端再连接进来。

图解1

启动服务端和客户端1

橙色为进程监听,蓝色为内存空间
客户端1连接过来,开一个绿色内核空间
conn.Read监听绿色,是否有客户端写数据

conn阻塞在读取内核,内核里默认为空的空间。客户端发给服务端一个apple

启动客户端2

拨号成功
服务端还在第一个 read的位置,还在监听第一个客户端的套接字对象,是否有数据发过来
客户端2发送数据到到第二个套接字对象里(内核态conn2空间)

启动客户端3

发送threeconn3,主进程还是卡在第一个conn的位置

客户端1,强制退出时

conn.Read 是否阻塞,取决于是否有数据
客户端1退出,conn.Read读取conn2里的two

没有做出实现效果,处理

显示的问题,处理。
当时用了continue,导致server在conn死循环,没有conn.Read到conn2

下午08 文件上传

ssh案例(存在问题)

注释小写转大写

服务端代码修改

package main

import (
   "bufio"
   "fmt"
   "net"
   "os"
   "strings"
)

func main() {
   conn,err:=net.Dial("tcp","127.0.0.1:8888")
   if err != nil {
      fmt.Println(err)
      return
   }
   defer conn.Close()

   for true {
      reader:=bufio.NewReader(os.Stdin)
      fmt.Print("输入执行命令>>>")
      text,_:=reader.ReadString('\n')
      text=strings.TrimSpace(text)

      fmt.Println("text",text)
      //发送数据
      conn.Write([]byte(text))

      res:=make([]byte,100000)
      n,err:=conn.Read(res)
      if err != nil {
         fmt.Println("err",err)
         return
      }
      fmt.Println("n",n)
      result:=res[:n]
      fmt.Printf("接收到数据:%s\n",string(result))
   }
}

ssh\server>go mod init server

ssh\server>go mod tidy

如果还飘红,重启goland或使用go get试试

下载代码 go get

ssh\server>go get "github.com/axgle/mahonia"

注意端口占用问题

大数据传输问题

数据量超过1024时,怎么办?

文件上传

package main

import (
   "bufio"
   "fmt"
   "net"
   "os"
   "strings"
)

func main() {
   //(1)通过网络三要素连接服务器
   conn,_:=net.Dial("tcp","127.0.0.1:8000")
   fmt.Println("conn",conn) //conn.Write() conn.Read()
   defer conn.Close() // 关闭套接字对象
   for true {
      //2向服务端发送内容
      reader:=bufio.NewReader(os.Stdin)
      content,_:=reader.ReadString('\n')
      content=strings.TrimSpace(content)

      // 正常退出
      if content == "exit" {
         break
      }

      // 将一个本地文件上传到server端
      n,_:=conn.Write([]byte(content))
      fmt.Println("n",n)
   }
}

阅读器

读取标准输入

reader:=bufio.NewReader(os.Stdin) //从标准输入读取对象
fmt.Print("输入执行命令>>>")
content,_:=reader.ReadString('\n') //读到换行
content=strings.TrimSpace(content)

获取命令和路径

//2向服务端发送内容
reader:=bufio.NewReader(os.Stdin) //从标准输入读取对象
fmt.Print("输入执行命令>>>")
content,_:=reader.ReadString('\n') //读到换行
content=strings.TrimSpace(content)

ret:=strings.Split(content," ")
cmd:=ret[0]
filePath:=ret[1]
fmt.Println(cmd,filePath)

判断输入的命令做相应操作

package main

import (
   "bufio"
   "fmt"
   "net"
   "os"
   "strings"
)

func main() {
   //(1)通过网络三要素连接服务器
   conn,_:=net.Dial("tcp","127.0.0.1:8000")
   fmt.Println("conn",conn) //conn.Write() conn.Read()
   defer conn.Close() // 关闭套接字对象
   for true {
      //2向服务端发送内容
      reader:=bufio.NewReader(os.Stdin) //从标准输入读取对象
      fmt.Print("输入执行命令>>>")
      content,_:=reader.ReadString('\n') //读到换行
      content=strings.TrimSpace(content)

      ret:=strings.Split(content," ")
      cmd:=ret[0]
      filePath:=ret[1]
      fmt.Println(cmd,filePath)

      switch cmd {
      case "put":

      case "get":fmt.Println("下载功能")

      }
      // 正常退出
      if content == "exit" {
         break
      }
      // 将一个本地文件上传到server端
      n,_:=conn.Write([]byte(content))
      fmt.Println("n",n)
   }
}

实现文件上传功能

构建阅读器

按行读字符串

func put(conn net.Conn,filePath string) {
   // 实现文件上传功能
   file,_:=os.Open(filePath)
   // 构建文件阅读器
   reader:=bufio.NewReader(file)
   // (1)按行读字符串
   bytes,_:=reader.ReadBytes('\n')
   conn.Write(bytes)
}

添加循环和判断

func put(conn net.Conn,filePath string) {
   // 实现文件上传功能
   file,_:=os.Open(filePath)
   // 构建文件阅读器
   reader:=bufio.NewReader(file)
   // (1)按行读字符串
   for true {
      bytes,err:=reader.ReadBytes('\n')
      conn.Write(bytes)
      if err==io.EOF{ //io.EOF读取到文件末尾
         //fmt.Println("读取到文件末尾!")
         break
      }
   }
}

服务端如何接收问题?

如何循环接收?

根据大小判断是否接收完

       //接收数据
      var receiveLen=0
      for receiveLen<1025 {
         data:=make([]byte,1024)
         n,_:=conn.Read(data)
         receiveLen+=n
      }
      fmt.Println("上传文件成功")
   }
}

问题:少文件的信息,大小怎么知道

// 接收文件的信息,文件大小和文件名称

调整客户端,发送文件名,文件大小

func put(conn net.Conn,filePath string) {
   // 实现文件上传功能
   fmt.Println("filePath",filePath)
   
   // 构建文件阅读器
   file,_:=os.Open(filePath)
   reader:=bufio.NewReader(file)
   //将文件大小和文件名称传递到服务器
   f,_:=os.Stat(filePath)
   //发送文件大小
   fsize:=f.Size()
   //获取文件名称
   fname:=f.Name()
   
   // (1)按行读字符串

转换int64为字符串,拼接,转为字节串,发给服务器

// 构建文件阅读器
file,_:=os.Open(filePath)
reader:=bufio.NewReader(file)
//将文件大小和文件名称传递到服务器
f,_:=os.Stat(filePath)
//发送文件大小
fsize:=f.Size()
//获取文件名称
fname:=f.Name()
strInt64:=strconv.FormatInt(fsize,10)
conn.Write([]byte(fname+" "+strInt64))

服务器端

       // 接收文件的信息,文件大小和文件名称
      fileInfo:=make([]byte,1024)
      n,_:=conn.Read(fileInfo)
      fmt.Println(string(fileInfo[:n])) //图片.jpg 23443
      
      //接收数据
      var receiveLen=0
      for receiveLen<1025 {
         data:=make([]byte,1024)
         n,_:=conn.Read(data)
         receiveLen+=n
      }
      fmt.Println("上传文件成功")
   }
}

再分割

       // 接收文件的信息,文件大小和文件名称
      fileInfo:=make([]byte,1024)
      n,_:=conn.Read(fileInfo)
      fmt.Println(string(fileInfo[:n])) //图片.jpg 23443
      ret:=strings.Split(string(fileInfo[:n])," ")
      fileName:=ret[0]
      fileSize:=ret[1]
      filesizeInt,_:=strconv.Atoi(fileSize)
      //循环接收数据到文件中
      file,_:=os.OpenFile(fileName,os.O_CREATE|os.O_WRONLY|os.O_TRUNC,0666)
      writer:=bufio.NewWriter(file)

      //接收数据
      var receiveLen=0
      for receiveLen

完成后测试

索引溢出

排错

// 排错,增加打印,查看打印内容。发现打印为空。排查客户端是否上传成功。
//fmt.Println(ret,reflect.TypeOf(ret))

最终确定客户端传入为空,发现为Working directory问题,修改Working directory

 

服务端完整代码

package main

import (
   "bufio"
   "fmt"
   "net"
   "os"
   "strconv"
   "strings"
)

func main() {
   //(1)通过网络三要素连接服务器
   listen,_:=net.Listen("tcp","0.0.0.0:8000")
   fmt.Println("listen",listen)
   for true{
      //(2)等待用户连接,没有连接的客户端保持阻塞
      fmt.Println("server is waiting....")
      conn,_:=listen.Accept()
      fmt.Println("conn",conn) //conn.Write() conn.Read()

      // 接收文件的信息,文件大小和文件名称
      fileInfo:=make([]byte,1024)
      n,_:=conn.Read(fileInfo)
      if n==0 {
         break
      }
      fmt.Println(string(fileInfo[:n])) //图片.jpg 23443
      ret:=strings.Split(string(fileInfo[:n])," ")
      // 排错,增加打印,查看打印内容。发现打印为空。排查客户端是否上传成功。
      //fmt.Println(ret,reflect.TypeOf(ret))
      fileName:=ret[0]
      fileSize:=ret[1]
      filesizeInt,_:=strconv.Atoi(fileSize)
      //循环接收数据到文件中
      file,_:=os.OpenFile(fileName,os.O_CREATE|os.O_WRONLY|os.O_TRUNC,0666)
      writer:=bufio.NewWriter(file)

      //接收数据
      var receiveLen=0
      for receiveLen



难点
    1 文件的信息
    2 for循环遍历 一点儿一点读  

有可能文件信息和循环混在一起?粘包效应

为什么用FormartInt,因为fileSize64

strInt64:=strconv.FormatInt(fileSize,10)

客户端完整代码

package main

import (
   "bufio"
   "fmt"
   "io"
   "net"
   "os"
   "strconv"
   "strings"
)

func put(conn net.Conn,filePath string) {
   // 实现文件上传功能
   fmt.Println("filePath",filePath)

   // 构建文件阅读器
   file,_:=os.Open(filePath)
   reader:=bufio.NewReader(file)
   //将文件大小和文件名称传递到服务器
   f,_:=os.Stat(filePath)
   //发送文件大小
   fileSize :=f.Size()
   //获取文件名称
   fileName :=f.Name()
   strInt64:=strconv.FormatInt(fileSize,10)
   fmt.Println(fileName +" "+strInt64)
   conn.Write([]byte(fileName +" "+strInt64)) //图片.jpg 23443

   // (1)按行读字符串
   for true {
      bytes,err:=reader.ReadBytes('\n')
      conn.Write(bytes)
      if err==io.EOF{ //io.EOF读取到文件末尾
         //fmt.Println("读取到文件末尾!")
         break
      }
   }
}

func main() {
   //(1)通过网络三要素连接服务器
   conn,_:=net.Dial("tcp","127.0.0.1:8000")
   fmt.Println("conn",conn) //conn.Write() conn.Read()
   defer conn.Close() // 关闭套接字对象
   for true {
      //2向服务端发送内容
      reader:=bufio.NewReader(os.Stdin) //从标准输入读取对象
      fmt.Print("输入执行命令>>>")
      content,_:=reader.ReadString('\n') //读到换行
      content=strings.TrimSpace(content)

      ret:=strings.Split(content," ")
      cmd:=ret[0]
      filePath:=ret[1]
      fmt.Println(cmd,filePath)

      switch cmd {
      case "put":
         put(conn,filePath)
      case "get":fmt.Println("下载功能")

      }
      // 正常退出
      if content == "exit" {
         break
      }
      // 将一个本地文件上传到server端
      n,_:=conn.Write([]byte(content))
      fmt.Println("n",n)
   }
}

图解过程

在客户端执行 ”put 图片.jpg” 命令,
先将图片名和图片大小传给服务器
服务器通过文件名确定报存文件的名,根据文件大小,循环查询写入磁盘。

socket做支撑,以文件收发作为过程实现

下午09 web开发

CS架构
BS架构(browser server)

go写的程序都是应用程序。
go写的面向客户端为浏览器,发送的请求基于http协议,响应的程序都称为web应用程序。

http协议

基于TCP协议,对内容做限制,格式

web服务器

收到数据后响应格式:协议版本 状态码 状态码原因短语

res:="HTTP/1.1 200 ok\r\n\r\nhello Web!"

接收完后,发送

package main

import (
   "fmt"
   "net"
)

// 服务端
func main() {
   // (1)确定网络三要素,建立服务
   listen,_:=net.Listen("tcp","127.0.0.1:8080")
   fmt.Println("listen",listen)
   for true {
      //(2)等待用户连接,没有连接的客户端保持阻塞
      fmt.Println("server is waiting...")
      conn,_:=listen.Accept()
      fmt.Println("conn",conn) //conn.Write() conn.Read()

      data:=make([]byte,1024)
      n,_:=conn.Read(data)
      fmt.Println("n",n)

      //接收完后,发送
      res:="HTTP/1.1 200 ok\r\n\r\nhello Web!"
      conn.Write([]byte(res))
   }
}

请求格式

修改代码

增加

标签,加粗

//接收完后,发送
res:="HTTP/1.1 200 ok\r\n\r\n

hello Web!

" conn.Write([]byte(res))

 

 

web服务器2

处理函数HandleFunc

import "net/http"

路径和函数

func main() {
   //路径和函数
   http.HandleFunc("/hi",foo)
   http.ListenAndServe(":7777",nil)
}

浏览器带的信息 r *http.Request
响应的 放到w里边 http.ResopnseWriter
这样就不用写HTTP/1.1 开头的部分信息

package main

import (
   "fmt"
   "net/http"
)

//服务端
func foo(w http.ResponseWriter,r *http.Request){
   fmt.Fprintf(w,"Hello Go!")
}

func main() {
   //路径和函数
   http.HandleFunc("/hi",foo)
   http.ListenAndServe(":7777",nil)
}

 

添加标签

package main

import (
   "fmt"
   "net/http"
)

//服务端
func foo(w http.ResponseWriter,r *http.Request){
   fmt.Fprintf(w,"

Hello Go!

") } func main() { //路径和函数 http.HandleFunc("/hi",foo) http.ListenAndServe(":7777",nil) }

打印时间

package main

import (
   "fmt"
   "github.com/jinzhu/now"
   "net/http"
)

// 服务器端
func foo(w http.ResponseWriter,r *http.Request){
   fmt.Fprintf(w,`

Hello Go!

`) } func timer(w http.ResponseWriter,r *http.Request){ fmt.Fprintf(w,now.BeginningOfMinute().String()) } func main() { http.HandleFunc("/timer",timer) http.HandleFunc("/hi",foo) http.ListenAndServe("0.0.0.0:7777",nil) }

相关