Socket编程

将CocoaAsyncSocket项目中的.h和.m文件拖拽到自己项目的Classes目录中
添加framework:CFNetwork

编写一个简单地TCP链接
HTTP其实就是建立在TCP协议上的,这里就用向网站发送请求和获得相应来演示。为了形象说明,先手工模拟一下HTTP,这需要用到telnet工具发出socket消息,并接收socket返回信息。下面说一下用CocoaAsyncSocket来实现的步骤:
首先是要实现相关的delegate:
#import <UIKit/UIKit.h>
#import "AsyncSocket.h"
@interface SocketDemosViewController : UIViewController<AsyncSocketDelegate>
然后,在实现代码中:
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port{
    NSLog(@"did connect to host");
}

- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    NSLog(@"did read data");
    NSString* message = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
    NSLog(@"message is: \n%@",message);
}
AsyncSocketDelegate中得代理方法都是可选的,我这里只实现了对建立连接后以及读取数据的监听。这些监听需要创建和使用AsyncSocket实例时才能被用到。
- (void)viewDidLoad {
    [super viewDidLoad];
   
    AsyncSocket *socket=[[AsyncSocket alloc] initWithDelegate:self];
    [socket connectToHost:@"www.baidu.com" onPort:80 error:nil];
   
    [socket readDataWithTimeout:3 tag:1];
    [socket writeData:[@"GET / HTTP/1.1\n\n" dataUsingEncoding:NSUTF8StringEncoding] withTimeout:3 tag:1];
我把这部分代码直接写到controller的viewDidLoad中了。
执行的日志如下:
2011-07-19 17:17:46.545 SocketDemos[27120:207] did connect to host
2011-07-19 17:17:46.620 SocketDemos[27120:207] did read data
2011-07-19 17:17:46.621 SocketDemos[27120:207] message is:
HTTP/1.1 200 OK
Date: Tue, 19 Jul 2011 09:17:46 GMT
Server: BWS/1.0
Content-Length: 7691
Content-Type: text/html;charset=gb2312
Cache-Control: private
Expires: Tue, 19 Jul 2011 09:17:46 GMT
Set-Cookie: BAIDUID=9389BA38262D7997D220A564154CCA87:FG=1; expires=Tue, 19-Jul-41 09:17:46 GMT; path=/; domain=.baidu.com
P3P: CP=" OTI DSP COR IVA OUR IND COM "
Connection: Keep-Alive
这里的HTTP响应被截断了,因为我们不是要编写真的接收HTTP响应的功能,HTTP请求是由服务器端来关闭的,因此,HTTP响应没有完全接收下来,服务器端未断掉连接,可以在客户端关闭连接。
[socket readDataWithTimeout:3 tag:1];
[socket writeData:[@"GET / HTTP/1.1\n\n" dataUsingEncoding:NSUTF8StringEncoding] withTimeout:3 tag:1];
[socket disconnect];
另外,可以实现delegate中的这个方法:
- (void)onSocketDidDisconnect:(AsyncSocket *)sock{
    NSLog(@"socket did disconnect");
}
这样就可以在日志中监控到关闭连接的信息。

socket连接经常遇到读取固定长度的字节的需求,下面通过下面的实例实现。还是基于HTTP连接做演示(比如取2次,每次50字节,然后停止socket)。可以这样写:
- (void)viewDidLoad {
    [super viewDidLoad];
   
    socket=[[AsyncSocket alloc] initWithDelegate:self];
    [socket connectToHost:@"www.baidu.com" onPort:80 error:nil];
   
    data=[[NSMutableData dataWithLength:50] retain];
   
    [socket readDataToLength:50 withTimeout:5 tag:1];
    [socket readDataToLength:50 withTimeout:5 tag:2];
    [socket writeData:[@"GET / HTTP/1.1\n\n" dataUsingEncoding:NSUTF8StringEncoding] withTimeout:3 tag:1];
在delegate中,主要是这个方法起作用:

- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)_data withTag:(long)tag{
    NSLog(@"did read data");
    NSString* message = [[[NSString alloc] initWithData:_data encoding:NSUTF8StringEncoding] autorelease];
    NSLog(@"message is: \n%@",message);
   
    if (tag==2) {
        [socket disconnect];
    }
}

日志类似这样:


标红色的是两次取出的字节内容。



编写服务器端Socket

编写了Echo示例,说明最简单的服务器端Socket写法。Echo就是回声,通过telnet发送什么,服务器端就返回什么。类似这样:


服务器端,需要监听客户端的连接。等待客户端发来信息。代码是这样的:

socket=[[AsyncSocket alloc] initWithDelegate:self];
NSError *err = nil;

if ([socket acceptOnPort:4322 error:&err]) {
    NSLog(@"accept ok.");
}else {
    NSLog(@"accept failed.");
}

if (err) {
    NSLog(@"error: %@",err);
}



这一步如果成功,应该只有一个日志信息:

2011-07-20 12:27:03.228 SocketDemos[611:707] accept ok.

这时如果有客户端与之建立连接,比如通过telnet。会依次调用AsyncSocket 的delegate的如下方法:

onSocket:didAcceptNewSocket: AsyncSocket创建了新的Socket用于处理和客户端的请求,如果这个新socket实例你不打算保留(retain),那么将拒绝和该客户端连接
onSocket:wantsRunLoopForNewSocket:,提供线程的runloop实例给AsyncSocket,后者将使用这个runloop执行socket通讯的操作
onSocketWillConnect:,将要建立连接,这时可以做一些准备工作,如果需要的话
onSocket:didConnectToHost:port:,这个方法是建立连接后执行的,一般会在这里调用写入或者读取socket的操作
在Echo示例中,不打算执行多线程,也不想支持多客户端连接,而且服务器端和客户端将建立长连接。直至客户端断开连接,服务器端才释放相应的socket。

代码如下:

- (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket{
    if (!acceptSocket) {
        acceptSocket=[newSocket retain];
        NSLog(@"did accept new socket");
    }
}

- (NSRunLoop *)onSocket:(AsyncSocket *)sock wantsRunLoopForNewSocket:(AsyncSocket *)newSocket{
    NSLog(@"wants runloop for new socket.");
    return [NSRunLoop currentRunLoop];
}

- (BOOL)onSocketWillConnect:(AsyncSocket *)sock{
    NSLog(@"will connect");
    return YES;
}

- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port{
    NSLog(@"did connect to host");
    [acceptSocket readDataWithTimeout:-1 tag:1];
}

- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    NSLog(@"did read data");
    NSString* message = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
    NSLog(@"message is: \n%@",message);
    [acceptSocket writeData:data withTimeout:2 tag:1];
}

- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag{
    NSLog(@"message did write");
    [acceptSocket readDataWithTimeout:-1 tag:1];
}

- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err{
    NSLog(@"onSocket:%p willDisconnectWithError:%@", sock, err);
}

- (void)onSocketDidDisconnect:(AsyncSocket *)sock{
    NSLog(@"socket did disconnect");
    [acceptSocket release];
    acceptSocket=nil;
}



这里timeout设置为-1,这样就可以保持长连接状态。



编写简单的UDP应用

首先,编写发送UDP数据报的示例。这需要有个服务器端能接收到内容。用Java写了个简单的接收端:

public static void main(String[] args) throws IOException {
    InetSocketAddress address = new InetSocketAddress("0.0.0.0", 5555);
    DatagramSocket datagramSocket=new DatagramSocket(address);
   
    System.out.println("start udp server");
   
    byte[] buffer=new byte[1024];
   
    for(;;){
        DatagramPacket datagramPacket=new DatagramPacket(buffer, buffer.length);
        datagramSocket.receive(datagramPacket);
        System.out.println("receive data:"+new String(datagramPacket.getData(),0,datagramPacket.getLength()));
    }
}



下面写发送的代码:

AsyncUdpSocket *socket=[[AsyncUdpSocket alloc]initWithDelegate:self];

NSData *data=[@"Hello from iPhone" dataUsingEncoding:NSUTF8StringEncoding];
[socket sendData:data toHost:@"192.168.0.165" port:5555 withTimeout:-1 tag:1];
NSLog(@"send upd complete.");



执行后,在接收端成功输出如下内容:


下面,写个接收端的代码:

AsyncUdpSocket *socket=[[AsyncUdpSocket alloc] initWithDelegate:self];

NSError *error = nil;
[socket bindToPort:5555 error:&error];

if (error) {
    NSLog(@"error: %@",error);
}

[socket receiveWithTimeout:-1 tag:1];
NSLog(@"start udp server");

另外,至少写这个delegate方法:

- (BOOL)onUdpSocket:(AsyncUdpSocket *)sock
     didReceiveData:(NSData *)data
            withTag:(long)tag
           fromHost:(NSString *)host
               port:(UInt16)port{
    NSLog(@"received data: %@",[[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]);
    return YES;
}



发送端,还是用java写个测试代码:

public static void main(String[] args) throws IOException {
    DatagramSocket datagramSocket = new DatagramSocket();
    byte[] buffer = "Hello!".getBytes();
    DatagramPacket datagramPacket = new DatagramPacket(buffer,
            buffer.length, new InetSocketAddress("192.168.0.144", 5555));
    datagramSocket.send(datagramPacket);
}



在iPhone日志中:

2011-07-20 15:23:33.571 SocketDemos[795:707] start udp server
2011-07-20 15:23:47.395 SocketDemos[795:707] received data: Hello!

收到了数据报。



使用UDP发送和接收组播

这里主要关注的是接收,一方面是需求上要求,另一方面,碰到过Android Wifi获取组播问题,担心iOS也有类似的机制。后来测试发现没有那么麻烦(打开组播锁)。

为了测试,还是用java编写了个发送UDP广播的简单代码:

public static void main(String[] args) throws IOException {
    int port=3333;
    MulticastSocket socket=new MulticastSocket(port);
    InetAddress address=InetAddress.getByName("239.0.0.1");
    socket.joinGroup(address);
    byte[] data="Hello everyone.".getBytes();
    DatagramPacket datagramPacket=new DatagramPacket(data,data.length,address,port);
    socket.send(datagramPacket);
    System.out.println("send ok.");



编写的iOS代码:

AsyncUdpSocket *socket=[[AsyncUdpSocket alloc] initWithDelegate:self];

NSError *error = nil;
[socket bindToPort:3333 error:&error];
[socket enableBroadcast:YES error:&error];
[socket joinMulticastGroup:@"239.0.0.1" error:&error];

if (error) {
    NSLog(@"error: %@",error);
}

[socket receiveWithTimeout:-1 tag:1];
NSLog(@"start udp server");



delegate和上面接收普通UDP一模一样:

- (BOOL)onUdpSocket:(AsyncUdpSocket *)sock
     didReceiveData:(NSData *)data
            withTag:(long)tag
           fromHost:(NSString *)host
               port:(UInt16)port{
    NSLog(@"received data: %@",[[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]);
    return YES;
}



测试得到的日志:

2011-07-20 16:14:30.338 SocketDemos[860:707] start udp server
2011-07-20 16:14:42.829 SocketDemos[860:707] received data: Hello everyone.

说明是收到了。

发送组播和前面的UDP发送类似,只是多了要做join group的操作。这里就不多说了。


高级iOS开发技术交流群:415239068,欢迎大家加入



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值