《创京东》:为用户创造价值,为员工谋福利

在 Kindle 上读完的第一本书——《创京东》,是朋友推荐给我的。最近我打算在电商和 O2O 方面做点事,这本书给了我一些启发,也让我了解了刘强东十年的电商创业之路。

1998 年 6 月 18 日,24 岁的刘强东带着工作两年积攒下来的 12000 元,在中关村租下 4 平米的摊位,买了一台二手电脑、一辆二手三轮车,一个人开始了自己的创业之旅。

中关村大大小小的卖场,以电子产品为主。北京人买电子产品,中关村是独一无二的选择。因为普通人不知道价格,卖家叫价很高,蒙一个算一个,而且到处充斥着山寨产品。。而刘强东不一样,他坚持明码标价,并且价格定得合理。一开始没什么生意,后来经过口口相传,累积了不少客户。开业三个月后,忙不过来,刘强东请了第一名员工。他坚持正品,开发票,不卖山寨货。他坚持薄利多销,相信有规模才有行业控制力和话语权,始终按照一个有较低利润的价格售卖。当时公司名叫京东多媒体,在光磁产品领域,最高曾占据全国 60% 的市场份额。

一路走来,风生水起。但到 2003 年突然遇到了麻烦,那就是“非典”。街头人影寥寥,当时中关村所有的电脑都在降价,平均降幅达到 30%~40%。京东的产品也没有了买家,21 天,京东亏损了 800 多万,公司账面资金只有两千多万。当时传言“非典”得要半年,甚至一年才会过去。货卖不出去,全公司人都很着急,再过两三个月公司就要死掉。

突然有位同事提出来,客户不能外出购物,为什么不通过互联网交易呢?于是员工们开始在网络上发帖,推销光盘。因为有线下 5 年正品的口碑积累,开始得到一些网友信任与支持,生意逐渐在网络上开展。当时在论坛里做团购活动。刘强东由此对互联网产生了极大的兴趣,连 QQ 号都没有的他,没日没夜地泡在网上,半夜 12 点在发帖子,凌晨 2 点回帖,5 点还在回帖。半年后,刘强东决定做独立网站卖产品。2004 年元旦,京东多媒体网站上线,有 100 多个单品。

2004 年,刘强东决定关掉所有的柜台和门店,转型为纯线上零售公司。但他的想法并没有获得很大的支持。员工的想法是,互联网普及率不高,很多人没有电脑,市场有多大也不知道。而 2003 年公司已经做到八九千万元的销售额,是国内最大的光磁产品销售商。按照规划,未来将开设 500 个门店。但刘强东决意已定,最终柜台都撤掉了。有些员工在这次关店行动中离开了。

客户信任京东多媒体这家公司之后,需求越来越多地涌现,要 CPU、要硬盘、要 CD 机,客户不断在论坛里发帖呼喊自己的需求。刘强东直接打电话给员工,你赶紧去采购某某品类拿来卖。谁也不知道什么货能卖出去,什么货卖不出去,完全由客户需求来引导采购。客户相信京东多媒体的货是真的,价格又便宜,就不断来买。

2007 年,京东获得第一笔融资,由此进入发展的快车道。这一年,京东多媒体正式更名为京东商城。刘强东希望用户的各种需求都能在这里得到满足,于是从 IT 产品拓展到小家电、日用百货、图书、大家电等品类。在线上经营的这几年,送货慢是用户反馈最多的问题之一。刘强东不顾投资人的反对,也是在这一年决定要自建配送队伍,确立仓储配送一体的物流战略。2007 年到 2010 年,京东每年的销售额是上一年的大约三倍。

京东每年有两次工作盘点,年中一次,年底一次,努力工作、做出成绩的人就加薪,还有奖金。员工发现,努力的人加薪了,不努力的人,看别人努力,自己也努力了,慢慢也上进了。2012 年,京东推出无息贷款,为员工买房提供首付。刘强东对一线配送员也特别关心,每年都会亲自体验送货一天,一来是了解送货装备和流程是否有改善的空间,二来是体验配送员的辛苦。夏天给配送员提供高温补贴,每月 300 元,发放 3 个月,并且配备饮料;冬天也有防寒补贴。2014 年,京东推出了针对仓储、配送体系的零食活动,每人每天 5 元零食预算,有八宝粥、饼干、火腿肠、方便面等。

刘强东做电商的一个战略观点是,中国的零售效率太低,中间商吃掉一大笔利润,如果能将中间环节去掉,利益拿来与社会用户分享,是很好的模式。京东直接从生产商拿货,产品进入京东的仓库,然后通过遍布全国的配送网络送到消费者手中。在线上的竞争,京东先后超越了新蛋网、当当网。

由于京东仓配一体的重模式,不得不持续融资,才能维持高增长。建设大型仓库,招聘配送员,完善配送网络,都需要大量的资金。2007 年京东员工只有 200 多人,2008 年京东员工超过千人,之后就是十几倍地增长。到 2013 年初,京东拥有 100 万平米仓储面积、3 万名员工。2007 年,京东开始请普华永道做审计,一年销售才几个亿,却为审计付出几百万元。所有财务数据都真实地呈现在投资人面前,在投资前有充分沟通的准备和交流。京东每年都必须融资,到 2014 年上市前,京东累计融资 20.26 亿美元。

在刘强东心里,每一位员工都是自己的兄弟。在 2007 年第一笔融资之前,刘强东把手上 13% 的股份分给了员工,当时公司只有百来号人,估值 4500 万美元。刘强东说过,他早年就抓两件事情,第一个是用户体验,每天花很多时间研究用户在想什么,京东有哪些不足。第二件事情是关注自己的员工,要知道团队在想什么,在公司过得好不好。“你只要真心对待你的下属。十几年来,员工都知道我是诚心的,他们自然会跟你说实话。” 2010 年年会有 200 桌,刘强东给每桌都敬酒。

在京东的历史上,有三次起着决定性作用的战略决策。第一次,是 2004 年转型做电商,京东得以抓住了未来 10 年乃至更长时期的消费趋势;第二次,是决定向全品类扩张,从只做 3C 产品转为一站式消费平台;第三次,是决定自建仓配一体的物流体系。

创业者要有死磕的精神,从某种程度上讲,执行比战略更重要,持之以恒地认准了,不断地干,坚持得久,付出得多,总会比别人走得更远一点。这些年,刘强东相当于在挖坑,挖一个巨大的深坑,自己跳进去,做全世界最难做成的事情,也不知道自己能否干成,坑挖出来了,就开始往上爬。如果有人来竞争,跳进坑里的话,他就再拿钱把坑挖得更深一点。难得的是,最终他爬出来了。

2014 年 5 月,京东在美国纳斯达克上市,融资 31 亿美元,当天市值达到 286 亿美元。上市后,因为 AB 股架构,刘强东以 18.8% 的持股比例占有 83.7% 的投票权。

刘强东带领着京东这艘航母,还在向更远的地方行驶。他有个朴实的想法,欧美日韩将公司开遍全球,将财富源源不断带回自己国家,而中国企业只赚中国人的钱,赚不到外国人的钱,这是中国人过不上好日子的原因之一。他敬佩联想、华为,靠自己的努力将业务延伸到全球各地。如果有更多的中国企业走出去,中国经济会好很多。

PHP 编码规范

PHP 编码规范

当多人协作编码时,一份共同遵守的编码规范必不可少。它有利于阅读和编写代码,既可以提高新成员熟悉代码的效率,也可以提高老成员编码的效率。

如果以下规范与框架规范冲突,则以框架规范为准。

本文档中使用了大量的“能愿动词”,说明如下:

  • 必须:绝对,严格遵循,请照做,无条件遵守;
  • 一定不能:禁令,严令禁止;
  • 应该:强烈建议这样做,但不强求;
  • 不该:强烈不建议这样做,但不强求;
  • 可以:更多的选择。

1. 命名

变量名、类的属性名 必须 使用小驼峰形式(如 $varName)。

常量名中所有字母 必须 大写,单词间 应该 以下划线分隔。

文件夹名所有字母 应该 使用小写字母,单词间 应该 以下划线分隔。

类名 必须 使用大驼峰形式(如 ClassName)。

定义类的文件名 应该 与类名相同,或者包含类名,同样 应该 使用大驼峰形式。

方法名 必须 使用小驼峰形式。

PHP 的关键字 必须 全部小写。

系统常量(true, false, null必须 小写。

2. 基础规范

代码 必须 使用 4 个空格符而不是 Tab 键进行缩进。

每行代码的字符数 应该 控制在 120 个以内。

类和方法的起始花括号({必须 在声明后自成一行,结束花括号(}必须 在代码主体后自成一行。

类的属性和方法 必须 添加访问修饰符(private, protected, public),abstractfinal 必须 写在访问修饰符之前,而 static 必须 写在访问修饰符之后。

<?php

abstract class ClassName
{
    protected static $fooVar;

    abstract protected function baz();

    final public static function barShow($a, $b = null)
    {
        // 方法主体
    }
}

赋值符号(=)的左右 必须 有且只有一个空格。

取反符号(!)后 必须 有一个空格。

纯 PHP 代码文件 应该 省略末尾的结束标签(?>)。

非空行后 不该 有多余的空格。

每行 一定不能 存在多条语句。

文件 应该 以一个空白行作为结束。

字符串 应该 使用单引号。

3. 编码风格

3.1 类、属性和方法

关键词 extendsimplements 必须 写在类名称的同一行。

<?php

class ClassName extends ParentClass implements ImplementName
{
    // 这里面是常量、属性、类方法
}

implements 的继承列表也 可以 分成多行,但每个继承接口名称都 必须 分开独立成行,包括第一个。

<?php

class ClassName extends ParentClass implements
    ImplementName1,
    ImplementName2,
    ImplementName3
{
    // 这里面是常量、属性、类方法
}

每个属性都 必须 添加访问修饰符。
一定不能 使用关键字 var 声明一个属性。
每条语句 一定不能 定义超过一个属性。
不该 使用下划线作为前缀,来区分属性是 protectedprivate

所有方法都 必须 添加访问修饰符。
不该 使用下划线作为前缀,来区分方法是 protectedprivate
方法名称后 一定不能 有空格符,参数左括号后和右括号前 一定不能 有空格。
参数列表中,每个逗号后面 必须 要有一个空格,而逗号前面 一定不能 有空格。
有默认值的参数,必须 放到参数列表的末尾。

<?php

class ClassName
{
    public function foo($arg1, &$arg2, $arg3 = [])
    {
        // 方法主体
    }
}

訪問修飾符 必須 按最小訪問原則。例如只在本類中使用的方法,必須使用 private;當該方法需要被外部訪問時,再改為 public

3.2 控制结构

控制结构的关键字(如 if)后 必须 有一个空格,而调用方法、函数的左括号前 一定不能 有空格。
控制结构的起始花括号 应该 写在声明的同一行,而最后一个结束花括号 必须 在主体后自成一行。
控制结构的左括号后,右括号前 一定不能 有空格。
结构体主体 必须 要有一次缩进。
注意,控制结构的花括号并不总是单独成行。

if 条件语句

<?php

if ($expr1) {
    // if 代码块
} elseif ($expr2) {
    // elseif 代码块
} else {
    // else 代码块
}

应该 使用关键词 elseif 代替所有 else if
主体部分,必须 使用花括号括起来,即使只有一句。

switch 语句

<?php

switch ($expr) {
    case 0:
        echo '第一种情况, 带有 break';
        break;
    case 1:
        echo '第二种情况';
        // no break
    case 2:
    case 3:
    case 4:
        echo '第三种情况, 直接 return';
        return;
    default:
        echo '默认情况';
        break;
}

case 语句 必须 相对 switch 进行一次缩进,而 break 语句以及 case 内的其它语句都 必须 相对 case 进行一次缩进。
如果存在非空的 case 直穿语句,主体中 必须 有类似 // no break 的注释。

while 和 do...while 语句

<?php

while ($expr) {
    // 循环主体
}

do {
    // 循环主体
} while ($expr);

for 语句

<?php

for ($i = 0; $i < 10; $i++) {
    // 循环主体
}

foreach 语句

<?php

foreach ($iterable as $key => $value) {
    // 循环主体
}

try...catch 语句

<?php

try {
    // 主体代码
} catch (FirstExceptionType $e) {
    // 异常处理代码
} catch (OtherExceptionType $e) {
    // 异常处理代码
}

3.3 闭包

关键词 function 后以及关键词 use 的前后都 必须 要有一个空格。
开始花括号 必须 写在声明的同一行,结束花括号 必须 紧跟主体结束的下一行。

<?php

$closureWithArgs = function ($arg1, $arg2) {
    // 主体
};

$closureWithArgsAndVars = function ($arg1, $arg2) use ($var1, $var2) {
    // 主体
};

参数列表以及变量列表 可以 分成多行,这样,包括第一个在内的每个参数或变量都 必须 单独成行,而列表的右括号与闭包的开始花括号 必须 放在同一行。

<?php

$longArgs_longVars = function (
    $longArgument,
    $longerArgument,
    $muchLongerArgument
) use (
    $longVar1,
    $longerVar2,
    $muchLongerVar3
) {
   // 代码主体
};

3.4 命名空间

namespace 声明后 必须 插入一个空白行。
所有 use 必须namespace 后声明。
每条 use 声明语句 必须 只有一个 use 关键词。
use 声明语句块后 必须 要有一个空白行。

<?php
namespace Vendor\Package;

use FooClass;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;

// 更多的代码...

3.5 注释与空行

单行注释 应该 放在行末,与代码之间 应该 保留 2 个空格,而较长的注释,应该 放在相应代码的上一行;注释符号(//)与注释之间 应该 保留 1 个空格。

较复杂的代码,应该 详细说明。

<?php

/**
 * 算法标题
 *
 * 首先,调用...取得...数据
 * 其次,对...数据进行循环处理,得到...
 * 最后,按...格式输出数据
 */

清楚明了的代码 不该 增加注释:

<?php

$criteria = new CDbCriteria;
// 仅获取id,parentid字段
$criteria->select = 'id,parentid';
// 排除已删除节点
$criteria->addCondition('is_delete=0');

$list = $modelClassName::model()->findAll($criteria);

代码块之间 应该 用一个空行分隔,如类的方法之间使用一个空行分隔。

附0 开发工具推荐

PhpStorm
PHPDesigner
PhpED

附1 Git 使用

提交时简明扼要地描述所变更代码的主要内容
至少形成一个基本逻辑再提交
不相关的修改应该分开提交
不必要的修改不要提交(如调试时多加了个空行,提交时查看变更内容可避免)
开发周期较长(超过一周)的需求应该新建一个分支

提交信息格式:
修改Bug:BF(###): xxxxxxx
完成需求:PR(###): xxxxxxx

参考文档

  1. PSR - PHP标准规范

Python 装饰器详解

什么是装饰器

装饰器是一个函数,一个用来包装函数的函数,接收一个函数对象,返回一个新的函数。新函数在原函数的基础上,增加了附加的功能。

1. 最简单的装饰器

def foo():
    print("foo() called.")

这是一个简单的函数,如果我们想在函数执行前做点事,可以通过以下装饰器来实现:

def decorator(func):
    def wrapper():
        print("before %s" % func.__name__)
        ret = func()
        return ret
    return wrapper

装饰器函数 decorator 接收一个函数对象作为参数,然后返回一个内部函数,内部函数的作用是在原函数执行前打印一行说明。这一行可以是其它更复杂的逻辑,当然也可以在原函数执行后做一些操作。

foo = decorator(foo)
foo()

现在执行 foo() 就看到我们要的效果了。

装饰器的实现,归功于 Python 对内部函数的支持,在函数内部创建另外一个函数,同时函数可以作为函数的参数。

2. 使用 Python 语法糖

上面对 foo 的修饰动作,有一种语法支持它。

@decorator
def foo():
    print("foo() called.")

即在函数声明前一行加上 @ 标记,这看起来更加简洁,逻辑更紧密。这个语法使得,foo 在声明时,就执行了 foo = decorator(foo)。

类的静态方法(staticmethod)和类方法(classmethod)的实现,通常装饰器使代码更直观。

3. 装饰带参数的函数

其实就是让返回的函数带参数,同时将参数传递到原函数让其调用。

def deco(func):
    def wrapper(*args, **kwargs):
        print("before %s" % func.__name__)
        ret = func(*args, **kwargs)
        return ret
    return wrapper

以上是通用的不确定参数的例子,如果参数确定,则将 wrapper 的形参定义与 原函数 func 相同即可。

4. 带参数的装饰器

装饰器自身带参数,使得装饰器更灵活。这需要在上面不带参数装饰器的基础上再包裹一层,意思是装饰器先调用自己的参数返回一新的装饰器,这个装饰器和上面的装饰器一样。

def deco(*args2, **kwargs2):
    # 这里可以使用参数 args2, kwargs2 处理一些逻辑,外部函数的参数在内部函数均可使用
    def _deco(func):
        def wrapper(*args, **kwargs):
            print("before %s" % func.__name__)
            ret = func(*args, **kwargs)
            return ret
        return wrapper
    return _deco

这也是一个通用的带参数的装饰器。使用示例:

def check_session(is_login=False):
    def _deco(func):
        def wrapper(*args, **kwargs):
            if is_login:
                # 根据相关条件判断是否已登录,若未登录则抛出错误
                pass
            return func(*args, **kwargs)
        return wrapper
    return _deco

@check_session(is_login=True)
def profile(user_id):
    pass

这个装饰器通过 is_login 参数来决定是否需要登录,才能获取用户的资料。

5. 多层装饰

对一个函数使用多个装饰器,也是合法的。

@deco2
@deco1
def foo(*args, **kwargs):
    pass

它相当于 foo = deco2(deco1(foo))。如果装饰器自身带参数,那么同样是先调用自身参数返回一个新装饰器,再执行上面的多层装饰逻辑。

6. 类也可以被装饰

类的装饰器,返回一个新的类。

def deco(cls):
    def show(self):
        print(self.__doc__)
    cls.show = show
    return cls

@deco
class MyObject:
    ''' class MyObject sample '''
    pass

这个装饰器给类增加一个 show() 方法,打印类的文档说明。

7. 包装内部函数

使用上面的装饰器,装饰过的函数,返回的是类似下面的对象:

<function deco.<locals>.wrapper at 0x000000C129C6F158>
<function deco.<locals>._deco.<locals>.wrapper at 0x000000C129C6F268>

foo.__name__ 返回的是 'wrapper' 字符串。

但我们希望 foo 仍像装饰前一样。这时,我们需要使用 functools.wraps 对内部函数再次包装。

from functools import wraps

def deco(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("before %s" % func.__name__)
        ret = func(*args, **kwargs)
        return ret
    return wrapper

它相当于 wrapper = wraps(func)(wrapper)。functools.wraps 装饰器能将被装饰的函数的特殊属性保留下来,这样就可以正常地对 foo 使用反射相关特性。

8. 装饰器也是一种设计模式

装饰模式,是动态地给一个对象添加额外的功能,它比生成子类更灵活。

装饰模式,使任何对象被装饰前后,遵循相同的交互方式。装饰后的对象,可以说是原对象的鸭子类型(duck typing)。

装饰器的作用

装饰器可以在被装饰的函数前执行预备代码,在它之后执行清理工作。

装饰器有点继承的感觉,它将函数的通用功能剥离出来,成为装饰器。然后在使用时,给需要某通用功能的函数装饰上去,让代码更简洁。

因此,装饰器擅长于记录日志、通过计时来测试代码性能、实现函数的事务处理等。

支付模块后端设计

现在很多 App 都有应用内购买项目,因此这些 App 都需要一个支付模块。

一般地,我们都是接入第三方支付,如支付宝、微信、网银等。支付的通用流程如下:

  1. 用户在 App 内发起支付请求;
  2. App 通过自己的服务端创建订单;
  3. App 将订单号和应付金额等信息,传递给第三方支付的 SDK,随即显示支付界面;
  4. 用户在第三方支付界面完成支付;
  5. 返回 App 界面,查看支付结果。

下面是一个使用支付宝支付的流程图
支付宝支付流程

App 这边主要维护一个订单表,当然需要自己的服务器,订单数据保存在服务器上。
订单表:

  • 订单号:需要生成一个全局唯一的订单号
  • 用户ID:标明该订单是哪个用户创建的
  • 商品名称
  • 商品描述
  • 商品价格:这是打折前的价格
  • 应付金额:用户实付金额,传递给第三方支付 SDK
  • 创建时间
  • 更新时间:该条记录更新的时间
  • 支付状态:NEW 新创建,PAID 支付成功,FAILURE 支付失败,DONE 订单完成

核心问题

1. 创建订单

关键是生成全局唯一的订单号。最简单的办法是直接使用数据库的自增字段。

一般我们会设计一个有意义的订单号:订单标识 + 时间标识(YYYYMMDDHHMMSS 或者时间戳) + 序号。
订单标识,是预定义好的,作为订单号的前缀,可用于区别不同的业务。
序号,可以是递增的序号,也可以是无意义的随机值,但要保证在同一秒内不重复。递增的序号,可以使用NoSQL 数据库生成,例如 redis 的 incr 操作。

2. 支付回调

上面的通用流程中,显示的支付结果,是同步结果(由第三方支付 SDK 返回的结果)。这可以明确告诉用户,支付已经成功。但是对订单的发货操作,需要等待服务端订单的更新。这就是服务端的异步回调,由第三方支付的服务器向 App 的服务器发送支付结果通知(即上图中的第 13 步),然后 App 的服务端更新订单,执行发货操作。

一般回调请求的参数是带签名或者加密的,App 服务端需要验证签名,以确保通知的合法性。

这里关键是并发的问题,如果同一个订单有多个支付通知同时发过来,而更新订单的逻辑里包括发货操作,且不是原子性的。这就是导致重复发货。对于虚拟商品,如游戏中的金币,就会多次给用户发放金币。所以更新订单的逻辑必须加锁处理,或者采用队列的方式。

Python Socket 编程

Socket (套接字)是进程通信的一种方式,主要应用于不同主机的进程之间的数据交换。

Unix/Linux 的基本哲学之一是:一切皆文件。遵循以下模式:

open -> write/read -> close

而 Socket 就是上述模式的一种实现方式。

一、如何标识进程

用一个三元组标识进程:(IP地址, 协议, 端口)。我们称之为半相关,它指定一个连接的半个部分。

一个完整的网间通信由两个进程组成,并且只能使用同一种高层协议,称之为全相关:(协议, 本地IP地址, 本地端口, 远程IP地址, 远程端口)。

操作系统的端口号,是一个 16 位的编号(2^16 = 65536),范围是 0~65535。一个进程在通讯时均会占用一个端口号。

1~255 知名端口、默认端口,如 80 是 Web 服务的端口,21 是 FTP 服务的端口。
256~1023 Unix 系统占用的端口,普通用户无权使用。
1024~5000 临时端口,一般客户端随机分配的端口在此范围。

协议包括地址族(Address Family)和套接字类型。

Address Family:

AF_UNIX / AF_LOCAL  本地进程间通信,以绝对路径作为地址
AF_INET / AF_INET6  网络间进程通信,用 ipv4/6 和端口号组合成地址
AF_NS
AF_ROUTE

套接字类型:

SOCK_STREAM  TCP流式套接字,面向连接,提供可靠的数据传输服务。
SOCK_DGRAM  UDP,数据报式套接字,无连接的,数据包以独立形式发送,可能丢失或重复,且不按顺序。
SOCK_RAW  原始套接字,允许使用较低层次协议,如 ICMP/IGMP 等。
SOCK_PACKET
SOCK_SEQPACKET  可靠的连续数据包服务。

二、相关函数

以下是 C 语言相关函数:

socket() 创建一个 socket
bind() 绑定本地地址
listen() 监听连接
accept() 接受连接,返回收到的套接字
send() 发送数据
recv() 接收数据
select() 输入输出多路复用
closesocket() 关闭套接字
connect() 与远程进程建立连接,它会自动给本地 socket 分配端口号

三、Python Socket 编程

Python 提供了两个基本的模块:

socket 提供了标准的 BSD Sockets API。
socketserver 提供了服务器相关方法,如开发网络服务器。

创建 socket 函数:

socket(family,type[,protocal]) # 使用给定的地址族、套接字类型、协议编号(默认为0)来创建套接字。

创建 TCP socket:

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

创建 UDP socket:

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

1. 服务端 socket 函数

s.bind(address)
将套接字绑定到地址, 在AF_INET下,以元组(host,port)的形式表示地址。

s.listen(backlog)
开始监听TCP传入连接。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,一般设为 5。

s.accept()
接受TCP连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。如果没有收到连接,则会挂起,一直等待下去。

2. 客户端 socket 函数

s.connect(address)
连接到address处的套接字。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。hostname 可以是域名或者IP地址。

s.connect_ex(adddress)
功能与connect(address)相同,但是成功返回0,失败返回errno的值。

3. 公共 socket 函数

s.recv(bufsize[,flag])
接收TCP套接字的数据。数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。

s.send(string[,flag])
发送TCP数据。将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。

s.sendall(string[,flag])
完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。

s.recvfrom(bufsize[,flag])
接收UDP套接字的数据。与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。

s.sendto(string[,flag],address)
发送UDP数据。将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。

s.close()
关闭套接字。

s.getpeername()
返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。

s.getsockname()
返回套接字自己的地址。通常是一个元组(ipaddr,port)

s.setsockopt(level,optname,value)
设置给定套接字选项的值。

s.getsockopt(level,optname[,buflen])
返回套接字选项的值。

s.settimeout(timeout)
设置套接字操作的超时时间,timeout 是一个浮点数,单位是秒。值为None表示没有超时时间。一般,超时时间应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())

s.gettimeout()
返回当前超时时间的值,单位是秒,如果没有设置,则返回None。

s.fileno()
返回套接字的文件描述符。

s.setblocking(flag)
如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。

s.makefile()
创建一个与该套接字相关连的文件。

说明:
TCP 发送数据时,已建立好连接,所以不需要指定地址。
UDP 是无连接的,因此每次发送数据都要指定地址。

参考文档:Python Socket 网络编程