注::質問を編集しました。接続しての最初のコールバックを実行しますが、後続のコールバックはまったく実行されません。Objective-Cのスレッディングとソケット
これは私の初めてのObjective-C(GNUstep付き、これは宿題用です)です。私は解決策を持っているが、私はそれに何かを追加しようとしている。このアプリは、サーバーに接続し、そこからデータを取得するGUIクライアントです。複数のクライアントが同じサーバーに接続できます。いずれかのクライアントがサーバー上のデータを変更すると、サーバーは登録されているすべてのクライアントにコールバックを送信します。このソリューションはもともとJava(クライアントとサーバーの両方)に実装されていました。最新の課題では、教授はObjective-Cクライアントを書くことを希望しました。彼はコールバックを処理する必要はないと言ったが、とにかく試してみたかった。
私はNSThread
を使用していると私はこのようなものを書いた:に提供されているCallbackInterceptorThread.m
#import <Foundation/Foundation.h>
#import "CallbackInterceptorThread.h"
#define MAXDATASIZE 4096
@implementation CallbackInterceptorThread
- (id) initWithClientPort: (NSString*) aClientPort
appDelegate: (AppDelegate*) anAppDelegate {
if((self = [super init])) {
[clientPort autorelease];
clientPort = [aClientPort retain];
[appDelegate autorelease];
appDelegate = [anAppDelegate retain];
}
return self;
}
- (void) main {
GSRegisterCurrentThread();
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
char* buffer = malloc(MAXDATASIZE);
Cst420ServerSocket* socket = [[Cst420ServerSocket alloc] initWithPort: clientPort];
[socket retain];
NSString* returnString;
while(YES) {
printf("Client waiting for callbacks on port %s\n", [clientPort cString]);
if([socket accept]) {
printf("Connection accepted!\n");
while(YES) {
printf("Inner loop\n");
sleep(1);
returnString = [socket receiveBytes: buffer maxBytes: MAXDATASIZE beginAt: 0];
printf("Received from Server |%s|\n", [returnString cString]);
if([returnString length] > 0) {
printf("Got a callback from server\n");
[appDelegate populateGui];
}
printf("Going to sleep now\n");
sleep(1);
}
[socket close];
}
}
}
@end
Cst420ServerSocket
を
CallbackInterceptorThread.h
#import <Foundation/Foundation.h>
#import "AppDelegate.h"
@interface CallbackInterceptorThread : NSThread {
@private
NSString* clientPort;
AppDelegate* appDelegate;
}
- (id) initWithClientPort: (NSString*) aClientPort
appDelegate: (AppDelegate*) anAppDelegate;
- (void) main;
@end
を私たちはインストラクター。それは次のようになります。
#import "Cst420Socket.h"
#define PORT "4444"
/**
* Cst420Socket.m - objective-c class for manipulating stream sockets.
* Purpose: demonstrate stream sockets in Objective-C.
* These examples are buildable on MacOSX and GNUstep on top of Windows7
*/
// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa){
if (sa->sa_family == AF_INET) {
return &(((struct sockaddr_in*)sa)->sin_addr);
}
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
@implementation Cst420ServerSocket
- (id) initWithPort: (NSString*) port{
self = [super init];
int ret = 0;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // use my IP
const char* portStr = [port UTF8String];
if ((rv = getaddrinfo(NULL, portStr, &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
ret = 1;
}else{
for(p = servinfo; p != NULL; p = p->ai_next) {
if ((sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))==-1){
perror("server: socket create error");
continue;
}
if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
#if defined(WINGS)
closesocket(sockfd);
#else
close(sockfd);
#endif
perror("server: bind error");
continue;
}
break;
}
if (p == NULL) {
fprintf(stderr, "server: failed to bind\n");
ret = 2;
}else{
freeaddrinfo(servinfo); // all done with this structure
if (listen(sockfd, BACKLOG) == -1) {
perror("server: listen error");
ret = 3;
}
}
if (ret == 0){
return self;
} else {
return nil;
}
}
}
- (BOOL) accept {
BOOL ret = YES;
#if defined(WINGS)
new_fd = accept(sockfd, NULL, NULL);
#else
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
#endif
if (new_fd == -1) {
perror("server: accept error");
ret = NO;
}
connected = ret;
return ret;
}
- (int) sendBytes: (char*) byteMsg OfLength: (int) msgLength Index: (int) at{
int ret = send(new_fd, byteMsg, msgLength, 0);
if(ret == -1){
NSLog(@"error sending bytes");
}
return ret;
}
- (NSString*) receiveBytes: (char*) byteMsg
maxBytes: (int) max
beginAt: (int) at {
int ret = recv(new_fd, byteMsg, max-1, at);
if(ret == -1){
NSLog(@"server error receiving bytes");
}
byteMsg[ret+at] = '\0';
NSString * retStr = [NSString stringWithUTF8String: byteMsg];
return retStr;
}
- (BOOL) close{
#if defined(WINGS)
closesocket(new_fd);
#else
close(new_fd);
#endif
connected = NO;
return YES;
}
- (void) dealloc {
#if defined(WINGS)
closesocket(sockfd);
#else
close(sockfd);
#endif
[super dealloc];
}
@end
私たちの教授はまた、私たちの単純なエコーサーバとクライアント(サーバが戻ったばかりのクライアントがそれを送ったものは何でも吐く)の例を提供し、私はスレッドで同じパターンを使用しました。
私の最初の問題は、私のコールバックインターセプタスレッドがサーバからの(コールバック)接続を受け入れなかったことです。サーバーはクライアントに接続できないと言いました(ConnectException
Javaから、 "Connection refused"と言われました)。私はインストラクターのコードを変更することでこれを解決することができました。 connect
関数(図示せず)では、AF_INET
の代わりにAF_UNSPEC
を使用するようヒントを設定していました。そこで、Javaは自分のlocalhost IPが0:0:0:0:0:0:0:1
(IPv6形式)となっているのを見ていました。 Javaがコールバックを送信するために戻って接続しようとすると、例外を受け取りました(なぜIPv6アドレスに接続できないのかわかりません)。
この問題を修正した後、もう一度アプリを試してみましたが、今回はクライアントからサーバーからのコールバックを受信しました。ただし、以降のコールバックは機能しません。最初のコールバックを受信した後、ビジーループは(必要に応じて)実行し続けます。しかし、サーバーが2番目のコールバックを送信すると、クライアントはそれを読み取れないように見えます。サーバー側では、コールバックをクライアントに正常に送信したことがわかります。クライアントがデータの読み込みに問題があるだけです。私はデバッグのために(上記参照)いくつかのprint文を追加し、これは私が得るものです:ここでは
Client waiting for callbacks on port 2020
Connection accepted!
Inner loop
Received from Server |A callback from server to 127.0.0.1:2020|
Got a callback from server
Going to sleep now
Inner loop
Received from Server ||
Going to sleep now
Inner loop
Received from Server ||
Going to sleep now
Inner loop
... (and it keeps going regardless of the second callback being sent)
は、私は(GUI)からスレッドを開始しています方法です:
CallbackInterceptorThread* callbackInterceptorThread = [[CallbackInterceptorThread alloc] initWithClientPort: clientPort appDelegate: self];
[callbackInterceptorThread start];
正しいIPとポートにバインドしていて、クライアントが実際に接続していることを確認してください。それは間違っていると私の推測だろう。 'ハングする'と思われる理由は、あなたのソケットがブロッキング用に設定され、accept callブロックが設定されているためです。これは、以前にバインドしたソケットに何かが実際に接続しようとするまで、 –
@Jasonはい、私はそれがブロッキングコールであると考えました(そして、それは私が欲しいものです)。あなたは部分的に正しいと思われる:)それは、IPv4の代わりに(サーバ側から)IPv6アドレスにバインドしようとしていて、それが問題を引き起こしているようだ。私はその問題を修正することができましたが、今は後続コールバックの問題が働いていません。 –