iOS網路(四)socket簡單應用
阿新 • • 發佈:2019-12-25
一、Socket概覽
-
Socket就是為網路服務提供的一種機制
-
通訊的兩端都是socket
-
網路通訊其實就是socket間的通訊
-
資料在兩個socket間通過IO傳輸
-
socket是純c語言的,是跨平臺的
-
雙工:A←→B雙向傳輸
半雙工:雙工中新增開關,若1開關開啟則A→B,若2開關開啟則B→A
-
socket牛逼之處
主動傳送請求 → 提高速度、節省頻寬、創造及時性 → 即時通訊
二、客戶端實現
1、建立socketID
/**
1: 建立socket
引數
domain:協議域,又稱協議族(family)。常用的協議族有AF_INET、AF_INET6、AF_LOCAL(或稱AF_UNIX,Unix域Socket)、AF_ROUTE等。協議族決定了socket的地址型別,在通訊中必須採用對應的地址,如AF_INET決定了要用ipv4地址(32位的)與埠號(16位的)的組合、AF_UNIX決定了要用一個絕對路徑名作為地址。
type:指定Socket型別。常用的socket型別有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。流式Socket(SOCK_STREAM)是一種面向連線的Socket,針對於面向連線的TCP服務應用。資料報式Socket(SOCK_DGRAM)是一種無連線的Socket,對應於無連線的UDP服務應用。
protocol:指定協議。常用協議有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,分別對應TCP傳輸協議、UDP傳輸協議、STCP傳輸協議、TIPC傳輸協議。
注意:1.type和protocol不可以隨意組合,如SOCK_STREAM不可以跟IPPROTO_UDP組合。當第三個引數為0時,會自動選擇第二個引數型別對應的預設協議。
返回值:
如果呼叫成功就返回新建立的套接字的描述符,如果失敗就返回INVALID_SOCKET(Linux下失敗返回-1)
*/
int socketID = socket(AF_INET,SOCK_STREAM,0);
self.clinenId= socketID;
if (socketID == -1)
{
NSLog(@"建立socket 失敗");
return;
}
複製程式碼
2、建立連線
//htons : 將一個無符號短整型的主機數值轉換為網路位元組順序,不同cpu 是不同的順序 (big-endian大尾順序,little-endian小尾順序)
#define SocketPort htons(8040)
//inet_addr是一個計算機函式,功能是將一個點分十進位制的IP轉換成一個長整數型數
#define SocketIP inet_addr("127.0.0.1")
/**
__uint8_t sin_len; 假如沒有這個成員,其所佔的一個位元組被併入到sin_family成員中
sa_family_t sin_family; 一般來說AF_INET(地址族)PF_INET(協議族)
in_port_t sin_port; // 埠
struct in_addr sin_addr; // ip
char sin_zero[8]; 沒有實際意義,只是為了 跟SOCKADDR結構在記憶體中對齊
*/
struct sockaddr_in socketAddr;
socketAddr.sin_family = AF_INET;
socketAddr.sin_port = SocketPort;
struct in_addr socketIn_addr;
socketIn_addr.s_addr = SocketIP;
socketAddr.sin_addr = socketIn_addr;
/**
引數
引數一:套接字描述符
引數二:指向資料結構sockaddr的指標,其中包括目的埠和IP地址
引數三:引數二sockaddr的長度,可以通過sizeof(struct sockaddr)獲得
返回值
成功則返回0,失敗返回非0,錯誤碼GetLastError()。
*/
// ip
int result = connect(socketID,(const struct sockaddr *)&socketAddr,sizeof(socketAddr));
if (result != 0)
{
NSLog(@"連結失敗");
return;
}
NSLog(@"連結成功");
複製程式碼
3、傳送資料
#pragma mark - 傳送訊息
- (IBAction)sendMsgAction:(id)sender
{
/**
3: 傳送訊息
s:一個用於標識已連線套介面的描述字。
buf:包含待發送資料的緩衝區。
len:緩衝區中資料的長度。
flags:呼叫執行方式。
返回值
如果成功,則返回傳送的位元組數,失敗則返回SOCKET_ERROR
一箇中文對應 3 個位元組!UTF8 編碼!
*/
if (self.sendMsgContent_tf.text.length==0)
{
NSLog(@"訊息為空,無法傳送");
return;
}
const char *msg = self.sendMsgContent_tf.text.UTF8String;
ssize_t sendLen = send(self.clinenId,msg,strlen(msg),0);
NSLog(@"傳送了:%ld位元組",sendLen);
[self showMsg:self.sendMsgContent_tf.text msgType:0];
self.sendMsgContent_tf.text = @"";
}
複製程式碼
4、監聽接收資料
#pragma mark - 接受資料
dispatch_async(dispatch_get_global_queue(0,0),^{
[self recvMsg];
});
- (void)recvMsg
{
// 4. 接收資料
/**
引數
1> 客戶端socket
2> 接收內容緩衝區地址
3> 接收內容快取區長度
4> 接收方式,0表示阻塞,必須等待伺服器返回資料
返回值
如果成功,則返回讀入的位元組數,失敗則返回SOCKET_ERROR
*/
while (1)
{
uint8_t buffer[1024];
ssize_t recvLen = recv(self.clinenId,buffer,sizeof(buffer),0);
NSLog(@"接收到了:%ld位元組",recvLen);
// 判斷如果 0 下面會奔潰
if (recvLen==0)
{
self.restartId ++;
if (self.restartId > 3)
{
self.restartId = 0;
return;
}
NSLog(@"此次傳輸長度為0 如果下次還為0 請檢查連線");
continue;
}
// 接收到的資料轉換
NSData *recvData = [NSData dataWithBytes:buffer length:recvLen];
NSString *recvStr = [[NSString alloc] initWithData:recvData encoding:NSUTF8StringEncoding];
NSLog(@"%@",recvStr);
self.restartId = 0;
dispatch_async(dispatch_get_main_queue(),^{
[self showMsg:recvStr msgType:1];
});
}
}
複製程式碼
5、關閉
close(self.clinenId);
if (self.clinenId)
{
// 7: 關閉socket連線
int close_result = close(self.clinenId);
if (close_result == -1)
{
NSLog(@"socket 關閉失敗");
return;
}
else
{
NSLog(@"socket 關閉成功");
}
}
複製程式碼
三、GCDAsySocket應用
1、連結
#pragma mark - 連線socket
- (IBAction)didClickConnectSocket:(id)sender
{
// 建立socket
if (self.socket == nil)
// 要自己寫併發佇列
// 其內部為同步
self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0,0)];
// 連線socket
if (!self.socket.isConnected)
{
NSError *error;
[self.socket connectToHost:@"127.0.0.1" onPort:8090 withTimeout:-1 error:&error];
if (error) NSLog(@"%@",error);
}
}
複製程式碼
2、傳送
#pragma mark - 傳送
- (IBAction)didClickSendAction:(id)sender
{
NSData *data = [self.contentTF.text dataUsingEncoding:NSUTF8StringEncoding];
[self.socket writeData:data withTimeout:-1 tag:10086];
}
複製程式碼
3、關閉
#pragma mark - 關閉socket
- (IBAction)didClickCloseAction:(id)sender
{
[self.socket disconnect];
self.socket = nil;
}
複製程式碼
4、代理
#pragma mark - GCDAsyncSocketDelegate
// 需要二次封裝block
// socketmanager直接呼叫資料
//已經連線到伺服器
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(nonnull NSString *)host port:(uint16_t)port
{
NSLog(@"連線成功 : %@---%d",host,port);
[self.socket readDataWithTimeout:-1 tag:10086];
// -1 代表永久監聽不失效
}
// 連線斷開
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
NSLog(@"斷開 socket連線 原因:%@",err);
}
//已經接收伺服器返回來的資料
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
NSLog(@"接收到tag = %ld : %ld 長度的資料",tag,data.length);
[self.socket readDataWithTimeout:-1 tag:10086]; // 收到後要標記,不然就是一次性
}
//訊息傳送成功 代理函式 向伺服器 傳送訊息
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
NSLog(@"%ld 傳送資料成功",tag);
}
複製程式碼
5、斷線重連
#pragma mark - 重連
- (IBAction)didClickReconnectAction:(id)sender
{
// 建立socket
if (self.socket == nil)
self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0,0)];
// 連線socket
if (!self.socket.isConnected){
NSError *error;
[self.socket connectToHost:@"127.0.0.1" onPort:8090 withTimeout:-1 error:&error];
if (error) NSLog(@"%@",error);
}
}
複製程式碼
四、粘包與拆包
1、概念
當資料太大時,因為頻寬限制,需要對資料進行分段處理
比如頻寬是1000,你要的東西的大小是1800,第一次給你傳1000,第二次又給你傳1000,多出來的200怎麼區分?
-
做標識<資料段1><資料段2>通過分隔符實現,使資料按照規則展示
-
通過傳送一個數據的長度+資料的型別+資料
#pragma mark - 傳送資料格式化 - (void)sendData:(NSData *)data dataType:(unsigned int)dataType { NSMutableData *mData = [NSMutableData data]; // 計算資料總長度 data unsigned int dataLength = 4+4+(int)data.length;// 資料長度+資料型別+原資料長度=總資料長度 NSData *lengthData = [NSData dataWithBytes:&dataLength length:4]; [mData appendData:lengthData];// 將資料長度拼接入資料 // 資料型別 data // 2.拼接指令型別(4~7:指令) NSData *typeData = [NSData dataWithBytes:&dataType length:4]; [mData appendData:typeData]; // 最後拼接資料 [mData appendData:data]; NSLog(@"傳送資料的總位元組大小:%ld",mData.length); // 發資料 [self.socket writeData:mData withTimeout:-1 tag:10086]; } 複製程式碼
-
心跳 - 反向心跳
有時候socket斷開是監聽不到的,比如負載的時候
保證彼此的連結-防止資料丟包
間隔時間不能太短或太長
-
重連機制
一般在websocket