Laravel 5.0 之 表单验证类 (Form Requests)

让人头痛的表单验证

本文译自 Matt Stauffer 的 系列文章 .

只要你曾经在使用 Laravel 框架的过程中试图找到有关用户输入验证的最佳实践, 你就应该了解这是一个争论最多并且几乎没有达成共识的话题. 我们可以在控制器中进行验证, 可以在单独的一个服务层进行验证, 可以在模型中进行验证, 当然还可以在 Javascript 中进行验证 (这只是一个玩笑, 谁都知道不能只依赖于客户端的验证). 但是, 哪一种做法才是最佳的呢?

Laravel 5.0 新引入的表单请求 (Form Request) 特性提供了集规范性 (差不多就是 "最佳实践" 的意思) 和便捷性 (这是比之前任何一种选择都更强大也更便捷的方式) 于一体的, 在 Laravel 中执行数据检查和验证的新手段.

说明: 本文中使用新的 view() 辅助方法代替了旧版本中的 View::make() .

Form Requests 使表单验证不再让人头痛

Laravel 5.0 带来了 Form Requests, 这是一种特殊的类型, 用于在提交表单时进行数据的检查和验证. 每个 Form Request 类至少包含一个 rules() 方法, 这个方法返回一组验证规则. 除此之外还必须包含一个 authorize() 方法, 该方法返回一个布尔值, 代表是否允许用户执行本次请求.

Laravel 会在解析 POST 路由之前自动把用户输入的信息传递给相应的表单请求, 因此我们的所有验证逻辑都可以移到独立于控制器和模型之外的 FormRequest 对象中.

开始实践: 快速创建一个 Laravel 5.0 项目

如果你还没有创建好的 Laravel 5.0 项目, 用下面的命令创建一个:

$ composer create-project laravel/laravel myProject dev-develop --prefer-dist

1. 添加路由

// app/Http/routes.phpRoute::get('/', 'FriendsController@getAddFriend');Route::post('/', 'FriendsController@postAddFriend');

2. 创建控制器

//app/Http/Controllers/FriendsController:namespace App\Http\Controllers;use App\Http\Requests\FriendFormRequest;use Illuminate\Routing\Controller;use Response;use View;class FriendsController extends Controller{	public function getAddFriend()
	{		return view('friends.add');
	}	public function postAddFriend(FriendFormRequest $request)
	{		return Response::make('Friend added!');
	}
}

3. 创建视图

<html><body>
	@foreach ($errors->all() as $error)		<p class="error">{{ $error }}</p>
	@endforeach	<form method="post">
		<label>First name</label><input name="first_name"><br>
		<label>Email address</label><input name="email_address"><br>
		<input type="submit">
	</form></body></html>

4. 创建 FormRequest

// app/http/requests/FriendFormRequest.phpnamespace App\Http\Requests;use Illuminate\Foundation\Http\FormRequest;use Response;class FriendFormRequest extends FormRequest{	public function rules()
	{		return [			'first_name' => 'required',			'email_address' => 'required|email'
		];
	}	public function authorize()
	{		// 只允许登陆用户
		// 返回 \Auth::check();
		// 允许所有用户登入
		return true;
	}	// 可选: 重写基类方法
	public function forbiddenResponse()
	{		// 这个是可选的, 当认证失败时返回自定义的 HTTP 响应. 
		// (框架默认的行为是带着错误信息返回到起始页面)
		// 可以返回 Response 实例, 视图, 重定向或其它信息
		return Response::make('Permission denied foo!', 403);
	}	// 可选: 重写基类方法
	public function response()
	{		// 如果需要自定义在验证失败时的行为, 可以重写这个方法
		// 了解有关基类中这个方法的默认行为,可以查看:
		// https://github.com/laravel/framework/blob/master/src/Illuminate/Foundation/Http/FormRequest.php
	}
}

接下来, 用 php artisan serve 或者你自己喜欢的方式启动服务器. 提交表单, 你可以看到我们并没有往控制器中添加任何一行验证逻辑, 但是验证规则已经生效了.

其它用例

如果对 "新增" 和 "编辑" 有不同的规则, 或者根据不同的输入进行不同的验证, 要怎么办呢? 这里有几个可以参考的例子, 虽然还不能确定这些就是 "最佳实践":

采用分开的 form requests

Laravel 并没有规定你不能对 "新增" 和 "编辑" 操作采用不同的 form request 类. 所以你可以创建一个包含所有规则的 FriendFormRequest 作为基类, 然后把它扩展为 addFriendFormRequest 和 editFriendFormRequest 两个子类, 每个子类都可以实现各自的默认行为.

采用条件判断逻辑

rules() 作为一个方法而不是属性, 带来的好处就是你可以在方法中添加判断逻辑:

...class UserFormRequest extends FormRequest{
	...	protected $rules = [	
		'email_address' => 'required',		'password' => 'required|min:8',
	];	public function rules()
	{
		$rules = $this->rules;		// 根据不同的情况, 添加不同的验证规则
		if ($someTestVariableShowingThisIsLoginInsteadOfSignup)
		{
			$rules['password'] = 'min:8';
		}		return $rules;
	}
}

也可以在 authorize 方法中添加逻辑, 比如:

...class FriendFormRequest extends FormRequest{
	...	public function authorize()
	{		if ( ! Auth::check() )
		{			return false;
		}
		$thingBeingEdited = Thing::find(Input::get('thingId'));		// 如果是编辑操作, 或者当前用户不是对象创建者
		if ( ! $thingBeingEdited || $thingBeingEdited->owner != Auth::id()) {			return false;
		}		return true;
	}
}

自定义校验

除了上面的方式, 如果需要对验证逻辑进行更深入的控制, 可以重写提供校验对象实例的方法. 下面是一个简单的实例, 后续会专门写一篇文章来解释:

...class FriendFormRequest extends FormRequest{	public function validator(ValidationService $service)
	{
		$validator = $service->getValidator($this->input());		// 可选: 通过新的 ->after() 方法来进行自定义
		$validator->after(function() use ($validator)) {			// 在这里可以做更多更深入的校验
			$validator->errors()->add('field', 'new error);
		}
	}
}

ValidatesWhenResolved 接口

后续还会有一篇有关 ValidatesWhenResolved 接口的文章, 不过那篇文章重点讨论的是对方法/路由等的校验. IOC 何时提供什么东西, 这个在 Laravel 5.0 版已经分离出一个单独的接口. 官方文档: https://github.com/illuminate/contracts/blob/master/Validation/ValidatesWhenResolved.php

其它可自定义的参数:

$redirect : 校验失败时要重定向到的 URI. $redirectRoute : 校验失败时要重定向到的路由.$redirectAction : 校验失败时要重定向到的方法. $dontFlash : 重定向时不要传递的输入项的键 (默认值: ['password', 'password_confirmation']).

写在最后

通过文本可以看到, Form Requests 对于简化表单请求的数据校验是非常强大和方便的. 如果你阅读本文觉得还不够, 可以观看关于 Form Request 的 这个视频 .

本文写作时, Laravel 5.0 还未正式发布, 因此上述内容最终可能还会有修改, 或者作者遗漏了某些东西. 如果你有建议或者对文章内容的修正, 可以在 给译者发邮件 或者(在 Twitter 上直接联系原作者)

[http://twitter.com/stauffermatt].

Service Provider 测试实例

简单归纳一下

1、服务类继承契约接口(为了对类的定义加以约束)

2、创建服务提供者(Service Provider)

3、在服务提供者的register内绑定服务类

4、注册服务提供者到config

5、创建服务容器(Service Container

6、将服务提供者依赖注入服务容器

7、服务容器使用该服务

1、定义服务类

有了上一节有关服务容器的讲述,理解起服务提供者来很简单。我们这里先定义一个绑定到容器的测试类TestService,为了对类的定义加以约束,我们同时还定义一个契约接口TestContract。

定义TestContract如下:

<?php

namespace App\Contracts;

interface TestContract
{
    public function callMe($controller);
}

定义TestService如下:

<?php

namespace App\Services;

use App\Contracts\TestContract;

class TestService implements TestContract
{
    public function callMe($controller)
    {
        dd('Call Me From TestServiceProvider In '.$controller);
    }
}

2、创建服务提供者

接下来我们定义一个服务提供者TestServiceProvider用于注册该类到容器。创建服务提供者可以使用如下Artisan命令:

php artisan make:provider TestServiceProvider

该命令会在app/Providers目录下生成一个TestServiceProvider.php文件,我们编辑该文件内容如下:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\TestService;

class TestServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register the application services.
     *
     * @return void
     * @author LaravelAcademy.org
     */
    public function register()
    {
        //使用singleton绑定单例
        $this->app->singleton('test',function(){
            return new TestService();
        });

        //使用bind绑定实例到接口以便依赖注入
        $this->app->bind('App\Contracts\TestContract',function(){
            return new TestService();
        });
    }
}

可以看到我们使用了两种绑定方法,更多绑定方法参考服务容器文档。

3、注册服务提供者

定义完服务提供者类后,接下来我们需要将该服务提供者注册到应用中,很简单,只需将该类追加到配置文件config/app.php的providers数组中即可:

'providers' => [

    //其他服务提供者

    App\Providers\TestServiceProvider::class,
],

4、测试服务提供者

这样我们就可以在应用中使用该服务提供者了,为了测试该服务提供者我们首先使用Artisan命令创建一个资源控制器TestController:

php artisan make:controller TestController

然后在路由配置文件routes.php中定义路由:

Route::resource('test','TestController');

最后去TestController中编写测试代码:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;

use App;
use App\Contracts\TestContract;

class TestController extends Controller
{
    //依赖注入
    public function __construct(TestContract $test){
        $this->test = $test;
    }

    /**
     * Display a listing of the resource.
     *
     * @return Response
     * @author LaravelAcademy.org
     */
    public function index()
    {
        // $test = App::make('test');
        // $test->callMe('TestController');
        $this->test->callMe('TestController');
    }

    ...//其他控制器动作
}

然后我们去浏览器中访问http://laravel.app:8000/test

分别测试使用App::make和依赖注入解析绑定类调用callMe方法的输出,结果一样,都是:

"Call Me From TestServiceProvider In TestController"

好了,大功告成,是不是很简单?!

此外,Laravel服务提供者还支持延迟加载,具体可参考服务提供者文档

控制反转(DI) 依赖注入(IoC)

<?php
Class Fight{
    public function __construct($options){
        //...
    }
}

Class Force{
    public function __construct($options){
        //...
    }
}


Class Shot{
    public function __construct($options){
        //...
    }
}

//超能力工厂类
Class SuperModuleFactory{
    public function makeModule($moduleName, $options){
        switch($moduleName){
            case 'Fight':
                return new Fight($options[0], $options[1]);
            case 'Force':
                return new Force($options[0]);
            case 'Shot':
                return new Shot($options[0], $options[1], $options[2]);
            // case 'more': .......
            // case 'and more': .......
            // case 'and more': .......
            // case 'oh no! its too many!': .......
        }
    }
}

Class Superman{
    protected $power;

    public function __construct(array $modules)
    {
        //初始化工厂
        $factory = new SuperModuleFactory;

        //通过工厂提供的方法制造需要的模块
//        $this->power = $factory->makeModule('Fight',[9,100]);
//        $this->power = $factory->makeModule('Force',[50]);
//        $this->power = $factory->makeModule('Shot',[99,150,20]);

        /*
        $this->power = array(
            $factory->makeModule('Force',[45]),
            $factory->makeModule('Shot',[99,40,10])
        );
        */

        //通过工厂提供的方法制造需要的模块
        foreach($modules as $moduleName => $moduleOptions){
            $this->power[] = $factory->makeModule($moduleName, $moduleOptions);
        }

    }
}

//创建超人
$superman = new Superman([
    'Fight' => [9, 100],
    'Shot' => [99, 50, 2]
]);
//“超人”的创建不再依赖任何一个“超能力”的类,如果修改了或者增加了新的超能力只需要针对修改SuperModuleFactory即可。




?>

<?php
//当超能力急需拓展的时候,如果依赖超能力工厂就会在switch里堆很多东西,我们需要制定统一接口作为一种“契约”,这样无论是谁创建出的模组,都符合这样的接口,就可以被正常使用。
interface SuperModuleInterface{
    /**
     * 超能力激活方法
     *
     * 任何一个超能力都得有该方法,并拥有一个参数
     *@param array $target 针对目标,可以是一个或多个,自己或他人
     */
    public function activate(array $target);
}

/**
 * X-超能量
 */
class XPower implements SuperModuleInterface{
    public function activate(array $target){
        // 这只是个例子。。具体自行脑补
    }
}

/**
 * 终极炸弹 (就这么俗)
 */
class UltraBomb implements SuperModuleInterface{
    public function activate(array $target){
        // 这只是个例子。。具体自行脑补
    }
}

//提供的模组实例必须是一个SuperModuleInterface接口的实现
//正是由于超人的创造变得容易,一个超人也就不需要太多的超能力,我们可以创造多个超人,并分别注入需要的超能力模组即可。
//开头到现在提到的一系列依赖,只要不是由内部生产(比如初始化、构造函数 __construct 中通过工厂方法、自行手动 new 的),而是由外部以参数或其他形式注入的,都属于依赖注入(DI)
class Superman{
    protected $module;

    public function __construct(SuperModuleInterface $module)
    {
        $this->module = $module;
    }
}

//下面就是一个典型的依赖注入

//超能力模组
$superModule = new XPower;
//初始化一个超人,并注入一个超能力模组依赖
$superMan = new Superman($superModule);




//我们需要自动化 —— 最多一条指令,千军万马来相见
//工厂模式升华 -- IoC容器
class Container{
    protected $binds;
    protected $instance;

    public function bind($abstract, $concrete){
        if($concrete instanceof Closure){
            //instanceof Closure判断$concrete是否是一个闭包
            $this->binds[$abstract] = $concrete;
        }else{
            // instance 方法绑定一个已存在的对象实例到容器,这里不是绑定只是把concrete放到数组里
            $this->instances[$abstract] = $concrete;
        }
    }

    public function make($abstract, $parameters = []){
        if(isset($this->instances[$abstract])){
            //已经是实例的 直接返回
            //实例是已经被初始化过的 所以不需要再传入参数实例化
            return $this->instances[$abstract];
        }

        array_unshift($parameters, $this);

        return call_user_func_array($this->binds[$abstract], $parameters);
    }
}


//创建一个容器(后面称作超级工厂)
$container = new Container;

//向该超级工厂添加超能力模组的生产脚本
$container->bind('xpower', function($container) {
    return new XPower;
});

//同上
$container->bind('ultrabomb', function($container){
    return new UltraBomb;
});

//向该超级工厂添加超人的生产脚本
$container->bind('superman', function($container, $moduleName){
    return new Superman($container->make($moduleName));
});

//绑定后的XPower以及UltraBomb等超能力 使用$container->make()就可以返回实例
//$container->make('superman','xpower')可以实例化superman注入xpower的实例
//因为superman bind的时候闭包实现了对第二个参数(moduleName)的实例化
//所以就可以直接通过超级工厂类穿件superman的实例
//*********************** 华丽丽的分割线 *************************
//开始启动生产
$superman_1 = $container->make('superman','xpower');
$superman_1 = $container->make('superman','ultrabomb');
$superman_1 = $container->make('superman','xpower');
// ...随意添加


//要添加特异功能的参数我觉得应该修改代码如下,当然也需要修改一下前面的XPower类
$container->bind('xpower',function($container, $parameters){
    return new XPower($parameters);
    //然后XPower的构造函数接收参数做相应处理
});

$container->bind('superman', function($container, $moduleName, $parameters){
    return new Superman($container->make($moduleName, $parameters));
});

$superman_x = $container->make('superman','xpower',array(9,50,25));
//代码没有运行过不知道这样可不可行 只是一个思路


//实际上,真正的 IoC 容器更为高级。
//我们现在的例子中,还是需要手动提供超人所需要的模组参数,但真正的 IoC 容器会根据类的依赖需求,自动在注册、绑定的一堆实例中搜寻符合的依赖需求,并自动注入到构造函数参数中去。
//Laravel 框架的服务容器正是这么做的。实现这种功能其实理论上并不麻烦,但我并不会在本文中写出,因为……我懒得写。
?>

http://laravelacademy.org/post/769.html

laravel 学习笔记

这几天纯看文档稍微记点东西吧

路由 RESTful 资源控制器 Route::resource

动词 路径 行为 路由名称

GET /photo 索引 photo.index
GET /photo/create 创建 photo.create
POST /photo 保存 photo.store
GET /photo/{photo} 显示 photo.show
GET /photo/{photo}/edit 编辑 photo.edit
PUT/PATCH /photo/{photo} 更新 photo.update
DELETE /photo/{photo} 删除 photo.destroy


DI 依赖注入

Ioc依赖反转

其实这些概念在angular和之前看设计模式的时候都接触到过

http://www.jianshu.com/p/b7edc9b22b8a

http://blog.leokim.cn/2017/04/18/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%ACdi-%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5ioc/

Service Container, Service Provider,Contracts, Facade

Service Container就是被注入的类,作为容器,在构造函数内被注入,或者使用facade在顶部声明


Service Provider的bind方法

其实就是在Service Prodide里用bind方法来绑定别名,这个地方文档里的鬼例子看着让人着实迷惑

$this->app->bind('post', function ($app) {
 return new App\Models\Post;
});

绑定后就可以直接

$app->make('post')


singleton和bind的区别

它们两个都是返回一个类的实例,不同的是singleton是单例模式,而bind是每次返回一个新的实例。

https://segmentfault.com/a/1190000004388879


修改更新查找MySQL5.7.x的root用户的默认密码

最近新安装了wamp3.0.4里面附带的mysql已经升级到了5.7版本了。

MySQL5.7在性能方面有很大的提升。安装成功之后默认root的密码为空能登录。

但是正常情况下需要给root重新设置新的密码。

对于MySQL5.7版本来说和之前的5.6及以下版本的user表不一样了(user表里面没有了password这个字段了,mysql5.7 中保存密码的字段是 authentication_string)。

直接上命令行。

mysql5.7 中保存密码的字段是 authentication_string
 
//如果你用Navicat的话,先默认密码登录直接执行sql语句
update mysql.user set authentication_string=password("leokim.cn") where User="root" and Host="localhost";
 
flush privileges; 
 
 
//如果你用dos的话--进入到bin目录
cd D:\wamp\bin\mysql\mysql5.7.11\bin
 
//输入mysql 用户名和密码
mysql -u root -p
 
//新开DOS, 输入mysql,出现mysql> 命令标识符 
mysql>update mysql.user set authentication_string=password("www_dodobook_net") where user="root" and Host="localhost"; 
mysql>flush privileges; 
mysql>quit;

如果你忘了密码想找回密码的话,可以参考下面的方法

其实想要重置 5.7 的密码很简单,就一层窗户纸:
1、修改 /etc/my.cnf,在 [mysqld] 小节下添加一行:skip-grant-tables=1
这一行配置让 mysqld 启动时不对密码进行验证
2、重启 mysqld 服务:systemctl restart mysqld
3、使用 root 用户登录到 mysql:mysql -u root 
4、切换到mysql数据库,更新 user 表:
update user set authentication_string = password('root'), password_expired = 'N', password_last_changed = now() where user = 'root';
在之前的版本中,密码字段的字段名是 password,5.7版本改为了 authentication_string
5、退出 mysql,编辑 /etc/my.cnf 文件,删除 skip-grant-tables=1 的内容
6、重启 mysqld 服务,再用新密码登录即可.

另外,MySQL 5.7 在初始安装后(CentOS7 操作系统)会生成随机初始密码.

并在 /var/log/mysqld.log 中有记录,可以通过 cat 命令查看,找 password 关键字.

找到密码后,在本机以初始密码登录,并且(也只能)通过命令

alter user 'root'@'localhost' identified by 'root'

修改 root 用户的密码为 root,然后退出,重新以root用户和刚设置的密码进行登录即可。

 

也可以在安装的时候不生成随机密码,用 sudo mysqld –initialize-insecure

然后在自己设置密码 sudo /usr/bin/mysqladmin -uroot password 密码

如果你是之前的版本MySQL5.5 MySQL5.6的话请直接使用以下语句和方法

//先输入密码。默认为空就直接按Enter即可
mysql> use mysql
Database changed
mysql> update user set password=PASSWORD('leokim') where user='root';
Query OK, 3 rows affected (0.02 sec)
Rows matched: 3  Changed: 3  Warnings: 0
  
mysql> flush privileges;
Query OK, 0 rows affected (0.01 sec)
  
mysql> quit

php 获取月份最后一天

<?php
    echo date("t",strtotime('2016-02'));
?>

常用方法

<?php
echo strtotime("now"), "\n";
echo strtotime("10 September 2000"), "\n";
echo strtotime("+1 day"), "\n";
echo strtotime("+1 week"), "\n";
echo strtotime("+1 week 2 days 4 hours 2 seconds"), "\n";
echo strtotime("next Thursday"), "\n";
echo strtotime("last Monday"), "\n";
?>

php生成的csv, 无法完整显示带前导0的数字

PHP生成csv文件时,如果数字的第一位为0的话,显示时则不会显示第一个0

可以在php中做特殊处理,使得生成的csv在显示时能显示出第一个0

有两种方法可以实现:

1、给数字加上引号,并且在引号前面再加上个等号,如数字0555,,则生成时为="0555"

2、在数字前面加上制表符,如是数字0555,则生成时为\t0555

代码如下:

$arr    = array (  
    array ('111', 2222, '0555'),  
    array ('222', 3333, '0666')  
);  
$fp    = fopen('t.csv', 'w');  
foreach ($arr as $row) {  
    fputcsv($fp, array_map('h', $row));  
}  
fclose($fp);  
  
function h($v) {  
    if (($v{0} == '0') && is_numeric($v)) {  
        $v    = '="' . $v . '"';    //第一种方式  
        //$v   = "\t{$v}";  //第二种方式  
    }  
    return $v;  
}

韩天峰对php的吐槽 哈哈

1  PHP的API太混乱,很多常用的函数 到现在我都记不住 参数顺序。 这个到现在都没改变,20年了。

2  PHP的Zend底层不是特别规范,Zend API 很不好用,语言开发者和应用开发者中间本该有一个 扩展开发者,现在这个比较弱。

深入浅出讲解:php的socket通信

对TCP/IP、UDP、Socket编程这些词你不会很陌生吧?随着网络技术的发展,这些词充斥着我们的耳朵。那么我想问:

1.         什么是TCP/IP、UDP?

2.         Socket在哪里呢?

3.         Socket是什么呢?

4.         你会使用它们吗?

什么是TCP/IP、UDP?

TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。

UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。

 这里有一张图,表明了这些协议的关系。

TCP/IP协议族包括运输层、网络层、链路层。现在你知道TCP/IP与UDP的关系了吧。

Socket在哪里呢?

在图1中,我们没有看到Socket的影子,那么它到底在哪里呢?还是用图来说话,一目了然。

原来Socket在这里。

Socket是什么呢?

  Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

你会使用它们吗?

  前人已经给我们做了好多的事了,网络间的通信也就简单了许多,但毕竟还是有挺多工作要做的。以前听到Socket编程,觉得它是比较高深的编程知识,但是只要弄清Socket编程的工作原理,神秘的面纱也就揭开了。

  一个生活中的场景。你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。 生活中的场景就解释了这工作原理,也许TCP/IP协议族就是诞生于生活中,这也不一定。

  先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

socket相关函数:

———————————————————————————————-

socket_accept() 接受一个Socket连接

socket_bind() 把socket绑定在一个IP地址和端口上

socket_clear_error() 清除socket的错误或者最后的错误代码

socket_close() 关闭一个socket资源

socket_connect() 开始一个socket连接

socket_create_listen() 在指定端口打开一个socket监听

socket_create_pair() 产生一对没有区别的socket到一个数组里

socket_create() 产生一个socket,相当于产生一个socket的数据结构

socket_get_option() 获取socket选项

socket_getpeername() 获取远程类似主机的ip地址

socket_getsockname() 获取本地socket的ip地址

socket_iovec_add() 添加一个新的向量到一个分散/聚合的数组

socket_iovec_alloc() 这个函数创建一个能够发送接收读写的iovec数据结构

socket_iovec_delete() 删除一个已经分配的iovec

socket_iovec_fetch() 返回指定的iovec资源的数据

socket_iovec_free() 释放一个iovec资源

socket_iovec_set() 设置iovec的数据新值

socket_last_error() 获取当前socket的最后错误代码

socket_listen() 监听由指定socket的所有连接

socket_read() 读取指定长度的数据

socket_readv() 读取从分散/聚合数组过来的数据

socket_recv() 从socket里结束数据到缓存

socket_recvfrom() 接受数据从指定的socket,如果没有指定则默认当前socket

socket_recvmsg() 从iovec里接受消息

socket_select() 多路选择

socket_send() 这个函数发送数据到已连接的socket

socket_sendmsg() 发送消息到socket

socket_sendto() 发送消息到指定地址的socket

socket_set_block() 在socket里设置为块模式

socket_set_nonblock() socket里设置为非块模式

socket_set_option() 设置socket选项

socket_shutdown() 这个函数允许你关闭读、写、或者指定的socket

socket_strerror() 返回指定错误号的详细错误

socket_write() 写数据到socket缓存

socket_writev() 写数据到分散/聚合数组

案例一:socket通信演示

服务器端:

<?php
//确保在连接客户端时不会超时
set_time_limit(0);

$ip = '127.0.0.1';
$port = 1935;

/*
 +-------------------------------
 *    @socket通信整个过程
 +-------------------------------
 *    @socket_create
 *    @socket_bind
 *    @socket_listen
 *    @socket_accept
 *    @socket_read
 *    @socket_write
 *    @socket_close
 +--------------------------------
 */

/*----------------    以下操作都是手册上的    -------------------*/
if(($sock = socket_create(AF_INET,SOCK_STREAM,SOL_TCP)) < 0) {
    echo "socket_create() 失败的原因是:".socket_strerror($sock)."\n";
}

if(($ret = socket_bind($sock,$ip,$port)) < 0) {
    echo "socket_bind() 失败的原因是:".socket_strerror($ret)."\n";
}

if(($ret = socket_listen($sock,4)) < 0) {
    echo "socket_listen() 失败的原因是:".socket_strerror($ret)."\n";
}

$count = 0;

do {
    if (($msgsock = socket_accept($sock)) < 0) {
        echo "socket_accept() failed: reason: " . socket_strerror($msgsock) . "\n";
        break;
    } else {
        
        //发到客户端
        $msg ="测试成功!\n";
        socket_write($msgsock, $msg, strlen($msg));
        
        echo "测试成功了啊\n";
        $buf = socket_read($msgsock,8192);
        
        
        $talkback = "收到的信息:$buf\n";
        echo $talkback;
        
        if(++$count >= 5){
            break;
        };
        
    
    }
    //echo $buf;
    socket_close($msgsock);

} while (true);

socket_close($sock);
?>

这是socket的服务端代码。然后运行cmd,注意是自己的程序存放路径啊。

没有反映,对现在服务端的程序已经开始运行,端口已经开始监听了。运行netstat -ano可以查看端口情况,我的是1935端口

看,端口已经处于LISTENING状态了。接下来我们只要运行客户端程序即可连接上。上代码

<?php
error_reporting(E_ALL);
set_time_limit(0);
echo "<h2>TCP/IP Connection</h2>\n";

$port = 1935;
$ip = "127.0.0.1";

/*
 +-------------------------------
 *    @socket连接整个过程
 +-------------------------------
 *    @socket_create
 *    @socket_connect
 *    @socket_write
 *    @socket_read
 *    @socket_close
 +--------------------------------
 */

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($socket < 0) {
    echo "socket_create() failed: reason: " . socket_strerror($socket) . "\n";
}else {
    echo "OK.\n";
}

echo "试图连接 '$ip' 端口 '$port'...\n";
$result = socket_connect($socket, $ip, $port);
if ($result < 0) {
    echo "socket_connect() failed.\nReason: ($result) " . socket_strerror($result) . "\n";
}else {
    echo "连接OK\n";
}

$in = "Ho\r\n";
$in .= "first blood\r\n";
$out = '';

if(!socket_write($socket, $in, strlen($in))) {
    echo "socket_write() failed: reason: " . socket_strerror($socket) . "\n";
}else {
    echo "发送到服务器信息成功!\n";
    echo "发送的内容为:<font color='red'>$in</font> <br>";
}

while($out = socket_read($socket, 8192)) {
    echo "接收服务器回传信息成功!\n";
    echo "接受的内容为:",$out;
}


echo "关闭SOCKET...\n";
socket_close($socket);
echo "关闭OK\n";
?>

至此客户端已经连接上服务端了。

案例二:代码详解

// 设置一些基本的变量

$host = "192.168.1.99";

$port = 1234;

// 设置超时时间

set_time_limit(0);

// 创建一个Socket

$socket = socket_create(AF_INET, SOCK_STREAM, 0) or die("Could not createsocket\n");

//绑定Socket到端口

$result = socket_bind($socket, $host, $port) or die("Could not bind tosocket\n");

// 开始监听链接

$result = socket_listen($socket, 3) or die("Could not set up socketlistener\n");

// accept incoming connections

// 另一个Socket来处理通信

$spawn = socket_accept($socket) or die("Could not accept incomingconnection\n");

// 获得客户端的输入

$input = socket_read($spawn, 1024) or die("Could not read input\n");

// 清空输入字符串

$input = trim($input);

//处理客户端输入并返回结果

$output = strrev($input) . "\n";

socket_write($spawn, $output, strlen ($output)) or die("Could not write

output\n");

// 关闭sockets

socket_close($spawn);

socket_close($socket);

下面是其每一步骤的详细说明:

1.第一步是建立两个变量来保存Socket运行的服务器的IP地址和端口.你可以设置为你自己的服务器和端口(这个端口可以是1到65535之间的数字),前提是这个端口未被使用.

[Copy to clipboard]

PHP CODE:

// 设置两个变量

$host = "192.168.1.99";

$port = 1234;

2.在服务器端可以使用set_time_out()函数来确保PHP在等待客户端连接时不会超时.

[Copy to clipboard]

PHP CODE:

// 超时时间

set_time_limit(0);

3.在前面的基础上,现在该使用socket_creat()函数创建一个Socket了—这个函数返回一个Socket句柄,这个句柄将用在以后所有的函数中.

[Copy to clipboard]

PHP CODE:

// 创建Socket

$socket = socket_create(AF_INET, SOCK_STREAM, 0) or die("Could not create

socket\n");

第一个参数”AF_INET”用来指定域名;

第二个参数”SOCK_STREM”告诉函数将创建一个什么类型的Socket(在这个例子中是TCP类型)

因此,如果你想创建一个UDP Socket的话,你可以使用如下的代码:

[Copy to clipboard]

PHP CODE:

// 创建 socket

$socket = socket_create(AF_INET, SOCK_DGRAM, 0) or die("Could not create

socket\n");

4.一旦创建了一个Socket句柄,下一步就是指定或者绑定它到指定的地址和端口.这可以通过socket_bind()函数来完成.

[Copy to clipboard]

PHP CODE:

// 绑定 socket to 指定地址和端口

$result = socket_bind($socket, $host, $port) or die("Could not bind to

socket\n");

5.当Socket被创建好并绑定到一个端口后,就可以开始监听外部的连接了.PHP允许你由socket_listen()函数来开始一个监听,同时你可以指定一个数字(在这个例子中就是第二个参数:3)

[Copy to clipboard]

PHP CODE:

// 开始监听连接

$result = socket_listen($socket, 3) or die("Could not set up socket

listener\n");

6.到现在,你的服务器除了等待来自客户端的连接请求外基本上什么也没有做.一旦一个客户端的连接被收到,socket_accept()函数便开始起作用了,它接收连接请求并调用另一个子Socket来处理客户端–服务器间的信息.

[Copy to clipboard]

PHP CODE:

//接受请求链接

// 调用子socket 处理信息

$spawn = socket_accept($socket) or die("Could not accept incoming

connection\n");

这个子socket现在就可以被随后的客户端–服务器通信所用了.

7.当一个连接被建立后,服务器就会等待客户端发送一些输入信息,这写信息可以由socket_read()函数来获得,并把它赋值给PHP的$input变量.

[Copy to clipboard]

PHP CODE:

// 读取客户端输入

$input = socket_read($spawn, 1024) or die("Could not read input\n");

?&gt;

socker_read的第而个参数用以指定读入的字节数,你可以通过它来限制从客户端获取数据的大小.

注意:socket_read函数会一直读取壳户端数据,直到遇见\n,\t或者\0字符.PHP脚本把这写字符看做是输入的结束符.

8.现在服务器必须处理这些由客户端发来是数据(在这个例子中的处理仅仅包含数据的输入和回传到客户端).这部分可以由socket_write()函数来完成(使得由通信socket发回一个数据流到客户端成为可能)

[Copy to clipboard]

PHP CODE:

// 处理客户端输入并返回数据

$output = strrev($input) . "\n";

socket_write($spawn, $output, strlen ($output)) or die("Could not write

output\n");

9.一旦输出被返回到客户端,父/子socket都应通过socket_close()函数来终止

[Copy to clipboard]

PHP CODE:

// 关闭 sockets

socket_close($spawn);

socket_close($socket);

PHP——Curl模拟POST请求及接受外部请求例子

今天在项目中用到了curl,最近几年不常用记忆有些模糊了,记录一下吧方便以后查看。


在php中要模拟post请求数据提交我们会使用到curl函数。

<?php$uri = "http://blog.leokim.cn/test.php";// 参数数组$data = array (        'name' => 'tanteng'// 'password' => 'password');
 
$ch = curl_init ();// print_r($ch);curl_setopt ( $ch, CURLOPT_URL, $uri );
curl_setopt ( $ch, CURLOPT_POST, 1 );
curl_setopt ( $ch, CURLOPT_HEADER, 0 );
curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt ( $ch, CURLOPT_POSTFIELDS, $data );
$return = curl_exec ( $ch );
curl_close ( $ch );
 
print_r($return);?>


接受php页面远程服务器:

<?phpif(isset($_POST['name'])){    if(!empty($_POST['name'])){        echo '您好,',$_POST['name'].'!';
    }
}?>

模拟POST请求 提交数据或上传文件 :

<?phpfunction execUpload(){
$file = '/doucment/Readme.txt';
$ch = curl_init();
$post_data = array(	'loginfield' => 'username',	'username' => 'ybb',	'password' => '123456','file' => '@d:usrwwwtranslatedocumentReadme.txt');
curl_setopt($ch, CURLOPT_HEADER, false);//启用时会发送一个常规的POST请求,类型为:application/x-www-form-urlencoded,就像表单提交的一样。curl_setopt($ch, CURLOPT_POST, true); 
curl_setopt($ch,CURLOPT_BINARYTRANSFER,true);
curl_setopt($ch, CURLOPT_POSTFIELDS,$post_data);
curl_setopt($ch, CURLOPT_URL, 'http://blog.leokim.cn/handleUpload.php');
$info= curl_exec($ch);
curl_close($ch);
print_r($info);
}2.http://www.b.com/handleUpload.phpfunction handleUpload(){
print_r($_POST);echo '===file upload info:';
print_r($_FILES);
}?><p>
<br/></p>

附curl函数简单介绍:

■curl_close — 关闭一个cURL会话

■curl_copy_handle — 复制一个cURL句柄和它的所有选项

■curl_errno — 返回最后一次的错误号

■curl_error — 返回一个保护当前会话最近一次错误的字符串

■curl_exec — 执行一个cURL会话

■curl_getinfo — 获取一个cURL连接资源句柄的信息

■curl_init — 初始化一个cURL会话

■curl_multi_add_handle — 向curl批处理会话中添加单独的curl句柄

■curl_multi_close — 关闭一组cURL句柄

■curl_multi_exec — 运行当前 cURL 句柄的子连接

■curl_multi_getcontent — 如果设置了CURLOPT_RETURNTRANSFER,则返回获取的输出的文本流

■curl_multi_info_read — 获取当前解析的cURL的相关传输信息

■curl_multi_init — 返回一个新cURL批处理句柄

■curl_multi_remove_handle — 移除curl批处理句柄资源中的某个句柄资源

■curl_multi_select — 等待所有cURL批处理中的活动连接

■curl_setopt_array — 为cURL传输会话批量设置选项■curl_close — 关闭一个cURL会话

■curl_copy_handle — 复制一个cURL句柄和它的所有选项

■curl_errno — 返回最后一次的错误号

■curl_error — 返回一个保护当前会话最近一次错误的字符串

■curl_exec — 执行一个cURL会话

■curl_getinfo — 获取一个cURL连接资源句柄的信息

■curl_init — 初始化一个cURL会话

■curl_multi_add_handle — 向curl批处理会话中添加单独的curl句柄

■curl_multi_close — 关闭一组cURL句柄

■curl_multi_exec — 运行当前 cURL 句柄的子连接

■curl_multi_getcontent — 如果设置了CURLOPT_RETURNTRANSFER,则返回获取的输出的文本流

■curl_multi_info_read — 获取当前解析的cURL的相关传输信息

■curl_multi_init — 返回一个新cURL批处理句柄

■curl_multi_remove_handle — 移除curl批处理句柄资源中的某个句柄资源

■curl_multi_select — 等待所有cURL批处理中的活动连接

■curl_setopt_array — 为cURL传输会话批量设置选项

■curl_setopt — 设置一个cURL传输选项

■curl_version — 获取cURL版本信息

■curl_setopt — 设置一个cURL传输选项

■curl_version — 获取cURL版本信息