使用TestFlightApp常见问题备忘录

1、正常情况下,发送邀请邮件给测试人员或者开发人员后,邀请者会收到被邀请人接受的邮件,邮件里含有被邀请人设备的UDID。你在获取到新的UDID后,需要在https://developer.apple.com/account/ios/device/deviceList.action这里把设备先注册好。(不超过100个)。

2、然后需要在https://developer.apple.com/account/ios/profile/profileList.action里某个APP的Devices选中需要参加测试和开发的设备。保存

3、在Xcode里的Organizer重新获取Provisioning Profiles

4、在Testflightapp里如果发现被邀请测试人员不能被加入到APP的Teammates In The Provisioning Profile里,则需要回到第2步,把app更新后的Provisioning Profile下载到本地,(.mobileprovision文件)

5、在Testflightapp里Permissions菜单项中,update profile,并选择刚才下载下来的.mobileprovision文件上传上去,才可以正常使用testflightapp。

6、如果某测试人员可以在手机的testfligh M中看到APP,但是install按钮是灰色的,那么一般是因为APP的编译版本支持的IOS版本号和测试人员的IOS版本号不匹配。

7、Testflightapp的SDK,提供了一个很傻的debug工具,稍微看了下,感觉很弱,谁有兴趣的话可以深入研究一下下。

8、建议一定安装Testflight Desktop App,这东西挺方便,只要你Archive Project,TestFlish Desktop App就能检测到,然后自己生成IPA上传。等等。

9、iphone在安装Testflight M的时候如果遇到问题,可以尝试把“设置->通用->描述文件”里TestFlight WebClip移除再安装试试。

BTW,最近通过testflightapp安装app速度越来越慢,越来越慢….

C#中WebBrowser与Winform互操作

在C# winform开发中,在form中使用webbrowser,实现一些尤其是界面方面的编程,显得尤其方便,但很多时候,我们需要winform和webbrowser里面的功能能相互调用,一般相互调用有几下集中情况:

1、在winform里呼叫webbrowser里的javascript函数

这个比较简单,在c#里建立一个点击事件,其中使用:

webBrowser1.Document.InvokeScript("callFromWinForm", new object[] { "hellow","world" });

就可以完成调用,其中callFromWinForm是在demo.html里的js函数。new Object[] {这里是参数,以”,”分割}

2、在winform里模拟webbrowser里的DOM事件

比如demo.html里有这段代码:

hello phpmsg!

那么在C#里建立一个事件,使用:

HtmlElement link = webBrowser1.Document.GetElementById("link");
link.InvokeMember("click");

即可模拟鼠标点击链接,上面的例子是根据ID获取,如果有多个链接需要模拟点击可以这样:

HtmlElementCollection links = webBrowser1.Document.GetElementsByTagName("a");
foreach(HtmlElement link in links){
    link.InvokeMember("click");
}

当然,除了click事件,其他比如mouseover等都可以用的。

3、捕获DOM事件,模拟调用C#中的函数

假设html中有这样一个按钮:


我们希望点击的时候执行c#中的函数,

我们在c#中这样处理:

private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
    // 注册这个事件捕获
    webBrowser1.Document.GetElementById("htmlBtn").Click += new HtmlElementEventHandler(htmlBtn_Click);
}



//响应函数

private void htmlBtn_Click(object sender,HtmlElementEventArgs e) {
     String value = webBrowser1.Document.GetElementById("htmlBtn").GetAttribute("value");
     MessageBox.Show("phpmsg.com from c# with value=" + value);
}

实际上,这个实现方式并不是“调用”,而仅仅是通过“响应”进行回调,他会在当前当前html中的click事件后“追加”一个click响应,如果你在html中本身已经为htmlBtn绑定了click事件,你会发现先执行html里的click事件,然后执行c#中的click。

笔者在开发过程中,遇到一些特殊情况:

我在html中有很多按钮,并且ID号是实现没有办法固定,我希望实现点击html中按钮的时候,C#可以为每个按钮打开一个新的winform窗体,并且要将一些参数传递过来。这时候发现C#中没有办法知道点击的具体是哪个按钮,当然,相关参数也不可能传递到C#中。

后来发现利用webbroser.Navigating可以绕道解决问题,大概代码如下:

你需要在html中,编写类似代码:

聊天室123
聊天室456

然后在c#中:

private void webBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
  string url = e.Url.ToString();
  if (url.StartsWith("chat://")){
    e.Cancel = true;
    MessageBox.Show(url);
   }
}

然后你只需要把需要传递的参数通过url传递过来,解析url里的参数,就完美了。

4、在webbrowser里呼叫C#中的函数

有时候更直接点,我们需要在webbrowser里直接呼叫C#中的函数,

一般需要利用window.external来完成,比如


C#中建立一个function

public void test(String s) {
    MessageBox.Show(s);
}

但是需要注意的是,在C#中需要在开始设定权限才可以:

[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name="FullTrust")]
[System.Runtime.InteropServices.ComVisibleAttribute(true)]

另外需要设定Webbrowser的

webBrowser1.ObjectForScripting = this;

以上几种情况,基本可以解决绝大部分问题,善加利用相信可以大幅度减少winform的编程量。

Nginx“413 Request Entity Too Large”解决方法

今天使用Wordpress的flash版文件上传“SEO知识分享”功能的时候,总是提示HTTP错误,很是郁闷。换小文件发现没有问题,php.ini设置是2M,但是上传的文件只有1.2M。很是郁闷。

切换到传统文件上传界面,重新上传一个大文件,这回出来错误提示了,413 Request Entity Too Large,google了一下,发现是Nginx的错误提示。

解决方法:打开nginx主配置文件nginx.conf,找到http{}段,添加
client_max_body_size 2m;

重启NGINX

kill -HUP `cat /usr/local/nginx/nginx.pid `
恢复正常

从零开始搭建node.js+xmlSocket的WEB聊天室(六)

六、说在最后的话

这个程序只是笔者为了熟悉node.js写的demo程序,其实node.js有个Package叫socket.io,可以自动根据客户端的情况自动选择合适的方法模拟/原生连接到socket server,不过这个包只能同node http server配合使用,不能独立使用,稍微有点遗憾,可以用NPM(Node Package Manager)来安装使用。

最后server.js需要作为daemon运行的话,可以安装forever来实现。

[root@phpmsg /]# npm install forever

[root@phpmsg /]# forever start server.js

笔者在做的时候程序调试通了后就没有再继续完善了,所以大家在用的时候可能会有一些bug,需要自行解决了,性能我没有做过测试,但是估计比类似phpfreechat这样的程序性能要高出n倍吧。

node.js是一门这几年才快速发展起来的语言,每天都有新的package出现,语言特性还有很多不足,版本升级也非常快,但是不知道会不会像Erlang一样,热一段时间就好像没什么声音了(据说Erlang目前在游戏公司被大量低调使用),但是node.js的易学易用性,个人认为在中小型的server端开发,尤其是作为大并发的socket server,webgame,聊天室等方面的应用在总TCO方面一定会有大量优势。

最后完善的聊天地址在:http://chat.phpmsg.com/

所有的源代码我都放在:http://chat.phpmsg.com/chat.tgz

从零开始搭建node.js+xmlSocket的WEB聊天室(五)

五、chat socket server实现

聊天室服务器是整个程序中最重要的部分了,因为这里只是个demo,所以尽量简化,所有聊天数据都没有做持久化存储,数据都由全局变量的形式存储,有兴趣的同学可以继续完善,比如用redis来存储聊天数据和其他相关数据。

var net = require('net');
var crypto = require('crypto');
var querystring = require('querystring');
var url = require('url'); < /code>

var serverListenPort = 88;
var server = net.createServer();

var policyListenPort = 843;
var policy = net.createServer(); // flash policy server

var onlines = [];
var rooms = [];

/**
动作cmd
system.connect // 连入服务器
system.close // 退出服务器
room.join // 进入房间
room.leave // 离开房间
room.broadcast // 在房间里说话
message.to // 对私人发送消息
*/
server.listen(serverListenPort);
server.on('connection', function(socket) {

    console.log("[" + now() + "][CNN][" + socket.remoteAddress + "] connected.");
    socket.setKeepAlive(true);

    socket.on('data', function(buffer) {
        if (buffer.toString() == '\0') {
            socket.end('\0');
            console.log("[" + now() + "][REQ][" + socket.remoteAddress + "] send cross-domain-policy from policy server on [" + serverListenPort + "] port.");
            return true;
        }
        var s = buffer.toString('utf8', 0, buffer.length).replace("\u0000", "").replace("\0", "");
        console.log("[" + now() + "][REV][" + socket.remoteAddress + "] " + s);

        var req = JSON.parse(s);
        var nickname = req.nickname;
        var nickid = md5(nickname);
        var cmd = req.cmd;
        var roomname = req.roomname;
        var roomid = md5(roomname);
        var body = req.body;

        if (cmd == 'system.connect') {
            system.connect(socket, nickname, nickid, req);
        }

        if (cmd == 'system.close') {
            system.close(nickid);
        }

        if (cmd == 'message.to') {
            message.to(nickname, nickid, req.toname, body);
        }

        if (cmd == 'room.join') {
            room.join(nickid, roomid);
            room.broadcast(nickname, roomid, body);
            room.onlines(nickname, roomid); // 通知数量变化
        }

        if (cmd == 'room.leave') {
            room.leave(nickid, roomid);
            room.broadcast(nickname, roomid, body); // 临终遗言
            room.onlines(nickname, roomid); // 通知数量变化
        }

        if (cmd == 'room.onlines') {
            var members = room.onlines(roomname);
            // todo
        }

        if (cmd == 'room.broadcast') {
            room.broadcast(nickname, roomid, body);
        }
    });

    socket.on('error', function(exception) {
        console.log("[" + now() + "][ERR][" + socket.remoteAddress + "] " + exception);
    });

    socket.on('close', function() {
        // socket已经断线,不能用remoteAddress获取ip地址。
        console.log("[" + now() + "][CLS][" + socket._peername.address + "] closed.");
    });

});

policy.listen(policyListenPort);
policy.on('connection', function(socket) {
    console.log("[" + now() + "][REQ][" + socket.remoteAddress + "] send cross-domain-policy from policy server on [" + policyListenPort + "] port.");
    socket.end('\0');

    socket.on('error', function(error) {
        console.log("[" + now() + "][ERR] " + error);
    })
})

function md5(str) {
    if (str == '' || str == 'undefined' || str == null) return false;
    var md5hash = crypto.createHash('md5');
    md5hash.update(str);
    return md5hash.digest('hex');
}

function now() {
    var date = new Date();
    var now = date.getFullYear() + "-" + date.getMonth() + "-" + date.getDate() + " " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds();
    return now;
}

var system = {}
system.connect = function(socket, nickname, nickid, query) {
    var member = {
        "socket": socket,
        "nickid": nickid,
        "nickname": nickname,
        "camera": query.camera,
        "microphone": query.microphone,
    };
    onlines[nickid] = member;
};

system.close = function(nickid) {
    delete(onlines[nickid]);
};

var room = {};
room.open = function(roomid) { // 开房?
    for (var id in rooms) {
        if (id == roomid) return id; // 房间已经存在,不要修改对象
    }

    var _room = {
        "admin": [],
        "onlines": []
    };

    rooms[roomid] = _room;
    return roomid;
}

room.join = function(nickid, roomid) {
    var member = onlines[nickid]; // 取得活动用户的信息

    this.open(roomid); // 取得房间id
    rooms[roomid].onlines[nickid] = member; // 加入,更新用户信息

}

room.onlines = function(from, roomid) { // 列出房间在线用户
    var body = [];
    var members = rooms[roomid].onlines;
    for (var nickid in members) {
        if (!members[nickid].socket.writable) { // 处理异常掉线
            delete(rooms[roomid].onlines[nickid]);
            delete(onlines[nickid]);
            continue;
        }

        var s = {
            "nickid": members[nickid].nickid,
            "nickname": members[nickid].nickname,
            "microphone": members[nickid].microphone,
            "camera": members[nickid].camera,
        }
        body.push(s);
    }

    var alived = rooms[roomid].onlines;
    for (var nickid in alived) { // 向所有活着的人发送
        var msg = {
            "cmd": "room.onlines",
            "from": from,
            "body": body
        };
        message.send(alived[nickid].socket, msg);
    }
}

room.block = function(nickname, roomname) { // 某用户在房间被禁言
    // todo:
}

room.leave = function(nickid, roomid) { // 离开房间
    // 离开之前先发表遗言
    delete(rooms[roomid].onlines[nickid]);
}

room.broadcast = function(from, roomid, body) {
    for (var nickid in rooms[roomid].onlines) {
        var msg = {
            "cmd": "room.broadcast",
            "from": from,
            "body": body
        };
        if (rooms[roomid].onlines[nickid].socket.writable) { // 向活着的人发消息
            message.send(rooms[roomid].onlines[nickid].socket, msg);
        } else {
            delete(rooms[roomid].onlines[nickid]);
            delete(onlines[nickid]);
        }
    }
}

var message = {}
message.send = function(socket, msg) {
    if (!socket.writable) return false; // 不可写
    if (msg.body == '' || msg.body == 'undefined' || msg.body == null) return false;

    var time = new Date();

    msg.dateline = time.getHours() + ":" + time.getMinutes() + ":" + time.getSeconds();
    msg.ip = socket.remoteAddress;

    socket.write(JSON.stringify(msg) + "\0");
    console.log("[" + now() + "][SNT][" + socket.remoteAddress + "] " + JSON.stringify(msg));
    return true;
}

message.to = function(fromname, fromid, toname, body) {
    var toid = md5(toname);

    var str = {
        "cmd": "message.to",
        "fromname": fromname,
        "toname": toname,
        "body": body
    }

    message.send(onlines[fromid].socket, str); // 发给自己

    if (!onlines[toid].socket.writable) return false;
    message.send(onlines[toid].socket, str); // 发给对方
}

保存为server.js,在服务器端用node.js来运行,您也可以在http://chat.phpmsg.com/server.js来下载代码。

注意:在调试的时候可能会发现死活就是连不上socket server,或者是不能收发消息,这很有可能是服务器所在机房对端口的黑名单造成的,这时候最好换个机房,或者干脆在本机调试时最好了。

 

从零开始搭建node.js+xmlSocket的WEB聊天室(四)

四、搭建flash 安全策略服务器

flash在进行socket通讯的时候会有安全策略问题,一般错误为:

securityErrorHandler信息: [SecurityErrorEvent type=”securityError” bubbles=false cancelable=false eventPhase=2 text=”Error #2048″]

在Flash player 9.0.124.0之后,当flash文件要进行socket通信的时候,需要向服务器端获取crossdomain.xml文件。如果找不到就出现客户端无法连接服务器的现象。

具体关于有关安全策略的问题,可以参看adobe的相关介绍

在本案中,我们使用node.js编写一个简单的服务监听在843端口,为flash提供安全策略文件,当然如果在之前编写.AS文件的时候指定了安全策略服务器位置,Security.loadPolicyFile(“xmlsocket://www.xxx.com:1234”),这里就要监听在相应的port了。

var net = require('net');

var policyListenPort = 843;
var policy = net.createServer();
policy.listen(policyListenPort);
policy.on('connection', function(socket){
	console.log("[" + now() + "][REQ][" + socket.remoteAddress + "] send cross-domain-policy from policy server on [" + policyListenPort + "] port.");
	socket.end('\0');

	socket.on('error',function(error){
		console.log("["+ now() + "][ERR] " + error);
	})
})

function now(){
	var date = new Date();
	var now = date.getFullYear() + "-" + date.getMonth() + "-" + date.getDate() + " " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds();
	return now;
}

上面的代码是node.js代码,需要在服务器上运行的,除了我们使用node.js编写策略文件服务器外,可以直接下载:http://www.adobe.com/content/dotcom/en/devnet/flashplayer/articles/socket_policy_files/_jcr_content/articlePrerequistes/multiplefiles/node_1277808777771/file.res/flashpolicyd_v0.6[1].zip 这是由官方提供的python和perl编写的服务器。

只有一个策略服务器事实上可能还会有些问题,某些客户端的防火墙会禁止对外部843端口的访问,基于flash socket policy 解决方法,我们会在聊天室socket server上也同时提供policy file 服务。这个在稍后的程序中提到,所以实际上这里的专用的policy file server是可以不提供的。

注意这段程序在通过socket发送内容的时候,最后有个 \0,这是因为flash xmlsocket认为\0是内容结束的标志。

从零开始搭建node.js+xmlSocket的WEB聊天室(三)

三、构建聊天界面

用纯静态页面搭建一个聊天界面,界面如下:http://chat.phpmsg.com/

因为整个聊天过程没有动态程序参与,所以需要一个js函数来获取通过url传递的nickname和聊天室的名称。


另外界面中有个私聊窗口,允许聊天室里的人可以相互私聊。

我们使用SINA CDN的swfobject来加载socket.swf文件。

考虑到数据传输的方便性,我们在客户端和服务端交换的数据都使用json格式,注意:在socket.js文件里的send函数:

function send(message){
  socket.send(JSON.stringify(message));
}

ie8(兼容模式),ie7和ie6没有JSON对象,不过http://www.json.org/提供了一个json.js,这样ie8(兼容模式),ie7和ie6就可以支持JSON对象以及其stringify()和parse()方法;你可以在https://github.com/douglascrockford/JSON-js上获取到这个js,一般现在用json2.js

我测试的时候因为是在chrome里做的,所以就没有去下载json2.js了。

从零开始搭建node.js+xmlSocket的WEB聊天室(二)

二、实现flash xmlSocket封装

因为所有逻辑中socket client变化最少,也最简单,封装完了基本不用更改了,所以先从这里开始。

打开用任意文本编辑器,新建一个.as文件。

代码如下:

package
{

	import flash.display.Sprite;
	import flash.events.DataEvent;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.external.ExternalInterface;
	import flash.media.Camera;
	import flash.media.Microphone;
	import flash.net.XMLSocket;

	public class socket extends Sprite {

		private var xmlSocket:XMLSocket;

		public function socket():void {

			ExternalInterface.marshallExceptions = true;
			xmlSocket = new XMLSocket();
			xmlSocket.addEventListener(SecurityErrorEvent.SECURITY_ERROR,onError);
			xmlSocket.addEventListener(Event.CLOSE,onClose);
			xmlSocket.addEventListener(Event.CONNECT,onConnect);
			xmlSocket.addEventListener(IOErrorEvent.IO_ERROR,onIoError);
			xmlSocket.addEventListener(DataEvent.DATA,onMessage);
			ExternalInterface.addCallback("connect", function(host: String, port: int): void {
				xmlSocket.connect(host,port);
			});
			
			ExternalInterface.addCallback("send", function(object: *): void{
				xmlSocket.send(object);
			});

			ExternalInterface.addCallback("close", function(): void{
				xmlSocket.close();
			});

			ExternalInterface.addCallback("hasCamera", function(): Boolean {
				return Camera.isSupported;
			});

			ExternalInterface.addCallback("hasMicrophone", function(): Boolean{
				return Microphone.isSupported;
			});

			ExternalInterface.call("onReady");
		}

		private function onConnect(e: Event): void{
			ExternalInterface.call("onConnect", e.toString());
		}

		private function onMessage(de: DataEvent): void{
			ExternalInterface.call("onMessage", de.data);
		}

		private function onClose(e:Event): void{
			ExternalInterface.call("onClose", e.toString());
		}

		private function onError(e:SecurityErrorEvent): void{
			ExternalInterface.call("onError", e.toString());
		}

		private function onIoError(e:IOErrorEvent): void{
			ExternalInterface.call("onError",e.toString());
		}

	}

}

在dos环境下编译.as文件成swf

c:\Program Files\Adobe\Adobe Flash Builder 4.6\sdks\4.6.0\bin>mxmlc –static-lin
k-runtime-shared-libraries socket.as
正在加载配置文件“C:\Program Files\Adobe\Adobe Flash Builder 4.6\sdks\4.6.0\fram
eworks\flex-config.xml”
C:\Program Files\Adobe\Adobe Flash Builder 4.6\sdks\4.6.0\bin\socket.swf(1109
字节)

当然你也可以直接在flash builder/flash cs里建立as文件,然后编译。

注意:要使用mxmlc命令,必须安装flash builder。

这一步所做的事情其实非常简单,将xmlsocket的几个“socket事件”绑定到前端的javascript函数上去,一旦socket事件触发,就会触发相应的javasctrip function,并且将数据在js端和server端传递,很多webgame都是基于这样的方式来实现socket的。

这里我们同时注册了hasCamera和hasMicrophone两个函数,用来判断客户端是否具有摄像头和麦克风,以后可以进一步扩展实现基于FMS的视频聊天室。

其实使用flash xmlsocket并不是原生的解决方案,基于HTML5的websocket或许才是最完美的方案,这里为了“看起来更象”html5的 websocket,几个函数也被命名和html5 websocket类似,只是没有进一步封装进javascript类,而且这里也只能对单一socket server连线,如果某些应用需要同时连接多个socket server,也需要进一步处理,有兴趣的同学可以进一步封装。

从零开始搭建node.js+xmlSocket的WEB聊天室(一)

一、一些基本思路

先说点废话,因为项目需要,要求做一个扩展性很强,性能又要好,花钱少,维护简单的全功能聊天室,类似新浪微秀或者是六间房这样的聊天室,顺便研究了一下现在主流的WEB聊天室技术。

现在一般的小型web聊天室的方案无非是ajax+http轮询或者http comet等,比如php开源项目phpfreechat的实现就是基于php+mysql+async ajax,我没有测试过性能,不过想来性能和容量也不会这么样,牛一点的专业聊天室更倾向使用socket解决方案,不过HTML5的推进速度太慢,目前基于WEB的Socket解决方案基本还是flash的天下,后端可能使用基于FMS(flash media server)或者免费开源的RED5,当然使用基于jabber协议的服务,比如ejabberdopenfire等更是大型架构需要考虑的方向,其中openfire比较完善,有针对flash的类库,还有java写的客户端,随便改改可以使用于多种地方。

其实多年前曾经用openfire+flash xmlsocket做过一个web聊天室,但是一旦并发用户超过200个,用户的login经常会出问题,并且频繁掉线,而且因为openfire有自己的用户认证系统,跟现有的网站的用户认证/session维护等还需要打通,反而变成了挺麻烦的一件事。

所以我决定抛弃FMS/RED5/Openfire,使用Flash xmlSocket + jQuery + Node.js来搭建一个轻量级但是性价比很高的web聊天室架子,使用node.js搭建socket server和flash策略服务器,使用flash xmlsocket连接socket server,使用jquery实现界面事件逻辑,免除了大量的flash界面设计,用户session由网站程序维护。

Node.js是一个Javascript运行环境,实际上它是对GoogleV8引擎(应用于Google Chrome浏览器)进行了封装,是一种“基于事件”的编程模式的实现。对熟悉ajax编程的同学是很容易入门的,关于Node.js的资料可以到官网查看。

整个聊天室主要有三部分逻辑程序:

1、后端node.js实现的socket server

2、前段flash xmlsocket封装的swf文件,作为socket client与socket server进行tcp连接;

3、前段jquery事件逻辑,主要是由flash xmlsocket里调用前段javascript函数。

其实还需要一个flash socket策略服务器监听在843端口上,这个我们在实现socket server的时候会顺便实现,因为这里只是一个demo聊天室,web网站部分,比如用户的登陆/注册等我这里就不实现了。

最后所有的源程序我会提供一个zip包下载。