标签:
Socket就是为网络服务提供的一种机制。网络通信其实就是Socket间的通信,通信的两端都是Socket,数据在两个Socket间通过IO传输。
在Web服务大行其道的今天,调用Web服务的代价是高昂的,尤其是仅仅是抓取少量数据的时候尤其如此。而使用Socket,可以只传送数据本身而不用进行XML封装,大大降低数据传输的开销。Socket允许使用长连接,允许应用程序运行在异步模式(提高效率),只有在需要的时候才接收数据
iOS中常用的两种Socket类型:
在iOS中使用流式Socket连接的方法:
其中:
1. NSStream:数据流的父类,用于定义抽象特性,例如:打开、关闭代理,继承自CFStream(Core Foundation)
2. NSInputStream:NSStream的子类,用于读取输入
3. NSOutputStream:NSSTream的子类,用于写输出
本程序完成聊天室的连接、登录、发送/接收数据三个功能。编写本程序之前,首先需要一个后台文件用于开启聊天室服务,端口为12345,并规定使用”iam:xxx”的指令登录一个名为”xxx”的用户,使用”msg:xxx”的指令发送一条内容为”xxx”的数据。
程序界面:
#import "ViewController.h"
@interface ViewController () <NSStreamDelegate, UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate>{
NSInputStream *_inputStream;
NSOutputStream *_outputStream;
}
- (IBAction)connectToHost:(id)sender;
- (IBAction)login:(id)sender;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomConstraint;
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (nonatomic, strong) NSMutableArray *msgs;
@end
@implementation ViewController
- (NSMutableArray *)msgs
{
if (_msgs == nil) {
_msgs = [NSMutableArray array];
}
return _msgs;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(kbWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(kbWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
#pragma mark 键盘即将显示
- (void)kbWillShow:(NSNotification *)noti
{
// NSLog(@"%@", noti.userInfo);
CGFloat kbHeight = [noti.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
self.bottomConstraint.constant = kbHeight;
}
#pragma mark 键盘即将隐藏
- (void)kbWillHide:(NSNotification *)noti
{
self.bottomConstraint.constant = 0;
}
#pragma mark 退出键盘
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
[self.view endEditing:YES];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark 连接服务器
- (IBAction)connectToHost:(id)sender
{
//iOS中需要使用C语言实现Socket的连接
//1.设置主机ip、端口地址
NSString *host = @"127.0.0.1";
int port = 12345;
//2.定义输入输出流
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
//3.分配输入输出流的内存空间
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream);
//4.把C语言对象转换为OC对象
_inputStream = (__bridge NSInputStream *)readStream;
_outputStream = (__bridge NSOutputStream *)writeStream;
//5.设置代理,监听数据接受的状态
_inputStream.delegate = self;
_outputStream.delegate = self;
//6.把输入输出流添加到主运行循环,主运行循环用于监听网络状态
[_inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[_outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
//7.打开输入输出流
[_inputStream open];
[_outputStream open];
}
#pragma mark 登录
- (IBAction)login:(id)sender
{
NSString *loginStr = @"iam:zhangsan";
[self sendDataToHost:loginStr];
}
#pragma mark NSStream代理方法
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode) {
case NSStreamEventOpenCompleted:
NSLog(@"%@", aStream);
NSLog(@"成功建立连接");
break;
case NSStreamEventHasBytesAvailable:
NSLog(@"有数据可读");
[self readData];
break;
case NSStreamEventHasSpaceAvailable:
NSLog(@"可以发送数据");
break;
case NSStreamEventErrorOccurred:
NSLog(@"出现错误");
break;
case NSStreamEventEndEncountered:
NSLog(@"正常地中断连接");
//断开连接后要记得关闭输入输出流,并从主运行循环中移除
[_inputStream close];
[_outputStream close];
[_inputStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[_outputStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
break;
default:
break;
}
}
#pragma mark 读取数据
- (void)readData
{
//定义缓冲区,这个缓冲区只能存储1024个字节
uint8_t buf[1024];
//len为读取到地实际字节数
NSInteger len = [_inputStream read:buf maxLength:sizeof(buf)];
//把缓冲区里地字节转换为字符串
NSString *receiveStr = [[NSString alloc] initWithBytes:buf length:len encoding:NSUTF8StringEncoding];
NSLog(@"%@", receiveStr);
[self.msgs addObject:receiveStr];
[self.tableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.msgs.count;
}
#pragma mark 设置tableView数据
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"msg";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:ID];
}
cell.textLabel.text = self.msgs[indexPath.row];
return cell;
}
#pragma mark 按下发送键
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
NSString *str = textField.text;
if (str.length == 0) {
return YES;
}
NSString *msg = [@"msg:" stringByAppendingString:str];
[self sendDataToHost:msg];
textField.text = nil;
return YES;
}
#pragma mark 发送数据
- (void)sendDataToHost:(NSString *)str
{
NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
[_outputStream write:data.bytes maxLength:data.length];
}
Socket的使用都是基于C语言的,不便于开发和维护,可以第三方框架CocoaAsyncSocket编写本程序。CocoaAsyncSocket支持TCP和UDP,其中,AsyncSocket类是支持TCP的,AsyncUdpSocket是支持UDP的。可根据需要导入相关的文件,本文使用的是AsyncSocket。AsyncSocket是封装了CFSocket和CFSteam的TCP/IP socket网络库,提供异步操作,它的代理方法都是在子线程下进行的。
上面的程序使用CocoaAsyncSocket实现,使用前导入GCDAsyncSocket.h,并新建成员变量GCDAsyncSocket *_socket,之后:
#pragma mark socket代理方法
#pragma mark 连接服务器
- (IBAction)connectToHost:(id)sender
{
//iOS中需要使用C语言实现Socket的连接
//1.设置主机ip、端口地址
NSString *host = @"127.0.0.1";
int port = 12345;
_socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
NSError *error = nil;
[_socket connectToHost:host onPort:port error:&error];
if (error) {
NSLog(@"连接失败%@", error);
}
}
#pragma mark 连接成功
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
NSLog(@"连接成功");
}
#pragma mark 断开连接
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
if (err) {
NSLog(@"出现错误,连接失败");
}else {
NSLog(@"断开连接");
}
}
#pragma mark 数据发送成功
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
[sock readDataWithTimeout:-1 tag:tag];
}
#pragma mark 读取数据
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
NSString *receiveStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (tag==101) { //登录
NSLog(@"登录成功");
}else if (tag==102) {//发送数据
[self.msgs addObject:receiveStr];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
}
#pragma mark 按下登录按钮
- (IBAction)login:(id)sender
{
NSString *loginStr = @"iam:zhangsan";
[_socket writeData:[loginStr dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:101];
}
UITextField按下发送键,发送数据:
[_socket writeData:[msg dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:102];
#import "ChatServer.h"
#import "GCDAsyncSocket.h"
@interface ChatServer () <GCDAsyncSocketDelegate>{
GCDAsyncSocket *_serverSocket;
}
/**
* 所有客户端连接的Socket
*/
@property (nonatomic, strong) NSMutableArray *clientSocket;
@end
@implementation ChatServer
- (NSMutableArray *)clientSocket
{
if (_clientSocket == nil) {
_clientSocket = [NSMutableArray array];
}
return _clientSocket;
}
- (instancetype)init
{
if (self = [super init]) {
_serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
}
return self;
}
#pragma mark 公共方法,开启服务器
- (void)startServer
{
NSError *error;
[_serverSocket acceptOnPort:54321 error:&error];
if (error) {
NSLog(@"服务器开启失败");
}else {
NSLog(@"服务器开启成功");
}
}
#pragma mark 有客户端建立连接
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
{
//这里sock指服务器的socket,newSocket指客户端的Socket,
NSLog(@"%@, %@", sock, newSocket);
//保存客户端的Socket,不然刚连接成功就会自动关闭
[self.clientSocket addObject:newSocket];
//sock只负责连接服务器,不负责读取数据,因此使用newSocket
[newSocket readDataWithTimeout:-1 tag:100];
}
#pragma mark 接收客户端发送过来的数据
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
//这里的sock指客户端的Socket
NSLog(@"%@", sock);
NSString *receiveStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
//去除换行符、回车符
receiveStr = [receiveStr stringByReplacingOccurrencesOfString:@"\r" withString:@""];
receiveStr = [receiveStr stringByReplacingOccurrencesOfString:@"\n" withString:@""];
if ([receiveStr hasPrefix:@"iam"]) {
NSString *user = [receiveStr componentsSeparatedByString:@":"][1];
NSString *responseStr = [user stringByAppendingString:@" has joined\n"];
[sock writeData:[responseStr dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
}
if ([receiveStr hasPrefix:@"msg"]) {
NSString *msg = [[receiveStr componentsSeparatedByString:@":"][1] stringByAppendingString:@"\n"];
[sock writeData:[msg dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
}
if ([receiveStr isEqualToString:@"quit"]) {
[sock disconnect];
[self.clientSocket removeObject:sock];
}
}
#pragma mark 服务器发送数据给客户端
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
//这里的sock依然指客户端的socket
NSLog(@"%@", sock);
//服务器每次写数据之前都需要读取一次数据,之后才可以返回数据
[sock readDataWithTimeout:-1 tag:100];
}
@end
int main(int argc, const char * argv[])
{
@autoreleasepool {
ChatServer *chatServer = [[ChatServer alloc] init];
[chatServer startServer];
//主线程永远不会销毁,在这里开启运行循环可以保证聊天室的服务器不会自动关闭
[[NSRunLoop currentRunLoop] run];
}
return 0;
}
可以使用上面的iOS程序进行测试,也可以在命令行中测试,如图:
Socket编程(简易聊天室客户端/服务器编写、CocoaAsyncSocket)
标签:
原文地址:http://blog.csdn.net/u011330638/article/details/51901294