V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
banxi1988
V2EX  ›  程序员

不了解历史不足以写代码: 学 Socket 编程有感.

  •  
  •   banxi1988 ·
    banxi1988 · 2017-02-20 07:55:53 +08:00 · 5105 次点击
    这是一个创建于 2819 天前的主题,其中的信息可能已经有所发展或是发生改变。

    背景

    最近在一个 iOS 项目中要用到 Socket 编程. Foundation 没有 Socket 模块. CoreFoundation 倒是有,但是我一直挺讨厌 CoreFoundation 的.看了一眼 CFSocket 相关 API,有种想吐的感觉. 作罢.

    理想的样子.

    Swift 具有跟 C 语言互操作的特性,可以直接调用 C 库函数 . Socket C 库的 API 看起来挺简洁的,决定试着封装一下. 理想中的结果应该像下面的代码一样,简洁明了.

            let addr = InternetAddress(hostname: "httpbin.org",
                                     port: 80)
          let socket = try TCPInternetSocket(address: addr)
          try socket.connect()
          try socket.send(data: "GET /\r\n\r\n".toBytes())
          //receiving data
          let received = try socket.recv()
          //converting data to a string
          let str = try received.toString()
          //yay!
          XCTAssertTrue(received.starts(with: "<!DOCTYPE html>".toBytes()), "Instead received: \(str)")
          try! socket.close()
    

    初遇 socket

    在 Swift 中只要 import Darwin 就可以使用系统的 C 库了. 创建一个 macOS 命令行项目, 创建一个 Swift 源文件,直接就写. 输入 socket 几个字,就有补全出来了,但是除了类型提示信息,其他都是空. 点进头声明看看. Swift 翻译过来的声明是这样的. public func socket(_: Int32, _: Int32, _: Int32) -> Int32 Swift 把 C 函数声明的参数名给丢了,而且也没有在前面加上注释. 按以往,对于不熟悉的函数一般是直接看下声明及其中的文档就基本可以用了. 不过对于导入到 Swift 的 C 头文件很多声明信息丢失了. 看来只有打开文档了. 直接在 man 手册页就可以查到 socket 函数的文档. 其实其声明是: int socket(int domain, int type, int protocol); 这个函数声明看起来是挺简单的,但是选择合适的参数,也是要费一些脑筋的. domain 似乎有点奇怪, 还有一系列 AF_XXPF_XX 参数. 我知道 TCP 和 UDP, 但是并没有直接指定 TCP 或 UDP 的参数.

    connectstruct sockaddr 相遇: 痛苦的源泉.

    初看之下 connect 函数好像挺简单的. int connect(int socket, const struct sockaddr *address,socklen_t address_len); 但是马上就有了困惑,这个 address_len 是什么? 看起来像是说地址的长度, 为什么需要提供这个参数? 这个参数到底是什么呢? 文档也没有说. 先不管, 先搞定第二个参数 struct sockaddr 再说. 此时我沉浸在对于简单的结构的想像中,如 struct point 直接 struct point p = {3,4} 就可以构造一个了.然而不幸的是, man 手册没有struct sockaddr 这个结构的说明. 直接打开 Swift 中导入的 struct sockaddr 声明.

       /*
     * [XSI] Structure used by kernel to store most addresses.
     */
    public struct sockaddr {
        public var sa_len: __uint8_t /* total length */
        public var sa_family: sa_family_t /* [XSI] address family */
        public var sa_data: (Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8) /* [XSI] addr value (actually larger) */
        public init()
        public init(sa_len: __uint8_t, sa_family: sa_family_t, sa_data: (Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8))
    }
    

    毫不夸张的说,看着有点傻眼.特别是这个 sa_data 到底要传什么? 还有为什么这里 sa_len 又要传什么长度? sa_family 是指协议簇吗? 可以我创建 socket 的时候已经声明过了啊? 此时,我觉得光看文档, 看头文件声明,已经不能继续把代码写下去了.

    地址系列

    后来经过 Google,我发现 Socket 编程的这个地址处理的水很深. 涉及到的结构有这么一些: - struct sockaddr - struct sockaddr_in - struct sockaddr_in6 - struct in_addr - struct addrinfo - struct sockaddr_storage

    每一个结构都是让人头晕的那种, 更何况我开始看的是 Swift 中翻译过来的声明. 我想静静.

    如何解毒?

    1. 后来我找到了 vapor/socks 这个 封装了 Socket 库的 Swift 库,代码和结构都非常 Nice,要使用 Swift 进行 TCP/UDP 编程的话,推荐使用这个库. 事实上,我刚开始贴的理想中的代码的样子,就是来自 socks 的一段测试代码.

    2. 如果想要搞清楚上面的系列 Socket 的地址结构. 不用多想, 直接找到 W. Richard StevensUNIX 网络编程 卷 1:套接字联网 API 一书. 翻到第三章: 套接字简介 (Sockets Introduction),然后就会找到答案了.

    点题

    原来是想直接凭文档和声明接口就可以进行 Socket 编程的. 实践下来这是根本不可行的. Socket 库 从 上个世纪 80 年代到现在已经有 30 多年的历史了. 随着时代的发展,其 API 自然会发生这样那样的变化, 如果没有老司机带路, 这路不好走. 老实说, Socket 库从 80 年代到现在,API 基本没什么变化. 除了 sockaddr 这一块比较复杂之外, 其他的都比较简洁明了. 另外一个明显的复杂度主要来源于期望的偏差, Socket 编程不仅仅是用来进行 TCP/UDP 编程,它具有更强大,更通用的能力.

    13 条回复    2017-02-24 11:03:34 +08:00
    Cbdy
        1
    Cbdy  
       2017-02-20 08:18:22 +08:00 via Android
    不看名著不足以写代码,还是把两卷书都看一下比较好。
    noli
        2
    noli  
       2017-02-20 08:23:16 +08:00 via iPhone
    不使用 CF 直接用 Bsd socket 恐怕写异步比较麻烦吧?
    pupboss
        3
    pupboss  
       2017-02-20 08:23:37 +08:00 via iPhone   ❤️ 1
    写了这么多我还以为你通宵造了个轮子 = =!
    bombless
        4
    bombless  
       2017-02-20 08:39:07 +08:00 via Android
    噗, po 主是哲学家
    stormpeach
        5
    stormpeach  
       2017-02-20 09:17:10 +08:00
    最近在看 UNP ,确实水很深, socket 要支持所有网络编程里面的功能,光是套接字选项都占了一整章。所以要用 socket 接口写代码要保证对网络方面的细节足够的熟悉才行,最好还是用各种封装的库吧。
    lrh3321
        6
    lrh3321  
       2017-02-20 09:24:32 +08:00
    以为你通宵造了个轮子 +1
    wohenyingyu02
        7
    wohenyingyu02  
       2017-02-20 09:33:10 +08:00 via iPhone
    为何都开发 ios 了 c socket 编程还是初遇……不是大一的内容么
    banxi1988
        8
    banxi1988  
    OP
       2017-02-20 13:47:53 +08:00
    @noli 异步的话, 结合 GCD , 目前我是这样做的.
    banxi1988
        9
    banxi1988  
    OP
       2017-02-20 13:49:26 +08:00
    @pupboss
    @lrh3321

    vapor/socks 这个轮子我觉得比我造的好, 我就不造了.
    programer
        10
    programer  
       2017-02-20 14:01:39 +08:00 via iPhone
    看来要买一本 《中华上下五千年了》
    neoblackcap
        11
    neoblackcap  
       2017-02-20 14:22:02 +08:00
    网络编程不看 UNP ,不看 man , socket API 表示我还能说什么,我也很无奈。
    rogerchen
        12
    rogerchen  
       2017-02-20 21:04:22 +08:00
    楼主这是面向头文件 socket 编程,是在下输了。
    tomato3
        13
    tomato3  
       2017-02-24 11:03:34 +08:00
    看《 c++网络编程》一书,你啥历史也没说明白
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2847 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 02:27 · PVG 10:27 · LAX 18:27 · JFK 21:27
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.