iOS 客户端和服务端实现通信
2013-05-28 19:14
190 查看
CocoaAsyncSocket支持tcp和udp。其中:
AsyncSocket类是支持TCP的
AsyncUdpSocket是支持UDP的
AsyncSocket是封装了CFSocket和CFSteam的TCP/IP
socket网络库。它提供了异步操作,本地cocoa类的基于delegate的完整支持。主要有以下特性:
队列的非阻塞的读和写,而且可选超时。你可以调用它读取和写入,它会当完成后告知你自动的socket接收。如果你调用它接收连接,它将为每个连接启动新的实例,当然,也可以立即关闭这些连接委托(delegate)支持。错误、连接、接收、完整的读取、完整的写入、进度以及断开连接,都可以通过委托模式调用基于run
loop的,而不是线程的。虽然可以在主线程或者工作线程中使用它,但你不需要这样做。它异步的调用委托方法,使用NSRunLoop。委托方法包括socket的参数,可让你在多个实例中区分自包含在一个类中。你无需操作流或者socket,这个类帮你做了全部支持基于IPV4和IPV6的TCP流
AsyncUdpSocket是UDP/IP socket网络库,包装自CFSocket。它的工作很像TCP版本,只不过是用于处理UDP的。它包括基于非阻塞队列的发送接收操作,完整的委托支持,基于runloop,自包含的类,以及支持IPV4和IPV6。
以下内容是根据官方网站参考:https://github.com/robbiehanson/CocoaAsyncSocket/wiki
准备工作:如何在iOS项目中使用
基本上是两步:
将CocoaAsyncSocket项目中的.h和.m文件拖拽到自己项目的Classes目录中
添加framework:CFNetwork.framework Security.framework
编写个简单的TCP连接应用。HTTP其实就是建立在TCP协议上的。这里就用向网站发起请求和获得响应来演示。
为了形象说明,先手工模拟一下HTTP。这需要用到telnet工具,这是个命令行工具,如果在命令行里敲:
C:\Users\Marshal Wu>telnet
‘telnet’ 不是内部或外部命令,也不是可运行的程序或批处理文件。
说明你使用的是windows vista或者windows7,因为windows xp是默认安装该软件的。
我用的是Mac OSX,上面自带这个工具。如果你出现上面的问题,可参照vista下使用telnet的做法安装telnet。
然后,可以使用这个工具发出socket信息,并接收socket返回信息。下面说一下步骤,如图:
下面用CocoaAsyncSocket来实现。
首先是要实现相关的delegate:
[cpp] view plaincopy
#import <UIKit/UIKit.h>
#import "AsyncSocket.h"
@interface SocketDemosViewController : UIViewController<AsyncSocketDelegate>
[cpp] view plaincopy
<span style="font-family: SimSun;font-size:14px; line-height: 24px; background-color: rgb(249, 249, 249);">然后,在实现代码中:</span>
[cpp] view plaincopy
- (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实例时才能被用到。下面就是这部分代码:
[cpp] view plaincopy
- (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中了。
执行的日志如下:
[cpp] view plaincopy
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请求应该是由服务器端来关闭,比如使用telent访问看到的是这样的结尾:
因此,HTTP响应没有完全接收下来,服务器端未断掉连接。可以在客户端关闭连接,这样:
[cpp] view plaincopy
[socket readDataWithTimeout:3 tag:1];
[socket writeData:[@"GET / HTTP/1.1\n\n" dataUsingEncoding:NSUTF8StringEncoding] withTimeout:3 tag:1];
[socket disconnect];
另外,可以实现delegate中的这个方法:
[cpp] view plaincopy
- (void)onSocketDidDisconnect:(AsyncSocket *)sock{
NSLog(@"socket did disconnect");
}
这样就可以在日志中监控到关闭连接的信息。
socket连接,经常碰到这样的需求,读取固定长度的字节。这可以通过下面的示例实现。
还是基于HTTP连接做演示。比如取2次,每次50字节。然后停止socket。
可以这样写:
[cpp] view plaincopy
- (void)onSocketDidDisconnect:(AsyncSocket *)sock{
NSLog(@"socket did disconnect");
}
- (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中,主要是这个方法起作用:
[cpp] view plaincopy
- (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];
}
}
日志类似这样:
标红色的是两次取出的字节内容。
2.服务器端
参考了demo 自己整理的
部分关键代码
#import "UIDevice-Reachability.h"
#define WELCOME_MSG 0
#define ECHO_MSG 1
#define WARNING_MSG 2
#define READ_TIMEOUT 15.0
#define READ_TIMEOUT_EXTENSION 10.0
- (IBAction)start:(id)sender {
if(!isRunning)
{
int port = [self.airport.text intValue];//监听端口
if(port < 0 || port > 65535)
{
port = 0;
}
NSError *error = nil;
if(![listenSocket acceptOnPort:port error:&error])
{
//[self logError:FORMAT(@"Error starting server: %@", error)];
NSLog(@"%@",[error localizedDescription]);
return;
}
//[self logInfo:FORMAT(@"Echo server started on port %hu", [listenSocket localPort])];
NSLog(@"%@",[listenSocket localAddress]);
NSLog(@"%@",[listenSocket localHost]);
NSLog(@"%u",[listenSocket localPort]);
isRunning = YES;
[self.airport setEnabled:NO];
[self.start setTitle:@"stop" forState:(UIControlStateNormal)];
}
else
{
// Stop accepting connections
[listenSocket disconnect];
// Stop any client connections
NSUInteger i;
for(i = 0; i < [connectedSockets count]; i++)
{
// Call disconnect on the socket,
// which will invoke the onSocketDidDisconnect: method,
// which will remove the socket from the list.
[[connectedSockets objectAtIndex:i] disconnect];
}
//[self logInfo:@"Stopped Echo server"];
isRunning = false;
[self.airport setEnabled:YES];
[self.start setTitle:@"start" forState:(UIControlStateNormal)];
}
}
#pragma asysocketdelegate
- (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket
{
[connectedSockets addObject:newSocket];
}
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
//[self logInfo:FORMAT(@"Accepted client %@:%hu", host, port)];
NSLog(@"host:%@,port%u",host,port);
NSString *welcomeMsg = @"Server:Welcome to the AsyncSocket Echo Server\r\n";
NSData *welcomeData = [welcomeMsg dataUsingEncoding:NSUTF8StringEncoding];
[sock writeData:welcomeData withTimeout:-1 tag:WELCOME_MSG];
[sock readDataToData:[AsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:0];
}
- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag
{
if(tag == ECHO_MSG)
{
[sock readDataToData:[AsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:0];
}
}
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
NSData *strData = [data subdataWithRange:NSMakeRange(0, [data length] - 2)];
NSString *msg = [[NSString alloc] initWithData:strData encoding:NSUTF8StringEncoding];
if(msg)
{
NSLog(@"the messeaege from clinet%@",msg);
}
else
{
NSLog(@"Error converting received data into UTF-8 String");
}
// Even if we were unable to write the incoming data to the log,
// we're still going to echo it back to the client.
[sock writeData:data withTimeout:-1 tag:ECHO_MSG];
}
/**
* This method is called if a read has timed out.
* It allows us to optionally extend the timeout.
* We use this method to issue a warning to the user prior to disconnecting them.
**/
- (NSTimeInterval)onSocket:(AsyncSocket *)sock
shouldTimeoutReadWithTag:(long)tag
elapsed:(NSTimeInterval)elapsed
bytesDone:(NSUInteger)length
{
if(elapsed <= READ_TIMEOUT)
{
NSString *warningMsg = @"Are you still there?\r\n";
NSData *warningData = [warningMsg dataUsingEncoding:NSUTF8StringEncoding];
[sock writeData:warningData withTimeout:-1 tag:WARNING_MSG];
return READ_TIMEOUT_EXTENSION;
}
return 0.0;
}
- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err
{
//[self logInfo:FORMAT(@"Client Disconnected: %@:%hu", [sock connectedHost], [sock connectedPort])];
NSLog(@"%@",[err localizedDescription]);
}
- (void)onSocketDidDisconnect:(AsyncSocket *)sock
{
[connectedSockets removeObject:sock];
}
-(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;
}
onSocket:didAcceptNewSocket: AsyncSocket创建了新的Socket用于处理和客户端的请求,如果这个新socket实例你不打算保留(retain),那么将拒绝和该客户端连接
onSocket:wantsRunLoopForNewSocket:,提供线程的runloop实例给AsyncSocket,后者将使用这个runloop执行socket通讯的操作
onSocketWillConnect:,将要建立连接,这时可以做一些准备工作,如果需要的话
onSocket:didConnectToHost:port:,这个方法是建立连接后执行的,一般会在这里调用写入或者读取socket的操作
下面是跟终端(客户端)通信过程
设置一个端口开始监听
输出窗口输出一下信息
2013-04-01 14:32:44.201 testService[1951:f803] Current host: localhost
2013-04-01 14:32:44.202 testService[1951:f803] Local: 192.168.0.3
2013-04-01 14:32:44.203 testService[1951:f803] All: (
"192.168.0.3",
"10.0.2.1",
"169.254.201.77"
)
2013-04-01 14:32:52.466 testService[1951:f803] <1002225e 00000000 00000000 00000000>
2013-04-01 14:32:52.466 testService[1951:f803] 0.0.0.0
2013-04-01 14:32:52.466 testService[1951:f803] 8798
打开终端输入以下信息
输出窗口输出的日志
2013-04-01 14:34:21.405 testService[1951:f803] wants runloop for new socket.
2013-04-01 14:34:21.405 testService[1951:f803] will connect
2013-04-01 14:34:21.406 testService[1951:f803] host:192.168.0.3,port55706
2013-04-01 14:34:23.825 testService[1951:f803] the messeaege from clinethello
2013-04-01 14:34:25.992 testService[1951:f803] the messeaege from clinetjinjiantong
2013-04-01 14:34:38.464 testService[1951:f803] the messeaege from clinetmy name is jinjiantong
2013-04-01 14:35:01.064 testService[1951:f803] the messeaege from clinetis time out?
2013-04-01 14:35:26.149 testService[1951:f803] Read operation timed out
这样完成简单的完成了客户端和服务器端的通信过程
通信过程很简单,服务器端扳回客户端输入的内容
如何加入项目
1、下载地址:https://github.com/robbiehanson/CocoaAsyncSocket
2、下载之后导入AsyncUdpSocket.h和AsyncUdpSocket.m文件进入你的项目,本人使用xcode4.2,不加类库可以使用,如果需要请另行加入CFNetwork.framework框架。
3、如果你的项目使用ARC的话,应该已经可以正常编译了,如果你的项目不是使用的ARC,在AyncUdpSocket.m文件中有说明,
加入编译条件:在项目target -> build phases -> compile sources -> AsyncUdpSocket文件后面加入 -fobjc-arc ,这是为了使编译器编译的时候将此文件在arc的条件下编译。
参考:http://blog.csdn.net/bjx327660180/article/details/8105289
首先,编写发送UDP数据报的示例。这需要有个服务器端能接收到内容。用Node.Js写了个简单的接收端:
[javascript] view plaincopy
var dgram = require("dgram");
var server = dgram.createSocket("udp4");
server.on("message", function (msg, rinfo) {
console.log('server got data:{ message : ' + msg + ' }【' + rinfo.address + ":" + rinfo.port+ "】");
var message=new Buffer("333333");
server.send(message, 0, message.length, rinfo.port, rinfo.address, function(err, bytes){
if(err){
console.log('error : '+err);
}
});
});
server.on("listening", function () {
var address = server.address();
console.log("server listening " +
address.address + ":" + address.port);
});
server.bind(8080);
下面写发送的代码:
在.h文件里定义socket
[cpp] view plaincopy
#import "AsyncUdpSocket.h"
@interface ViewController : UIViewController{
AsyncUdpSocket *socket;
}
在.m文件里使用socket(在.m文件里定义socket时deleget将不起作用,所以:一定要把socket定义在.h文件里)
[cpp] view plaincopy
socket=[[AsyncUdpSocket alloc]initWithDelegate:self];
NSData *data=[@"Hello from iPhone" dataUsingEncoding:NSUTF8StringEncoding];
[socket sendData:data toHost:@"192.168.0.165" port:8080 withTimeout:-1 tag:1];
NSLog(@"send upd complete.");
下面,写个接收端的代码:
[cpp] view plaincopy
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方法:
[cpp] view plaincopy
- (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;
}
发送端,还是用Node.Js写个测试代码:
[javascript] view plaincopy
var dgram = require('dgram');
var client = dgram.createSocket("udp4");
var message = new Buffer('I\n');
client.send(message, 0, message.length, 8080, "localhost", function(err, bytes){
if(err){
console.log('error : '+err);
}else{
console.log('bytes : '+bytes);
}
client.close();
});
client.bind(8080);
在iPhone日志中:
收到了数据报。
这里主要关注的是接收,一方面是需求上要求,另一方面,碰到过Android Wifi获取组播问题,担心iOS也有类似的机制。后来测试发现没有那么麻烦(打开组播锁)。
为了测试,还是用java编写了个发送UDP广播的简单代码:
[java] view plaincopy
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代码:
在.h文件里定义socket
[cpp] view plaincopy
#import "AsyncUdpSocket.h"
@interface ViewController : UIViewController{
AsyncUdpSocket *socket;
}
在.m文件里使用socket(在.m文件里定义socket时deleget将不起作用,所以:一定要把socket定义在.h文件里)
[cpp] view plaincopy
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一模一样:
[cpp] view plaincopy
- (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;
}
测试得到的日志:
[cpp] view plaincopy
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.
AsyncSocket类是支持TCP的
AsyncUdpSocket是支持UDP的
AsyncSocket是封装了CFSocket和CFSteam的TCP/IP
socket网络库。它提供了异步操作,本地cocoa类的基于delegate的完整支持。主要有以下特性:
队列的非阻塞的读和写,而且可选超时。你可以调用它读取和写入,它会当完成后告知你自动的socket接收。如果你调用它接收连接,它将为每个连接启动新的实例,当然,也可以立即关闭这些连接委托(delegate)支持。错误、连接、接收、完整的读取、完整的写入、进度以及断开连接,都可以通过委托模式调用基于run
loop的,而不是线程的。虽然可以在主线程或者工作线程中使用它,但你不需要这样做。它异步的调用委托方法,使用NSRunLoop。委托方法包括socket的参数,可让你在多个实例中区分自包含在一个类中。你无需操作流或者socket,这个类帮你做了全部支持基于IPV4和IPV6的TCP流
AsyncUdpSocket是UDP/IP socket网络库,包装自CFSocket。它的工作很像TCP版本,只不过是用于处理UDP的。它包括基于非阻塞队列的发送接收操作,完整的委托支持,基于runloop,自包含的类,以及支持IPV4和IPV6。
以下内容是根据官方网站参考:https://github.com/robbiehanson/CocoaAsyncSocket/wiki
准备工作:如何在iOS项目中使用
基本上是两步:
将CocoaAsyncSocket项目中的.h和.m文件拖拽到自己项目的Classes目录中
添加framework:CFNetwork.framework Security.framework
编写简单的TCP连接
编写个简单的TCP连接应用。HTTP其实就是建立在TCP协议上的。这里就用向网站发起请求和获得响应来演示。为了形象说明,先手工模拟一下HTTP。这需要用到telnet工具,这是个命令行工具,如果在命令行里敲:
C:\Users\Marshal Wu>telnet
‘telnet’ 不是内部或外部命令,也不是可运行的程序或批处理文件。
说明你使用的是windows vista或者windows7,因为windows xp是默认安装该软件的。
我用的是Mac OSX,上面自带这个工具。如果你出现上面的问题,可参照vista下使用telnet的做法安装telnet。
然后,可以使用这个工具发出socket信息,并接收socket返回信息。下面说一下步骤,如图:
下面用CocoaAsyncSocket来实现。
首先是要实现相关的delegate:
[cpp] view plaincopy
#import <UIKit/UIKit.h>
#import "AsyncSocket.h"
@interface SocketDemosViewController : UIViewController<AsyncSocketDelegate>
[cpp] view plaincopy
<span style="font-family: SimSun;font-size:14px; line-height: 24px; background-color: rgb(249, 249, 249);">然后,在实现代码中:</span>
[cpp] view plaincopy
- (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实例时才能被用到。下面就是这部分代码:
[cpp] view plaincopy
- (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中了。
执行的日志如下:
[cpp] view plaincopy
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请求应该是由服务器端来关闭,比如使用telent访问看到的是这样的结尾:
因此,HTTP响应没有完全接收下来,服务器端未断掉连接。可以在客户端关闭连接,这样:
[cpp] view plaincopy
[socket readDataWithTimeout:3 tag:1];
[socket writeData:[@"GET / HTTP/1.1\n\n" dataUsingEncoding:NSUTF8StringEncoding] withTimeout:3 tag:1];
[socket disconnect];
另外,可以实现delegate中的这个方法:
[cpp] view plaincopy
- (void)onSocketDidDisconnect:(AsyncSocket *)sock{
NSLog(@"socket did disconnect");
}
这样就可以在日志中监控到关闭连接的信息。
TCP连接读取指定长度的数据
socket连接,经常碰到这样的需求,读取固定长度的字节。这可以通过下面的示例实现。还是基于HTTP连接做演示。比如取2次,每次50字节。然后停止socket。
可以这样写:
[cpp] view plaincopy
- (void)onSocketDidDisconnect:(AsyncSocket *)sock{
NSLog(@"socket did disconnect");
}
- (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中,主要是这个方法起作用:
[cpp] view plaincopy
- (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];
}
}
日志类似这样:
标红色的是两次取出的字节内容。
2.服务器端
参考了demo 自己整理的
部分关键代码
#import "UIDevice-Reachability.h"
#define WELCOME_MSG 0
#define ECHO_MSG 1
#define WARNING_MSG 2
#define READ_TIMEOUT 15.0
#define READ_TIMEOUT_EXTENSION 10.0
- (IBAction)start:(id)sender {
if(!isRunning)
{
int port = [self.airport.text intValue];//监听端口
if(port < 0 || port > 65535)
{
port = 0;
}
NSError *error = nil;
if(![listenSocket acceptOnPort:port error:&error])
{
//[self logError:FORMAT(@"Error starting server: %@", error)];
NSLog(@"%@",[error localizedDescription]);
return;
}
//[self logInfo:FORMAT(@"Echo server started on port %hu", [listenSocket localPort])];
NSLog(@"%@",[listenSocket localAddress]);
NSLog(@"%@",[listenSocket localHost]);
NSLog(@"%u",[listenSocket localPort]);
isRunning = YES;
[self.airport setEnabled:NO];
[self.start setTitle:@"stop" forState:(UIControlStateNormal)];
}
else
{
// Stop accepting connections
[listenSocket disconnect];
// Stop any client connections
NSUInteger i;
for(i = 0; i < [connectedSockets count]; i++)
{
// Call disconnect on the socket,
// which will invoke the onSocketDidDisconnect: method,
// which will remove the socket from the list.
[[connectedSockets objectAtIndex:i] disconnect];
}
//[self logInfo:@"Stopped Echo server"];
isRunning = false;
[self.airport setEnabled:YES];
[self.start setTitle:@"start" forState:(UIControlStateNormal)];
}
}
#pragma asysocketdelegate
- (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket
{
[connectedSockets addObject:newSocket];
}
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
//[self logInfo:FORMAT(@"Accepted client %@:%hu", host, port)];
NSLog(@"host:%@,port%u",host,port);
NSString *welcomeMsg = @"Server:Welcome to the AsyncSocket Echo Server\r\n";
NSData *welcomeData = [welcomeMsg dataUsingEncoding:NSUTF8StringEncoding];
[sock writeData:welcomeData withTimeout:-1 tag:WELCOME_MSG];
[sock readDataToData:[AsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:0];
}
- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag
{
if(tag == ECHO_MSG)
{
[sock readDataToData:[AsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:0];
}
}
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
NSData *strData = [data subdataWithRange:NSMakeRange(0, [data length] - 2)];
NSString *msg = [[NSString alloc] initWithData:strData encoding:NSUTF8StringEncoding];
if(msg)
{
NSLog(@"the messeaege from clinet%@",msg);
}
else
{
NSLog(@"Error converting received data into UTF-8 String");
}
// Even if we were unable to write the incoming data to the log,
// we're still going to echo it back to the client.
[sock writeData:data withTimeout:-1 tag:ECHO_MSG];
}
/**
* This method is called if a read has timed out.
* It allows us to optionally extend the timeout.
* We use this method to issue a warning to the user prior to disconnecting them.
**/
- (NSTimeInterval)onSocket:(AsyncSocket *)sock
shouldTimeoutReadWithTag:(long)tag
elapsed:(NSTimeInterval)elapsed
bytesDone:(NSUInteger)length
{
if(elapsed <= READ_TIMEOUT)
{
NSString *warningMsg = @"Are you still there?\r\n";
NSData *warningData = [warningMsg dataUsingEncoding:NSUTF8StringEncoding];
[sock writeData:warningData withTimeout:-1 tag:WARNING_MSG];
return READ_TIMEOUT_EXTENSION;
}
return 0.0;
}
- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err
{
//[self logInfo:FORMAT(@"Client Disconnected: %@:%hu", [sock connectedHost], [sock connectedPort])];
NSLog(@"%@",[err localizedDescription]);
}
- (void)onSocketDidDisconnect:(AsyncSocket *)sock
{
[connectedSockets removeObject:sock];
}
-(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;
}
onSocket:didAcceptNewSocket: AsyncSocket创建了新的Socket用于处理和客户端的请求,如果这个新socket实例你不打算保留(retain),那么将拒绝和该客户端连接
onSocket:wantsRunLoopForNewSocket:,提供线程的runloop实例给AsyncSocket,后者将使用这个runloop执行socket通讯的操作
onSocketWillConnect:,将要建立连接,这时可以做一些准备工作,如果需要的话
onSocket:didConnectToHost:port:,这个方法是建立连接后执行的,一般会在这里调用写入或者读取socket的操作
下面是跟终端(客户端)通信过程
设置一个端口开始监听
输出窗口输出一下信息
2013-04-01 14:32:44.201 testService[1951:f803] Current host: localhost
2013-04-01 14:32:44.202 testService[1951:f803] Local: 192.168.0.3
2013-04-01 14:32:44.203 testService[1951:f803] All: (
"192.168.0.3",
"10.0.2.1",
"169.254.201.77"
)
2013-04-01 14:32:52.466 testService[1951:f803] <1002225e 00000000 00000000 00000000>
2013-04-01 14:32:52.466 testService[1951:f803] 0.0.0.0
2013-04-01 14:32:52.466 testService[1951:f803] 8798
打开终端输入以下信息
输出窗口输出的日志
2013-04-01 14:34:21.405 testService[1951:f803] wants runloop for new socket.
2013-04-01 14:34:21.405 testService[1951:f803] will connect
2013-04-01 14:34:21.406 testService[1951:f803] host:192.168.0.3,port55706
2013-04-01 14:34:23.825 testService[1951:f803] the messeaege from clinethello
2013-04-01 14:34:25.992 testService[1951:f803] the messeaege from clinetjinjiantong
2013-04-01 14:34:38.464 testService[1951:f803] the messeaege from clinetmy name is jinjiantong
2013-04-01 14:35:01.064 testService[1951:f803] the messeaege from clinetis time out?
2013-04-01 14:35:26.149 testService[1951:f803] Read operation timed out
这样完成简单的完成了客户端和服务器端的通信过程
通信过程很简单,服务器端扳回客户端输入的内容
编写简单的UDP应用
如何加入项目1、下载地址:https://github.com/robbiehanson/CocoaAsyncSocket
2、下载之后导入AsyncUdpSocket.h和AsyncUdpSocket.m文件进入你的项目,本人使用xcode4.2,不加类库可以使用,如果需要请另行加入CFNetwork.framework框架。
3、如果你的项目使用ARC的话,应该已经可以正常编译了,如果你的项目不是使用的ARC,在AyncUdpSocket.m文件中有说明,
加入编译条件:在项目target -> build phases -> compile sources -> AsyncUdpSocket文件后面加入 -fobjc-arc ,这是为了使编译器编译的时候将此文件在arc的条件下编译。
参考:http://blog.csdn.net/bjx327660180/article/details/8105289
首先,编写发送UDP数据报的示例。这需要有个服务器端能接收到内容。用Node.Js写了个简单的接收端:
[javascript] view plaincopy
var dgram = require("dgram");
var server = dgram.createSocket("udp4");
server.on("message", function (msg, rinfo) {
console.log('server got data:{ message : ' + msg + ' }【' + rinfo.address + ":" + rinfo.port+ "】");
var message=new Buffer("333333");
server.send(message, 0, message.length, rinfo.port, rinfo.address, function(err, bytes){
if(err){
console.log('error : '+err);
}
});
});
server.on("listening", function () {
var address = server.address();
console.log("server listening " +
address.address + ":" + address.port);
});
server.bind(8080);
下面写发送的代码:
在.h文件里定义socket
[cpp] view plaincopy
#import "AsyncUdpSocket.h"
@interface ViewController : UIViewController{
AsyncUdpSocket *socket;
}
在.m文件里使用socket(在.m文件里定义socket时deleget将不起作用,所以:一定要把socket定义在.h文件里)
[cpp] view plaincopy
socket=[[AsyncUdpSocket alloc]initWithDelegate:self];
NSData *data=[@"Hello from iPhone" dataUsingEncoding:NSUTF8StringEncoding];
[socket sendData:data toHost:@"192.168.0.165" port:8080 withTimeout:-1 tag:1];
NSLog(@"send upd complete.");
下面,写个接收端的代码:
[cpp] view plaincopy
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方法:
[cpp] view plaincopy
- (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;
}
发送端,还是用Node.Js写个测试代码:
[javascript] view plaincopy
var dgram = require('dgram');
var client = dgram.createSocket("udp4");
var message = new Buffer('I\n');
client.send(message, 0, message.length, 8080, "localhost", function(err, bytes){
if(err){
console.log('error : '+err);
}else{
console.log('bytes : '+bytes);
}
client.close();
});
client.bind(8080);
在iPhone日志中:
1 2 | 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广播的简单代码:
[java] view plaincopy
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代码:
在.h文件里定义socket
[cpp] view plaincopy
#import "AsyncUdpSocket.h"
@interface ViewController : UIViewController{
AsyncUdpSocket *socket;
}
在.m文件里使用socket(在.m文件里定义socket时deleget将不起作用,所以:一定要把socket定义在.h文件里)
[cpp] view plaincopy
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一模一样:
[cpp] view plaincopy
- (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;
}
测试得到的日志:
[cpp] view plaincopy
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.
相关文章推荐
- IOS-18-网络通信之Socket通信中服务端与客户端的实现
- RSA客户端(android/ios)与服务端的通信实现
- 基于开源SuperSocket实现客户端和服务端通信项目实战
- 利用socket TCP协议实现客户端与服务端简单通信
- Android Socket 聊天工具(一个服务端实现多个客户端间通信)
- Java IO——Socket:通过多线程实现多客户端与一个服务端通信
- 流式套接字实现简单的客户端/服务端通信过程
- 搭建minisipserver服务器实现局域网内IOS客户端idoubs的通信(可打电话 发短信)
- Android作为客户端,PC作为服务端:实现网络通信
- 【socket】用PHP的socket实现客户端到服务端的通信
- 【socket】用PHP的socket实现客户端到服务端的通信
- Socket跨平台通信——服务端Android、客户端iOS
- 搭建Minisipserve服务器实现局域网内IOS客户端idoubs的通信
- Android作为客户端,PC作为服务端:实现网络通信!
- 利用Tcp和socket实现的客户端与服务端的简单通信
- Tomcat单向Https验证搭建,亲自实现与主流浏览器、Android/iOS移动客户端安全通信
- socket实现客户端与服务端通信(一)服务端
- 使用AIDL实现IPC通信之——实现服务端主动发送数据给客户端
- Java IO——Socket:通过多线程实现多客户端与一个服务端通信
- Netty实现客户端和服务端的通信