iOS 原生Socket和CocoaAsyncSocket框架的简单使用

网友投稿 1755 2022-10-26

iOS 原生Socket和CocoaAsyncSocket框架的简单使用

iOS 原生Socket和CocoaAsyncSocket框架的简单使用

注意:* demo只是展现了socket的基本的内容,不可进行套用* host:127.0.0.1 port:1024-65535(操作系统上端口号1024以下是系统保留的,从1024-65535是用户使用的)

SocketManager

iOS 原生Socket和CocoaAsyncSocket框架的简单使用

一、Socket到底是什么?

1、Socket原理

1.1、套接字(Socket)概念

1.2、给套接字赋予地址

依照建立套接字的目的不同,赋予套接字地址的方式有两种:服务器端使用bind,客户端使用connect。 bind:给服务器端中的套接字赋予通信的地址和端口,IP和Port便可以区分一个TCP/IP链接通道,如果要区分特定的主机间链接,还需要提供Hostname。 connect:客户端向特定网络地址的服务器发送连接请求。

1.3、建立Socket连接

建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket,另一个运行于服务器端,称为ServerSocket。 套接字之间的连接过程分为三个步骤:服务器监听(bind、listen),客户端请求(connect),连接确认(accept)。

1.4TCP连接

创建Socket链接时,可以制定不同的传输层协议(TCP或UDP),当使用TCP协议进行链接时,该Socket链接便是TCP链接。

TCP连接建立(三次握手)----客户端执行connect触发

TCP连接终止(四次挥手)----客户端或服务端执行close触发

2、客户端/服务器端模式的理解

首先服务器先启动对端口的监听,等待客户端的链接请求。

服务器端:

(1)服务器调用socket创建Socket; (2)服务器调用listen设置缓冲区; (3)服务器通过accept接受客户端请求建立连接; (4)服务器与客户端建立连接之后,就可以通过send/receive向客户端发送或从客户端接收数据; (5)服务器调用close关闭 Socket;

客户端:

二、基于C的BSD Socket客户端的实现

1、接口介绍

//socket 创建并初始化 socket,返回该 socket 的文件描述符,如果描述符为 -1 表示创建失败。int socket(int addressFamily, int type,int protocol)//关闭socket连接int close(int socketFileDescriptor)//将 socket 与特定主机地址与端口号绑定,成功绑定返回0,失败返回 -1。int bind(int socketFileDescriptor,sockaddr *addressToBind,int addressStructLength)//接受客户端连接请求并将客户端的网络地址信息保存到 clientAddress 中。int accept(int socketFileDescriptor,sockaddr *clientAddress, int clientAddressStructLength)//客户端向特定网络地址的服务器发送连接请求,连接成功返回0,失败返回 -1。int connect(int socketFileDescriptor,sockaddr *serverAddress, int serverAddressLength)//使用 DNS 查找特定主机名字对应的 IP 地址。如果找不到对应的 IP 地址则返回 NULL。hostent* gethostbyname(char *hostname)//通过 socket 发送数据,发送成功返回成功发送的字节数,否则返回 -1。int send(int socketFileDescriptor, char *buffer, int bufferLength, int flags)//从 socket 中读取数据,读取成功返回成功读取的字节数,否则返回 -1。int receive(int socketFileDescriptor,char *buffer, int bufferLength, int flags)//通过UDP socket 发送数据到特定的网络地址,发送成功返回成功发送的字节数,否则返回 -1。int sendto(int socketFileDescriptor,char *buffer, int bufferLength, int flags, sockaddr *destinationAddress, int destinationAddressLength)//从UDP socket 中读取数据,并保存发送者的网络地址信息,读取成功返回成功读取的字节数,否则返回 -1 。int recvfrom(int socketFileDescriptor,char *buffer, int bufferLength, int flags, sockaddr *fromAddress, int *fromAddressLength)

2、实现

BSDSocketManager.h

#import @interface BSDSocketManager : NSObject@property (nonatomic, copy) void(^returnStateInformation)(NSString *stateInformation);+ (instancetype)shareInstance;- (void)connectToHost:(NSString *)ip port:(NSNumber *)port;- (void)disConnect;- (void)sendMessage:(NSString *)message;

BSDSocketManager.m

#import "BSDSocketManager.h"#import #import @interface BSDSocketManager ()@property (nonatomic, assign) int socketFileDescriptor;@property (nonatomic, assign, getter=isCanRecieveMessage) BOOL canRecieveMessage;@end@implementation BSDSocketManager+ (instancetype)shareInstance { static dispatch_once_t onceToken; static BSDSocketManager *manager = nil; dispatch_once(&onceToken, ^{ manager = [[self alloc] init]; }); return manager;}#pragma mark - 连接- (void)connectToHost:(NSString *)ip port:(NSNumber *)port { //每次链接前,先断开连接 if (self.socketFileDescriptor != 0) { [self disConnect]; self.socketFileDescriptor = 0; } /**创建客户端socket 创建一个socket,返回值为int。(socket类型就是int类型) 第一个参数(addressFamily): IPv4(AF_INET) 或者 IPv6(AF_INET6) 第二个参数(type): socket类型,通常是流stream(SOCK_STREAM) 或数据报文datagram(SOCK_DGRAM) 第三个参数(protocol): 通常设置为0,以便让系统自动为选择我们合适的协议,对于 stream socket 来说会是 TCP 协议(IPPROTO_TCP),而对于 datagram来说会是 UDP 协议(IPPROTO_UDP) */ self.socketFileDescriptor = socket(AF_INET, SOCK_STREAM, 0); if (self.socketFileDescriptor == -1) { NSLog(@"创建链接失败"); return; } //设置sockaddr_in结构体 struct sockaddr_in socketParameters = {0}; socketParameters.sin_len = sizeof(socketParameters); //设置IPv4 socketParameters.sin_family = AF_INET; //使用 DNS 查找特定主机名字对应的 IP 地址 struct hostent *remoteHostEnt = gethostbyname([ip UTF8String]); if (remoteHostEnt == NULL) { [self disConnect]; NSLog(@"找不到IP地址"); return; } struct in_addr *remoteInAddr = (struct in_addr *)remoteHostEnt->h_addr_list[0]; socketParameters.sin_addr = *remoteInAddr; //htons是将整形变量从主机字节顺序转变成网络字节顺序,赋值端口号 socketParameters.sin_port = htons([port intValue]); /**用scoket和服务端地址,发起连接 客户端向特定网络地址的服务器发送连接请求,连接成功返回0,失败返回 -1。 注意:该接口调用会阻塞当前线程,直到服务器返回。 */ int ret = connect(self.socketFileDescriptor, (struct sockaddr *)&socketParameters, sizeof(socketParameters)); if (ret == -1) { [self disConnect]; NSLog(@"连接失败"); return; } NSLog(@"连接成功"); self.canRecieveMessage = YES; [self recieveMessage];}#pragma mark - 断开链接- (void)disConnect { //关闭连接 int ret = close(self.socketFileDescriptor); if (ret == -1) { NSLog(@"断开链接失败"); return; } NSLog(@"断开链接成功"); self.canRecieveMessage = NO;}#pragma mark - 发送消息- (void)sendMessage:(NSString *)message { const char *sendMessage = [message UTF8String]; ssize_t ret = send(self.socketFileDescriptor, sendMessage, strlen(sendMessage) + 1, 0); if (ret == -1) { NSLog(@"发送失败"); return; } NSLog(@"发送成功");}#pragma mark - 接收服务器发送的消息- (void)recieveMessage { dispatch_async(dispatch_get_global_queue(0, 0), ^{ __weak typeof(self) weak_Self = self; [NSTimer scheduledTimerWithTimeInterval:0.2 repeats:YES block:^(NSTimer * _Nonnull timer) { __strong typeof(weak_Self) self = weak_Self; char revieveMessage[1024] = {0}; if (recv(self.socketFileDescriptor, revieveMessage, sizeof(revieveMessage), 0) != -1) { NSLog(@"接收到消息:%s", revieveMessage); } }]; while (self.isCanRecieveMessage) { [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; } });}@end

三、基于Socket原生的CocoaAsyncSocket客户端的实现

GCDSocketManager.h

#import @interface GCDSocketManager : NSObject@property (nonatomic, copy) void(^returnStateInformation)(NSString *stateInformation);+ (instancetype)shareInstance;- (BOOL)connectToHost:(NSString *)ip port:(NSNumber *)port;- (void)disConnect;- (void)sendMessage:(NSString *)message;@end

GCDSocketManager.m

#import "GCDSocketManager.h"#import @interface GCDSocketManager ()@property (nonatomic, strong) GCDAsyncSocket *client;@property (nonatomic, assign) int fileLength;@property (nonatomic, strong) NSMutableData *receiveData;@end@implementation GCDSocketManager//读取数据长度static int readLength = 4;+ (instancetype)shareInstance { static dispatch_once_t onceToken; static GCDSocketManager *manager = nil; dispatch_once(&onceToken, ^{ manager = [[GCDSocketManager alloc] init]; }); return manager;}- (GCDAsyncSocket *)client { if (!_client) { _client = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; } return _client;}//建立连接- (BOOL)connectToHost:(NSString *)ip port:(NSNumber *)port { return [self.client connectToHost:ip onPort:port.intValue error:nil];}//断开连接- (void)disConnect { [self.client disconnect];}//发送消息- (void)sendMessage:(NSString *)message { NSMutableData *sendData = [NSMutableData data]; NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding]; int dataLength = (int)[data length]; NSData *lengthData = [[NSData alloc] initWithBytes:&dataLength length:sizeof(int)]; [sendData appendData:lengthData];//发送数据长度 [sendData appendData:data];//发送数据实体 //第二个参数,请求超时时间 [self.client writeData:sendData withTimeout:-1 tag:0];}#pragma mark - <代理方法>//连接成功的调用- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port { self.returnStateInformation([NSString stringWithFormat:@"连接成功,host:%@,port:%d", host, port]); [self.client readDataToLength:sizeof(int) withTimeout:-1 tag:0]; self.fileLength = 0; //MARK: - 心跳检测写在这...}//断开连接的调用- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err { self.returnStateInformation([NSString stringWithFormat:@"断开连接,host:%@,port:%d", sock.localHost, sock.localPort]); //MARK: - 断线重连写在这...}//发送消息成功的回调- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag { self.returnStateInformation([NSString stringWithFormat:@"发送消息,host:%@,port:%d", sock.localHost, sock.localPort]);}//发送分段消息成功的回调- (void)socket:(GCDAsyncSocket *)sock didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag {}//为上一次设置的发送数据代理续时 (如果设置超时为-1,则永远不会调用到)- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutWriteWithTag:(long)tag elapsed:(NSTimeInterval)elapsed bytesDone:(NSUInteger)length { self.returnStateInformation([NSString stringWithFormat:@"来延时,tag:%ld,elapsed:%f,length:%ld",tag,elapsed,length]); return 10;}//收到消息的回调- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { int fileLength = 0; if (self.fileLength == 0) { self.receiveData = [NSMutableData data]; [data getBytes:&fileLength length:sizeof(int)]; self.fileLength = fileLength; } if (!fileLength) { [self.receiveData appendData:data]; } if ([self.receiveData length] < self.fileLength) { int leftoverLength = (int)(self.fileLength - [self.receiveData length]); if (leftoverLength < readLength) { [sock readDataToLength:leftoverLength withTimeout:-1 tag:1]; } else { [sock readDataToLength:readLength withTimeout:-1 tag:1]; } } else { self.returnStateInformation([NSString stringWithFormat:@"接收数据为:%@",[[NSString alloc] initWithData:self.receiveData encoding:NSUTF8StringEncoding]]); //再次开启 [self.client readDataToLength:sizeof(int) withTimeout:-1 tag:0]; self.fileLength = 0; }}//收到分段消息的回调- (void)socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag { self.returnStateInformation([NSString stringWithFormat:@"%lu",(unsigned long)partialLength]);}//为上一次设置的读取数据代理续时 (如果设置超时为-1,则永远不会调用到)- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutReadWithTag:(long)tag elapsed:(NSTimeInterval)elapsed bytesDone:(NSUInteger)length { self.returnStateInformation([NSString stringWithFormat:@"来延时,tag:%ld,elapsed:%f,length:%ld",tag,elapsed,length]); return 10;}@end

四、基于CocoaAsyncSocketMac服务器的实现

GCDSocketManager.h

#import @interface GCDSocketManager : NSObject@property (nonatomic, copy) void(^returnStateInformation)(NSString *stateInformation);+ (instancetype)shareInstance;- (BOOL)acceptOnPort:(NSNumber *)port;- (void)disConnect;- (void)sendMessage:(NSString *)message;@end

GCDSocketManager.m

#import "GCDSocketManager.h"#import @interface GCDSocketManager ()@property (nonatomic, strong) GCDAsyncSocket *server;@property (nonatomic, strong) NSMutableArray *clientArrsSocket;@property (nonatomic, assign) int fileLength;@property (nonatomic, strong) NSMutableData *receiveData;@end@implementation GCDSocketManager//读取数据长度static int readLength = 4;+ (instancetype)shareInstance { static dispatch_once_t onceToken; static GCDSocketManager *manager = nil; dispatch_once(&onceToken, ^{ manager = [[GCDSocketManager alloc] init]; }); return manager;}//创建服务器链接管道- (GCDAsyncSocket *)server { if (!_server) { _server = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; } return _server;}- (NSMutableArray *)clientArrsSocket { if (!_clientArrsSocket) { _clientArrsSocket = [NSMutableArray array]; } return _clientArrsSocket;}#pragma mark - <对外接口>//通过socket 监听\绑定- (BOOL)acceptOnPort:(NSNumber *)port { NSError *error; BOOL result = [self.server acceptOnPort:port.intValue error:&error]; if (!error) { self.returnStateInformation([NSString stringWithFormat:@"正在监听:host:%@,port:%d", self.server.localHost, self.server.localPort]); } else { self.returnStateInformation([NSString stringWithFormat:@"监听失败:error:%@", error.localizedFailureReason]); } return result;}//断开链接- (void)disConnect { [self.server disconnect];}//发送消息- (void)sendMessage:(NSString *)message { NSMutableData *sendData = [NSMutableData data]; NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding]; int dataLength = (int)[data length]; NSData *lengthData = [[NSData alloc] initWithBytes:&dataLength length:sizeof(int)]; [sendData appendData:lengthData];//发送数据长度 [sendData appendData:data];//发送数据实体 //第二个参数,请求超时时间 for (GCDAsyncSocket *newSocket in self.clientArrsSocket) { [newSocket writeData:sendData withTimeout:-1 tag:0]; }}#pragma mark - <代理方法>//新的客户端连接监听的Socket服务器端口- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket { //保存newSocket,解决clientsocket是局部变量导致连接关闭的状况 [self.clientArrsSocket addObject:newSocket]; self.returnStateInformation([NSString stringWithFormat:@"端口链入:host:%@,port:%d", newSocket.localHost, newSocket.localPort]); //通过制定newScoket 读取数据(只能读取1条数据) [newSocket readDataToLength:sizeof(int) withTimeout:-1 tag:1]; self.fileLength = 0; //MARK: - 心跳检测写在这...}//断开监听- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err { if ([self.clientArrsSocket containsObject:sock]) { [self.clientArrsSocket removeObject:sock]; } //MARK: - 断线重连写在这...}/** int i = 1; NSData *data = [NSData dataWithBytes: &i length: sizeof(i)]; int i; [data getBytes: &i length: sizeof(i)]; *///接收数据- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { int fileLength = 0; if (self.fileLength == 0) { self.receiveData = [NSMutableData data]; [data getBytes:&fileLength length:sizeof(int)]; self.fileLength = fileLength; } if (!fileLength) { [self.receiveData appendData:data]; } if ([self.receiveData length] < self.fileLength) { int leftoverLength = (int)(self.fileLength - [self.receiveData length]); if (leftoverLength < readLength) { [sock readDataToLength:leftoverLength withTimeout:-1 tag:1]; } else { [sock readDataToLength:readLength withTimeout:-1 tag:1]; } } else { self.returnStateInformation([NSString stringWithFormat:@"接收数据为:%@",[[NSString alloc] initWithData:self.receiveData encoding:NSUTF8StringEncoding]]); [sock readDataToLength:sizeof(int) withTimeout:-1 tag:1]; self.fileLength = 0; }}- (void)socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag { NSLog(@"%lu",(unsigned long)partialLength);}//发送消息成功的回调- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag { self.returnStateInformation([NSString stringWithFormat:@"发送消息成功:host:%@,port:%d", sock.localHost, sock.localPort]);}@end

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:解析探秘fescar分布式事务实现原理
下一篇:455. 分发饼干
相关文章

 发表评论

暂时没有评论,来抢沙发吧~