GO和PHP可以在一起愉快的玩耍了

PHP,是一种被广泛应用的开源通用脚本语言,尤其适用于 Web 开发并可嵌入 HTML 中去,而Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易.在此我想给PHPer提供另外一种方式来学习go,这样不但学习了新语言go,同时对php的内部机制也会有深入的了解。

1:PHP环境搭建

首先,我们来生成PHP的动态库,这是学习的基础环境这一,只要在平时我们编译PHP的时候,加上如下参数即可

--enable-embed

这样就会在php的安装目录(即–prefix的目录,默认应该在/usr/local/php)下的lib目录看到一个libphp5.so或libphp7.so文件,具体那个文件取决于你编译的php版本。

生成动态库后,为了方便程序引用动态库,我们需要修改下库文件的配置。/etc/ld.so.conf 此文件记录了编译时使用的动态库的路径,也就是加载so库的路径,默认情况下,编译器只会使用/lib和/usr/lib这两个目录下的库文件,我们执行如下命令即可。

echo "/usr/local/php/lib" > /etc/ld.so.conf.d/php.conf
ldconfig

ldconfig这个程序它的作用是将文件/etc/ld.so.conf列出的路径下的库文件缓存到/etc/ld.so.cache以供使用,因此当安装完一些库文件,或者修改/etc/ld.so.conf增加了库的新的搜索路径,需要运行一下ldconfig,使所有的库文件都被缓存到文件/etc/ld.so.cache中,如果没做,即使修改了配置文件可能也会找不到刚安装的库。

2:GO环境搭建

go的环境没有什么特殊之处,只要下载相应版本,配置好PATH和GOROOT环境变量即可,在此不做过多说明.

3:Cphp

PHP_EMBED_START_BLOCK 表示PHP嵌入代码块开始

PHP_EMBED_END_BLOCK    表示PHP嵌入代码块结束

zend_eval_string   执行变量内的PHP代码

php_execute_script      执行PHP脚本文件

3.1执行php代码变量

demo1.c代码如下

#include "sapi/embed/php_embed.h"
 
int main(int argc, char * argv[]){
    PHP_EMBED_START_BLOCK(argc,argv);
    char * script = " echo \"Hello World!\n\";";
    zend_eval_string(script, NULL,"Simple Hello World App" TSRMLS_CC);
    PHP_EMBED_END_BLOCK();
    return 0;
}

编译文件

gcc -I /usr/local/php/include/php/ -I /usr/local/php/include/php/main/ -I /usr/local/php/include/php/Zend/ -I /usr/local/php/include/php/TSRM/  -lphp7 -o demo1 demo1.c 

gcc 是编译器,一般linux系统都自带了,不用安装。-I参数指定包含头文件的搜索路径,要不然找不到php的头文件,-l指定库文件,-o指定编译文件和目标文件。

运行目标文件

./demo1

 输出如下内容

Hello World!

3.2执行php代码文件

demo2.c代码如下

#include <stdio.h>
#include <sapi/embed/php_embed.h>
int main(int argc, char *argv[]) {
    zend_file_handle    script;
    if ( argc <= 1 ) {
        fprintf(stderr, "Usage: %s <filename.php> <arguments>\n", argv[0]);
        return -1;
    }
    

    script.type             = ZEND_HANDLE_FP;  
    script.filename         = argv[1];  
    script.opened_path      = NULL;  
    script.free_filename    = 0;  

    if ( !(script.handle.fp = fopen(script.filename, "rb")) ) {
        fprintf(stderr, "Unable to open: %s\n", argv[1]);
        return -1;
    }
    argc --;
    argv ++;
    PHP_EMBED_START_BLOCK(argc, argv)
        php_execute_script(&script TSRMLS_CC);
    PHP_EMBED_END_BLOCK()
    return 0;
}

编译文件

gcc -I /usr/local/php/include/php/ -I /usr/local/php/include/php/main/ -I /usr/local/php/include/php/Zend/ -I /usr/local/php/include/php/TSRM/  -lphp7 -o demo2 demo2.c 

 运行目标文件

./demo2 test.php

test.php文件代码可以任意,比如”<?php echo ‘Hello World’; “,最终后输出”Hello World”。

4:CGO

在很多场景下,在Go的程序中需要调用c函数或者是用c编写的库那么该如何调用呢?Go可是更好的C语言啊,当然提供了和c语言交互的功能,称为Cgo,Cgo封装了#cgo伪c文法,参数CFLAGS用来传入编译选项,LDFLAGS来传入链接选项。这个用来调用非c标准的第三方c库。

示例代码如下,大家一看即懂。

package main

/*
#include <stdio.h>

void hi() {
    printf("hello world!\n");
}
*/
import "C" //这里可看作封装的伪包C, 这条语句要紧挨着上面的注释块,不可在它俩之间间隔空行!

func main() {
        C.hi()
}

5:GO和PHP愉快的玩耍

package main

/*
#cgo CFLAGS: -I/usr/local/php5/include/php
#cgo CFLAGS: -I/usr/local/php5/include/php/main
#cgo CFLAGS: -I/usr/local/php5/include/php/TSRM
#cgo CFLAGS: -I/usr/local/php5/include/php/Zend
#cgo LDFLAGS: -lphp5 -L/usr/local/php5/lib/

#include "sapi/embed/php_embed.h"

void eval_str() {
    int argc = 1;
    char * argv[] = {"test"};
    PHP_EMBED_START_BLOCK(argc,argv);
    char * script = " echo \"Hello World!\n\";";
    zend_eval_string(script, NULL,"Simple Hello World App" TSRMLS_CC);
    PHP_EMBED_END_BLOCK();
}
*/
import "C"

func main() {
        C.eval_str()
}

这篇我们就可以让go与php愉快的玩耍了,是不是很好玩呀?

这篇文章只是抛砖引玉,更多玩耍方式及其应用还需要大家的研究,在此列举以下资料供大家参考

1:PHP混合Go协程并发

2:golang调用php7

发表在 好文推荐, 网站开发 | 标签为 | 留下评论

golang实现的一个不关注响应的http代理服务

假设有一个这样的场景

1:调用方不关注接口响应内容

2:接口响应比较慢,会阻塞服务

    典型的场景可能就是注册账号的时候邮件发送了,这种情况下一般都不会等邮件是否发送成功,因为都会提供一个重新发送的功能,而由于发送邮件一般比较耗时,所以一般都做类似任务队列的形式来实现,而现在我们就在真实接口的上游做一个有点任务队列功能的http代理服务

这个服务主要需要考虑以下几点

1:限流,限制向下游的并发以防止将下游打跨

2:队列长度,防止自己无限接受请求,把自己搞死

代码示例如下:

package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var (
	ProjectName     string           = "proxy"                   //项目名称
	Version         string           = "0.1"                     //版本
	Port            int              = 1215                      //监听端口
	Concurrency     int              = 200                       //向下游的并发数
	ConcurrencyChan chan int                                     //控制下游的并发chan
	Timeout         int              = 2000                      //下游超时时间
	BufferSize      int              = 10000                     //任务队列最大长度,超过则丢弃
	RequestContent  chan interface{}                             //任务队列chan
	Upstream        string           = "http://www.xtgxiso.com" //下游地址
)

//任务结构,代表一个请求
type Tast struct {
	URL    string
	Method string
	Header interface{}
	Body   []byte
}

func main() {
	viper.AutomaticEnv()
	var rootCmd = &cobra.Command{
		Use:     ProjectName,
		Short:   fmt.Sprintf("%s %s", ProjectName, Version),
		Run:     run,
		PreRunE: preRunE,
	}
	rootCmd.Flags().Int("port", 1215, "Listen port")
	viper.BindPFlag("port", rootCmd.Flags().Lookup("port"))
	rootCmd.Flags().Int("concurrency", 200, "Concurrency")
	viper.BindPFlag("concurrency", rootCmd.Flags().Lookup("concurrency"))
	rootCmd.Flags().Int("timeout", 2000, "timeout")
	viper.BindPFlag("timeout", rootCmd.Flags().Lookup("timeout"))
	rootCmd.Flags().Int("buffersize", 10000, "buffersize")
	viper.BindPFlag("buffersize", rootCmd.Flags().Lookup("buffersize"))
	rootCmd.Flags().String("upstream", "http://www.xtgxiso.com", "upstream")
	viper.BindPFlag("upstream", rootCmd.Flags().Lookup("upstream"))
	rootCmd.Execute()
}

func run(cmd *cobra.Command, args []string) {
	r := gin.Default()
	r.NoRoute(product)
	fmt.Printf("%s %s Running on %d\n", ProjectName, Version, Port)
	fmt.Printf("Concurrency:%d \n", Concurrency)
	fmt.Printf("Timeout:%d \n", Timeout)
	fmt.Printf("BufferSize:%d \n", BufferSize)
	fmt.Printf("Upstream:%s \n", Upstream)
	r.Run(fmt.Sprintf(":%d", Port))

}

func preRunE(cmd *cobra.Command, args []string) error {
	Port = viper.GetInt("port")
	Concurrency = viper.GetInt("concurrency")
	Timeout = viper.GetInt("timeout")
	BufferSize = viper.GetInt("bufferSize")
	Upstream = viper.GetString("upstream")
	RequestContent = make(chan interface{}, BufferSize)
	go consume()
	ConcurrencyChan = make(chan int, Concurrency)
	return nil
}

//任务生产者
func product(c *gin.Context) {
	body, _ := ioutil.ReadAll(c.Request.Body)
	s := Tast{
		URL:    Upstream + c.Request.URL.RequestURI(),
		Method: string(c.Request.Method),
		Header: c.Request.Header,
		Body:   body,
	}
	select {
	case RequestContent <- s:
		c.String(http.StatusOK, "OK")
	case <-time.After(time.Millisecond * 10):
		c.String(http.StatusOK, "timeout")
	}
}

//任务消费者
func consume() {
	for {
		select {
		case ConcurrencyChan <- 1:
			r := <-RequestContent
			go request(r)
		case <-time.After(time.Millisecond * 1000):
			fmt.Printf("写入ConcurrencyChan超时")
		}
	}
}

//向下游发请求
func request(r interface{}) {
	c := r.(Tast)
	remote, _ := url.Parse(c.URL)
	Body := ioutil.NopCloser(bytes.NewReader(c.Body))
	req := &http.Request{
		URL:    remote,
		Method: c.Method,
		Header: c.Header.(http.Header),
		Body:   Body,
	}
	client := &http.Client{
		Timeout: time.Duration(Timeout) * time.Millisecond,
	}
	client.Do(req)
	/*调试用,正常不用等请求
	var (
		resp *http.Response
		err  error
		body []byte
	)
	resp, err = client.Do(req)

	if err != nil {
		fmt.Println(err)
	}
	defer resp.Body.Close()
	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(string(body))
	*/
	<-ConcurrencyChan
}

在这个项目中,涉及到如下知识点,大家可以搜索相关键词来学习

1:通过chan来控制并发数

2 :github.com/gin-gonic/gin  一个不错的http api 框架开发库

3:github.com/spf13/viper    一个不错的应用配置库,支持多种格式配置文件,环境变量读取,命令行参数读取等功能

4:github.com/spf13/cobra   一个用来创建强大的现代CLI命令行的golang库,也是一个生成程序应用和命令行文件的程序

发表在 网站开发 | 标签为 | 留下评论

php下foreach引用的坑

下面一段代码,大家猜测一下会输出什么结果?

<?php

$list = array(1,2,3);

// step1
foreach($list as $key => &$value) {
  var_dump($value);
}

echo "<br/>\n";

// step2
foreach($list as $key => $value) {
  var_dump($value);
}

大家执行后会发现,step2输出的结果不对,原因可以查看http://php.net/manual/zh/control-structures.foreach.php

总结 : 解决方法有如下几种

1:在step1后,unset($value),移除$value的指向

2:在step2中遍历时,同样使用&引用

3:在step2中遍历时,修改$value的变量名称,只要和step1的不一样即可

发表在 网站开发 | 标签为 | 留下评论

php中header方法的注意事项

Header方法的注意事项

1:location跳转之后务必用”exit”退出,避免继续执行引发错误

2:要求header前没有任何输出

发表在 网站开发 | 标签为 , | 留下评论

php7.1问题总结

升级到php7.1相对来说会遇到一些坑,这里简单总结下:

1:需要将session.sid_length设置为40,否则登陆可能会有问题

2:如果自定义了session,其中用到了session_decode,可能会session_decode失败,请用如下方法替换:

class XtgxisoSession {
    public static function unserialize($session_data) {
        $method = ini_get("session.serialize_handler");
        switch ($method) {
            case "php":
                return self::unserialize_php($session_data);
                break;
            case "php_binary":
                return self::unserialize_phpbinary($session_data);
                break;
            default:
                throw new Exception("Unsupported session.serialize_handler: " . $method . ". Supported: php, php_binary");
        }
    }

    private static function unserialize_php($session_data) {
        $return_data = array();
        $offset = 0;
        while ($offset < strlen($session_data)) {
            if (!strstr(substr($session_data, $offset), "|")) {
                throw new Exception("invalid data, remaining: " . substr($session_data, $offset));
            }
            $pos = strpos($session_data, "|", $offset);
            $num = $pos - $offset;
            $varname = substr($session_data, $offset, $num);
            $offset += $num + 1;
            $data = unserialize(substr($session_data, $offset));
            $return_data[$varname] = $data;
            $offset += strlen(serialize($data));
        }
        return $return_data;
    }

    private static function unserialize_phpbinary($session_data) {
        $return_data = array();
        $offset = 0;
        while ($offset < strlen($session_data)) {
            $num = ord($session_data[$offset]);
            $offset += 1;
            $varname = substr($session_data, $offset, $num);
            $offset += $num;
            $data = unserialize(substr($session_data, $offset));
            $return_data[$varname] = $data;
            $offset += strlen(serialize($data));
        }
        return $return_data;
    }
}

发表在 好文推荐, 网站开发, 网站架构 | 标签为 | 评论关闭

Facebook的hhvm的安装和使用

HHVM是Facebook推出的用来执行PHP代码的虚拟机,它是一个PHP的JIT(Just-In-Time)编译器,同时具有产生快速代码和即时编译的优点.

首先我们在centos7下安装好hhvm的环境

yum update -y

yum install -y epel-release

yum install cpp gcc-c++ cmake3 git psmisc {binutils,boost,jemalloc,numactl}-devel \
{ImageMagick,sqlite,tbb,bzip2,openldap,readline,elfutils-libelf,gmp,lz4,pcre}-devel \
lib{xslt,event,yaml,vpx,png,zip,icu,mcrypt,memcached,cap,dwarf}-devel \
{unixODBC,expat,mariadb}-devel lib{edit,curl,xml2,xslt}-devel \
glog-devel oniguruma-devel ocaml gperf enca libjpeg-turbo-devel openssl-devel \
mariadb mariadb-server {fastlz,double-conversion,re2}-devel make -y

yum install {fribidi,libc-client,glib2}-devel -y

git clone https://github.com/facebook/hhvm -b master  hhvm  --recursive

cd hhvm

cmake3 .

make -j$(($(nproc)+1))

./hphp/hhvm/hhvm --version

make install

hhvm --version

增加hhvm的配置文件

echo "date.timezone="America/New_York"" > /etc/hhvm/php.ini

/etc/hhvm/server.ini文件内容如下

pid = /var/run/hhvm/pid

hhvm.server.ip = 0.0.0.0
hhvm.server.port = 1215
hhvm.server.type = proxygen
hhvm.server.default_document = index.php
hhvm.server.source_root = /alidata/xtgxiso/hhvm
hhvm.server.thread_count = 2
hhvm.resource_limit.max_socket = 65536
hhvm.hack.lang.look_for_typechecker = 0

hhvm.jit = true
hhvm.jit_a_size = 67108864
hhvm.jit_a_stubs_size = 22554432
hhvm.jit_global_data_size = 22554432

;logging
hhvm.log.use_syslog = false
hhvm.log.use_log_file = true
hhvm.log.file = /var/log/hhvm/error.log
hhvm.log.level = Warning
hhvm.log.always_log_unhandled_exceptions = true
hhvm.log.runtime_error_reporting_level = 8191
hhvm.repo.central.path = /var/cache/hhvm/hhvm.hhbc

hhvm.php7.all = 1

通过以上命令,我们基本完成了hhvm的安装和配置,接下来我们将hhvm做成centos下的系统服务。

touch /usr/lib/systemd/system/hhvm.service

文件内容如下:

[Unit]
Description=HipHop Virtual Machine

[Service]
ExecStart=/usr/local/bin/hhvm -c /etc/hhvm/server.ini -c /etc/hhvm/php.ini --user hhvm --mode daemon

[Install]
WantedBy=multi-user.target

然后我们就可以通过如下命令来测试服务是否配置成功

systemctl status hhvm
systemctl stop hhvm
systemctl start hhvm
systemctl restart hhvm

最后我们服务启动后,在/alidata/xtgxiso/hhvm/新建个index.php文件来测试是否成功.

<?php
echo phpinfo();

测试访问地址是:http://127.0.0.1:1215/index.php,如果能访问成功,说明安装并配置成功了.

发表在 好文推荐, 网站开发 | 标签为 | 26 条评论

golang多核的使用

对于多核编程,go是天生支持,那么我们在什么情况下应该用多核心来加速程序,而在什么情况下用单核即可呢?

现在我们用一简单的程序来说明下:

package main

import (
        "runtime"
        "fmt"
        "sync"
        "database/sql"
        _ "github.com/go-sql-driver/mysql"
	"time"
)

//定义任务队列
var waitgroup sync.WaitGroup

func xtgxiso(num int) {
        //fmt.Println(num)
        db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8")
        if err != nil {
                fmt.Println(err)
        }
        defer db.Close()
        rows, err := db.Query("select sleep(1) as a")
        if err != nil {
                fmt.Println(err)
        }
        defer rows.Close()
        var a string
        for rows.Next() {
                err = rows.Scan(&a)
                if err != nil {
                        fmt.Println(err)
                } else {
                        //fmt.Println(a)
                }
        }
        waitgroup.Done() //任务完成,将任务队列中的任务数量-1,其实.Done就是.Add(-1)
}

func main() {
	//记录开始时间
	start := time.Now()
        //设置最大的可同时使用线程数
        runtime.GOMAXPROCS(1)
        for i := 1; i <= 10; i++ {
                waitgroup.Add(1) //每创建一个goroutine,就把任务队列中任务的数量+1
                go xtgxiso(i)
        }
        waitgroup.Wait() //Wait()这里会发生阻塞,直到队列中所有的任务结束就会解除阻塞
	//记录结束时间
	end :=  time.Now()
	//输出执行时间,单位为秒。
	fmt.Println(end.Sub(start).Seconds())
}

这个程序是执行十次”select sleep(1) as a“.如果是顺序阻塞执行的话,执行时间肯定是10s以上,而我们用的协程不会有这种情况。

我们可以修改“runtime.GOMAXPROCS(1)”来设置最大可同时执行的线程数,对比结果发现,都是1s多点,有时多线程反而会比单线程慢些,这是为什么呢?

这是因为这个程序是IO为主的,启用多线程反而有上下文切换,所以对于以涉及IO操作的主的程序启用多线程对于加速程序意义不大,

runtime.GOMAXPROCS(1) 保证了mysql的操作(golang底层走的是非阻塞io)都在一个线程上执行,也就没有了cpu在多个线程之间来回调度

那么什么程序启用多线程呢?我们来看如下程序:

package main

import (
        "runtime"
        "fmt"
        "sync"
	"time"
)

//定义任务队列
var waitgroup sync.WaitGroup

func xtgxiso(num int) {
      	for i:=1;i<=1000000000;i++{
		num = num+i
		num = num-i
		num = num*i
		num = num/i
	}
        waitgroup.Done() //任务完成,将任务队列中的任务数量-1,其实.Done就是.Add(-1)
}

func main() {
	//记录开始时间
	start := time.Now()
        //设置最大的可同时使用的线程数
        runtime.GOMAXPROCS(1)
        for i := 1; i <= 10; i++ {
                waitgroup.Add(1) //每创建一个goroutine,就把任务队列中任务的数量+1
                go xtgxiso(i)
        }
        waitgroup.Wait() //Wait()这里会发生阻塞,直到队列中所有的任务结束就会解除阻塞 线//记录结束时间
	end :=  time.Now()
	//输出执行时间,单位为秒。
	fmt.Println(end.Sub(start).Seconds())
}

对比结果发现,多线程比单线程快,所以对于CPU的运行上,多线程运行加速效果是很明显的.

发表在 网站开发 | 标签为 | 27 条评论

使用sync.WaitGroup来同步golang程序

golang中有2种方式同步程序,一种使用channel,另一种就是sync.WaitGroup,今天我们介绍比较简单的sync.WaitGroup。

sync.WaitGroup只有3个方法,Add(),Done(),Wait()。其中Done()是Add(-1)的别名。简单的来说,使用Add()添加计数,Done()减掉一个计数,计数不为0, 阻塞Wait()的运行。
要注意的有一点。sync文档已经说明了的,在运行main函数的goroutine里运行Add()函数,在其他的goroutine里面运行Done()函数。

代码示例如下:

package main

import (
	"runtime"
	"time"
	"fmt"
	"sync"
)

//定义任务队列
var waitgroup sync.WaitGroup

func xtgxiso(num int) {
	fmt.Println(num)
	time.Sleep(1*time.Second)
	waitgroup.Done() //任务完成,将任务队列中的任务数量-1,其实.Done就是.Add(-1)
}

func main() {
	//设置最大的可同时使用的CPU核数和实际cpu核数一致
	runtime.GOMAXPROCS(runtime.NumCPU())
	for i := 0; i < 10; i++ {
		waitgroup.Add(1) //每创建一个goroutine,就把任务队列中任务的数量+1
		go xtgxiso(i)
	}
	waitgroup.Wait() //Wait()这里会发生阻塞,直到队列中所有的任务结束就会解除阻塞
	fmt.Println("finish")
}

这样我们就完成了一个简单的程序,用runtime.GOMAXPROCS(runtime.NumCPU())来控制最大协程数量,用waitgroup.Wait()来等待所有子协程完成后主协程才退出!

发表在 网站开发, 网站架构 | 标签为 , | 26 条评论

php调用go的json协议的rpc服务

我们用php代码来调用go的json协议的rpc服务。

go的服务代码:

// json_rpc_server project main.go
package main

import (
	"net/rpc"
	"net"
	"log"
	"net/rpc/jsonrpc"
)

//自己的数据类
type MyMath struct{
	
}

//加法--只能两个参数
func (mm *MyMath) Add(num map[string]float64,reply *float64) error {
    *reply = num["num1"] + num["num2"]
    return nil
}

//减法--只能两个参数
func (mm *MyMath) Sub(num map[string]string,reply *string) error {
    *reply = num["num1"] + num["num2"]
    return nil
}

func main() {
	//注册MyMath类,以代客户端调用
    rpc.Register(new(MyMath))
    listener, err := net.Listen("tcp", ":1215")
    if err != nil {
        log.Fatal("listen error:", err)
    }
    for {
        conn, err := listener.Accept()
        if err != nil {
            continue
        }
		//新协程来处理--json
        go jsonrpc.ServeConn(conn)
    }
}

php调用代码:

<?php
class JsonRPC
{
    private $conn;

    function __construct($host, $port) {
        $this->conn = fsockopen($host, $port, $errno, $errstr, 3);
        if (!$this->conn) {
            return false;
        }
    }

    public function Call($method, $params) {
        if ( !$this->conn ) {
            return false;
        }
        $err = fwrite($this->conn, json_encode(array(
                'method' => $method,
                'params' => array($params),
                'id'     => 0,
            ))."\n");
        if ($err === false)
            return false;
        stream_set_timeout($this->conn, 0, 3000);
        $line = fgets($this->conn);
        if ($line === false) {
            return NULL;
        }
        return json_decode($line,true);
    }
}

$client = new JsonRPC("127.0.0.1", 1215);
$r = $client->Call("MyMath.Add",array('num1'=>1,'num2'=>2));
var_export($r);
echo "<br/>";
$r = $client->Call("MyMath.Sub",array('num1'=>'1','num2'=>'2'));
var_dump($r);

这样我们就可以在php与go之间调用了

发表在 网站开发 | 标签为 , , | 25 条评论

golang利用反射调用类方法续

上一篇只是动态调用方法,这次我们得用反射动态获取方法和每个方法的参数.代码举例如下:

package main

import (
	"fmt"
	"reflect"
	"strconv"
)

type MyMath struct{
	a int
}


func (mm *MyMath) Add(num1 float64,num2 float64 ) float64 {
    reply := num1+num2 
	return reply
}


func (mm *MyMath) Sub(num1 float64,num2 float64 ) float64 {
    reply := num1-num2 
	return reply
}


func main() {
	m := new(MyMath)
	typ := reflect.TypeOf(m)
	//遍历方法
	for i := 0; i < typ.NumMethod(); i++ {
		method := typ.Method(i)
		mname := method.Name//方法名字
		fmt.Println("method:"+mname)
		fun := reflect.ValueOf(m).MethodByName(mname)
		ty := method.Type
		args := make([]reflect.Value, ty.NumIn()-1)
		//遍历参数
		for j:= 1; j< ty.NumIn(); j++ {
			//参数类型
			arg_type := ty.In(j).Kind().String()
			fmt.Println("参数" + strconv.Itoa(j) + ":" + arg_type)
			switch arg_type {
				case "float64":
					args[j-1] = reflect.ValueOf(float64(j))
				default:
					fmt.Println("i down'n knowon type " + arg_type)
				
			}
		}
		ret := fun.Call(args)
		fmt.Println(ret[0])
	}
	
}

这样,我们以后就可以知道一个类下有多少个方法,每个方法下有多少个参数且类型是什么,方便我们与其他弱语言相互调用的时候来处理类型!

发表在 网站开发 | 标签为 | 23 条评论