用 C 语言实现的 服务端 和客户端 可以研究一下 udp广播模式

之前在os x和ios上都写过一些简单的网络通信程序,都是用的基于c的bsd socket,因为之前在linux,windows上写过很多网络通信程序,都是用的c语言版本的socket,所以os x/ios上也用这套东西了。用基本的bsd socket,比较灵活,但是相对也比较难控制一点,需要关注很多细节,也比较繁琐。其实每个平台上面,都有一些封装好的类库,比如windows上有mfc,甚至boost,当然boost是跨平台的,linux上也可以用。os x/ios上则有cfnetwork,还有cocoa的stream等等。昨天下午突然有兴趣,就看了一下cfnetwork和stream,写了个简单的测试程序。


服务端

服务端采用cfnetwork写的,结合了run-loop。先看看监听socket的创建,整体步骤跟c语言的差不多,了不起就是用不同的函数。

[cpp]  view plain  copy
  1. - (BOOL)createServer  
  2. {  
  3.      PART 1: Create a socket that can accept connections  
  4.       
  5.     // Socket context  
  6.     //  struct CFSocketContext {  
  7.     //   CFIndex version;  
  8.     //   void *info;  
  9.     //   CFAllocatorRetainCallBack retain;  
  10.     //   CFAllocatorReleaseCallBack release;  
  11.     //   CFAllocatorCopyDescriptionCallBack copyDescription;  
  12.     //  };  
  13.     CFSocketContext socketContext = {0, self, NULL, NULL, NULL};  
  14.       
  15.     listeningSocket = CFSocketCreate(  
  16.                                      kCFAllocatorDefault,  
  17.                                      PF_INET,        // The protocol family for the socket  
  18.                                      SOCK_STREAM,    // The socket type to create  
  19.                                      IPPROTO_TCP,    // The protocol for the socket. TCP vs UDP.  
  20.                                      kCFSocketAcceptCallBack,  // New connections will be automatically accepted and the callback is called with the data argument being a pointer to a CFSocketNativeHandle of the child socket.  
  21.                                      (CFSocketCallBack)&serverAcceptCallback,  
  22.                                      &socketContext );  
  23.       
  24.     // Previous call might have failed  
  25.     if ( listeningSocket == NULL ) {  
  26.         status = @"listeningSocket Not Created";  
  27.         return FALSE;  
  28.     }  
  29.     else {  
  30.         status = @"listeningSocket Created";  
  31.         int existingValue = 1;  
  32.           
  33.         // Make sure that same listening socket address gets reused after every connection  
  34.         setsockopt( CFSocketGetNative(listeningSocket),  
  35.                    SOL_SOCKET, SO_REUSEADDR, (void *)&existingValue,  
  36.                    sizeof(existingValue));  
  37.           
  38.           
  39.          PART 2: Bind our socket to an endpoint.  
  40.         // We will be listening on all available interfaces/addresses.  
  41.         // Port will be assigned automatically by kernel.  
  42.         struct sockaddr_in socketAddress;  
  43.         memset(&socketAddress, 0, sizeof(socketAddress));  
  44.         socketAddress.sin_len = sizeof(socketAddress);  
  45.         socketAddress.sin_family = AF_INET;   // Address family (IPv4 vs IPv6)  
  46.         socketAddress.sin_port = 0;           // Actual port will get assigned automatically by kernel  
  47.         socketAddress.sin_addr.s_addr = htonl(INADDR_ANY);    // We must use "network byte order" format (big-endian) for the value here  
  48.           
  49.         // Convert the endpoint data structure into something that CFSocket can use  
  50.         NSData *socketAddressData =  
  51.         [NSData dataWithBytes:&socketAddress length:sizeof(socketAddress)];  
  52.           
  53.         // Bind our socket to the endpoint. Check if successful.  
  54.         if ( CFSocketSetAddress(listeningSocket, (CFDataRef)socketAddressData) != kCFSocketSuccess ) {  
  55.             // Cleanup  
  56.             if ( listeningSocket != NULL ) {  
  57.                 status = @"Socket Not Binded";  
  58.                 CFRelease(listeningSocket);  
  59.                 listeningSocket = NULL;  
  60.             }  
  61.               
  62.             return FALSE;  
  63.         }  
  64.         status = @"Socket Binded";  
  65.           
  66.          PART 3: Find out what port kernel assigned to our socket  
  67.         // We need it to advertise our service via Bonjour  
  68.         NSData *socketAddressActualData = [(NSData *)CFSocketCopyAddress(listeningSocket) autorelease];  
  69.           
  70.         // Convert socket data into a usable structure  
  71.         struct sockaddr_in socketAddressActual;  
  72.         memcpy(&socketAddressActual, [socketAddressActualData bytes],  
  73.                [socketAddressActualData length]);  
  74.           
  75.         port = ntohs(socketAddressActual.sin_port);  
  76.           
  77.    //     char* ip = inet_ntoa(socketAddressActual.sin_addr);  
  78.           
  79.           
  80.          PART 4: Hook up our socket to the current run loop  
  81.         CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent();  
  82.         CFRunLoopSourceRef runLoopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, listeningSocket, 0);  
  83.         CFRunLoopAddSource(currentRunLoop, runLoopSource, kCFRunLoopCommonModes);  
  84.         CFRelease(runLoopSource);  
  85.           
  86.         KAppDelegate* d = (KAppDelegate*)[self delegate];  
  87.         [d ShowLog:[NSString stringWithFormat:@"Create server socket successfully, port: %d", port]];  
  88.   
  89.           
  90.         return TRUE;  
  91.     }  
  92. }  
基本步骤就是:

1. 创建一个监听socket

2. 绑定本地地址和端口

3. 绑定run-loop

注意:在CFSocketCreate中的参数kCFSocketAcceptCallBack,这里就是设置了一个回调,当有客户端连上来的时候,当前线程的run-loop将会被调用这个回调。

看看这个回调函数:

[cpp]  view plain  copy
  1. static void serverAcceptCallback(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {  
  2.       
  3.     // We can only process "connection accepted" calls here  
  4.     if ( type != kCFSocketAcceptCallBack ) {  
  5.         return;  
  6.     }  
  7.       
  8.     // for an AcceptCallBack, the data parameter is a pointer to a CFSocketNativeHandle  
  9.     CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle*)data;  
  10.       
  11.     KServer *server = (KServer*)info;  
  12.     [server handleNewNativeSocket:nativeSocketHandle];  
  13. }  
这个回调函数里面就是简单的调用另外一个函数:

[cpp]  view plain  copy
  1. // Handle new connections  
  2. - (void)handleNewNativeSocket:(CFSocketNativeHandle)nativeSocketHandle {  
  3.     ClientSocket* c = [[[ClientSocket alloc] init] autorelease];  
  4.     c.sock = nativeSocketHandle;  
  5.     [m_AllClients addObject:c];  
  6.       
  7.     uint8_t name[SOCK_MAXADDRLEN];  
  8.     socklen_t nameLen = sizeof(name);  
  9.     if (0 != getpeername(nativeSocketHandle, (struct sockaddr *)name, &nameLen)) {  
  10.         NSLog(@"error");  
  11.         exit(1);  
  12.     }  
  13.       
  14.     KAppDelegate* d = (KAppDelegate*)[self delegate];  
  15.     struct sockaddr_in* addr = (struct sockaddr_in *)name;  
  16.     [d ShowLog:[NSString stringWithFormat:@"connected, client: %s, %d", inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)]];  
  17.       
  18.     //写一些数据给客户端  
  19.     CFReadStreamRef iStream;  
  20.     CFWriteStreamRef oStream;  
  21.     // 创建一个可读写的socket连接  
  22.     CFStreamCreatePairWithSocket(kCFAllocatorDefault, nativeSocketHandle, &iStream, &oStream);  
  23.     if (iStream && oStream) {  
  24.         CFStreamClientContext streamContext = {0, self, NULL, NULL};  
  25.         if (!CFReadStreamSetClient(iStream, kCFStreamEventHasBytesAvailable,  
  26.                                    readStream, // 回调函数,当有可读的数据时调用  
  27.                                    &streamContext)){  
  28.             exit(1);  
  29.         }  
  30.           
  31.         if (!CFWriteStreamSetClient(oStream, kCFStreamEventCanAcceptBytes, writeStream, &streamContext)){  
  32.             exit(1);  
  33.         }  
  34.           
  35.         CFReadStreamScheduleWithRunLoop(iStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);  
  36.    //     CFWriteStreamScheduleWithRunLoop(oStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);  
  37.         CFReadStreamOpen(iStream);  
  38.    //     CFWriteStreamOpen(oStream);  
  39.           
  40.         NSString *stringTosend = @"Welcome CFSocker server";  
  41.         [self SendData:stringTosend];  
  42.     } else {  
  43.         close(nativeSocketHandle);  
  44.     }  
  45. }  
在这里,我们简单的读取了一下client的ip和端口,并且显示。

然后给新生成的用于处理远程client的socket创建一个readstream,并且集成到run-loop中,这样client有数据发过来的时候,run-loop就会触发一个响应。这个例子里面一旦有client连上,就先给client发一些信息。另外,我还将每个处理client的socket放到了一个array中。
看一下读取信息的回调函数

[cpp]  view plain  copy
  1. // 读取数据  
  2. void readStream(CFReadStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo) {  
  3.     UInt8 buff[255];  
  4.     CFReadStreamRead(stream, buff, 255);  
  5.   //  printf("received: %s", buff);  
  6.     KServer* context = (KServer*)clientCallBackInfo;  
  7.     [context ShowLog:[NSString stringWithFormat:@"recv: %s", buff]];  
  8. }  
收取数据后,打印一下。

哈哈,这个简单的server,基本就是这个样子。

其实,这个server例子只是用最最简单的cfnetwork函数创建了一个最最简单的server,这些代码还有很多问题,比如:

1. 各种网络错误处理,如果断线等

2. 并发性的问题

3. 甚至一些可能的内存泄漏

等等,还有很多其他的问题。anyway,这段代码只是用来演示基本的cfnetwork函数调用。


客户端

有了服务端,在创建一个客户端。客户端也可以用cfnetwork。

如:

[cpp]  view plain  copy
  1. -(void) CreateSocketClient: (NSString*) serverIP PORT: (in_port_t) port  
  2. {  
  3.     CFSocketContext sockContext = {0, // 结构体的版本,必须为0  
  4.         self,  // 一个任意指针的数据,可以用在创建时CFSocket对象相关联。这个指针被传递给所有的上下文中定义的回调。  
  5.         NULL, // 一个定义在上面指针中的retain的回调, 可以为NULL  
  6.         NULL, NULL};  
  7.       
  8.     _client = CFSocketCreate(  
  9.                    kCFAllocatorDefault,  
  10.                    PF_INET,        // The protocol family for the socket  
  11.                    SOCK_STREAM,    // The socket type to create  
  12.                    IPPROTO_TCP,    // The protocol for the socket. TCP vs UDP.  
  13.                    kCFSocketConnectCallBack,  // New connections will be automatically accepted and the callback is called with the data argument being a pointer to a CFSocketNativeHandle of the child socket.  
  14.                    (CFSocketCallBack)&TCPServerConnectCallBack,  
  15.                    &sockContext );  
  16.       
  17.       
  18.     if (_client != nil) {  
  19.         int existingValue = 1;  
  20.           
  21.         // Make sure that same listening socket address gets reused after every connection  
  22.         setsockopt( CFSocketGetNative(_client),  
  23.                    SOL_SOCKET, SO_REUSEADDR, (void *)&existingValue,  
  24.                    sizeof(existingValue));  
  25.   
  26.           
  27.         struct sockaddr_in addr4;   // IPV4  
  28.         memset(&addr4, 0, sizeof(addr4));  
  29.         addr4.sin_len = sizeof(addr4);  
  30.         addr4.sin_family = AF_INET;  
  31.         addr4.sin_port = htons(port);  
  32.         addr4.sin_addr.s_addr = inet_addr([serverIP UTF8String]);  // 把字符串的地址转换为机器可识别的网络地址  
  33.           
  34.         // 把sockaddr_in结构体中的地址转换为Data  
  35.         CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr4, sizeof(addr4));  
  36.         CFSocketConnectToAddress(_client, // 连接的socket  
  37.                                  address, // CFDataRef类型的包含上面socket的远程地址的对象  
  38.                                  -1  // 连接超时时间,如果为负,则不尝试连接,而是把连接放在后台进行,如果_socket消息类型为kCFSocketConnectCallBack,将会在连接成功或失败的时候在后台触发回调函数  
  39.                                  );  
  40.           
  41.         CFRunLoopRef cRunRef = CFRunLoopGetCurrent();    // 获取当前线程的循环  
  42.         // 创建一个循环,但并没有真正加如到循环中,需要调用CFRunLoopAddSource  
  43.         CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _client, 0);  
  44.         CFRunLoopAddSource(cRunRef, // 运行循环  
  45.                            sourceRef,  // 增加的运行循环源, 它会被retain一次  
  46.                            kCFRunLoopCommonModes  // 增加的运行循环源的模式  
  47.                            );  
  48.         CFRelease(sourceRef);  
  49.           
  50.                   
  51.   
  52.     }  
  53.       
  54.      
  55. }  
基本过程就是:

1. 创建一个socket

2. connect服务器

注意这次在CFSocketCreate里面,我们使用了kCFSocketConnectCallback的回调,这样当client的connect动作完成的时候,都会调用这个回调(无论成功与否)。

[cpp]  view plain  copy
  1. // socket回调函数的格式:  
  2. static void TCPServerConnectCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)  
  3. {  
  4.     if (data != NULL) {  
  5.         // 当socket为kCFSocketConnectCallBack时,失败时回调失败会返回一个错误代码指针,其他情况返回NULL  
  6.         NSLog(@"连接失败");  
  7.         return;  
  8.     }  
  9.       
  10.     KClient *client = (KClient *)info;  
  11.       
  12.     CFReadStreamRef iStream;  
  13.       
  14.     CFSocketNativeHandle h = CFSocketGetNative(socket);  
  15.     CFStreamCreatePairWithSocket(kCFAllocatorDefault, h, &iStream, nil);  
  16.     //   CFStreamCreatePairWithSocket(kCFAllocatorDefault, socket, &iStream, nil);  
  17.     if (iStream) {  
  18.         CFStreamClientContext streamContext = {0, NULL, NULL, NULL};  
  19.         if (!CFReadStreamSetClient(iStream, kCFStreamEventHasBytesAvailable |  
  20.                                    kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered,  
  21.                                    readStream, // 回调函数,当有可读的数据时调用  
  22.                                    &streamContext)){  
  23.             exit(1);  
  24.         }  
  25.           
  26.         CFReadStreamScheduleWithRunLoop(iStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);  
  27.   //      CFReadStreamOpen(iStream);  
  28.           
  29.         //       size_t sent = send(CFSocketGetNative(socket), "client sent", 11, 0);  
  30.           
  31.         char buf[100] = {0};  
  32.         //    recv(CFSocketGetNative(socket), buf, 100, 0);  
  33.           
  34.         printf("recv: %s", buf);  
  35.     }  
  36.   
  37.       
  38. }  
在这个回调里面如果使用recv可以接收到服务端发过来的信息,但是使用ReadStream好象收不到,我也不知道为什么,我没有花太多时间去研究它。如果哪位大哥能够指出错误的话,将不胜感激。

其实apple公司在Cocoa里面还提供了一些其他的用于网络通信的接口,比如NSStream,这个是基于objective-c语言的。使用也是相当的方便。

写了几行测试代码:

[cpp]  view plain  copy
  1. - (void)work_thread:(NSURL *)url  
  2. {  
  3.     NSInputStream * readStream;  
  4.     NSOutputStream* writeStream;  
  5.       
  6.     NSString* strHost = [url host];  
  7.  //   strHost = @"127.0.0.1";  
  8.     int port = [[url port] integerValue];  
  9.      
  10.     [NSStream getStreamsToHostNamed:strHost port: port inputStream:&readStream outputStream:&writeStream];  
  11.           
  12.     [readStream setDelegate:self];  
  13.     [readStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];  
  14.     [readStream open];  
  15.       
  16.     [writeStream open];  
  17.     [writeStream write:"abcd" maxLength:4];  
  18.     [[NSRunLoop currentRunLoop] run];  
  19. }  
这次我们将通信程序放到一个线程里面。上面就是一个线程函数,在这个函数里面,指定了服务端的ip和端口,并且创建了2个stream,一个input,一个output。然后集成到这个线程的run-loop里面。

为了接收nsstream的响应,必须给nsstream指定一个delegate,并且实现stream函数。

[cpp]  view plain  copy
  1. @interface KAppDelegate : NSObject <NSApplicationDelegate, NSStreamDelegate>  

实现:

[cpp]  view plain  copy
  1. - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode  
  2. {  
  3.     NSLog(@" >> NSStreamDelegate in Thread %@", [NSThread currentThread]);  
  4.       
  5.     switch (eventCode) {  
  6.         case NSStreamEventHasBytesAvailable: {  
  7.               
  8.               
  9.             uint8_t buf[100] = {0};  
  10.             int numBytesRead = [(NSInputStream *)stream read:buf maxLength:100];  
  11.               
  12.             NSString* str = [NSString stringWithFormat:@"recv: %s", buf];  
  13.             [self performSelectorOnMainThread:@selector(ShowLog:) withObject:str waitUntilDone:YES];  
  14.               
  15.             break;  
  16.         }  
  17.               
  18.         case NSStreamEventErrorOccurred: {  
  19.              
  20.             break;  
  21.         }  
  22.               
  23.         case NSStreamEventEndEncountered: {  
  24.               
  25.                          
  26.             break;  
  27.         }  
  28.               
  29.         default:  
  30.             break;  
  31.     }  
  32. }  
真的是相当的简单,在stream函数里面可以处理各种事件。

最后只要启动这个线程就可以了,如:

[cpp]  view plain  copy
  1. NSURL * url = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%@",  [self TextIP].stringValue, [self TextPort].stringValue]];  
  2.   
  3. NSThread * backgroundThread = [[NSThread alloc] initWithTarget:self  
  4.                                                       selector:@selector(work_thread:)  
  5.                                                         object:url];  
  6. [backgroundThread start];  

这个线程启动后,就会在线程函数里面创建inputstream和outputstream,这样就可以发送和读取数据了,使用NSStream,真的可以很轻松的实现网络通信,我们所要做的就是:

1. 创建发送和读取stream;

2. 实现一个delegate,用于处理各种网络事件。

所以,我们在写os x/ios网络程序的时候,应该尽量使用NSStream等这种高级的工具,因为apple公司已经帮我们处理了很多细节问题,可以节省很多的时间。

另外有一个需要注意的就是:NSStream并没有提供连接服务端的基于字符串的函数,没有关系,我们可以扩展一个,如下:

[cpp]  view plain  copy
  1. @implementation NSStream(StreamsToHost)  
  2.   
  3. + (void)getStreamsToHostNamed:(NSString *)hostName  
  4.                          port:(NSInteger)port  
  5.                   inputStream:(out NSInputStream **)inputStreamPtr  
  6.                  outputStream:(out NSOutputStream **)outputStreamPtr  
  7. {  
  8.     CFReadStreamRef     readStream;  
  9.     CFWriteStreamRef    writeStream;  
  10.       
  11.     assert(hostName != nil);  
  12.     assert( (port > 0) && (port < 65536) );  
  13.     assert( (inputStreamPtr != NULL) || (outputStreamPtr != NULL) );  
  14.       
  15.     readStream = NULL;  
  16.     writeStream = NULL;  
  17.       
  18.     CFStreamCreatePairWithSocketToHost(  
  19.                                        NULL,  
  20.                                        (__bridge CFStringRef) hostName,  
  21.                                        port,  
  22.                                        ((inputStreamPtr  != NULL) ? &readStream : NULL),  
  23.                                        ((outputStreamPtr != NULL) ? &writeStream : NULL)  
  24.                                        );  
  25.       
  26.     if (inputStreamPtr != NULL) {  
  27.         *inputStreamPtr  = CFBridgingRelease(readStream);  
  28.     }  
  29.       
  30.     if (outputStreamPtr != NULL) {  
  31.         *outputStreamPtr = CFBridgingRelease(writeStream);  
  32.     }  
  33. }  
  34.   
  35. @end  
通过这个函数,我们可以方便地通过字符串表示的ip和端口来连接服务端。

例子使用的OS X,其实在IOS里面也是类似的做法。

 

代码:http://download.csdn.net/detail/zj510/5388471

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值