现在在做一个多屏互动项目,需要Iphone设备与终端管理系统通信。
调研了下,决定使用Thrift框架实现通信模块。准备在Mac上安装Thrift,支持Objective-c和Java。
环境:Mac mini
Mac OS X Lion 10.7.5
环境要求:http://wiki.apache.org/thrift/ThriftRequirements
pkg-config autoconf macros (pkg.m4) (Use MacPorts for Mac OS X)
安装方法:http://wiki.apache.org/thrift/GettingOsxPackages
Download the boost library and bjam installer from http://www.boost.org Untar and place bjam in the boost folder and then compile with
$ sudo ./bjam toolset=darwin link=shared threading=multi runtime-link=shared variant=release address-model=64 stage install
Download libevent from http://monkey.org/~provos/libevent/ untar and compile with
$ ./configure --prefix=/usr/local --disable-static $ make $ sudo make install
Download apache thrift from http://thrift.apache.org untar and compile with
$ ./configure --prefix=/usr/local/ --disable-static --with-boost=/usr/local --with-libevent=/usr/local --without-python --without-csharp --without-ruby --without-perl --without-php --without-haskell --without-erlang $ make $ sudo make install
安装完完后,执行文件为/usr/local/bin/thrift
http://wiki.apache.org/thrift/ThriftUsageObjectiveC
This tutorial will walk you through creating a sample project which is a bulletin board application using Thrift. It consists of an iOS client app written in Objective-C and a Java server. It is assumed that you will have a basic knowledge of Objective-C and Xcode to complete this tutorial. Note that the client runs on Mac OS with small modification.
Acknowledgment: A part of this tutorial was borrowed from newacct's tutorial.
You can download the sample project from here. It includes:
Server.java and BulletinBoard.java in gen-java/ directory
Make sure that your system meets the requirements as noted in ThriftRequirements.
The following are required for the Java server; but, not the Objective-C client.
SLF4J (available at http://www.slf4j.org/download.html)
We will use a simple thrift IDL file, myThriftApp/idl.thrift. This defines a bulletin board service. You can upload your message and date using add() method. get()method returns a list of the struct so that we demonstrate how to send/receive an array of structs in Objective-C.
// idl.thrift struct Message { 1: string text, 2: string date } service BulletinBoard { void add(1: Message msg), listget()
}
thrift --gen java idl.thrift thrift --gen cocoa idl.thrift
The objective-c client is a simple app that allows the user to fill out a text field add/get them from the server.
Product name is myThriftApp.
Check to Use Automatic Reference Counting
Right click on myThriftApp and select Add files to "myThirtApp". Choose "gen-cocoa".
Right click on myThriftApp and select Add files to "myThirtApp". Choose "thrift-x.x.x/lib/cocoa/src".
rename group name from src to thrift.
Always Search User Path: YES
Framework Search Paths: add $(SRCROOT) and $(inherited)
Copy the following text to ViewController.h
#import@class BulletinBoardClient; @interface ViewController : UIViewController { BulletinBoardClient *server; } @property (strong, nonatomic) IBOutlet UITextField *textField; @property (strong, nonatomic) IBOutlet UITextView *textView; - (IBAction)addPressed:(id)sender; @end
Open the ViewController_iPhone.xib
Place a Text Field, a Round Rect Button, and a Text View.
Connect the Text Field to textField variable in ViewController.h.
Connect the Button to addPressed: method in ViewController.h. Give add title to the button.
Connect the Text View to textView variable in ViewController.h. Clear the content of the Text View.
Copy the following code into ViewController.m
#import#import #import "ViewController.h" #import "idl.h" @implementation ViewController @synthesize textField; @synthesize textView; - (void)viewDidLoad { [super viewDidLoad]; // Talk to a server via socket, using a binary protocol TSocketClient *transport = [[TSocketClient alloc] initWithHostname:@"localhost" port:7911]; TBinaryProtocol *protocol = [[TBinaryProtocol alloc] initWithTransport:transport strictRead:YES strictWrite:YES]; server = [[BulletinBoardClient alloc] initWithProtocol:protocol]; } - (void)viewDidUnload { [self setTextField:nil]; [self setTextView:nil]; [super viewDidUnload]; } - (IBAction)addPressed:(id)sender { Message *msg = [[Message alloc] init]; msg.text = textField.text; msg.date = [[NSDate date] description]; [server add:msg]; // send data NSArray *array = [server get]; // receive data NSMutableString *s = [NSMutableString stringWithCapacity:1000]; for (Message *m in array) [s appendFormat:@"%@ %@\n", m.date, m.text]; textView.text = s; } - (BOOL)textFieldShouldReturn:(UITextField*)aTextField { [aTextField resignFirstResponder]; return YES; } @end
create the file BulletinBoardImpl.java
import org.apache.thrift.TException; import java.util.List; import java.util.ArrayList; class BulletinBoardImpl implements BulletinBoard.Iface { private Listmsgs; public BulletinBoardImpl() { msgs = new ArrayList (); } @Override public void add(Message msg) throws TException { System.out.println("date: " + msg.date); System.out.println("text: " + msg.text); msgs.add(msg); } @Override public List get() throws TException { return msgs; } }
import java.io.IOException; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TBinaryProtocol.Factory; import org.apache.thrift.server.TServer; import org.apache.thrift.server.TServer.Args; import org.apache.thrift.server.TSimpleServer; import org.apache.thrift.transport.TServerTransport; import org.apache.thrift.transport.TServerSocket; import org.apache.thrift.transport.TTransportException; public class Server { private void start() { try { BulletinBoard.Processor processor = new BulletinBoard.Processor(new BulletinBoardImpl()); TServerTransport serverTransport = new TServerSocket(7911); TServer server = new TSimpleServer(new Args(serverTransport).processor(processor)); System.out.println("Starting server on port 7911 ..."); server.serve(); } catch (TTransportException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } public static void main(String args[]) { Server srv = new Server(); srv.start(); } }
javac *.java
This will run a server that implements the service BulletinBoard on port 7911. The service simply stores Message structures in a List and returns the list when requested.
java Server
Build and run the myThriftApp in the iPhone simulator. Since it tries to connect to localhost, you need to run it in the iPhone simulator rather than an iPhone.
When a service with multiple arguments is compiled, argument names are omitted.
// IDL service TestService { void method(1: i32 value1, 2: i32 value2), }
void method:(int)value1 :(int)value2; // [server method:24 :33];
Struct members can be accessed via attributes of an instance.
// IDL struct Person { 1: string name, 2: string city, }
Person *p = [[Person alloc] init]; p.name = @"HIRANO"; p.city = @"Tsukuba";
Since 0.9.0 (0.9.0-dev), initialization of struct and const is supported.
// IDL struct Person { 1: string name = "your name", 2: string city = "your city", } const listMyFamily = [{name: "Satoshi", city: "Tsukuba"}, {name: "Akiko", city: "Tokyo"}]
string is converted into UTF-8 encoding before sending. The UTF-8 encoding is converted to NSString* after receiving.
Note all language versions convert encoding automatically. For example Python does not do encoding conversion. (Thus, you need to do conversion by yourself).
Thrift for Objective-C supports binary type as NSData*.
// IDL struct Person { 1: string name, 2: binary photo, service TestService { void register(1: Person person), }
Person *p = [[Person alloc] init]; p.name = @"HIRANO Satoshi"; NSImage *image = [NSImage imageNamed:@"hirano.png"]; // load an image NSData *tiffData = [image TIFFRepresentation]; // obtain data p.photo = tiffData; [server register:p];
The objective-C version supports collection types, list, set and map as NSArray, NSSet, and NSDictionary.
// IDL struct TestData { listlistData, set setData, map mapData, }
TestData *t = [[TestData alloc] init]; t.listData = [NSArray arrayWithObjects:@"a", @"b", nil]; t.setData = [NSSet setWithObjects::@"e", @"f", nil]; t.mapData = [NSDictionary dictionaryWithObjectsAndKeys:@"name", @"HIRANO", @"city", @"Tsukuba", nil];
You can mainly use the socket transport and the HTTP transport.
Here is a client side example for the socket transport.
#import#import - (void) connect { // Talk to a server via socket, using a binary protocol TSocketClient *transport = [[TSocketClient alloc] initWithHostname:@"localhost" port:7911]; TBinaryProtocol *protocol = [[TBinaryProtocol alloc] initWithTransport:transport strictRead:YES strictWrite:YES]; server = [[BulletinBoardClient alloc] initWithProtocol:protocol];
Here is a client side example for the HTTP transport. You may use https. You can connect to Google App Engine for example.
#import#import - (void) connect { NSURL *url = [NSURL URLWithString:@"http://localhost:8082"]; // url = [NSURL URLWithString:@"https://myapp145454.appspot.com"]; // Talk to a server via HTTP, using a binary protocol THTTPClient *transport = [[THTTPClient alloc] initWithURL:url]; TBinaryProtocol *protocol = [[TBinaryProtocol alloc] initWithTransport:transport strictRead:YES strictWrite:YES]; server = [[TestServiceClient alloc] initWithProtocol:protocol];
The asynchronous operation means that an RPC call returns immediately without waiting for the completion of a server operation. It is different from the oneway operation. An asynchronous operation continues in background to wait for completion and it is possible to receive a return value. This feature plays very important role in GUI based apps. You don't want to block for long time when a user pushes a button.
Unlike Java version, Objective-C version does not support asynchronous operation.
However, it is possible to write asynchronous operations using NSOperationQueue. The basic usage of NSOperationQueue is like this. Your async block is executed in a background thread.
[asyncQueue addOperationWithBlock:^(void) { // your async block is here. int val = [remoteServer operation]; }];
There are some points to be considered.
Your async blocks are done in asyncQueue, an instance of NSOperationQueue.
Strong objects may not be accessed from a block. Since self is also a strong object, we need to avoid to access self within an async block. We use weakSelf, a weak reference to self.
GUI operations must be in the main thread. For example, uilabel.text = @"foo"; must be done in the main thread and you may not write it in the above async block. A block that handles GUI is added to the mainQueue which represents the main thread. We use weaksSelf2 in the nested async block.
Here are the fragments of ThriftTestViewController class.
// IDL service TestService { i32 sum(1: i32 value1, 2: i32 value2), }
@interface ThriftTestViewController : UIViewController { IBOutlet UILabel *msg; } @property (nonatomic, retain) UILabel *msg; @property (nonatomic, strong) TestServiceClient *server; @property (nonatomic, strong) NSOperationQueue *asyncQueue; @property (nonatomic, strong) NSOperationQueue *mainQueue;
- (void )viewDidLoad { asyncQueue = [[NSOperationQueue alloc] init]; [asyncQueue setMaxConcurrentOperationCount:1]; // serial mainQueue = [NSOperationQueue mainQueue]; // for GUI, DB NSURL *url = [NSURL URLWithString:@"http://localhost:8082"]; // Talk to a server via HTTP, using a binary protocol THTTPClient *transport = [[THTTPClient alloc] initWithURL:url]; TBinaryProtocol *protocol = [[TBinaryProtocol alloc] initWithTransport:transport strictRead:YES strictWrite:YES]; // Use the service defined in profile.thrift server = [[TestServiceClient alloc] initWithProtocol:protocol]; NSLog(@"Client init done %@", url); } -(void)doCalc { __unsafe_unretained ThriftTestViewController *weakSelf = self; [asyncQueue addOperationWithBlock:^(void) { __unsafe_unretained ThriftTestViewController *weakSelf2 = weakSelf; @try { weakSelf.msg.text = nil; int result = [weakSelf.server calc:24 :32]; [weakSelf.mainQueue addOperationWithBlock:^(void) { weakSelf2.msg.text = [NSString stringWithFormat:@"%d", result]; }]; } @catch (TException *e) { NSString *errorMsg = e.description; NSLog(@"Error %@", errorMsg); [weakSelf.mainQueue addOperationWithBlock:^(void) { weakSelf2.msg.text = errorMsg; }]; } }]; }
http://thrift.apache.org/tutorial/cocoa/
http://wiki.apache.org/thrift/ThriftRequirements
http://thrift.apache.org/docs/install/os_x/
http://wiki.apache.org/thrift/ThriftUsageObjectiveC
http://wiki.apache.org/thrift/GettingOsxPackages