IOS Socket 总结 (涉及内容Amr,protobuf,CFSocket)

来源:互联网 发布:来肯云商软件怎么样 编辑:程序博客网 时间:2024/06/06 00:51

本文主要讲网络层实现

一、先简单说说什么是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类型从网络序转换到主机序




三、实例

一、简介:简单的实现类似微信的效果,按住录音,松手保存发送,服务器接到数据后广播给每位用户..主要采用opencore-amrnb(音频压缩)、Google Protobuf、(CFSocket/BSDSocket各一点),以下仅仅为核心代码
思路: (1)本地音频压缩大概流程是:先用AVAudioRecorder 录音成wav格式,再对其转换成amr格式保存本地(PS:IOS4.3以后不支持录音原生AMR格式,
         所以坑啊,要注意下,第三方库设置的转换参数得和转换前的一致以防止转码出错,变成杂音).
         (2)服务器给出的通讯格式大概是:  长度(2Byte,short)、协议号(2Byte,short)、分隔符(1Byte,且为0)、Protobuf序列化后的数据
         (3)先通过第三方库从amr转码至wav保存本地,再用AVAudioPlayer进行播放。
      其流程大致就是:
                 与服务器通讯流程大概是:  客户端登陆(协议号:1)->服务端记录用户名和IP(协议号:1)->客户端发送音频(协议号:2)->服务端接收到数据,
                 并找到IP对应的用户名(协议号:2),并将其用户名数据进行下发(协议号:2)->客户端收到数据,保存,转码,播放(协议号:2).
  

二、Google-Protobuf  

    xxxx.proto文件内容(至于使用,生成方面的问题,可参考我之前写过的文章)

// 登陆message LoginUp {required string name = 1;}message LoginDown {}// 发送消息message SendUp {required bytes voice = 1;}message SendDown {required string name = 1;required bytes voice = 2;}


Network.h  (Network头文件)主要包含机个对外的方法,(创建连接,发送,接收).

#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;}

创建Socket连接

-(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方法得到相关数据。
            (2)根据相关数据赋值给信息头结构体
            (3)  根据相关数据设置protobuf变量,并序列化.
            (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("发送失败");    }   }

至于较上层的 writeData方法 返回的则是对应的相关的xyh/flag/数据流


数据接收
思路:
(1)先读取2字节长度,再读取2字节协议号以及1字节分隔符
(2)网络序转主机序
  (3)  判断条件是否满足,根据长度分配堆空间,并写入数据,然后得到临时名字,保存在本地
  (4)  转码播放
-(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  0x00FFshort 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

0 0