@@ -1,58 +0,0 @@ 30.Socket编程 | 凤凰涅槃进阶之路

30.Socket编程

Abel sun2023年1月6日约 1040 字大约 3 分钟

30.Socket编程

1,什么是Socket

Socket起源于Unix,而Unix基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用:Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。

常用的Socket类型有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。

2,TCP的C/S架构

1560678262840

3,示例代码

1,服务端

通过如下代码,创建一个可以提供服务的服务端程序。

package main

import (
 "fmt"
 "net"
)

func main() {
 //监听
 listener, err := net.Listen("tcp", "127.0.0.1:8000")
 if err != nil {
  fmt.Println("err = ", err)
  return
 }
 defer listener.Close()
 //阻塞等待用户链接
 conn, err := listener.Accept()
 if err != nil {
  fmt.Println("err = ", err)
  return
 }
 //接收用户的请求
 buf := make([]byte, 1024)
 n, err1 := conn.Read(buf)
 if err1 != nil {
  fmt.Println("err1 = ", err1)
  return
 }
 fmt.Println("buf = ", string(buf[:n]))
 defer conn.Close() //关闭当前用户链接
}

这时,开两个窗口,一个运行程序提供服务,一个连接服务模拟客户端。

1560678486753

这个地方在使用natcat工具的时候,使用了绝对路径调用的方式,如果想要全局引用,需要先加入到系统环境变量,不然等会儿会有一个坑。

2,用代码写一个客户端

package main

import (
 "fmt"
 "net"
)

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

 defer conn.Close()

 //发送数据
 conn.Write([]byte("are u ok?"))

}

1560678947848

3,简单版并发服务器

基本上就是创建一个服务器,可以接收多个请求,代码如下:

package main

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

func HandleConn(conn net.Conn) {
 //函数调用完毕,自动关闭conn
 defer conn.Close()
 //获取客户端的网络地址信息
 addr := conn.RemoteAddr().String()
 fmt.Println(addr, "connect successful")
 buf := make([]byte, 2048)
 for {
  //读取用户数据
  n, err := conn.Read(buf)
  if err != nil {
   fmt.Println("err = ", err)
   return
  }
  fmt.Printf("[%s]: %s\n", addr, string(buf[:n]))
  //fmt.Println("len = ", len(string(buf[:n])))
  if "exit" == string(buf[:n-1]) {
   fmt.Println(addr, "exit")
   return
  }
  //把数据转换为大写,在发送给用户
  conn.Write([]byte(strings.ToUpper(string(buf[:n]))))
  fmt.Println("发送成功")
 }
}

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

 //接收多个用户
 for {
  conn, err := listener.Accept()
  if err != nil {
   fmt.Println("err = ", err)
   return
  }

  //处理用户请求,新建一个协程
  go HandleConn(conn)
 }
}

这个时候,同样是在一个窗口运行程序,然后使用工具进行连接测试,但是刚刚踩了一个坑,我依旧使用的是上边绝对路径的方式,结果发现效果如下:

1560685149931

可以注意到一个细节就是,原本应该是我输入一个内容就直接返回的,结果却是在第二次发送的时候,返回了第一次的内容,好奇怪,不知道啥原因。最后使用加入环境变量,相对路径调用的方式来进行。

1560685314638

刚刚是通过nc来模拟的客户端请求,现在写一个客户端程序来进行测试。

package main

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

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

 go func() {
  //从键盘输入内容,给服务器发送内容
  str := make([]byte, 1024)
  for {
   n, err := os.Stdin.Read(str) //从键盘读取内容
   if err != nil {
    fmt.Println("os.Stdin err = ", err)
    return
   }
   conn.Write(str[:n])
  }
 }()

 //接收服务器回复的数据
 //创建一个切片
 buf := make([]byte, 1024)
 for {
  n, err := conn.Read(buf) //接收服务器的请求
  if err != nil {
   fmt.Println("conn.Read err = ", err)
   return
  }
  fmt.Println(string(buf[:n])) //打印服务器返回的结果
 }
}

然后分别运行两边来看效果:

1561000904926

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