前言

本项目基于workerman实现支持websocket、tcp协议的PHP框架,mvc模式,跟传统http框架运行原理相差不多,只是入口依赖于workerman接收到的消息
项目源码可以移步github参考,欢迎star
https://github.com/feixuekeji/flysocket

入口

入口文件是GatewayWorker下Event.php
入口处需要引入下自动加载函数,以便框架里函数调用
require_once DIR . ‘/…/lib/Autoloader.php’;
主要有两个函数
onWorkerStart 项目启动时初始化框架
onMessage 接受到客户端信息,收到消息转发给框架执行请求,相当于收到http请求信息

require_once __DIR__ . '/../lib/Autoloader.php';/*** 进程启动后初始化*/public static function onWorkerStart($worker){// 执行应用并响应Container::get('app')->init($worker->id);}/**/*** 当客户端发来消息时触发* @param int $client_id 连接id* @param mixed $message 具体消息*/public static function onMessage($client_id, $message){if ($message == 'ping')return;App::run($client_id, $message);}

app

app主要包含框架初始化跟执行两个功能
初始化init函数主要功能包括
路由加载,Session,数据库等初始化

运行函数run 负责收到请求后具体执行,
解析请求数据
组装request请求对象
路由分发执行
将执行结果返回客户端

App.php

public function __construct(){$this->rootPath = __DIR__ . DIRECTORY_SEPARATOR . '../';$this->routePath   = $this->rootPath . 'route' . DIRECTORY_SEPARATOR;$this->configPath  = $this->rootPath . 'config' . DIRECTORY_SEPARATOR;$this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;}public function run($client_id, $message){try {$message = json_decode($message,true);!is_array($message) && $message = [];$request = Container::get('request',[$message]);$res = Route::dispatch($request);$response = $request->response($res['data'],$res['code'],$res['msg']);} catch (\Throwable $e) {Error::exception($e);$response = $request->response('',$e->getCode() ?: 1,$e->getMessage());}
//        Log::info('response',$response);// 向当前client_id发送数据Gateway::sendToClient($client_id, json_encode($response));//清空requestContainer::remove('request');}/***初始化* @author xingxiong.fei@163.com* @date 2020-09-03 9:43*/public function init($workerId){try {$log = Container::get('log',[Config::get('','log')]);$cache = Container::get('cache',[Config::get('','cache')]);Container::get('session',[Config::get('','session')]);//加载路由Container::get('route')->import();//数据库初始化Db::setConfig(Config::get('','database'));Db::setCache($cache);Db::setLog($log);$workerId == 0 && $this->corn();} catch (\Exception $e) {Error::exception($e);}}

自动加载

spl_autoload_register(‘\lib\Autoloader::load’); 注册自动加载函数,程序执行时如果该文件没有加载会调用自动加载函数进行文件引入。

采用命名空间方式定义和自动加载类库文件,遵循psr-0规范,只需要给类库正确定义所在的命名空间,并且命名空间的路径与类库文件的目录一致,那么就可以实现类的自动加载
命名空间跟文件路径一致,只需将类名中的斜杠转成反斜杠就是文件所在路径,如果有更多规则,可具体判断加载

如Admin.php的命名空间为 namespace application\admin\controller;则加载的文件路径为application/admin/controller/Admin.php

Autoloader.php

namespace lib;/*** 自动加载*/
class Autoloader
{public static function load($className){$classPath = str_replace('\\', '/', $className);$classFile = __DIR__ .'/../'.$classPath.'.php';if (is_file($classFile)) {require_once($classFile);if (class_exists($className, false)) {return true;}}return false;}
}spl_autoload_register('\lib\Autoloader::load');

路由分发

dispatch函数
根据request请求里的模块、控制器、方法名,拼装出需要调用的类名,
调用目标类
然后使用call_user_func_array调用类中方法执行

import
读取路由配置文件,加载路由表,Request里获取模块名,控制器,方法时用到

Route.php

class Route
{/*** 路由表* @var array*/protected $routeList = [];/*** 路由分发* @param Request $request* @return mixed|void*/public static function dispatch(Request $request){$module = $request->module();$controller = $request->controller();$action = $request->action();if (!$module || !$controller || !$action)throw new \Exception('api  is not exists',100);//将api转换为控制器方法的命名空间$className = '\\application\\' . $module . '\\controller\\' . ucfirst($controller);$obj = new $className($request);if (!method_exists($obj, $action))throw new \Exception('method ' . $action . ' is not exists',100);$res = call_user_func_array(array($obj, $action), array($request));return $res;}/*** desc:导入* author: xxf<waxiongfeifei@gmail.com>* date: 2021/4/22* time: 上午11:04*/public function import(){$path = Container::get('app')->getRoutePath();$files = is_dir($path) ? scandir($path) : [];foreach ($files as $file) {if (strpos($file, '.php')) {$filename = $path . DIRECTORY_SEPARATOR . $file;// 导入路由配置$rules = include $filename;if (is_array($rules)) {$this->routeList = array_merge($this->routeList,$rules);}}}}public function getRoute($api){if (array_key_exists($api, $this->routeList))//获取真实路径$api = $this->routeList[$api];return $api;}
}

请求

主要功能包括请求信息(如IP,端口,参数)的初始化以及参数的获取安全过滤,以供在控制起来使用
本项目里自定义请求信息如下,以json文本形式传输

$param = ['api' => 'my-info',//接口地址'app' => 'iphone',//客户端设备'ver' => '1.0',//版本号'data' => [//具体数据],];

Request.php

    private static $_instance; //存储对象public function __construct(array $options = []){$this->init($options);self::$_instance = $this;}public function init(array $options = []){$this->filter = Config::get('default_filter');$this->param = $options['data'] ?? [];$this->api =  $options['api'] ?? '';$this->route =  $options['api'] ?? '';$this->app =  $options['app'] ?? '';$this->ver =  $options['ver'] ?? '';$this->route = Container::get('route')->getRoute($this->route);$api = explode('/',$this->route);$this->setModule($api[0] ?? '');$this->setController($api[1] ?? '');$this->setAction($api[2] ?? '');$this->ip =  $this->ip();$this->setPort();$this->setGatewayIp();$this->setGatewayPort();$this->setClientId();\lib\facade\Log::info('request',get_object_vars($this));}

配置文件

用于从文件里获取配置信息

Config.php

class Config
{protected static $config;// 加载配置文件static function load($file){if (!isset(self::$config[$file])){$confFile = __DIR__ . '/../config/' . $file .'.php';if (is_file($confFile)){self::$config[$file] = include_once $confFile;}}}/***获取配置参数 为空则获取所有配置* @param null $name* @param string $file* @param null $default* @return |null* @author xingxiong.fei@163.com* @date 2020-08-26 16:22*/public static function get($name = null,$file = 'config', $default = null){self::load($file);// 无参数时获取所有if (empty($name)) {return self::$config[$file];}$name    = explode('.', $name);$name[0] = strtolower($name[0]);$config  = self::$config[$file];// 按.拆分成多维数组进行判断foreach ($name as $val) {if (isset($config[$val])) {$config = $config[$val];} else {return $default;}}return $config;}

redis

Redis.php

class Redis
{private static $_instance; //存储对象private function __construct( ){$config = Config::get('redis');self::$_instance = new \Redis();//从配置读取self::$_instance->connect($config['host'], $config['port']);self::$_instance->select($config['db_index']);if ('' != $config['password']) {self::$_instance->auth($config['password']);}}public static function getInstance( ){if (!self::$_instance) {new self();}else{try {@trigger_error('flag', E_USER_NOTICE);self::$_instance->ping();$error = error_get_last();if($error['message'] != 'flag')throw new \Exception('Redis server went away');} catch (\Exception $e) {// 断线重连new self();}}return self::$_instance;}/*** 禁止clone*/private function __clone(){}/*** 其他方法自动调用* @param $method* @param $args* @return mixed*/public function __call($method,$args){return call_user_func_array([self::$_instance, $method], $args);}/*** 静态调用* @param $method* @param $args* @return mixed*/public static function __callStatic($method,$args){self::getInstance();return call_user_func_array([self::$_instance, $method], $args);}

缓存

缓存遵循psr-16规范,本项目里只支持Redis的实现

Cache.php

/*** 缓存类* Class Cache* @package lib*/
class Cache implements Psr16CacheInterface
{/*** 驱动句柄* @var object*/protected $handler = null;/*** 缓存参数* @var array*/protected $options = ['expire'     => 0,'prefix'     => '','serialize'  => true,];public function __construct($options = []){if (!empty($options)) {$this->options = array_merge($this->options, $options);}}/*** {@inheritdoc}*/public function get($name, $default = null){$this->handler = Redis::getInstance();$key    = $this->getCacheKey($name);$value = $this->handler->get($key);if (is_null($value) || false === $value) {return $default;}return $this->unserialize($value);}/*** 写入缓存* @access public* @param  string            $name 缓存变量名* @param  mixed             $value  存储数据* @param  integer|\DateTime $expire  有效时间(秒)* @return boolean* @throws \Psr\SimpleCache\InvalidArgumentException*/public function set($name, $value, $expire = null){$this->handler = Redis::getInstance();if (is_null($expire)) {$expire = $this->options['expire'];}$value = $this->serialize($value);$key    = $this->getCacheKey($name);if ($expire) {$result = $this->handler->setex($key, $expire, $value);} else {$result = $this->handler->set($key, $value);}return $result;}/*** 批量获取* @param iterable $keys* @param null $default* @return array|iterable* @throws \Psr\SimpleCache\InvalidArgumentException*/public function getMultiple($keys, $default = null){if (!\is_array($keys)) {throw new \Exception(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));}$result = [];foreach ($keys as $key) {$result[$key] = $this->get($key);}return $result;}/***批量设置* @param iterable $values* @param null $expire* @return bool* @throws \Exception* @author xxf* @date 2020-08-26 15:37*/public function setMultiple($values, $expire = null){if (!\is_array($values)) {throw new \Exception(sprintf('Cache values must be array or Traversable, "%s" given', \is_object($values) ? \get_class($values) : \gettype($values)));}if (is_null($expire)) {$expire = $this->options['expire'];}try {foreach ($values as $key => $value) {if (\is_int($key)) {$key = (string) $key;}$this->set($key,$value,$expire);}return true;} catch (\Exception $e) {return false;}}/*** 删除缓存* @access public* @param  string $name 缓存变量名* @return boolean*/public function delete($name){$this->handler = Redis::getInstance();$key = $this->getCacheKey($name);try {$this->handler->del($key);return true;} catch (\Exception $e) {return false;}}/*** {@inheritdoc}*/public function deleteMultiple($keys){$this->handler = Redis::getInstance();if (!\is_array($keys)) {throw new Exception(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));}foreach ($keys as &$item){$item = $this->getCacheKey($item);}try {$this->handler->del($keys);return true;} catch (\Exception $e) {return false;}}/*** 清除缓存* @access public* @param  string $tag 标签名* @return boolean*/public function clear(){$this->handler = Redis::getInstance();return $this->handler->flushDB();}/*** 判断缓存* @access public* @param  string $name 缓存变量名* @return bool*/public function has($name){$this->handler = Redis::getInstance();$key = $this->getCacheKey($name);return $this->handler->exists($key);}/*** 序列化数据* @access protected* @param  mixed $data* @return string*/protected function serialize($data){if (is_scalar($data) || !$this->options['serialize']) {return $data;}$data = 'serialize:'.serialize($data);return $data;}/*** 反序列化数据* @access protected* @param  string $data* @return mixed*/protected function unserialize($data){if ($this->options['serialize'] && 0 === strpos($data, 'serialize:')) {return unserialize(substr($data, 10));} else {return $data;}}/*** 获取实际的缓存标识* @access protected* @param  string $name 缓存名* @return string*/protected function getCacheKey($name){return $this->options['prefix'] . $name;}

数据库ORM

ORM自己造轮子有点复杂就使用了ThinkOrm,大多数方法跟tp一致

日志

日志日志遵循PSR-3规范基于Monolog实现

Log.php

class Log implements LoggerInterface
{protected $loggers;/*** 是否允许日志写入* @var bool*/protected $allowWrite = true;protected $config = ['path'        => '',// 日志通道名'channel'        => 'app','level' => 'debug','max_files'   => 0,'file_permission'  => 0666,'sql_level'  => 'info',];// 实例化并传入参数public function __construct($config = []){if (is_array($config)) {$this->config = array_merge($this->config, $config);}if (!empty($config['close'])) {$this->allowWrite = false;}if (empty($this->config['path'])) {$this->config['path'] = \lib\facade\App::getRootPath() . 'runtime/logs' . DIRECTORY_SEPARATOR;} elseif (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) {$this->config['path'] .= DIRECTORY_SEPARATOR;}}private function createLogger($name,$fileName = ''){if (empty($this->loggers[$name.$fileName])) {// 根据业务域名与方法名进行日志名称的确定$channel       = $this->config['channel'];// 日志文件目录$path       = $this->config['path'];// 日志保存时间$maxFiles       = $this->config['max_files'];// 日志等级$level = Logger::toMonologLevel($this->config['level']);// 权限$filePermission =  $this->config['file_permission'];// 创建日志$logger    = new Logger($channel);// 日志文件相关操作$logFileName = empty($fileName) ? $name : $name . '-' .$fileName;$handler   = new RotatingFileHandler("{$path}{$logFileName}.log", $maxFiles, $level, true, $filePermission);// 日志格式$formatter = new LineFormatter("%datetime% %channel%:%level_name% %message% %context% %extra%\n", "Y-m-d H:i:s", false, true);$handler->setFormatter($formatter);$logger->pushHandler($handler);$this->loggers[$name.$fileName] = $logger;}return $this->loggers[$name.$fileName];}/*** 记录日志信息* @access public* @param  mixed  $message       日志信息* @param  string $level      日志级别* @param  array  $context   替换内容* @param string $fileName  文件名* @return $this*/public function record($message, $level = 'info', array $context = [],$fileName = ''){if (!$this->allowWrite) {return;}$logger = $this->createLogger($level,$fileName);$level = Logger::toMonologLevel($level);if (!is_int($level)) $level = Logger::INFO;// $backtrace数组第$idx元素是当前行,第$idx+1元素表示上一层,另外function、class需再往上取一个层次// PHP7 不会包含'call_user_func'与'call_user_func_array',需减少一层if (version_compare(PCRE_VERSION, '7.0.0', '>=')) {$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);$idx       = 0;} else {$backtrace = debug_backtrace();$idx       = 1;}$trace = basename($backtrace[$idx]['file']) . ":" . $backtrace[$idx]['line'];if (!empty($backtrace[$idx + 1]['function'])) {$trace .= '##';$trace .= $backtrace[$idx + 1]['function'];}$message = sprintf('==> LOG: %s -- %s', $message, $trace);return $logger->addRecord($level, $message, $context);}/*** 记录日志信息* @access public* @param  string $level     日志级别* @param  mixed  $message   日志信息* @param  array  $context   替换内容* @return void*/public function log($level, $message, array $context = [], $fileName = ''){if ($level == 'sql')$level = $this->config['sql_level'];$this->record($message, $level, $context, $fileName);}/*** 记录emergency信息* @access public* @param  mixed  $message   日志信息* @param  array  $context   替换内容* @return void*/public function emergency($message, array $context = []){$this->log(__FUNCTION__, $message, $context);}/*** 记录警报信息* @access public* @param  mixed  $message   日志信息* @param  array  $context   替换内容* @return void*/public function alert($message, array $context = []){$this->log(__FUNCTION__, $message, $context);}/*** 记录紧急情况* @access public* @param  mixed  $message   日志信息* @param  array  $context   替换内容* @return void*/public function critical($message, array $context = []){$this->log(__FUNCTION__, $message, $context);}/*** 记录错误信息* @access public* @param  mixed  $message   日志信息* @param  array  $context   替换内容* @return void*/public function error($message, array $context = []){$this->log(__FUNCTION__, $message, $context);}/*** 记录warning信息* @access public* @param  mixed  $message   日志信息* @param  array  $context   替换内容* @return void*/public function warning($message, array $context = []){$this->log(__FUNCTION__, $message, $context);}/*** 记录notice信息* @access public* @param  mixed  $message   日志信息* @param  array  $context   替换内容* * @param string $fileName* @return void*/public function notice($message, array $context = [],$fileName = ''){$this->log(__FUNCTION__, $message, $context, $fileName);}/*** 记录一般信息* @access public* @param  mixed  $message   日志信息* @param  array  $context   替换内容* @param string $fileName* @return void*/public function info($message, array $context = [],$fileName = ''){$this->log(__FUNCTION__, $message, $context, $fileName);}/*** 记录调试信息* @access public* @param  mixed  $message   日志信息* @param  array  $context   替换内容* @return void*/public function debug($message, array $context = []){$this->log(__FUNCTION__, $message, $context);}/*** 记录sql信息* @access public* @param  mixed  $message   日志信息* @param  array  $context   替换内容* @return void*/public function sql($message, array $context = []){$this->log(__FUNCTION__, $message, $context);}

容器

session

主要包括session的读写功能

Session.php

public function __construct(array $config = []){$this->config = $config;}/*** 设置或者获取session作用域(前缀)* @access public* @param  string $prefix* @return string|void*/public function prefix($prefix = ''){if (empty($prefix) && null !== $prefix) {return $this->prefix;} else {$this->prefix = $prefix;}}public static function __make(Config $config){return new static($config->get('','session'));}/*** 配置* @access public* @param  array $config* @return void*/public function setConfig(array $config = []){$this->config = array_merge($this->config, array_change_key_case($config));if (isset($config['prefix'])) {$this->prefix = $config['prefix'];}}/*** session初始化* @access public* @param  array $config* @return void* @throws \think\Exception*/public function init(array $config = []){$config = $config ?: $this->config;if (isset($config['prefix'])) {$this->prefix = $config['prefix'];}$this->init = true;return $this;}/***设置session* @param $name* @param $value* @param null $prefix* @throws \think\Exception* @date 2020-09-10 14:26*/public function set($name, $value, $prefix = null){is_null($this->init) && $this->init();$prefix = !is_null($prefix) ? $prefix : $this->prefix;if (strpos($name, '.')) {// 二维数组赋值list($name1, $name2) = explode('.', $name);if ($prefix) {$_SESSION[$prefix][$name1][$name2] = $value;} else {$_SESSION[$name1][$name2] = $value;}} elseif ($prefix) {$_SESSION[$prefix][$name] = $value;} else {$_SESSION[$name] = $value;}}/***获取* @param string $name* @param null $prefix* @return array|mixed|null* @throws \think\Exception* @date 2020-09-10 14:26*/public function get($name = '', $prefix = null){is_null($this->init) && $this->init();$prefix = !is_null($prefix) ? $prefix : $this->prefix;$value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION;if ('' != $name) {$name = explode('.', $name);foreach ($name as $val) {if (isset($value[$val])) {$value = $value[$val];} else {$value = null;break;}}}return $value;}/***获取后删除* @param $name* @param null $prefix* @return array|mixed|void|null* @throws \think\Exception* @date 2020-09-10 14:27*/public function pull($name, $prefix = null){$result = $this->get($name, $prefix);if ($result) {$this->delete($name, $prefix);return $result;} else {return;}}/***删除* @param $name* @param null $prefix* @throws \think\Exception* @author xingxiong.fei@163.com* @date 2020-09-10 14:24*/public function delete($name, $prefix = null){is_null($this->init) && $this->init();$prefix = !is_null($prefix) ? $prefix : $this->prefix;if (is_array($name)) {foreach ($name as $key) {$this->delete($key, $prefix);}} elseif (strpos($name, '.')) {list($name1, $name2) = explode('.', $name);if ($prefix) {unset($_SESSION[$prefix][$name1][$name2]);} else {unset($_SESSION[$name1][$name2]);}} else {if ($prefix) {unset($_SESSION[$prefix][$name]);} else {unset($_SESSION[$name]);}}}/***清空* @param null $prefix* @throws \think\Exception* @author xingxiong.fei@163.com* @date 2020-09-10 14:25*/public function clear($prefix = null){is_null($this->init) && $this->init();$prefix = !is_null($prefix) ? $prefix : $this->prefix;if ($prefix) {unset($_SESSION[$prefix]);} else {$_SESSION = [];}}/***判断是否存在* @param $name* @param null $prefix* @return bool* @throws \think\Exception* @author xingxiong.fei@163.com* @date 2020-09-10 14:22*/public function has($name, $prefix = null){is_null($this->init) && $this->init();$prefix = !is_null($prefix) ? $prefix : $this->prefix;$value  = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION;$name = explode('.', $name);foreach ($name as $val) {if (!isset($value[$val])) {return false;} else {$value = $value[$val];}}return true;}/***追加到session数组* @param $key* @param $value* @throws \think\Exception* @date 2020-09-10 14:28*/public function push($key, $value){$array = $this->get($key);if (is_null($array)) {$array = [];}$array[] = $value;$this->set($key, $array);}

异常

用来注册异常处理函数,捕捉全局异常并记录错误日志
Error.php

class Error
{/*** 配置参数* @var array*/protected static $exceptionHandler;/*** 注册异常处理* @access public* @return void*/public static function register(){error_reporting(E_ALL);set_error_handler([__CLASS__, 'error']);set_exception_handler([__CLASS__, 'exception']);register_shutdown_function([__CLASS__, 'shutdown']);}/**** @param $e* @author xingxiong.fei@163.com* @date 2020-09-02 17:36*/public static function exception($e){self::report($e);}public static function report(Throwable $exception){$data = ['file'    => $exception->getFile(),'line'    => $exception->getLine(),'message' => $exception->getMessage(),'code'    => $exception->getCode(),];\lib\facade\Log::error('错误信息',$data);\lib\facade\Log::error('错误跟踪',$exception->getTrace());}/*** Error Handler* @access public* @param  integer $errno   错误编号* @param  integer $errstr  详细错误信息* @param  string  $errfile 出错的文件* @param  integer $errline 出错行号* @throws ErrorException*/public static function error($errno, $errstr, $errfile = '', $errline = 0): void{$data = ['file'    => $errfile,'line'    =>$errline,'message' => $errstr,'code'    => $errno,];\lib\facade\Log::error('错误信息',$data);}public static function errorLog(\Error $error): void{$data = ['file'    => $error->getFile(),'line'    => $error->getLine(),'message' => $error->getMessage(),'code'    => $error->getCode(),];\lib\facade\Log::error('错误信息',$data);}/*** Shutdown Handler* @access public*/public static function shutdown(){if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) {self::error($error);}}/*** 确定错误类型是否致命** @access protected* @param  int $type* @return bool*/protected static function isFatal($type){return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]);}

控制器

控制器基类 初始化时依赖注入Request对象,继承该类的控制器可直接使用。

Controlle.php

class Controller
{/*** Request实例* @var \think\Request*/protected $request;/*** 构造方法* @access public*/public function __construct(Request $request){$this->request = Container::get('request');// 控制器初始化$this->initialize();}// 初始化protected function initialize(){}}

使用方法

移步 https://www.kancloud.cn/xiongfeifei/ver1/2131252

从零开始写一个PHP开发框架websocket框架相关推荐

  1. 如何搭建python框架_从零开始:写一个简单的Python框架

    原标题:从零开始:写一个简单的Python框架 Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发. 你为什么想搭建一个Web框架?我想有下面几个原因: 有一个 ...

  2. 从零开始写一个武侠冒险游戏-3-地图生成

    2019独角兽企业重金招聘Python工程师标准>>> 从零开始写一个武侠冒险游戏-3-地图生成 概述 前面两章我们设计了角色的状态, 绘制出了角色, 并且赋予角色动作, 现在是时候 ...

  3. 从零开始写一个武侠冒险游戏-6-用GPU提升性能(1)

    从零开始写一个武侠冒险游戏-6-用GPU提升性能(1) ----把帧动画的实现放在GPU上 作者:FreeBlues 修订记录 2016.06.19 初稿完成. 2016.08.05 增加对 XCod ...

  4. 从零开始写一个武侠冒险游戏-8-用GPU提升性能(3)

    从零开始写一个武侠冒险游戏-8-用GPU提升性能(3) ----解决因绘制雷达图导致的帧速下降问题 作者:FreeBlues 修订记录 2016.06.23 初稿完成. 2016.08.07 增加对 ...

  5. dotnet 从零开始写一个人工智能 从一个神经元开始

    现在小伙伴说的人工智能都是弱智能,可以基于神经网络来做.而神经网络是有多层网络,每一层网络都有多个神经元.那么最简单的神经网络就是只有一层,而这一层只有一个神经元,也就是整个神经网络只是有一个神经元. ...

  6. 从零开始写一个抖音App——Apt代码生成技术、gradle插件开发与protocol协议

    1.讨论--总结前两周评论中有意义的讨论并给予我的解答 2.mvps代码生成原理--将上周的 mvps 架构的代码生成原理进行解析 3.开发一款gradle插件--从 mvps 的代码引出 gradl ...

  7. 从零开始写一个武侠冒险游戏-2-帧动画

    从零开始写一个武侠冒险游戏-2-帧动画 ---- 用基本绘图函数实现帧动画 作者:FreeBlues 修订记录 2016.06.10 初稿完成. 2016.08.03 增加对 XCode 项目文件的说 ...

  8. mysql c测试程序_Linux平台下从零开始写一个C语言访问MySQL的测试程序

    Linux 平台下从零开始写一个 C 语言访问 MySQL 的测试程序 2010-8-20 Hu Dennis Chengdu 前置条件: (1) Linux 已经安装好 mysql 数据库: (2) ...

  9. 用python从零开始写一个注册机(新手也能操作)-前言

    今天开始带领大家从零开始写一个网站的账号注册机,达到批量注册的目的. 涉及到的相关知识包含: python的基本使用 playwright库的相关用法 验证码的识别 欢迎大家关注.

最新文章

  1. 硬盘重装Ubuntu12.04的感受
  2. c++ 多key_详解Zabbix自定义Key监控Rabbitmq(监控特定队列)
  3. 如何完全自定义NavigationBar
  4. 学会用各种方式备份MySQL数据库
  5. hdu5126stars
  6. python的正则表达式 re
  7. 关于WinCE中config.bib的问题
  8. maftools|TCGA肿瘤突变数据的汇总,分析和可视化
  9. 信息学奥赛一本通 1225:金银岛 | OpenJudge NOI 4.6 1797:金银岛
  10. 帮你快速拿Offer!java面向对象程序设计实验报告
  11. 【HIHOCODER 1133】 二分·二分查找之k小数
  12. Graphics.TranslateTransform设置旋转角度不起作用?
  13. atitit.atitit.hb many2one relate hibernate 多对一关联配置..
  14. VFP用Foxjson玩转JSON,超简单的教程
  15. arcgis python 百度网盘 视频_arcgis软件零基础入门视频教程27讲百度网盘链接
  16. 如何理解上下文有关文法(1型)和上下文无关文法(2型)
  17. python问卷星微信登录_Python填写问卷星
  18. 【​观察】POWER9,一个全面开创AI时代的芯片来了!
  19. 鸿蒙车载系统丰田,华为公布三大鸿蒙车载操作系统
  20. 转载:farey(法莱)数列

热门文章

  1. 基于UOS的离线仓库部署
  2. 计算机视觉学习2_局部图像描述子_SIFT算法_特征匹配_集美大学地标
  3. 听某软件开发公司老总讲座有感
  4. 模仿支付宝芝麻信用 绘图 多种因素占比多边形
  5. node 自动上传文件到服务器,利用nodejs监控文件变化并使用sftp上传到服务器
  6. linux 下安装erlang
  7. easyUI中的linkbutton取文本及改变属性的方法
  8. JAVASE 第五式
  9. Parasoft Jtest让单元测试重获青睐
  10. linux虚拟网卡ping不通网关,解决虚拟机或物理机ping不通网关故障的方法与思路...