Go36-46-访问网络服务(socket)

访问网络服务

这篇开始讲网络编程。不过网络编程的内容过于庞大,这里主要讲socket。而socket可以讲的东西也太多了,因此,这里只围绕Go语言介绍一些它的基础知识。

IPC方法

所谓socket,是一种IPC(Inter-Process Communication)方法,可以被翻译为进程间通信。顾名思义,IPC这个概念(或者说规范)主要定义的是多个进程之间,相互通信的方法。这些方法主要包括:

  • 系统信号(signal),os包和os/signal包有针对系统信号的API
  • 管道(pipe),os.Pipe函数可以创建命名管道,os/exec包支持另一类管道:匿名管道
  • 套接字(socket),net包中提供支持
  • 文件锁(file lock)
  • 消息队列(message queue)
  • 信号灯(semaphore),也称为信号量

现存的主要操作系统大都对IPC提供了强有力的支持,尤其是socket。

socket

socket,常被称作套接字,它是网络编程世界中最为核心的知识之一。
毫不夸张的说,在众多IPC方法中,socket是最为通用和灵活的一种。与其他的IPC方法不同,利用socket进行通信的进程,可以不局限在同一台计算机当中。通信双方只要能够通过网络进行互联,就可以使用socket。
支持socket的操作系统一般都会对外提供一套API。跑在它们之上的应用程序,利用这套API就可以与互联网上的另一台计算机中的程序、同一台计算机中的其他程序,甚至同一个程序中的其他线程进行通信。例如,在Linux操作系统中,用于创建socket实例的API,就是由一个名为socket的系统调用代表的。这个系统调用是Linux内核的一部分。所谓的系统调用,你可以理解为特殊的C语言函数。它们是连接应用程序和操作系统内核的桥梁,也是应用程序使用操作系统功能的唯一渠道。

syscall包

在Go语言标准库的syscall包中,有一个与这个socket系统调用相对应的函数。这两者的函数签名是基本一致的,它们都会接受三个int类型的参数,并会返回一个可以代表文件描述符的结果。但不同的是,syscall包中的Socket函数本身是平台不相关的。在其底层,Go语言为它支持的每个操作系统都做了适配,这样这个函数无论在哪个平台上,总是有效的。
在syscall.Socket函数中的三个参数分别是:

  • socket实例的通信域
  • socket实例的类型
  • socket实例的使用协议

下面,通过这3个参数来了解一下socket的基础知识。

通信域

Socket的通信域主要有3种,分别对应syscall包中的一个常量:

  1. AF_INET : IPv4域
  2. AF_INET6 : IPv6域
  3. AF_UNIX : Unix域

关于IPv4和IPv6就不讲了,Unix域简单提一下。
Unix域,指的是一种类Unix操作系统中特有的通信域。在装有此类操作系统的同一台计算机中,应用程序可以基于此域建立socket连接。

类型

Socket的类型一个有4种,在syscall包中有同名的常量对应:

  1. SOCK_DGRAM
  2. SOCK_STREAM
  3. SOCK_SEQPACKET
  4. SOCK_RAW

上面的4种类型,前两个更加常用。

UDP
SOCK_DGRA中的DGRAM就是datagram,即数据报文。它是一种有消息边界但没有逻辑连接的非可靠socket类型,UDP协议的网络通信就是这类。
有消息边界的意思是,与socket相关的操作系统内核中的程序,即内核程序,在发送或接收数据的时候是以消息为单位的。这里可以把消息理解为带有固定边界的一段数据。内核程序可以自动的识别和维护这种边界。在必要的时候,把数据切割成一个一个的消息,或者把多个消息串接成连续的数据。这样,应用程序值需要面向消息进行处理就可以了。
只要应用程序指定好对方的网络地址,内核程序就可以立即把数据报文发送出去。这有优势也有劣势。优势是,发送速度快,不长期占用网络资源,并且每次发送都可以指定不同的网络地址。最后一条既是优势也是劣势,因为这会使数据报文更长。其他劣势还有,无法保证传输的可靠性,不能实现数据的有序性,以及数据只能单向进行传输。

TCP
SOCK_STREAM类型,是没有消息边界但有逻辑连接,能够保证传输的可靠性和数据的有序性,同时还可以实现数据的双向传输。TCP协议的网络通信就是这类。
有逻辑连接是指,通信双方在收发数据之前必须先建立网络连接。等连接建立好之后,双方就可以一对一的进行数据传输了。
这样的网络通信传输数据的形式是字节流,而不是数据报文。字节流是以字节为单位的。内核程序无法感知一段字节流中包含了多少个消息,以及这些消息是否完整,这完全需要应用程序自己来把控。不过,此类网络通信中的一段,总会忠实的按照另一端发送数据是的字节排列顺序,接收和缓存它们。所以,应用程序需要根据双方的约定去数据中查找消息边界,并按照边界切割数据。

使用协议

通常只要明确指定了前两个参数值,就无需在去确定这里的使用协议了,一般把它置为0就可以了。这时,内核程序会自行选择最合适的协议。

不完整的示例

package main

import (
    "fmt"
    "os"
    "syscall"
)

func main() {
    fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
        return
    }
    defer syscall.Close(fd)
    fmt.Println("socket的文件描述符:", fd)
    // 之后就省略了,要使用syscall包来建立网络连接,过程太繁琐
}

这个代码包的使用太底层,通常也不需要我们直接使用。Go语言的net包中的很多程序实体,都会直接或间接的使用到syscall.Socket函数,并且无需给定细致的参数。但是,在使用这些API的时候,现在我们就应该知道上面这些基础知识了。

net.Dial函数

net.Dial函数会接受两个参数,network和address,具体看下面:

func Dial(network, address string) (Conn, error) {
    var d Dialer
    return d.Dial(network, address)
}

network参数

参数network常用的可选值一共有9个,这些值分别代表了程序底层创建的socket实例可使用的不同通信协议:

  1. "tcp" : 代表TCP协议,其基于的IP协议的版本根据参数address的值自适应
  2. "tcp4" : 代表基于IPv4协议的TCP协议
  3. "tcp6" : 代表基于IPv6协议的TCP协议
  4. "udp" : 代表UDP协议,其基于的IP协议的版本根据address的值自适应
  5. "udp4" : 代表基于IPv4协议的UDP协议
  6. "udp6" : 代表基于IPv6协议的UDP协议
  7. "unix" : 代表Unix通信域下的一种内部socket协议,以SOCK_STREAM为socket类型
  8. "unixgram" : 代表Unix通信域下的一种内部socket协议,以SOCK_DGRAM为socket类型
  9. "unixpacket" : 代表Unix通信域下的一种内部socket协议,以SOCK_SEQPACKET为socket类型

net包发送http请求

对于http请求,在标准库里还有更高级的封装,不过http本质上也是socket,这里展示用net包发送请求的示例:

package main

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

func main() {
    conn, err := net.Dial("tcp", "baidu.com:80")
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
        return
    }
    defer conn.Close()

    reqStr := "HEAD / HTTP/1.1\r\n" + // HEAD请求,只返回请求头
        "Host: baidu.com\r\n" +
        "Connection: close\r\n" + // 返回后,服务器会断开连接,默认是keep-alive
        "\r\n"  // 请求头结束
    _, err = io.WriteString(conn, reqStr)
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
        return
    }

    buf := make([]byte, 1024)
    for {
        n, err := conn.Read(buf)
        fmt.Println(string(buf[:n]))
        if err != nil {
            if err == io.EOF {
                fmt.Println("END")
                break
            } else {
                fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
            }
        }
    }
}

如果是https的请求,还需要借助crypto/tls包,而调用起来基本是一样的:

package main

import (
    "crypto/tls"
    "fmt"
    "io"
    "os"
)

func main() {
    tlsConf := &tls.Config{
        InsecureSkipVerify: true,
        MinVersion:         tls.VersionTLS10,
    }

    conn, err := tls.Dial("tcp", "gitee.com:443", tlsConf)
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
        return
    }
    defer conn.Close()

    reqStr := "HEAD / HTTP/1.1\r\n" + // HEAD请求,只返回请求头
        "Host: gitee.com\r\n" +
        "Connection: close\r\n" + // 返回后,服务器会断开连接,默认是keep-alive
        "\r\n" // 请求头结束
    _, err = io.WriteString(conn, reqStr)
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
        return
    }

    buf := make([]byte, 1024)
    for {
        n, err := conn.Read(buf)
        fmt.Println(string(buf[:n]))
        if err != nil {
            if err == io.EOF {
                fmt.Println("END")
                break
            } else {
                fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
            }
        }
    }
}

net.DialTimeout函数

net.DialTimeout函数和net.Dial函数相比,多接受了一个参数timeout。而底层实现可以看到是一样的,只是对Dialer结构体的Timeout字段进行了设置,而在net.Dial函数里结构体都是默认值:

func DialTimeout(network, address string, timeout time.Duration) (Conn, error) {
    d := Dialer{Timeout: timeout}
    return d.Dial(network, address)
}

超时时间

这里的超时时间,出函数为网络连接建立完成而等待的最长时间。
开始的时间点几乎是调用net.DialTimeout函数的那一刻。在这之后,时间会主要花费在解析参数的network值和address值,以及创建socket实例并建立网络连接这两件事情上。如果超时了而网络连接还没有建立完成,该函数就会返回一个I/O操作超时的错误值。
在解析address的值的时候,函数会确定网络服务的IP地址、端口号等必要信息,并在需要的时候访问DNS服务。另外,如果解析出的IP地址有多个,函数会串行或并行的尝试建立连接。无论用什么方式尝试,函数总会以最先建立成功的那个连接为准。同时还会根据超时时间的剩余时间去设定对每次连接尝试的超时时间。
找一个国外的网站,或者干脆找一个连不上的地址,看下超时时间的作用:

package main

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

func main() {
    tStart := time.Now()
    conn, err := net.DialTimeout("tcp", "godoc.org:80", time.Second * 10)
    tEnd := time.Now()
    fmt.Println("连接持续时间:", time.Duration(tEnd.Sub(tStart)))
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
        return
    }
    defer conn.Close()
    fmt.Println("本地连接地址:", conn.LocalAddr())
    fmt.Println("对端连接地址:", conn.RemoteAddr())
}

原文地址:http://blog.51cto.com/steed/2348985

时间: 2024-10-01 04:41:54

Go36-46-访问网络服务(socket)的相关文章

Python网络编程——Socket

一.网络的基础知识 1.OSI参考模型 OSI是Open System Interconnect的缩写,意为开放式系统互联.一般都叫OSI参考模型,是ISO组织在1985年研究的网络互联模型.该体系结构标准定义了网络互连的七层框架,在这一框架下进一步详细规定了每一层的功能,以实现开放系统环境中的互连性.互操作性和应用的可移植性. OSI七层参考模型的各个层次的划分遵循下列原则: 1.同一层中的各网络节点都有相同的层次结构,具有同样的功能. 2.同一节点内相邻层之间通过接口(可以是逻辑接口)进行通

访问网络文件共享服务

第七单元 一 挂载网络文件系统 网络文件系统是由网络附加存储服务器通过网络向多个主机提供的一种文件系统 , 而不是由块设备 ( 例如硬盘驱动器 ) 提供的.客户端通过特殊的文件系统协议和格式访问远程存储 Linux 中有两种主要协议可用访问网络文件系统 : NFS 和CIFS 1 )CIFS: 通用网络文件系统 CIFS 是针对 Microsoft Windows 操作系统的本地网络文件系统Linux 系统可以挂载和访问 CIFS 文件共享 , 如同常见的网络文件系统一样. samba-clie

2-7.访问网络文件共享服务

##访问网络文件共享服务## ##学习目标 挂载网络共享 自动挂载网络共享 1.1##挂载网络文件系统 网络文件系统是由网络附加存储服务器通过网络向多个主机提供的一种文件系统,而不是由块设备(例如硬盘驱动器)提供的.客户端通过特殊的文件系统协议和格式访问远程存储 Linux 中有两种主要协议可用访问网络文件系统 : NFS 和CIFS . NFS ( Network File System ) 可看作是 Linux .UNIX 及其它类似操作系统的标准文件系统. CIFS( Comon Inte

【unit7 & unit9】cifs网络文件系统访问;vsftp服务

****************************** ******7.访问网络文件系统********* ****************************** ****cifs网络文件系统访问***** 1.安装个共享访问客户端 yum install samba-client -y 2.识别共享目录 smbclient -L //172.25.254.253 [[email protected] mnt]# smbclient -L //172.25.254.253 Enter

获取网络访问权限套餐提供国外网络访问配置服务外网访问服务

获取网络访问权限套餐提供国外网络访问配置服务外网访问服务 可以访问youtube等国外网站 获取网络访问权限套餐 预览:http://www.tudou.com/v/56Qa6nyxTg4/&rpid=24434369&resourceId=24434369_04_05_99/v.swf 提供YouTube.Google+.Facebook.Twitter.App Store.维基百科等最全网站访问权限,终身享受远程设置套餐服务. 预览视频 http://www.tudou

解决Windows服务无法访问网络映射盘的问题

下载工具psexec 下载地址:https://docs.microsoft.com/zh-cn/sysinternals/downloads/psexec 百度地址:https://pan.baidu.com/s/1Yw5fQpsxe2Tdh94R-A_2ug       提取码:y5ik 用psexec创建网络驱动器 1.运行CMD,执行 psexec -i -s cmd.exe 2.在新开的CMD窗口中运行网络映射 net use z: \\10.10.x.xxx\test /persis

c#编写的服务中访问网络位置的共享文件夹

使用LocalSystem运行的服务不能访问共享文件夹,即使共享文件夹权限是对“EveryOne”可读写也不行,目前使用过两种方式,但都需要设置用户密码. 1.使用用户名加密码的方式运行服务. 将 ProjectInstaller 的 Account设置为User 然后打开 ProjectInstaller 的设计器代码 在 InitializeComponent 中设置 ProjectInstaller的Username和Password.运行服务时会直接以设置的用户名运行,可以直接访问该用户

远程主机访问sshd服务

##########功能:让远程主机可以通过网络访问sshd服务,开始一个安全shell ##########ssh 远程主机用户@远程主机ip[[email protected] ~]# ssh [email protected]The authenticity of host '172.25.0.11 (172.25.0.11)' can't be establishedA key fingerprint is eb:24:0e:07:96:26:b1:04:c2:37:0c:78:2d:b

初学linux网络服务之vsftp服务实验

实验拓扑: Linux Client -----RHEL5.9(vmnet1)----------(vmnet1) Win7 Client 实验一:测试默认安装vsftpd的结果 匿名用户与本地用户都可以登录 匿名用户登录到/var/ftp,只能下载不能上传 本地用户登录到本地用户的家目录,可以上传和下载 服务器端设置 [[email protected] ~]# cd /misc/cd/Server                 //进入RHEL5.9光盘 [[email protected