バックグラウンド
私はソケットとネットワーク プログラミングについて、さまざまなサンプル コードと古典的なUnix ネットワーク プログラミングの教科書から独学で学んできましたが、同時に、現在取り組んでいるアプリでその知識を活用しようとしています。私は現在、単純なクライアント サーバー セットアップを必要とするアプリケーションの一部に取り組んでいます。
これが現在の様子です(まあ、どうあるべきか):
- サーバーはそれ自体を公開し
NSNetService
、使用してソケットを作成しますCFSocketCreateWithNative()
- クライアントはサーバーを見つけます
NSNetServiceBrowser
- クライアントは検出されたサービスを解決します
- サーバーは CFSocket からコールバックを取得し
MyConnection
、接続を処理するクラス ( ) の新しいインスタンスを作成します。接続の読み取りおよび書き込みストリームは、 で取得されCFStreamCreatePairWithSocket()
ます。 - クライアントがサーバーにメッセージを送る (@"hi")
- サーバーは、クライアントから受け取ったデータをクライアントに送り返します(これが私の問題です)
- クライアントは文字列を UIAlertView に表示します
2 つの質問
以下の接続コードで注釈が付けられているように、サーバーからクライアントにデータを送り返そうとすると、「操作が進行中です」というエラーが表示されます。これは、
NSOutputStream
利用可能なスペースがないためだと思います。これに対処する最善の方法は何ですか?イベントを待つ必要があることはわかっていますが、NSStreamEventHasSpaceAvailable
イベントが発生していないようです...<br> 編集: 当然... このエラーが発生したとき、iPhone シミュレーターのみでアプリをテストし、それをサーバーとして機能させていました。私はまだ新しいアパートにインターネットを持っていなかったので、クライアント:P 2 つの実際のデバイスを使用する場合は問題にならないようです。各接続オブジェクトからのデータの送受信が、他の接続オブジェクトからのデータの送受信をブロックしないように、このサーバーを作成することはできますか? 新しい接続オブジェクトはそれぞれ、新しい実行ループまたはスレッドなどに配置する必要がありますか? 私はリンゴの同時実行ドキュメントを釣り上げましたが、何も飛び出していません...目標は、サーバーに接続されている他のクライアントの数に関係なく、できるだけ早くクライアントに応答を送信することです。
更新: このサーバーへの同時接続を許可する代わりに、各クライアントに送信する必要があるデータの量が非常に少ないため、接続をキューに入れ、一度に 1 つずつ処理することを検討しています。これは最善の決定ですか?キューに何百ものクライアントがある場合はどうなりますか? 再考すると、接続の確立には高速なローカル ネットワークでは 1 ~ 2 秒かかり、Bluetooth ではさらに時間がかかるため、これは悪い考えかもしれません... この問題について専門家のアドバイスをいただければ幸いです :)
関連コード
注: APNetService および APNetServiceBrowser は、NSNetService および NSNetServiceBrowser に類似しています。
サーバーコード
- (void) startServerForGroup:(NSString *)name
{
self.groupName = name;
NSInteger port = [self prepareListeningSocket];
self.service = [[APNetService alloc] initWithDomain:@"local."
type:@"_example._tcp."
name:self.groupName
port:port];
self.service.delegate = self;
[self.service publish];
}
- (NSInteger) prepareListeningSocket
{
int listenfd, err, junk, port;
BOOL success;
struct sockaddr_in addr;
port = 0;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
success = (listenfd != -1);
if (success) {
bzero(&addr, sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET;
addr.sin_port = 0;
addr.sin_addr.s_addr = INADDR_ANY;
err = bind(listenfd, (const struct sockaddr *) &addr, sizeof(addr));
success = (err == 0);
}
if (success) {
err = listen(listenfd, 5);
success = (err == 0);
}
if (success) {
socklen_t addrLen;
addrLen = sizeof(addr);
err = getsockname(listenfd, (struct sockaddr *) &addr, &addrLen);
success = (err == 0);
if (success) {
assert(addrLen == sizeof(addr));
port = ntohs(addr.sin_port);
}
}
if (success) {
CFSocketContext context = { 0,(__bridge void*) self, NULL, NULL, NULL };
CFSocketRef socket = CFSocketCreateWithNative(
NULL,
listenfd,
kCFSocketAcceptCallBack,
AcceptCallback,
&context
);
if (socket) {
self.listeningSocket = socket;
CFRelease(socket);
success = YES;
}
if (success) {
CFRunLoopSourceRef rls;
listenfd = -1;
rls = CFSocketCreateRunLoopSource(NULL, self.listeningSocket, 0);
assert(rls != NULL);
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
CFRelease(rls);
}
}
if ( success ) {
return port;
}
else {
NSLog(@"FAILED TO START SERVER");
if (listenfd != -1) {
junk = close(listenfd);
assert(junk == 0);
}
return -1;
}
}
#pragma mark - Callback
// Called by CFSocket when someone connects to the listening socket
static void AcceptCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
{
MyServer * obj;
obj = (__bridge MyServer *) info;
assert(s == obj->_listeningSocket);
MyConnection *newCon = [[MyConnection alloc] initWithFileDescriptor:*(int*)data];
[newCon startReceive];
//add the new connection object to the servers mutable array of connections
[obj.connections addObject:newCon];
}
接続コード
- (void) startReceive
{
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocket(NULL, self.fd, &readStream, &writeStream);
self.inputStream = (__bridge_transfer NSInputStream *) readStream;
self.outputStream = (__bridge_transfer NSOutputStream*) writeStream;
[self.inputStream setProperty:(id)kCFBooleanTrue forKey:(NSString *)kCFStreamPropertyShouldCloseNativeSocket];
[self.outputStream setProperty:(id)kCFBooleanTrue forKey:(NSString *)kCFStreamPropertyShouldCloseNativeSocket];
self.inputStream.delegate = self;
self.outputStream.delegate = self;
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.inputStream open];
[self.outputStream open];
}
#pragma mark - NSStreamDelegate
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode) {
case NSStreamEventHasBytesAvailable: {
NSInteger bytesRead;
uint8_t buffer[32768];
bytesRead = [self.inputStream read:buffer maxLength:sizeof(buffer)];
if (bytesRead == -1)...
else if (bytesRead == 0)...
else {
NSData *data = [NSData dataWithBytes:buffer length:bytesRead];
[self didReceiveData:data];
}
} break;
case NSStreamEventHasSpaceAvailable: {
self.space = YES;
} break;
. . .
}
}
- (void) didReceiveData:(NSData *)data
{
if (self.space)
NSLog(@"SPACE");
else
NSLog(@"NO SPACE"); //this gets printed
NSInteger i = [self.outputStream write:data.bytes maxLength:data.length];
if (i < 0) {
printf("%s",strerror(errno)); //"Operation now in progress" error
}
}
クライアントコード
#pragma mark - APNetServiceBrowserDelegate
- (void) browser:(APNetServiceBrowser *)browser didAddService:(APNetService *)service moreComing:(BOOL)moreComing
{
//omitting checks that determine which server to connect to, if multiple
service.delegate = self;
[service resolveWithTimeout:20];
}
#pragma mark - APNetServiceDelegate
- (void) netServiceDidResolveAddress:(APNetService *)service
{
NSInputStream *input;
NSOutputStream *output;
[service getInputStream:&input outputStream:&output];
self.inputStream = input;
self.outputStream = output;
self.inputStream.delegate = self;
self.outputStream.delegate = self;
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.inputStream open];
[self.outputStream open];
}
#pragma mark - NSStreamDelegate
- (void) stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode) {
case NSStreamEventHasBytesAvailable: {
NSInteger bytesRead;
uint8_t buffer[32768];
bytesRead = [self.inputStream read:buffer maxLength:sizeof(buffer)];
if (bytesRead == -1) NSLog(@"Error reading data");
else if (bytesRead == 0) NSLog(@"no bytes read");
else {
NSData *data = [NSData dataWithBytes:buffer length:bytesRead];
[self didReceiveData:data];
}
} break;
case NSStreamEventHasSpaceAvailable: {
if (!self.isWaitingForReply) {
[self sendHelloMessage];
}
} break;
//omitted other NSStreamEvents
}
}
- (void) sendHelloMessage
{
NSData *d = [NSKeyedArchiver archivedDataWithRootObject:@"hi"];
[self.outputStream write:d.bytes maxLength:d.length];
self.isWaiting = YES;
}
- (void) didReceiveData:(NSData *)data
{
NSString *string = [NSKeyedUnarchiver unarchiveObjectWithData:data];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Message"
message:string
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
}