基于Socket网络编程

    SimpleNetworkStreams展示了如何基于Socket网络编程,实现了一个很典型的局域网内网络数据传输的场景,一个是client向server端发送本地的图片文件,另一个是client向server端下载图片到本地文件。抽取出来的一般流程:s...

     SimpleNetworkStreams展示了如何基于Socket网络编程,实现了一个很典型的局域网内网络数据传输的场景,一个是client向server端发送本地的图片文件,另一个是client向server端下载图片到本地文件。抽取出来的一般流程:

  • server开启socket监听

此处IOS的一般做法是三步走:

第一步:创建系统级的socket,并绑定端口

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
port = 0;  
    fd = socket(AF_INET, SOCK_STREAM, 0);
    success = (fd != -1);
                                                                                                                                                                       
    if (success) {
        memset (&addr, 0, 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(fd, ( const struct sockaddr *) &addr, sizeof (addr));
        success = (err == 0);
    }
    if (success) {
        err = listen(fd, 5);
        success = (err == 0);
    }
    if (success) {
        socklen_t   addrLen;
        addrLen = sizeof (addr);
        err = getsockname(fd, ( struct sockaddr *) &addr, &addrLen);
        success = (err == 0);
                                                                                                                                                                           
        if (success) {
            assert (addrLen == sizeof (addr));
            port = ntohs(addr.sin_port);
        }
    }

这里用port=0是让系统自动随机找一个空闲端口。其他都是基于c风格对系统函数的直接调用。

第二步:用IOS的socket(CFSocket)包装系统socket


  
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CFSocketContext context = { 0, (__bridge void *) self, NULL, NULL, NULL };
assert (self->_listeningSocket == NULL);
self->_listeningSocket = CFSocketCreateWithNative(
     NULL,
     fd,
     kCFSocketAcceptCallBack,
     AcceptCallback,
     &context
);
success = (self->_listeningSocket != NULL);
if (success) {
     CFRunLoopSourceRef  rls;
                                                                                                                                                  
     fd = -1;        // listeningSocket is now responsible for closing fd
     rls = CFSocketCreateRunLoopSource(NULL, self.listeningSocket, 0);
     assert (rls != NULL);
                                                                                                                                                  
     CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
                                                                                                                                                  
     CFRelease(rls);
}

这里包装socket的目的是便于后面的事件侦听和处理,把基于原生态socket的开发转到IOS的层面上来,这里accept事件侦听函数是AcceptCallback,并在单独thread中执行。

第三步:通过NSNetService发布socket


  
  
1
2
3
4
5
6
7
8
9
10
11
if (success) {
     self.netService = [[NSNetService alloc] initWithDomain:@ "local." type:@ "_x-SNSUpload._tcp." name:@ "Test" port:port];
     success = (self.netService != nil);
}
if (success) {
     self.netService.delegate = self;
                                                                                                                                  
     [self.netService publishWithOptions:NSNetServiceNoAutoRename];
                                                                                                                                  
     // continues in -netServiceDidPublish: or -netService:didNotPublish: ...
}

这里是基于NSNetService把先前创建的socket发布出去,便于clienti连接和请求。

  • client发起socket连接请求

这里是client通过前面server发布出来了netservice,发起对socket的连接:

netService = [[NSNetService alloc] initWithDomain:@"local." type:@"_x-SNSUpload._tcp." name:@"Test"];

  • server监听并处理数据请求

server会在accept的事件侦听的回调函数里对socket打开stream,并侦听stream上的各种IO事件:

  
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
CFStreamCreatePairWithSocket(NULL, fd, &readStream, NULL);
assert (readStream != NULL);
self.networkStream = (__bridge NSInputStream *) readStream;
CFRelease(readStream);
[self.networkStream setProperty:(id)kCFBooleanTrue forKey:(NSString *)kCFStreamPropertyShouldCloseNativeSocket];
self.networkStream.delegate = self;
[self.networkStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.networkStream open];
- ( void )stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
     // An NSStream delegate callback that's called when events happen on our
     // network stream.
{
     assert (aStream == self.networkStream);
     #pragma unused(aStream)
     switch (eventCode) {
         case NSStreamEventOpenCompleted: {
             [self updateStatus:@ "Opened connection" ];
         } break ;
         case NSStreamEventHasBytesAvailable: {
             NSInteger       bytesRead;
             uint8_t         buffer[32768];
             [self updateStatus:@ "Receiving" ];
             // Pull some data off the network.
                                                                                                     
             bytesRead = [self.networkStream read:buffer maxLength: sizeof (buffer)];
             if (bytesRead == -1) {
                 [self stopReceiveWithStatus:@ "Network read error" ];
             } else if (bytesRead == 0) {
                 [self stopReceiveWithStatus:nil];
             } else {
                 NSInteger   bytesWritten;
                 NSInteger   bytesWrittenSoFar;
                 // Write to the file.
                                                                                                         
                 bytesWrittenSoFar = 0;
                 do {
                     bytesWritten = [self.fileStream write:&buffer[bytesWrittenSoFar] maxLength:bytesRead - bytesWrittenSoFar];
                     assert (bytesWritten != 0);
                     if (bytesWritten == -1) {
                         [self stopReceiveWithStatus:@ "File write error" ];
                         break ;
                     } else {
                         bytesWrittenSoFar += bytesWritten;
                     }
                 } while (bytesWrittenSoFar != bytesRead);
             }
         } break ;
         case NSStreamEventHasSpaceAvailable: {
             assert (NO);     // should never happen for the output stream
         } break ;
         case NSStreamEventErrorOccurred: {
             [self stopReceiveWithStatus:@ "Stream open error" ];
         } break ;
         case NSStreamEventEndEncountered: {
             // ignore
         } break ;
         default : {
             assert (NO);
         } break ;
     }
}

   以上代码是server监听到有数据发过来时,把数据写入本地文件中,这里实际上就是把网络inputstream写入File的outputstream。这里handleevent方法是当设置了self.networkStream.delegate = self后IO事件的回调函数,handleevent里就需要根据不同的事件类型进行不同的处理。

  • client发送和接受数据流

client的数据处理与server类似也是通过对网络stream的事件监听来完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
self.networkStream = output;
self.networkStream.delegate = self;
[self.networkStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.networkStream open];
- ( void )stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
     // An NSStream delegate callback that's called when events happen on our
     // network stream.
{
     assert (aStream == self.networkStream);
     #pragma unused(aStream)
     switch  (eventCode) {
         case  NSStreamEventOpenCompleted: {
             [self updateStatus:@ "Opened connection" ];
         break ;
         case  NSStreamEventHasBytesAvailable: {
             assert (NO);      // should never happen for the output stream
         break ;
         case  NSStreamEventHasSpaceAvailable: {
             [self updateStatus:@ "Sending" ];
                                                                             
             // If we don't have any data buffered, go read the next chunk of data.
                                                                             
             if  (self.bufferOffset == self.bufferLimit) {
                 NSInteger   bytesRead;
                                                                                 
                 bytesRead = [self.fileStream read:self.buffer maxLength:kSendBufferSize];
                                                                                 
                 if  (bytesRead == -1) {
                     [self stopSendWithStatus:@ "File read error" ];
                 else  if  (bytesRead == 0) {
                     [self stopSendWithStatus:nil];
                 else  {
                     self.bufferOffset = 0;
                     self.bufferLimit  = bytesRead;
                 }
             }
                                                                             
             // If we're not out of data completely, send the next chunk.
                                                                             
             if  (self.bufferOffset != self.bufferLimit) {
                 NSInteger   bytesWritten;
                 bytesWritten = [self.networkStream write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - self.bufferOffset];
                 assert (bytesWritten != 0);
                 if  (bytesWritten == -1) {
                     [self stopSendWithStatus:@ "Network write error" ];
                 else  {
                     self.bufferOffset += bytesWritten;
                 }
             }
         break ;
         case  NSStreamEventErrorOccurred: {
             [self stopSendWithStatus:@ "Stream open error" ];
         break ;
         case  NSStreamEventEndEncountered: {
             // ignore
         break ;
         default : {
             assert (NO);
         break ;
     }
}

这里的过程与server端正好相反,是从file的Inputstream中读入数据,并写入网络的outputsteam中。


IOS上的socket通信完整源码:

客户端

导入头文件:

#import <sys/socket.h>

#import <netinet/in.h>

#import <arpa/inet.h>

#import <unistd.h>

1. 创建连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
CFSocketContext sockContext = {0,  // 结构体的版本,必须为0
  self,
  // 一个任意指针的数据,可以用在创建时CFSocket对象相关联。这个指针被传递给所有的上下文中定义的回调。
  NULL,  // 一个定义在上面指针中的retain的回调, 可以为NULL
  NULL, NULL};
CFSocketRef _socket = (kCFAllocatorDefault,  // 为新对象分配内存,可以为nil
  PF_INET,  // 协议族,如果为0或者负数,则默认为PF_INET
  SOCK_STREAM,  // 套接字类型,如果协议族为PF_INET,则它会默认为SOCK_STREAM
  IPPROTO_TCP,  // 套接字协议,如果协议族是PF_INET且协议是0或者负数,它会默认为IPPROTO_TCP
  kCFSocketConnectCallBack,  // 触发回调函数的socket消息类型,具体见Callback Types
  TCPServerConnectCallBack,  // 上面情况下触发的回调函数
  &sockContext  // 一个持有CFSocket结构信息的对象,可以为nil
  );
if  (_socket != nil) {
     struct  sockaddr_in addr4;    // IPV4
     memset (&addr4, 0,  sizeof (addr4));
     addr4.sin_len =  sizeof (addr4);
     addr4.sin_family = AF_INET;
     addr4.sin_port = htons(8888);
     addr4.sin_addr.s_addr = inet_addr([strAddress UTF8String]);   // 把字符串的地址转换为机器可识别的网络地址
     // 把sockaddr_in结构体中的地址转换为Data
     CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr4,  sizeof (addr4));
     CFSocketConnectToAddress(_socket,  // 连接的socket
  address,  // CFDataRef类型的包含上面socket的远程地址的对象
  -1   // 连接超时时间,如果为负,则不尝试连接,而是把连接放在后台进行,如果_socket消息类型为
kCFSocketConnectCallBack,将会在连接成功或失败的时候在后台触发回调函数
  );
     CFRunLoopRef cRunRef = CFRunLoopGetCurrent();     // 获取当前线程的循环
     // 创建一个循环,但并没有真正加如到循环中,需要调用CFRunLoopAddSource
     CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0);
     CFRunLoopAddSource(cRunRef,  // 运行循环
     sourceRef,   // 增加的运行循环源, 它会被retain一次
     kCFRunLoopCommonModes   // 增加的运行循环源的模式
     );
     CFRelease(courceRef);
}



2. 设置回调函数


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// socket回调函数的格式:
   static  void  TCPServerConnectCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef
address,  const  void  *data,  void  *info) {
   3      if  (data != NULL) {
   4          // 当socket为kCFSocketConnectCallBack时,失败时回调失败会返回一个错误代码指针,其他情况返
回NULL
   5         NSLog(@ "连接失败" );
   6          return ;
   7     }
   8     TCPClient *client = (TCPClient *)info;
   9      // 读取接收的数据
  10     [info performSlectorInBackground:@selector(readStream) withObject:nil];
  11 }
  12 3. 接收发送数据
  13  // 读取接收的数据
  14 - ( void )readStream {
  15      char  buffer[1024];
  16     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  17      while  (recv(CFSocketGetNative(_socket),  //与本机关联的Socket 如果已经失效返回-
1:INVALID_SOCKET
  18            buffer,  sizeof (buffer), 0)) {
  19         NSLog(@ "%@" , [NSString stringWithUTF8String:buffer]);
  20     }
  21 }
  22  // 发送数据
  23 - ( void )sendMessage {
  24     NSString *stringTosend = @ "你好" ;
  25      char  *data = [stringTosend UTF8String];
  26     send(SFSocketGetNative(_socket), data,  strlen (data) + 1, 0);
  27 }
     
28 服务器端:
  29 CFSockteRef _socket;
  30 CFWriteStreamRef outputStream = NULL;
  31  int  setupSocket() {
  32     _socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP,
kCFSocketAcceptCallBack, TCPServerAcceptCallBack, NULL);
  33      if  (NULL == _socket) {
  34         NSLog(@ "Cannot create socket!" );
  35          return  0;
  36     }
  37  
  38      int  optval = 1;
  39     setsockopt(CFSocketGetNative(_socket), SOL_SOCKET, SO_REUSEADDR,  // 允许重用本地地址和端口
  40 ( void  *)&optval,  sizeof (optval));
  41  
  42      struct  sockaddr_in addr4;
  43      memset (&addr4, 0,  sizeof (addr4));
  44     addr4.sin_len =  sizeof (addr4);
  45     addr4.sin_family = AF_INET;
  46     addr4.sin_port = htons(port);
  47     addr4.sin_addr.s_addr = htonl(INADDR_ANY);
  48     CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr4,  sizeof (addr4));
  49  
  50      if  (kCFSocketSuccess != CFSocketSetAddress(_socket, address)) {
  51         NSLog(@ "Bind to address failed!" );
  52          if  (_socket)
  53              CFRelease(_socket);
  54         _socket = NULL;
  55          return  0;
  56     }
  57      
  58     CFRunLoopRef cfRunLoop = CFRunLoopGetCurrent();
  59     CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0);
  60     CFRunLoopAddSource(cfRunLoop, source, kCFRunLoopCommonModes);
  61     CFRelease(source);
  62  
  63      return  1;
  64 }
  65  // socket回调函数,同客户端
  66  void  TCPServerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address,
const  void  *data,  void  *info) {
  67      if  (kCFSocketAcceptCallBack == type) {
  68          // 本地套接字句柄
  69         CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
  70         uint8_t name[SOCK_MAXADDRLEN];    
  71         socklen_t nameLen =  sizeof (name);
  72          if  (0 != getpeername(nativeSocketHandle, ( struct  sockaddr *)name, &nameLen)) {
  73             NSLog(@ "error" );
  74              exit (1);
  75         }
  76         NSLog(@ "%@ connected." , inet_ntoa( (( struct  sockaddr_in *)name)->sin_addr )):
  77         CFReadStreamRef iStream;
  78         CFWriteStreamRef oStream;
  79          // 创建一个可读写的socket连接
  80         CFStreamCreatePairWithSocket(kCFAllocatorDefault, nativeSocketHandle, &iStream,
&oStream);
  81          if  (iStream && oStream) {
  82             CFStreamClientContext streamContext = {0, NULL, NULL, NULL};
  83              if  (!CFReadStreamSetClient(iStream, kCFStreamEventHasBytesAvaiable,
  84                                        readStream,  // 回调函数,当有可读的数据时调用
  85                                        &streamContext)){
  86                  exit (1);
  87             }
  88              if  (!CFReadStreamSetClient(iStream, kCFStreamEventCanAcceptBytes, writeStream,
&streamContext)){
  89                  exit (1);
  90             }
  91             CFReadStreamScheduleWithRunLoop(iStream, CFRunLoopGetCurrent(),
kCFRunLoopCommomModes);
  92             CFWriteStreamScheduleWithRunLoop(wStream, CFRunLoopGetCurrent(),
kCFRunLoopCommomModes);
  93             CFReadStreamOpen(iStream);
  94             CFWriteStreamOpen(wStream);
  95         }  else  {
  96              close(nativeSocketHandle);
  97         }
  98     }
  99 }
100  // 读取数据
101  void  readStream(CFReadStreamRef stream, CFStreamEventType eventType,  void  *clientCallBackInfo) {
102     UInt8 buff[255];
103     CFReadStreamRead(stream, buff, 255);
104      printf ( "received: %s" , buff);
105 }
106  void  writeStream (CFWriteStreamRef stream, CFStreamEventType eventType,  void  *clientCallBackInfo)
{
107     outputStream = stream;
108 }
109 main {
110      char  *str =  "nihao" ;
111  
112      if  (outputStream != NULL) {
113         CFWriteStreamWrite(outputStream, str,  strlen (line) + 1);
114     }  else  {
115         NSLog(@ "Cannot send data!" );
116     }
117 }
118  // 开辟一个线程线程函数中
119  void  runLoopInThread() {
120      int  res = setupSocket();
121      if  (!res) {
122          exit (1);
123     }
124     CFRunLoopRun();     // 运行当前线程的CFRunLoop对象
125 }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值