【iOS】Socket通信
Socket是网络传输层的一种技术,跟http有本质的区别,http是应用层的一个网络协议。
Socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 Socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。
Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。
http和Socket的区别:
- Socket是长连接。http是短连接
- Socket是双向通信,http是单向的,只能客户端向服务器发送数据
- Socket的数据完全由自己组织,http必须按照http协议来发送
下载CocoaAsyncSocket
GCDAsyncSocket 网络请求使用
我们使用Socket时,可以选择tcp或者udp,所以下面以tcp为例
ViewController.h
#import <UIKit/UIKit.h>
enum
{
SOCKET_CONNECT_USER, //用户断开连接
SOCKET_CONNECT_SERVER, //服务器连接失败
};
@interface ViewController : UIViewController
@end
ViewController.m
#import "ViewController.h"
/*导入头文件*/
#import "GCDAsyncSocket.h"
#define BaseHost @"wangys.com" //host地址
#define BasePort 80 //端口
#define BaseHeartStr @"wys" //心跳数据
#define BaseSocktConnectStr @"request_data" //请求链接数据
/* 记住要设置代理 */
@interface ViewController ()<GCDAsyncSocketDelegate>
@property(nonatomic,strong)GCDAsyncSocket * mysocket;
@property(nonatomic,strong)NSTimer * myTimer;
@property (weak, nonatomic) IBOutlet UIButton * sendDataBtn;
@property (weak, nonatomic) IBOutlet UITextField * DataStrText;
@property (weak, nonatomic) IBOutlet UITextField * receiveText;
@end
@implementation ViewController
-(void)viewDidLoad
{
[super viewDidLoad];
}
#pragma mark - creat连接
//创建连接
-(void)createSocket
{
if (self.mysocket != nil) return;
self.mysocket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
[self tcpConnectWithData:BaseSocktConnectStr];
}
-(void)tcpConnectWithData:(NSString *)userData
{
[self.mysocket setUserData:userData];
NSError * error = nil;
if (![self.mysocket connectToHost:BaseHost onPort:BasePort withTimeout:2.0f error:&error])
{
NSLog(@"error:%@",error);
}
}
#pragma mark - 发送心跳包
//定时发送心跳包
-(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
self.myTimer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:self selector:@selector(sendHeartData) userInfo:nil repeats:YES];
[self.myTimer fire]; //先执行一次
}
-(void)sendHeartData
{
NSString * heartStr = BaseHeartStr;
NSData * hData = [heartStr dataUsingEncoding:NSUTF8StringEncoding];
[self.mysocket writeData:hData withTimeout:10.0f tag:0];
}
#pragma mark - 向服务器发送数据
//发送数据
-(IBAction)sendData:(UIButton *)sender
{
NSString * dataStr = self.DataStrText.text;
NSData * dataForTesxt = [dataStr dataUsingEncoding:NSUTF8StringEncoding];
[self.mysocket writeData:dataForTesxt withTimeout:10.0f tag:1];
}
//向服务器发送完数据之后回调
-(void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
if (tag == 1)
{
NSLog(@"发送成功");
}
}
#pragma mark - 服务器向客户端发送数据
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
[self.mysocket readDataWithTimeout:10.0f tag:tag];
[self receiveData:data];
}
-(void)receiveData:(NSData *)data
{
//显示
self.receiveText.text = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
}
#pragma mark - 断网处理
//如果不是手动切断,是其他原因,所以要重新连接,断开连接的时候会调用这个函数
-(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
if ([sock.userData isEqualToString:[NSString stringWithFormat:@"%d",SOCKET_CONNECT_SERVER]])
{
//服务器掉线 重新连接
[self tcpConnectWithData:BaseSocktConnectStr];
}
else
{
return;
}
}
//用户手动断开网络(这个方法我没写调用)
-(void)disconnectSocket
{
self.mysocket.userData = SOCKET_CONNECT_USER;
[self.myTimer timeInterval];
[self.mysocket disconnect];
}
@end
Cocoa Streams使用详解
Cocoa Streams实际上是Objective-C对CFNetwork的简单封装,主要包含了三个类:NSStream, NSInputStream, and NSOutputStream。
它使用名为 NSStreamDelegate 的协议来实现 CFNetwork 中的回调函数的作用,同样,runloop 也与 NSStream 结合的很好。
#import "ViewController.h"
#define BaseHost @"wangys.com"
#define BasePort 12345
@interface ViewController ()<NSStreamDelegate>
{
NSInputStream * inputStream;
NSOutputStream * outputStream;
}
@end
@implementation ViewController
-(void)viewDidLoad
{
[super viewDidLoad];
}
//建立连接
-(void)connectToHost:(UIButton *)button
{
NSString * host = BaseHost;
int port = BasePort;
// 1.定义C语言输入输出流
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)(host), port, &readStream, &writeStream);
// 2.把C语言的输入输出流转化成OC对象
inputStream = (__bridge NSInputStream *)(readStream);
outputStream = (__bridge NSOutputStream *)(writeStream);
// 3.设置代理
inputStream.delegate = self;
outputStream.delegate = self;
// 4.输入输出流添加到runloop循环中
[inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
// 5.打开输入输出流
[inputStream open];
[outputStream open];
}
//登录
-(void)loginClick:(UIButton *)button
{
/**
* 如果要登录,发送的数据格式为 "iam:wys";
* 如果要发送聊天消息,数据格式为 "msg:send a message";
*/
//登录指令
NSString * loginString = @"iam:wys";
NSData * data = [loginString dataUsingEncoding:NSUTF8StringEncoding];
[outputStream write:data.bytes maxLength:data.length];
}
//处理流事件
-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
NSLog(@"current thread : %@",[NSThread currentThread]);
/**
* NSStreamEventOpenCompleted = 1UL << 0 //输入输出流打开完成
* NSStreamEventHasBytesAvailable = 1UL << 1 //有字节可读
* NSStreamEventHasSpaceAvailable = 1UL << 2 //可以发放字节
* NSStreamEventErrorOccurred = 1UL << 3 //连接出现错误
* NSStreamEventEndEncountered = 1UL << 4 //连接结束
*/
switch (eventCode) {
case NSStreamEventOpenCompleted:
break;
case NSStreamEventHasBytesAvailable:
[self havaBytesToRead];
break;
case NSStreamEventHasSpaceAvailable:
break;
case NSStreamEventErrorOccurred:
break;
case NSStreamEventEndEncountered:
[self endConnect];
break;
default:
break;
}
}
//有字节可读
-(void)havaBytesToRead
{
// 1.建立一个缓冲区 可以放1024字节
uint8_t buf[1024];
// 2.返回实际装的字节数
NSInteger length = [inputStream read:buf maxLength:sizeof(buf)];
// 3.把字节数组转化成字符串
NSData * data = [NSData dataWithBytes:buf length:length];
NSString * readStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
[self showMessage:readStr];
}
//关闭连接
-(void)endConnect
{
// 关闭输出输入流
[inputStream close];
[outputStream close];
//从主运行循环中移除
[inputStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
//显示信息
-(void)showMessage:(NSString *)string
{
NSLog(@"showMessage:%@",string);
}
@end
2016-12-23更新
TCP与UDP的区别
基于连接与无连接
对系统资源的要求(TCP较多,UDP少)
UDP程序结构较简单
流模式与数据报模式
TCP保证数据正确性,UDP可能丢包
TCP保证数据顺序,UDP不保证
UDP Server不需要调用listen和accept
UDP收发数据用sendto/recvfrom函数
TCP:地址信息在connect/accept时确定
UDP:在sendto/recvfrom函数中每次均 需指定地址信息
UDP:shutdown函数无效
参考文献
IOS开发之SOCKET长连接的使用
Socket简介
iOS中socket使用
GCDAsyncSocket 网络请求
iOS学习之Socket使用简明教程- AsyncSocket