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类型从网络序转换到主机序




三、实例

一、简介:简单的实现类似微信的效果,按住录音,松手保存发送,服务器接到数据后广播给每位用户..主要采用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  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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值