8 channel、反射、网络编程【Go语言教程】

╰半橙微兮° 2024-03-22 15:44 159阅读 0赞

8 channel、反射、网络编程【Go语言教程】

1 channel

errChan := make(chan err) 不指定管道长度则表明创建了一个无缓冲通道

  • 对于无缓冲通道,发送和接收操作是同步的。发送操作会等待接收方准备好接收值,而接收操作会等待发送方准备好发送值。因此,无缓冲通道的发送和接收操作必须在不同的协程中进行,以避免死锁。

在 Go 中,通道(channel)的长度是固定的,一旦创建就无法更改。当我们使用 make 函数创建通道时,可以选择指定通道的容量(长度)。

如果我们不指定通道的容量,则默认为无缓冲通道(unbuffered channel),也就是长度为 0 的通道。无缓冲通道在发送操作时会阻塞,直到有协程准备好接收该值;在接收操作时也会阻塞,直到有协程准备好发送值。

对于无缓冲通道,发送和接收操作是同步的。发送操作会等待接收方准备好接收值,而接收操作会等待发送方准备好发送值。因此,无缓冲通道的发送和接收操作必须在不同的协程中进行,以避免死锁。

对于带有缓冲的通道(buffered channel),我们可以在创建通道时指定通道的容量。带有缓冲的通道允许在发送操作时不立即阻塞,只有当通道的缓冲区满时才会阻塞。类似地,在接收操作时,只有当通道的缓冲区为空时才会阻塞。

需要注意的是,通道的容量只影响通道的缓冲区大小,而不是通道可以存放的值的数量。无论通道的容量是多少,我们都可以向通道发送任意数量的值,只要接收方及时接收这些值。

综上所述,通道的容量是指通道的缓冲区大小,而不是通道可以存放的值的数量。无缓冲通道的容量为 0,而带有缓冲的通道可以指定任意大于 0 的容量。

1.1 概念及快速入门

channel:管道,主要用于不同goroutine之间的通讯

需求:现在要计算 1-200 的各个数的阶乘,并且把各个数的阶乘放入到 map 中。最后显示出来。要求使用 goroutine 完成

分析思路:

  1. 使用 goroutine 来完成,效率高,但是会出现并发/并行安全问题.
  2. 这里就提出了不同 goroutine 如何通信的问题

在这里插入图片描述

  1. package main
  2. import(
  3. "fmt"
  4. "time"
  5. )
  6. var (
  7. myMap = make(map[int]int, 10)
  8. )
  9. //计算n!,将计算结果放入myMap
  10. func test(n int){
  11. res := 1
  12. for i := 1; i <= n; i++ {
  13. res *= i
  14. }
  15. myMap[n] = res //concurrent map writes?
  16. }
  17. func main(){
  18. //开启200个协程
  19. for i := 1; i <= 200; i++ {
  20. go test(i)
  21. }
  22. //休眠10s[防止主线程直接跑完,而协程中的任务未完成]
  23. time.Sleep(time.Second * 10)
  24. for i, v := range myMap {
  25. fmt.Printf("map[%d]=%d\n", i, v)
  26. }
  27. }

运行代码会出现并发问题:
在这里插入图片描述

不同goroutine之间如何通讯:

  1. 全局变量的互斥锁
  2. 使用管道channel来解决

计算 1-200 的各个数的阶乘,并且把各个数的阶乘放入到 map 中。最后显示出来。用下面方法改进:

①使用全局变量的互斥锁[并发、并行问题]
  • 没有对全局变量m加索,会出现资源争夺问题,提示:concurrent map writes
  • 加入互斥锁
  • 我们的数的阶乘很大,结果会越界,可以将求阶乘改成 sum += i
  1. package main
  2. import(
  3. "fmt"
  4. "time"
  5. "sync"
  6. )
  7. var (
  8. myMap = make(map[int]int, 10)
  9. //声明一个全局的互斥锁
  10. //lock 是一个全局的互斥锁
  11. //sync 是包, synchronized 同步
  12. //Mutex:是互斥
  13. lock sync.Mutex
  14. )
  15. //计算n!,将计算结果放入myMap
  16. func test(n int){
  17. res := 1
  18. for i := 1; i <= n; i++ {
  19. res += int(i)
  20. }
  21. //加锁
  22. lock.Lock()
  23. myMap[n] = res //concurrent map writes?
  24. //解锁
  25. lock.Unlock()
  26. }
  27. func main(){
  28. //开启200个协程
  29. for i := 1; i <= 200; i++ {
  30. go test(i)
  31. }
  32. //休眠10s[防止主线程直接跑完,而协程中的任务未完成]
  33. time.Sleep(time.Second * 10)
  34. for i, v := range myMap {
  35. fmt.Printf("map[%d]=%d\n", i, v)
  36. }
  37. }

在这里插入图片描述

②使用channel
1. 为什么需要channel
  • 为什么需要channel?
  1. 前面使用全局变量加锁同步来解决 goroutine 的通讯,但不完美
  2. 主线程在等待所有 goroutine 全部完成的时间很难确定,我们这里设置 10 秒,仅仅是估算。
  3. 如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有 goroutine 处于工作状态,这时也会随主线程的退出而销毁
  4. 通过全局变量加锁同步来实现通讯,也并不利用多个协程对全局变量的读写操作。
  5. 上面种种分析都在呼唤一个新的通讯机制-channel
2. 基本介绍
  1. channle 本质就是一个数据结构-队列【示意图】
  2. 数据是先进先出【FIFO : first in first out】
  3. 线程安全,多 goroutine 访问时,不需要加锁,就是说 channel 本身就是线程安全的
  4. channel 有类型的,一个 string 的 channel 只能存放 string 类型数据。
    在这里插入图片描述
3. 声明、定义channel
  • var 变量名 chan 数据类型
    举例:
    var intChan chan int (intChan 用于存放 int 数据)
    var mapChan chan map[int]string (mapChan 用于存放 map[int]string 类型) var perChan chan Person
    var perChan2 chan *Person
  • 说明:
    channel 是引用类型
    channel 必须初始化才能写入数据, 即 make 后才能使用管道是有类型的,intChan 只能写入 整数 int
4. 快速入门及注意事项

(1)快速入门:

管道的初始化,写入数据到管道,从管道读取数据及基本的注意事项

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main(){
  6. //1. 创建一个可以存放3个int类型的管道
  7. var intChan chan int
  8. intChan = make(chan int, 3)
  9. //2. 看看intChan是什么
  10. fmt.Printf("intChan的值=%v intChan本身地址=%p\n", intChan, &intChan)
  11. //3. 向管道写入数据
  12. intChan <- 10
  13. num := 211
  14. intChan <- num
  15. intChan <- -50
  16. //intChan <- 100 //注意:我们在给管道写入数据时,不能超过其容量
  17. //4. 看看管道的长度和cap(容量)
  18. fmt.Printf("channel len=%v cap=%v\n", len(intChan), cap(intChan))
  19. //5. 从管道中读取数据
  20. var num2 int
  21. num2 = <- intChan
  22. fmt.Println("num2=", num2)
  23. fmt.Printf("channel len=%v cap=%v\n", len(intChan), cap(intChan))
  24. //6. 在没有使用协程的情况下,如果管道中的数据已经全部取出,再取就会报告deadlock
  25. num3 := <- intChan
  26. num4 := <- intChan
  27. fmt.Printf("num3=%v, num4=%v", num3, num4)
  28. // num5 := <- intChan
  29. // fmt.Println("num5=", num5) //fatal error: all goroutines are asleep - deadlock!
  30. }

在这里插入图片描述
(2)注意事项:

  1. channel 中只能存放指定的数据类型
  2. channle 的数据放满后,就不能再放入了
  3. 如果从 channel 取出数据后,可以继续放入
  4. 在没有使用协程的情况下,如果 channel 数据取完了,再取,就会报 dead lock
5. channel的使用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
练习题:

在这里插入图片描述

  1. package main
  2. import (
  3. "fmt"
  4. "math/rand"
  5. "time"
  6. "strconv"
  7. )
  8. type Person struct {
  9. Name string
  10. Age int
  11. Address string
  12. }
  13. func main(){
  14. var personChan chan Person
  15. //make给chan开辟空间
  16. personChan = make(chan Person, 10)
  17. //取纳秒时间戳作为种子,保证每次的随机种子都不同
  18. //给rand种种子
  19. rand.Seed(time.Now().UnixNano())
  20. for i := 1; i <= 10; i++ {
  21. index := rand.Int()
  22. fmt.Println("index===", index)
  23. person := Person{
  24. Name: "zhangsan" + strconv.Itoa(index),
  25. Age: i,
  26. Address: "beijing" + strconv.Itoa(index),
  27. }
  28. personChan <- person
  29. }
  30. len := len(personChan)
  31. for i := 0; i < len; i++ {
  32. p := <- personChan
  33. fmt.Println(p)
  34. }
  35. }
6. channel的关闭和遍历

(1)channel的关闭

使用内置函数close 可以关闭 channel, 当 channel 关闭后,就不能再向 channel 写数据了,但是仍然可以从该 channel 读取数据

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main(){
  6. intChan := make(chan int, 5)
  7. intChan <- 10
  8. intChan <- 20
  9. close(intChan) //close
  10. //关闭之后不能再向chan写入数据,但是可以读取
  11. // intChan <- 30 //panic: send on closed channel
  12. n1 := <- intChan
  13. fmt.Println("n1=", n1) //n1= 10
  14. }

(2)channel的遍历

channel 支持 for–range 的方式进行遍历,请注意两个细节

  1. 在遍历时,如果 channel 没有关闭,则回出现 deadlock 的错误
  2. 在遍历时,如果 channel 已经关闭,则会正常遍历数据,遍历完后,就会退出遍历。
  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main(){
  6. intChan := make(chan int, 5)
  7. intChan <- 10
  8. intChan <- 20
  9. intChan <- 50
  10. close(intChan) //close
  11. //关闭之后不能再向chan写入数据,但是可以读取
  12. // intChan <- 30 //panic: send on closed channel
  13. n1 := <- intChan
  14. fmt.Println("n1=", n1) //n1= 10
  15. for v := range intChan {
  16. fmt.Printf("value=%v\n", v)
  17. }
  18. }
7. 综合实例
  • 需求:
    要求统计 1-8000的数字中,哪些是素数?使用 goroutine
    和 channel 的知识完成
  • 分析思路:
    传统的方法,就是使用一个循环,循环的判断各个数是不是素数【ok】。
    使用并发/并行的方式,将统计素数的任务分配给多个(4 个)goroutine 去完成,完成任务时间短。
    在这里插入图片描述
  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. //向intChan放入1-8000个数
  7. func putNum(intChan chan int){
  8. for i := 1; i <= 8000; i++ {
  9. intChan <- i
  10. }
  11. //关闭chan
  12. close(intChan)
  13. }
  14. //从intChan取出数据,并判断是否是素数,如果是,就放入到primeChan
  15. func primeNum(intChan chan int, primeChan chan int, exitChan chan bool){
  16. var flag bool
  17. for {
  18. time.Sleep(time.Millisecond * 10)
  19. num, ok := <- intChan
  20. if !ok {
  21. //取不到数据了,就退出
  22. break
  23. }
  24. flag = true //假设是素数
  25. for i := 2; i < num; i++ {
  26. if num % i == 0 {
  27. flag = false
  28. break
  29. }
  30. }
  31. if flag {
  32. primeChan <- num
  33. }
  34. }
  35. fmt.Println("有一个primeNum协程因为取不到数据,退出")
  36. //这里我们还不能关闭primeChan
  37. //向exitChan写入true
  38. exitChan <- true
  39. }
  40. func main(){
  41. intChan := make(chan int, 1000)
  42. primeChan := make(chan int, 2000)
  43. //标识退出的管道
  44. exitChan := make(chan bool, 4)
  45. //开启一个协程,向intChan放入1-8000个数
  46. go putNum(intChan)
  47. //开启4个协程,从intChan取出数据,并判断是否是素数
  48. for i := 0; i < 4; i++ {
  49. go primeNum(intChan, primeChan, exitChan)
  50. }
  51. //主线程进行处理
  52. go func(){
  53. for i:= 0; i < 4; i++{
  54. <-exitChan
  55. }
  56. //当我们从exitChan取出了4个结果,就可以放心的关闭primeChan
  57. close(primeChan)
  58. }()
  59. //遍历primeChan,把结果取出
  60. for {
  61. res, ok := <- primeChan
  62. if !ok {
  63. break
  64. }
  65. //将结果取出
  66. fmt.Printf("素数=%d\n", res)
  67. }
  68. }

结果:
在这里插入图片描述

8. 只读、只写管道及注意事项
  • 只读、只写管道:
    在这里插入图片描述
    在这里插入图片描述
  • 注意事项
  1. channel 可以声明为只读,或者只写性质
  2. 只读只写案例
    在这里插入图片描述
    3)使用 select 可以解决从管道取数据的阻塞问题

select用法:

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func main(){
  7. //使用select可以解决管道取数据的阻塞问题
  8. //1. 定义一个管道 10 int
  9. intChan := make(chan int, 10)
  10. for i := 0; i < 10; i++ {
  11. intChan <- i
  12. }
  13. //2. 定义一个管道 5 string
  14. stringChan := make(chan string, 5)
  15. for i := 0; i < 5; i++ {
  16. stringChan <- "hello" + fmt.Sprintf("%d", i)
  17. }
  18. //传统的方法在遍历管道时,如果不关闭,则会因阻塞导致deadlock
  19. //可是我们在实际开发中,我们可能不好确定什么时候关闭管道
  20. //办法:我们可以使用select方式解决
  21. //label:
  22. for {
  23. select {
  24. //注意:这里,如果intChan一直没有关闭,不会一直阻塞而deadlock,会自动到下一个case匹配
  25. case v := <- intChan:
  26. fmt.Printf("从intChan读取的数据=%d\n", v)
  27. time.Sleep(time.Second)
  28. case v := <- stringChan:
  29. fmt.Printf("从stringChan读取的数据=%s\n", v)
  30. time.Sleep(time.Second)
  31. default:
  32. fmt.Printf("不玩了,都取不到了【程序员可以在这里加入自己的逻辑】\n")
  33. time.Sleep(time.Second)
  34. return
  35. //break label
  36. }
  37. }
  38. }

在这里插入图片描述

2 反射

2.1 概念

  1. 反射可以在运行时动态获取变量的各种信息, 比如变量的类型(type),类别(kind)
  2. 如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法)
  3. 通过反射,可以修改变量的值,可以调用关联的方法。
  4. 使用反射,需要 import (“reflect”)
    在这里插入图片描述
    5)反射常见的应用场景
    在这里插入图片描述

2.2 反射中重要的函数

在这里插入图片描述

  1. 变量、interface{} 和 reflect.Value 是可以相互转换的,这点在实际开发中,会经常使用到。
    在这里插入图片描述

2.3 快速入门

请编写一个案例,演示对(结构体类型、interface{}、reflect.Value)进行反射的基本操作

  1. package main
  2. import (
  3. "reflect"
  4. "fmt"
  5. )
  6. type Student struct {
  7. Name string
  8. Age int
  9. }
  10. func reflectTest01(b interface{
  11. }){
  12. //通过反射获取到传入变量的type、kind值
  13. //1. 先获取到reflect.Type
  14. rType := reflect.TypeOf(b)
  15. fmt.Println("rType=", rType)
  16. //2. 获取到reflect.Value
  17. rVal := reflect.ValueOf(b)
  18. n2 := 2 + rVal.Int()
  19. fmt.Println("n2=", n2)
  20. fmt.Printf("rVal=%v rType=%T\n", rVal, rType)
  21. //下面我们将rVal转成interface{}
  22. iV := rVal.Interface()
  23. //将interface{}通过断言转成需要的类型
  24. num2 := iV.(int)
  25. fmt.Println("num2=", num2)
  26. }
  27. //对结构体的反射
  28. func reflectTest02(b interface{
  29. }){
  30. //通过反射获取到传入的变量的type、kind,值
  31. //1. 先获取到reflect.Type
  32. rType := reflect.TypeOf(b)
  33. fmt.Println("rType=", rType)
  34. //2. 获取到reflect.Value
  35. rVal := reflect.ValueOf(b)
  36. //下面我们将rVal转成interface{}
  37. iV := rVal.Interface()
  38. fmt.Printf("iv=%v iv type=%T\b", iV, iV)
  39. //将interface{}通过断言转成需要的类型
  40. //这里,我们使用类型断言【同学们可以使用switch的断言形式来更加灵活的判断】
  41. stu, ok := iV.(Student)
  42. if ok {
  43. fmt.Printf("stu.Name=%v\n", stu.Name)
  44. }
  45. }
  46. func main(){
  47. //1. 基本数据类型 反射
  48. var num int = 100
  49. reflectTest01(num)
  50. //2. 定义一个Student实例
  51. stu := Student{
  52. Name: "tom",
  53. Age: 20,
  54. }
  55. reflectTest02(stu)
  56. }

在这里插入图片描述

2.4 反射的细节和注意事项

  1. reflect.Value.Kind,获取变量的类别,返回的是一个常量
    在这里插入图片描述
  2. Type 和 Kind 的区别【type更加具体】
    Type 是类型, Kind 是类别, Type 和 Kind 可能是相同的,也可能是不同的. 比如: var num int = 10 num 的 Type 是 int , Kind 也是 int
    比如: var stu Student stu 的 Type 是 pkg1.Student , Kind 是 struct
    在这里插入图片描述

  3. 通过反射的来修改变量, 注意当使用 SetXxx 方法来设置需要通过对应的指针类型来完成, 这样才能改变传入的变量的值, 同时需要使用到reflect.Value.Elem()方法
    在这里插入图片描述

  4. reflect.Value.Elem() 应该如何理解?
    在这里插入图片描述

2.5 综合案例

使用反射来遍历结构体的字段,调用结构体的方法,并获取结构体标签的值

  1. package main
  2. import (
  3. "reflect"
  4. "fmt"
  5. )
  6. type Monster struct {
  7. Name string `json:"name"`
  8. Age int `json:"monster_age"`
  9. Score float32 `json:"成绩"`
  10. Sex string
  11. }
  12. //方法,返回两数的和
  13. func (s Monster) GetSum(n1, n2 int) int {
  14. return n1 + n2
  15. }
  16. //方法,接收四个值,给结构体赋值
  17. func (s Monster) Set(name string, age int, score float32, sex string){
  18. s.Name = name
  19. s.Age = age
  20. s.Score = score
  21. s.Sex = sex
  22. }
  23. //方法,显示结构体的值
  24. func (s Monster) Print(){
  25. fmt.Println("----start----")
  26. fmt.Println(s)
  27. fmt.Println("----end----")
  28. }
  29. func TestStruct(a interface{
  30. }){
  31. //获取reflect.Type类型
  32. typ := reflect.TypeOf(a)
  33. //获取reflect.Value类型
  34. val := reflect.ValueOf(a)
  35. //获取到a对应的类别
  36. kd := val.Kind()
  37. //如果传入的不是struct,就退出
  38. if kd != reflect.Struct {
  39. fmt.Println("expect struct")
  40. return
  41. }
  42. //是结构体,获取该结构体有几个字段
  43. num := val.NumField()
  44. fmt.Printf("struct has %d fields\n", num)
  45. for i := 0; i < num; i++ {
  46. fmt.Printf("Field %d值为=%v\n", i, val.Field(i))
  47. //获取到struct标签,注意需要通过reflect.Type来获取tag标签的值
  48. tagVal := typ.Field(i).Tag.Get("json") //因为前面定义结构体用到了'json标签'
  49. //如果该字段有tag标签就显示,否则就不显示
  50. if tagVal != "" {
  51. fmt.Printf("Field %d tag 为=%v\n", i, tagVal)
  52. }
  53. }
  54. //获取到该结构体有多少个方法
  55. numOfMethod := val.NumMethod()
  56. fmt.Printf("struct has %d methods\n", numOfMethod)
  57. //var params []reflect.Value
  58. //方法的排序默认是按照函数名的排序(ASCII码)
  59. val.Method(1).Call(nil) //获取到第二个【下标为1】方法,调用它 【传参为空】
  60. //调用结构体的第1个方法 Method(0)
  61. var params []reflect.Value //声明了 []reflect.Value()
  62. params = append(params, reflect.ValueOf(10))
  63. params = append(params, reflect.ValueOf(40))
  64. res := val.Method(0).Call(params) //传入的参数是[]reflect.Value,返回[]reflect.Value
  65. fmt.Println("res=", res[0].Int()) //返回结果,返回的结果是[]reflect.Value
  66. }
  67. func main(){
  68. var a Monster = Monster{
  69. Name: "黄鼠狼精",
  70. Age: 400,
  71. Score: 30.9,
  72. }
  73. TestStruct(a)
  74. }

在这里插入图片描述

3 网络编程

3.1 概念及前置知识

  1. TCP socket 编程,是网络编程的主流。之所以叫 Tcp socket 编程,是因为底层是基于 Tcp/ip 协议的. 比如: QQ 聊天
  2. b/s 结构的 http 编程,我们使用浏览器去访问服务器时,使用的就是 http 协议,而 http 底层依旧是用 tcp socket 实现的。 比如: 京东商城 【这属于 go web 开发范畴 】
  3. 计算机间要相互通讯,必须要求网线,网卡,或者是无线网卡.

在这里插入图片描述

  1. 协议
    在这里插入图片描述

在这里插入图片描述

  1. 端口
  • 0 号是保留端口.
  • 1-1024 是固定端口(程序员不要使用),又叫有名端口,即被某些程序固定使用,一般程序员不使用.
  • 常见端口:22: SSH 远程登录协议 23: telnet 使用 21: ftp 使用
    25: smtp 服务使用 80: iis 使用 7: echo 服务
  • 1025-65535 是动态端口
    这些端口,程序员可以使用.

注意:

  1. 在计算机(尤其是做服务器)要尽可能的少开端口
  2. 一个端口只能被一个程序监听
  3. 如果使用 netstat –an 可以查看本机有哪些端口在监听
  4. 可以使用 netstat –anb 来查看监听端口的 pid,在结合任务管理器关闭不安全的端口

3.2 快速入门

  • 服务端的处理流程
  1. 监听端口 8888
  2. 接收客户端的 tcp 链接,建立客户端和服务器端的链接.
  3. 创建 goroutine,处理该链接的请求(通常客户端会通过链接发送请求包)
  • 客户端的处理流程
  1. 建立与服务端的链接
  2. 发送请求数据[终端],接收服务器端返回的结果数据
  3. 关闭链接
①服务端功能及代码

功能:

  • 编写一个服务器端程序,在 8888 端口监听可以和多个客户端创建链接
    链接成功后,客户端可以发送数据,服务器端接受数据,并显示在终端上. 先使用 telnet 来测试,然后编写客户端程序来测试

代码:

  1. package main
  2. import (
  3. "fmt"
  4. "net"
  5. )
  6. func process(conn net.Conn) {
  7. //循环接收客户端发送的数据
  8. defer conn.Close()
  9. for {
  10. //创建一个新的切片
  11. buf := make([]byte, 1024)
  12. //1. 等待客户端通过conn发送消息
  13. //2. 如果客户端没有write(发送消息),那么协程就阻塞在这里
  14. fmt.Printf("服务器在等待客户端%s 发送信息\n", conn.RemoteAddr().String())
  15. n, err := conn.Read(buf) //从conn中读取
  16. if err != nil {
  17. fmt.Printf("客户端退出 err=%v", err)
  18. return
  19. }
  20. //3. 显示客户端给服务端发送的数据(打印在控制台上)
  21. fmt.Print(string(buf[:n]))
  22. }
  23. }
  24. func main() {
  25. fmt.Println("服务器开始监听...")
  26. //1. tcp表示使用的网络协议是tcp
  27. //2. 0.0.0.0:8888表示在本地监听8888端口
  28. listen, err := net.Listen("tcp", "0.0.0.0:8888")
  29. if err != nil {
  30. fmt.Println("listen err=", err)
  31. return
  32. }
  33. defer listen.Close() //延时关闭listen
  34. //循环等待客户端来连接服务端
  35. for {
  36. //等待客户端连接
  37. fmt.Println("等待客户端来连接...")
  38. conn, err := listen.Accept()
  39. if err != nil {
  40. fmt.Println("Accept() err=", err)
  41. } else {
  42. fmt.Printf("Accept() success con=%v 客户端ip=%v\n", conn, conn.RemoteAddr().String())
  43. }
  44. //这里准备起一个协程,为客户端服务
  45. go process(conn)
  46. }
  47. }

运行之后效果:

  1. 服务器开始监听...
  2. 等待客户端来连接...
②客户端功能及代码
  1. 编写一个客户端端程序,能链接到 服务器端的 8888 端口
  2. 客户端可以发送单行数据,然后就退出
  3. 能通过终端输入数据(输入一行发送一行), 并发送给服务器端
  4. 在终端输入 exit,表示退出程序.
  1. package main
  2. import (
  3. "bufio"
  4. "fmt"
  5. "net"
  6. "os"
  7. "strings"
  8. )
  9. func main() {
  10. conn, err := net.Dial("tcp", "192.168.1.100:8888")
  11. if err != nil {
  12. fmt.Println("client dial err=", err)
  13. return
  14. }
  15. //功能一:客户端可以发送单行数据,然后就退出
  16. reader := bufio.NewReader(os.Stdin) //os.Stdin 表示标准输入:【终端】
  17. for {
  18. //从终端读取一行用户输入,并发送给服务端
  19. line, err := reader.ReadString('\n')
  20. if err != nil {
  21. fmt.Println("readString err=", err)
  22. }
  23. //功能二:当用户输入exit就退出
  24. line = strings.Trim(line, "\r\n")
  25. if line == "exit" {
  26. fmt.Println("客户端退出...")
  27. break
  28. }
  29. n, err := conn.Write([]byte(line))
  30. if err != nil {
  31. fmt.Println("conn Write err=", err)
  32. }
  33. fmt.Printf("客户端发送了 %d字节的数据\n", n)
  34. }
  35. }
③运行效果

在这里插入图片描述

发表评论

表情:
评论列表 (有 0 条评论,159人围观)

还没有评论,来说两句吧...

相关阅读

    相关 GO语言channel通道

    通道可以被认为是Goroutines通信的管道。类似于管道中的水从一端到另一端的流动,数据可以从一端发送到另一端,通过通道接收。 在前面讲Go语言的并发时候,我们就说过,当多

    相关 Go语言反射

    `一、反射介绍` > 在Go语言标准库中reflect包提供了运行时反射,程序运行过程中动态操作结构体 > 当变量存储结构体属性名称,想要对结构体这个属性赋值

    相关 go语言网络编程

    现在我们几乎每天都在使用互联网,我们前面已经学习了如何编写Go语言程序,但是如何才能让我们的程序通过网络互相通信呢?本章我们就一起来学习下Go语言中的网络编程。 关于网络编程其