IOS Socket 总结 (涉及内容Amr,protobuf,CFSocket)
本文主要讲网络层实现
一、先简单说说什么是Socket?
Socket又称套接字,最早出现在Unix上,主要描述端口和IP,是一个通讯句柄
Socket 是TCP/IP协议设计的应用层编程接口(可以理解成对TCP/IP协议的封装、应用)
IOS中有2种Socket:
(1)BSDSocket(Unix原生).
(2)CFSocket(苹果对BSDsocket的封装).
BSDSocket是Unix系统中的网络通用接口,嘿嘿,Android跟IOS,你懂得
网上还有一种叫asyncsocket(对CFSocket以及CFSteam的封装)
PS:TCP/IP Transmission Control Protocol/Internet Protocol的简写,具体介绍: TCP/IP协议 百度百科
二、Socket常用的几个函数(序)
(1)htons 把unsigned short类型从主机序转换到网络序
(2)htonl 把unsigned long类型从主机序转换到网络序
(3)ntohs 把unsigned short类型从网络序转换到主机序
(4)ntohl 把unsigned long类型从网络序转换到主机序
三、实例
(1)本地音频压缩大概流程是:先用
AVAudioRecorder 录音成wav格式,再对其转换成amr格式保存本地
(PS:IOS4.3以后不支持录音原生AMR格式,
长度(2Byte,short)、协议号(2Byte,short)、分隔符(1Byte,且为0)、Protobuf序列化后的数据
AVAudioPlayer进行播放。
// 登陆
message LoginUp {
required string name = 1;
}
message LoginDown {}
// 发送消息
message SendUp {
required bytes voice = 1;
}
message SendDown {
required string name = 1;
required bytes voice = 2;
}
#import <Foundation/Foundation.h>
///Blocks 传输数据长度
typedef short(^Datalength)(short length);
@protocol HuiNetworkDelegate <NSObject>
/**
@return 即将传输的数据
@brief 即将发送的数据
**/
-(const void*)writeData;
/**
@brief 数据回来后的回调
**/
-(void)readName:(NSString*)name filePath:(NSString*)filePath;
@end
@interface HuiNetwork : NSObject
{
CFSocketRef _socket;//IOS对BSDSocket封装的结构体
char * _ip;
}
@property(assign,nonatomic)id<HuiNetworkDelegate> delegate;
/**
@brief 创建链接
**/
-(void)createConnect;
/**
@brief 发送请求
**/
-(void)sendMessage:(Datalength)callBack;
/**
@brief 读取数据
**/
-(void)readMessage;
/**
@param Ip 地址
@return 对象
@brief 初始化所需参数
**/
-(id)initWithIp:(NSString*)ip;
@end
Network.m(Network实现文件) 接收信息方法里,IOS不能直接播放Amr格式,所以我们需要通过第三方库将文件转换为IOS可播放的文件(wav)
- (void)dealloc
{
CFRelease(_socket);
free(_ip);///将内存状态置为可用
[super dealloc];
}
-(id)initWithIp:(NSString*)ip
{
self = [super init];
if (self) {
_ip=(char*)malloc(sizeof(ip.UTF8String)*sizeof(char));///不用多说了吧?堆分配
strcpy(_ip, ip.UTF8String);
}
return self;
}
-(void)createConnect
{
CFSocketContext socketContext={
0,
self,
NULL,
NULL,
NULL
};
_socket=CFSocketCreate(
kCFAllocatorDefault,
PF_INET,
SOCK_STREAM,
IPPROTO_TCP,
kCFSocketConnectCallBack,
CreateSocketCallBack,
&socketContext);
/*
创建结构体
下面初始化相关参数
*/
if (_socket) {
///设置地址结构体信息
struct sockaddr_in ipv4; //IPV4 sockaddr_in6--->IPV6
memset(&ipv4, 0, sizeof(ipv4));
ipv4.sin_len=sizeof(ipv4);
ipv4.sin_port=htons(2554);
ipv4.sin_addr.s_addr=inet_addr(_ip);
//结构体变成CFdata,便于CFsocket利用
CFDataRef addressRef=CFDataCreate(kCFAllocatorDefault, (UInt8*)&ipv4, sizeof(ipv4));
///需要链接的Socket,地址访问对象,连接超时时间
CFSocketConnectToAddress(_socket, addressRef, -1);
CFRunLoopRef runRef=CFRunLoopGetCurrent();///获取当前的线程中获取CFRUNLOOP结构体对象
///创建一个CFRunLoopSource
CFRunLoopSourceRef sourceRef=CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0);
///加入Runloop中
CFRunLoopAddSource(runRef, sourceRef, kCFRunLoopCommonModes);
CFRelease(sourceRef);
}
CFSocketCreate 配置完成,尝试连接后的C回调(创建连接时有取该函数的指针)
static void CreateSocketCallBack(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
{
NSString* msg=nil;
if (data!=NULL) {
msg=@"连接失败";
}
else
{
msg=@"连接成功";
HuiNetwork* currentNetwork=(HuiNetwork*)info;
///创建一条线程跑读取
NSThread* whileRead=[[NSThread alloc]initWithTarget:currentNetwork
selector:@selector(whileReadMessage)
object:currentNetwork];
[whileRead start];
}
弹出连接成功失败信息
UIAlertView* alertView=[[UIAlertView alloc]initWithTitle:@"提示"
message:msg
delegate:nil
cancelButtonTitle:@"确定"
otherButtonTitles:nil, nil];
[alertView show];
[alertView release];
}
///一条读取线程
-(void)whileReadMessage
{
while (true) {
@autoreleasepool {
[self readMessage];
}
}
}
头信息的结构体,PS:注意内存对齐问题
lengt: 长度
xyh: 协议号
flag:分隔符
#pragma pack(1) <—-设置对齐大小
typedef struct
{
short length;
short xyh;
HuiByte flag;
}Pack;
登陆(这里就不写了,就写个发送语音的就好了,原理一样)
(1)通过Delegate方法得到相关数据。
(4)发送数据,(PS:不要多传字节数,会进坑的)
发送语音的方法
-(void)sendMessage2
{
PackgeTmp* sendData=(PackgeTmp*)[_delegate writeData];///这个其实就是xyh+flag+数据流
Pack pack;
pack.xyh=sendData->xyh;
pack.flag=sendData->flag;
///序列化操作
SendUp up;
up.set_voice(sendData->data,sendData->length);
void* data=malloc(up.ByteSize());
bool serializeBool=up.SerializeToArray(data,up.ByteSize());
pack.length=htons(up.ByteSize()+2+1);///N字节 Protobuf 流数据长度,+2字节 协议号+1字节 分隔符
///如果protobuf序列化成功
if (serializeBool) {
send(CFSocketGetNative(_socket), &pack, sizeof(Pack), 0);
send(CFSocketGetNative(_socket), data, up.ByteSize(), 0);
}
else
{
printf("发送失败");
}
}
-(void)readMessage
{
short length=0;
short xyh=0;
float flag=0;
recv(CFSocketGetNative(_socket), &length, 2, 0);///先拿2字节(得到长度)
recv(CFSocketGetNative(_socket), &xyh, 2, 0);
recv(CFSocketGetNative(_socket), &flag, 1, 0);///分隔符
length=ntohs(length)-2-1;
xyh=ntohs(xyh);
///播放
if (length!=0&&length>1&&xyh==2) {
printf("\n长度%d\n",length);
SendDown down;
void * data=malloc(length);
recv(CFSocketGetNative(_socket), data, length, 0);
down.ParseFromArray(data, length);
NSData* audioDat=[NSData dataWithBytes:down.voice().data() length:length];
NSDate* date=[NSDate date];
NSDateFormatter* datFormatter=[[NSDateFormatter alloc]init];
[datFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
NSString* dateName=[datFormatter stringFromDate:date];
NSString* amrFile=[NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.amr",dateName]];
NSString* wavFile=[NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.wav",dateName]];
///保存
[audioDat writeToFile:amrFile atomically:YES];
///转码
[VoiceConverter amrToWav:amrFile wavSavePath:wavFile];
AudioRecordOrPlay* player=[[AudioRecordOrPlay alloc]init];
[player startPlayWithData:[NSData dataWithContentsOfFile:wavFile]];
[player release];
}
}
short 高低位交换(或直接调用库函数)
#define Mask 0x00FF
short exchange(short temp)
{
short int a=temp,b,c;
b=(a>>8)&Mask;//得出左边
c=(a<<8)&(~Mask);//得出右边
a=b|c; //得出高低位交换后的值
return a;
}
麻绳理工 MIT BSDSocket API: http://web.mit.edu/macdev/Development/MITSupportLib/SocketsLib/Documentation/sockets.html
socket面试题: http://hi.baidu.com/haven2002/item/d5bf44d648fb8a55d73aae4f