分类 其它 下的文章

图片

头文件的结构体的定义为:

typedef struct{
 u8  bmRequestType; 
 u8  bRequest; 
 u16 wValue; 
 u16 wIndex; 
 u16 wLength; 
}USB_Request_Header_t;

虽然粉丝最后都理解了,但是仍想趁着这个机会整理一下结构体的用法,希望能够对刚入行的童鞋们有所帮助。

结构体

在实际的开发过程中,一组数据往往具有不同的数据类型,此时数组是不能够满足需求了。因为数组中各元素的类型必须是一致的。为了解决这个需求,C中给出了另一种数据类型-结构体,每一个成员可以是任意一种数据类型。

定义一个结构体的类型为:

struct 结构名 {
  成员列表
};

其中struct后面的叫做结构体名,而struct 结构名叫做结构体类型,只有结构体类型才可以定义变量,结构体名是不能定义变量的。注意}后面是需要加;的。

struct stu {XXX};

stu stu1;        //fail
struct stu stu1; //true

三种结构体类型变量说明

结构变量有以下三种方式。

1.先定义结构,再定义结构变量

struct stu{
  int age;
};

struct stu stu1; //定义了变量stu1

2.定义结构体类型的同时说明变量

struct stu{
  int age;
}stu1;

3.直接说明结构变量

struct {
  int age;
}stu1;

这种方法和第一种方法相比,就是省略了结构体名,而省略的结构体名,就无法组合结构体类型了,而无这种结构体类型,自然就不能再定义这种结构体类型的变量了。

这种结构体也被称为匿名结构体,即没有结构体名。匿名结构体通常作为结构体成员的一个变量去使用。如下:

struct stu{
  int age;
  struct {
    int month;
  }birthday;
}stu1;

sut1.birthday.month = 10//用法

结构体成员表示方法

struct stu stu1, *pstu1;
pstu1 = &stu1;

stu1.age     = 10;  //true
(&stu1)->age = 10;  //true
pstu1->age   = 10;  //true
(*pstu1).age = 10;  //true
*pstu1.age   = 10;  //fail

如果对变量访问成员,则使用.运算符,如果是对指针访问成员则使用->运算符。

上例中除了最后一个都是对的。因为.运算符的优先级高于*,所以会先执行pstu1.age,而pstu1是一个指针,不可以使用.运算符去访问,需要正确使用->运算符访问。所以最终会报错。

结构体指针做参数

struct stu stu1;
void fun(struct stu *val);
fun(&stu1);

在这里也有新手误解的地方,在函数入参这一块,传入的是一个指针,并不是说外部一定要先定义一个指针变量struct stu *xxx,然后把xxx传进去。就像下面一样。

struct stu stu1;
struct stu *p_stu1 = &stu1;
fun(p_stu1);

结构体初始化

定义变量的同时初始化

struct stu{
  int age;
}stu1 = {18};

先定义在初始化

struct stu stu1,stu2;
stu1.age = 18;
stu2 = stu1;  //结构体之间可以直接赋值

常用初始化

在开发过程中,一个结构体之间会存在若干成员,极其复杂。这个时候通常是将成员全部清零,然后再对需要的成员进行构造。

struct stu stu1;
memset(&stu1, 0 ,sizeof(stu1));
XXX_Init(&stu1.xxx);

typedef与struct

常规定义结构体类型需要用struct 结构名的方式,比较繁琐。所以结构体定义往往与typedef相结合使用。

如果使用下面这种方法,结构体名我通常是省略的,因为我已经不打算使用struct方式定义变量了。

typedef struct _stu{
  int age;
}STU;

此时STU就等价于struct _stu,只不过换了个名字。

struct _stu stu1;  //true
STU stu2;          //true
struct STU stu3;   //fail,多了struct
_stu stu4;         //fail,缺少struct

前置声明

在定义结构体的时候,往往会碰到这种情况,结构体成员中需要用到此结构体的类型。首先下面定义是正确的,如下:

struct stu {
  int age;
  struct stu stu2;
}stu1;

如果和typedef一块使用,然后用重新定义的类型。就需要前置声明。

typedef struct stu STU;
struct stu{
  int age;
  STU stu2;
};

这样成员变量中就不用struct加结构体名的形式定义了,直接可以用STU定义所需变量。

结构体对齐

结构如何对齐呢,使用的是伪指令#pragma

#pragma pack(push,2)
typedef struct stu {
  char sex;
  int  age;
}STU;
#pragma pack(pop)

2代表是以2个字节对齐的,此时sizeof(STU)等于6,因为sex为char型占1个字节,但是指定了两个字节对齐,所以分配给sex的内存实际有两个字节的空间。

注意字节对齐是以2的n次幂对齐的。即1、2、4、8等。不能是3字节对齐。

图片


首先,问题中描述的65535个连接指的是客户端连接数的限制。
在tcp应用中,server事先在某个固定端口监听,client主动发起连接,经过三路握手后建立tcp连接。那么对单机,其最大并发tcp连接数是多少呢?

图片
如何标识一个TCP连接
在确定最大连接数之前,先来看看系统如何标识一个tcp连接。系统用一个4四元组来唯一标识一个TCP连接:{localip, localport,remoteip,remoteport}。

图片
client最大tcp连接数
client每次发起tcp连接请求时,除非绑定端口,通常会让系统选取一个空闲的本地端口(local port),该端口是独占的,不能和其他tcp连接共享。tcp端口的数据类型是unsigned short,因此本地端口个数最大只有65536,端口0有特殊含义,不能使用,这样可用端口最多只有65535,所以在全部作为client端的情况下,一个client最大tcp连接数为65535,这些连接可以连到不同的serverip。

图片
server最大tcp连接数
server通常固定在某个本地端口上监听,等待client的连接请求。不考虑地址重用(unix的SO_REUSEADDR选项)的情况下,即使server端有多个ip,本地监听端口也是独占的,因此server端tcp连接4元组中只有remoteip(也就是clientip)和remote port(客户端port)是可变的,因此最大tcp连接为客户端ip数×客户端port数,对IPV4,不考虑ip地址分类等因素,最大tcp连接数约为2的32次方(ip数)×2的16次方(port数),也就是server端单机最大tcp连接数约为2的48次方。

图片
实际的tcp连接数
上面给出的是理论上的单机最大连接数,在实际环境中,受到机器资源、操作系统等的限制,特别是sever端,其最大并发tcp连接数远不能达到理论上限。在unix/linux下限制连接数的主要因素是内存和允许的文件描述符个数(每个tcp连接都要占用一定内存,每个socket就是一个文件描述符),另外1024以下的端口通常为保留端口。
所以,对server端,通过增加内存、修改最大文件描述符个数等参数,单机最大并发TCP连接数超过10万,甚至上百万是没问题的。
这明显是进入了思维的误区,65535是指可用的端口总数,并不代表服务器同时只能接受65535个并发连接
举个例子:
我们做了一个网站,绑定的是TCP的80端口,结果是所有访问这个网站的用户都是通过服务器的80端口访问,而不是其他端口。可见端口是可以复用的。
图片

即使Linux服务器只在80端口侦听服务, 也允许有10万、100万个用户连接服务器。Linux系统不会限制连接数至于服务器能不能承受住这么多的连接,取决于服务器的硬件配置、软件架构及优化。

我们知道两个进程如果需要进行通讯最基本的一个前提是:能够唯一的标识一个进程。在本地进程通讯中我们可以使用PID来唯一标识一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大。

这时候就需要另辟它径了,IP地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样可以利用IP地址+协议+端口号唯一标示网络中的一个进程。

能够唯一标识网络中的进程后,它们就可以利用socket进行通信了。socket(套接字)是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用以实现进程在网络中通信。

图片

socket源自Unix,是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。另外,搜索公众号顶级科技后台回复“API接口”,获取一份惊喜礼包。

唯一能够确定一个连接有4个东西:

  • 服务器的IP
  • 服务器的Port
  • 客户端的IP
  • 客户端的Port

服务器的IP和Port可以保持不变,只要客户端的IP和Port彼此不同就可以确定一个连接数。

图片

一个socket是可以建立多个连接的,一个TCP连接的标记为一个四元组(source_ip, source_port, destination_ip, destination_port),即(源IP,源端口,目的IP,目的端口)四个元素的组合。只要四个元素的组合中有一个元素不一样,那就可以区别不同的连接。

举个例子:

你的主机IP地址是1.1.1.1, 在8080端口监听

当一个来自 2.2.2.2 发来一条连接请求,端口为5555。这条连接的四元组为(1.1.1.1, 8080, 2.2.2.2, 5555)

这时2.2.2.2又发来第二条连接请求,端口为6666。新连接的四元组为(1.1.1.1, 8080, 2.2.2.2, 6666)

那么,你主机的8080端口建立了两条连接;

(2.2.2.2)发来的第三条连接请求,端口为5555(或6666)。第三条连接的请求就无法建立,因为没有办法区分于上面两条连接。

同理,可以在同一个端口号和IP地址上绑定一个TCP socket和一个UDP socket 因为端口号虽然一样,但由于协议不一样,所以端口是完全独立的。TCP/UDP一般采用五元组来定位一个连接:source_ip, source_port, destination_ip, destination_port, protocol_type即(源IP,源端口,目的IP,目的端口,协议号)

综上所述,服务器的并发数并不是由TCP的65535个端口决定的。服务器同时能够承受的并发数是由带宽、硬件、程序设计等多方面因素决定的。

所以也就能理解淘宝、腾讯、头条、百度、新浪、哔哔哔哔等为什么能够承受住每秒钟几亿次的并发访问,是因为他们采用的是服务器集群。服务器集群分布在全国各地的大型机房,当访问量小的时候会关闭一些服务器,当访问量大的时候会不断的开启新的服务器。

图片
65535从哪来的,干啥的?


图片

牛逼啊!接私活必备的 N 个开源项目!赶快收藏

要解释好这个问题,就要先说清楚65535的含义。在Linux系统中,如果两个机器要通信,那么相互之间需要建立TCP连接,为了让双方互相认识,Linux系统用一个四元组来唯一标识一个TCP连接:{local ip, local port, remote ip, remote port},即本机IP、本机端口、远程IP、远程端口,IP和端口就相当于小区地址和门牌号,只有拿到这些信息,通信的双方才能互相认知。在Linux系统中,表示端口号(port)的变量占16位,这就决定了端口号最多有2的16次方个,即65536个,另外端口0有特殊含义不给使用,这样每个服务器最多就有65535个端口可用。因此,65535代表Linux系统支持的TCP端口号数量,在TCP建立连接时会使用。

图片
TCP怎么建立连接,与端口号是什么关系?


图片

Linux服务器在交互时,一般有两种身份:客户端或者服务器端。典型的交互场景是:

  • 服务器端主动创建监听的socket,并绑定对外服务端口port,然后开始监听
  • 客户端想跟服务器端通信时,就开始连接服务器的端口port
  • 服务端接受客户端的请求,然后再生成新的socket
  • 服务器和客户端在新的socket里进行通信

可以看到,端口port主要用在服务器和客户端的“握手认识”过程,一旦互相认识了,就会生成新的socket进行通信,这时候port就不再需要了,可以给别的socket通信去使用,所以很明显TCP连接的数量可以大于TCP端口号的数量65,535。

考虑一下两个极端场景,即某台Linux服务器只作为客户端或者服务器端

(1)Linux服务器只作为客户端

这时候每发起一个TCP请求,系统就会指定一个空闲的本地端口给你用,而且是独占式的,不会被别的TCP连接抢走,这样最多可以建立65535个连接,每个连接都与不同的服务器进行交互。这种场景,就是题主所描述的样子,但是由于条件过于苛刻,属于小概率事件,所以更多的还是理论上的可能,现实的环境中几乎不会出现。

(2)Linux服务器只作为服务端

这种场景下,服务端就会固定的监听本地端口port,等着客户端来向它发起请求。为了计算简单,我们假设服务器端的IP跟端口是多对一的,这样TCP四元组里面就有remote ip和remote port是可变的,因此最大支持创建TCP个数为2的32次方(IP地址是32位的)乘以2的16次方(port是16位的)等于2的48次方。

现实中单台Linux服务器支持的TCP连接数量

通过前面的分析我们知道,在现实场景中,由于存在端口port复用的情况,服务器可同时支持的TCP连接数跟65535没有一一对应关系,事实上,真正影响TCP连接数量的,是服务器的内存以及允许单一进程同时打开文件的数量,因为每创建一个TCP连接都要创建一个socket句柄,每个socket句柄都占用一部分系统内存,当系统内存被占用殆尽,允许的TCP并发连接数也就到了上限。一般来讲,通过增加服务器内存、修改最大文件描述符个数等,可以做到单台服务器支持10万+的TCP并发。

图片

当然,在真实的商用场景下,单台服务器都会编入分布式集群,通过负载均衡算法动态的调度不同用户的请求给最空闲的服务器,如果服务器平均内存使用超过80%的警戒线,那么就会及时采用限流或者扩展集群的方式来保证服务,绝对不会出现服务器的内存被耗尽的情况,那样就算事故了。

总之,65535只是Linux系统中可使用端口port数量的上限,端口port数量与TCP连接数量并非完全一一对应的关系,服务器支持的TCP并发连接数量主要跟服务器的内存以及允许单个进程同时打开的文件数量有关系,通过端口复用及调整服务器参数等手段,单台服务器支持的TCP并发连接数是可以高于65535的。

  声明:本文部分素材转载自互联网,如有侵权立即删除 。


使用Flask创建REST
   Web服务很简单,使用熟悉的route()装饰器及其methods可选参数可以声明服务所提供资源URL的路由,处理JSON数据同样简单,因为请求中包含的JSON数据可通过request.json这个Python字典获取,并且需要包含JSON的响应可以使用Flask提供的辅助函数jsonify()从Python字典中生成

创建API蓝本

REST API 相关的路由是一个自成一体的程序子集,所以为了更好的组织代码,我们最好把这些路由放到独立的蓝本中,这个程序API蓝本的基本结构如下:

|-flasky|-app/|-api_1_0|-__init__.py|-users.py|-posts.py|-comments.py|-authentication.py|-errors.py|-decorators.py      12345678910

注意,API包的名字中有个版本号,如果需要创建一个向前兼容的API版本,可以添加一个版本号不同的包,让程序同时支持两个版本的API

在这个API蓝本中,各资源分别在不同的模块中实现,蓝本中还包含处理认证、错误以及提供自定义装饰器的模块,蓝本的构造文件如下所示:

# app/api_1_0/__init__.pyfrom flask import Blueprint

api = Blueprint('api', __name__)from . import authentication, posts, users, comments, errors#...12345678

注册API蓝本的代码如下:

# app/__init__.pydef create_app(config_name):#...from .api_1_0 import api as api_1_0_blueprint
app.register_blueprint(api_1_0_blueprint, url_prefix='/api/v1.0')return app12345678

错误处理

REST Web服务将请求的状态告知客户端时,会在响应中发送适当的HTTP状态码,并将额外信息放入响应主体,客户端能从Web服务得到的常见状态码如下表

HTTP状态码名称说明
200OK(成功)请求成功完成
201Created(已创建)请求成功完成并创建了一个新资源
400Bad request(坏请求)请求不可用或不一致
401Unauthorized(未授权)请求中为包含认证信息
403Forbidden(禁止)请求中发送的认证密令无权访问目标
404Notfound(未找到)URL对应资源不存在
405Methods not allowed(不允许使用的方法)指定资源不支持请求使用方法
500Internal server error(内部服务器错误)处理请求的过程中发生意外错误

处理404和500状态码时会有点小麻烦,因为这两个错误是由Flask自己生成的,而且一般会返回HTML响应,这很可能会让API客户端困惑

为所有客户端生成适当相应的一种方式是,在错误处理程序中根据客户端请求的格式改写响应,这种技术成为内容协商
   下例是改进后的404错误处理程序,它向Web服务客户端发送JSON格式响应,除此之外都发送HTML格式响应,500错误处理程序的写法类似

app/main/errors.py@main.app_errorhandler(404)def page_not_found(e):if request.accept_mimetypes.accept_json and \not request.accept_mimetypes.accept_html:
response = jsonify({'error': 'not found'})
response.status_code = 404return responsereturn render_template('404.html'), 404123456789

这个新版错误处理程序检查Accept请求首部(Werkzeug将其编码为request.accept_mimetypes),根据首部的值决定客户端期望接受的响应格式,浏览器一般不限制响应的格式,所以只为接受JSON格式而不接受HTML格式的客户端生成JSON响应

其他状态码都是由Web服务生成,因此可在蓝本的errors.py模块作为辅助函数实现,下例是403错误的处理程序,其他错误处理程序的写法类似

# app/api_1_0/errors.pydef forbidden(message):response = jsonify({'error':'forbidden', 'message': message})
response.status_code = 403return response123456

现在,Web服务的视图函数可以调用这些辅助函数生成错误响应了

使用Flask-HTTPAuth认证用户

和普通的Web程序一样,Web服务也需要保护信息,确保未经授权的用户无法访问,为此RIA必须询问用户的登录密令,并将其传给服务器进行验证

REST Web服务的特征之一是无状态,即在服务器在两次请求之间不能“记住”客户端的任何信息,客户端必须在发出的请求中包含所有必要信息,因此所有请求都必须包含用户密令

程序当前的登录功能是在Flask-Login的帮助下实现的,可以把数据存储在用户会话中,默认情况下,Flask把会话保存在客户端cookie中,因此服务器没有保存任何用户相关的信息,都转交给客户端保存,这种实现方式看起来遵守了REST架构的无状态要求,但在REST
   Web服务中使用cookie有点不现实,因为Web浏览器之外的客户端很难提供对cookie的支持,鉴于此,使用cookie并不是一个很好的设计选择

REST架构的无状态看起来似乎过于严格,但这并是不随意提出的要求,无状态的服务器伸缩起来更加简单,如果服务器保存了客户端的相关信息,就必须提供一个所有服务器都能访问的共享缓存,这样才能保证一直使用同一台服务器处理特定客户端的请求,这样的需求很难实现

因为REST架构基于HTTP协议,所以发送密令的最佳方式是使用HTTP认证,基本认证和摘要认证都可以,在HTTP认证中,用户密令包含在请求的Authorization首部中

HTTP认证协议很简单,可以直接实现,不过Flask-HTTPAuth拓展提供了一个便利的包装,可以把协议的细节隐藏在装饰器之中,类似于Flask-Login提供的login_required装饰器

Flask-HTTPAuth使用pip安装,在将HTTP基本认证的扩展进行初始化之前,我们先要创建一个HTTPBasicAuth类对象,和Flask-Login一样,Flask-HTTPAuth不对验证用户命令所需的步骤做任何假设,因此所需的信息在回调函数中提供,下例展示了如何初始化Flask-HTTPAuth扩展,以及如何在回调函数中验证密令

# app/api_1_0/authentication.pyfrom flask_httpauth import HTTPBasicAuth
auth = HTTPBasicAuth()@auth.verify_passworddef verify_password(email, password):if email == '':
g.current_user = AnonymousUser()return Trueuser = User.query.filter_by(email = email).first()if not user:return Falseg.current_user = userreturn user.verify_password(password)123456789101112131415

由于这种用户认证方法只在API蓝本中使用,所以Flask-HTTPAuth扩展只能在蓝本包中初始化,而不像其他扩展那样要在程序包中初始化

电子邮件和密码使用User模型中现有的方法验证,如果登录密令正确,这个验证回调函数就返回True,否则返回False,API蓝本也支持匿名用户访问,此时客户端发送的电子邮件字段必须为空

验证回调函数把通过认证的用户保存在Flask的全局对象g中,这样一来,视图函数便能进行访问,注意在匿名登录时,这个函数返回True并把Flask-Login提供的AnonymousUser类实例赋值给g.current_user

由于每次请求时都要传送用户密令,所以API路由最好通过安全的HTTP提供,加密所有的请求和响应

如果认证密令不正确,服务器向客户端返回401错误,默认情况下,Flask-HTTPAuth自动生成这个状态码,但为了和API返回的其他错误保持一致,我们可以自定义这个错误响应:

#app/api_1_0/authentication.py#...@auth.error_headlerdef auth_error():return unauthorized('Invalid credentials')1234567

为了保护路由,可使用装饰器auth.login_required

@api.route('/posts')@auth.login_requireddef get_posts():pass1234

不过,这个蓝本中的所有路由都要使用相同的方式进行保护,所以我们可以在before_request处理程序中使用一次login_required装饰器,应用到整个蓝本,如下例所示:

#app/api_1_0/authentication.pyfrom .errors import forbidden@api.before_request@auth.login_requireddef before_request():if not g.current_user.is_anonymous and \not g.current_user.comfirmed:return forbidden('Uncofirmed account')123456789

现在,API蓝本中的所有路由都能进行自动认证,而且作为附加认证,before_request处理程序还会拒绝已通过认证但没有确认账户的用户

基于令牌的认证

每次请求时,客户端都要发送认证密令,为了避免总是发送敏感信息,我们可以提供一种基于令牌的认证方案

使用基于令牌的认证方案时,客户端要先把登录密令发送给一个特殊的URL,从而生成认证令牌,一旦客户端获得令牌,就可用令牌代替登录密令认证请求,处于安全考虑,令牌有过期时间,令牌过期后,客户端必须重新发送登陆密令以生成新令牌,令牌落入他人之手所带来的安全隐患受限于令牌的短暂使用期限,为了生成和验证认证令牌,我们要在User模型中定义两个新方法,这两个新方法用到了itsdangerous包,如下

# app/models.pyclass User(db.Model):#....def generate_auth_token(self, expiration):s = Serializer(current_app.config['SECRET_KEY'],
   expires_in=expiration)return s.dumps({'id': self.id})@staticmethoddef verify_auth_token(token):s = Serializer(current_app.config['SECRET_KEY'])try:
data = s.loads(token)except:return Nonereturn User.query.get(data['id'])1234567891011121314151617

generate_auth_token()方法使用编码后的用户id字段值生成一个签名令牌,还指定了以秒为单位的过期时间,verify_auth_token()方法接受的参数是一个令牌,如果令牌可用就返回对应的对象,verify_auth_token()是静态方法,因为只有解码令牌后才能知道用户是谁

为了能够认证包含令牌的请求,我们必须修改Flask-HTTPAuth提供的verify_password回调,除了普通的密令之外,还要接受令牌,修改后的回调函数如下:

# app/api_1_0/authentication.py@auth.verify_passworddef verify_password(email_or_token, password):if email_or_token == '':
g.current_user = AnonymousUser()return Trueif password == '':
g.current_user = User.verify_auth_token(email_or_token)
g.token_used = Truereturn g.current_user is not Noneuser = User.query.filter_by(email = email_or_token).first()if not user:return Falseg.current_user = user
g.token_used = Falsereturn user.verify_password(password)123456789101112131415161718

在这个新版本中,第一个认证参数可以是电子邮件地址或认证令牌,如果这个参数为空,那就和之前一样,假定是匿名用户,如果密码为空,那就假定email_or_token参数提供的是令牌,按照令牌的方式进行认证,如果两个参数都不为空,假定使用常规的邮件地址和密码进行认证,在这种实现方式中,基于令牌的认证是可选的,由客户端决定是否使用,为了让视图函数能区分这两种认证方式,我们添加了g.token_used变量

把认证令牌发送给客户端的路由也要添加到API蓝本中,具体实现如下:

# app/api_1_0/authentication.py#...@api.route('/token')def get_token():if g.current_user.is_anonymous() or g.token_used:return unauthorized("Invalid credentials")return jsonify({'token': g.current_user.generate_auth_token(
expiration=3600), 'expiration': 3600})1234567891011

这个路由也在蓝本中,所以添加到before_request处理程序上的认证机制也会用在这个路由上,为了避免客户端使用旧令牌申请新令牌,要在视图函数中检查g.token_used变量的值,如果使用令牌进行认证就拒绝请求,这个视图函数返回JSON格式的响应,其中包含了过期时间为1小时的令牌,JSON格式的响应也包含过期时间

项目背景


公司 App 上准备针对客户的ip来推荐最优服务器来快连设备,目前有4个节点,北京、俄勒冈、新加坡、法兰克福。客户端上报 IP,云端根据分配规则返回 server 代号。

资料搜集


一开始打算找第三方的一些API,测试了很多ip定位接口,效果并不好,有的收费,有的需要申请key有调用次数限制,大部分API只有国家和城市的信息,而且不规范,不是国家代码,抓取到结果还需要进一步匹配,因为没有大洲信息,还要自己根据国家去
   mapping 大洲。

上面的各种缺点,直接放弃第三方的API,于是找到了 GeoIP
   这个扩展,这个有纯PHP版本的,但是我没找到如何获取大洲,直接上C扩展版的,性能肯定没问题,装好扩展,geoip_continent_code_by_name 直接获取大洲简称代码。

php 扩展安装


我的是 Docker 环境 Ubuntu14.04 php5.6, 下面是扩展的安装命令。

1
apt-get -y --force-yes install php5.6-geoip

注意:--no-install-recommends 这个参数一定不要加,有这个的话安装完成会把 IP 数据包删除的, 我 build docker 镜像的时候吃过亏了,下面是 IP 数据包的目录。

1
2
root@1e1931746c6c:/usr/share/GeoIP# ls
GeoIP.dat  GeoIPv6.dat

GeoIP 扩展源码下载: https://pecl.php.net/package/geoip
   以下是源码安装步骤:

1
2
3
4
5
6
$ cd geoip-1.1.1
$ phpize
$ ./configure
$ make
$ sudo make install

编写代码


在 phpinfo 能看到 GeoIP,就说明扩展安装好了。
   这是 GeoIP 扩展的文档,一个函数获取想要的信息,完全满足了我的需求,比第三方 API 好用的多,直接省去网络请求。
   http://php.net/manual/zh/book.geoip.php

官方文档:https://wiki.swoole.com/wiki/page/6.html

Swoole扩展是按照PHP标准扩展构建的。使用phpize来生成编译检测脚本,./configure来做编译配置检测,make进行编译,make install进行安装。
上面所说都是编译 php 扩展的通用过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 下载 release 版本 #
# 注意:在国内下载 github 的资源,有时候不稳定,可以用迅雷等下载工具在本机下载好,再通过 SFTP 传到服务器 #
cd /usr/local/src
 
# 解压、进入目录 #
tar xzvf swoole-src-master.tar.gz
cd swoole-src-master
 
# 生成编译检测脚本、配置检查、编译安装 #
# phpize、php-config 请使用你自己的路径 #
/www/server/php/72/bin/phpize && \
./configure --with-php-config=/www/server/php/72/bin/php-config \
--enable-coroutine --enable-openssl  \
--enable-http2  \
--enable-async-redis \
--enable-sockets \
--enable-mysqlnd && \
make clean && make && sudo make install

精简默认安装

1
2
3
/www/server/php/72/bin/phpize && \
./configure --with-php-config=/www/server/php/72/bin/php-config && \
make clean && make && sudo make install

如果上述步骤顺利的话,就会有 Build complete 输出:

1
2
3
4
5
Build complete.
Don't forget to run 'make test'.
 
Installing shared extensions:     /www/server/php/72/lib/php/extensions/no-debug-non-zts-20170718/
Installing header files:          /www/server/php/72/include/php/

cd /www/server/php/72/lib/php/extensions/no-debug-non-zts-20170718/ 就能找到 swoole.so 文件了

修改 php.ini 文件,将 swoole.so 加载进去

1
2
[swoole]
extension = /www/server/php/72/lib/php/extensions/no-debug-non-zts-20170718/swoole.so

通过 php -m | grep swoole 来查看是否成功加载了swoole

相关错误

编译安装swoole报错 Enable http2 support, require nghttp2 library.

swoole安装异步redis –enable-async-redis出错 hiredis/hiredis.h: No such file or directory 解决方法