推薦閱讀:備戰(zhàn)2020——iOS全新面試題總結(jié)
一、分別用C語(yǔ)言、python、GCDAsyncUdpSocket來(lái)實(shí)現(xiàn)UDP通信
1、C語(yǔ)言方式
- 首先初始化
socket
對(duì)象,Udp要用SOCK_DGRAM
- 然后初始化
sockaddr_in
網(wǎng)絡(luò)通信對(duì)象,如果作為服務(wù)端要綁定socket
對(duì)象與通信鏈接,來(lái)接收消息 - 然后開(kāi)啟一個(gè)循環(huán),循環(huán)調(diào)用
recvfrom
來(lái)接收消息 - 收到消息后,保存下發(fā)消息對(duì)象的地址,以便之后回復(fù)消息
- (void)initCSocket
{
char receiveBuffer[1024];
__uint32_t nSize = sizeof(struct sockaddr);
if ((_listenfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
perror("socket() error. Failed to initiate a socket");
}
bzero(&_addr, sizeof(_addr));
_addr.sin_family = AF_INET;
_addr.sin_port = htons(_destPort);
if(bind(_listenfd, (struct sockaddr *)&_addr, sizeof(_addr)) == -1)
{
perror("Bind() error.");
}
_addr.sin_addr.s_addr = inet_addr([_destHost UTF8String]);//ip可是是本服務(wù)器的ip,也可以用宏INADDR_ANY代替,代表0.0.0.0,表明所有地址
while(true){
long strLen = recvfrom(_listenfd, receiveBuffer, sizeof(receiveBuffer), 0, (struct sockaddr *)&_addr, &nSize);
NSString * message = [[NSString alloc] initWithBytes:receiveBuffer length:strLen encoding:NSUTF8StringEncoding];
_destPort = ntohs(_addr.sin_port);
_destHost = [[NSString alloc] initWithUTF8String:inet_ntoa(_addr.sin_addr)];
NSLog(@"來(lái)自%@---%zd:%@",_destHost,_destPort,message);
}
}
-
由于開(kāi)啟while循環(huán)來(lái)一直接收消息,所以為了避免阻塞主線程,這里要將
initCSocket
函數(shù)放在子線程中調(diào)用
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self initCSocket];
});
- 調(diào)用
sendto
方法來(lái)發(fā)送消息
- (void)sendMessage:(NSString *)message
{
NSData *sendData = [message dataUsingEncoding:NSUTF8StringEncoding];
sendto(_listenfd, [sendData bytes], [sendData length], 0, (struct sockaddr *)&_addr, sizeof(struct sockaddr));
}
2、GCDAsyncUdpSocket方式
-
GCDAsyncUdpSocket地址
首先初始化
Socket
對(duì)象綁定端口,調(diào)用
beginReceiving:
方法來(lái)接收消息
- (void)initGCDSocket
{
_receiveSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self
delegateQueue:dispatch_get_global_queue(0, 0)];
NSError *error;
// 綁定一個(gè)端口(可選),如果不綁定端口, 那么就會(huì)隨機(jī)產(chǎn)生一個(gè)隨機(jī)的唯一的端口
// 端口數(shù)字范圍(1024,2^16-1)
[_receiveSocket bindToPort:test_port error:&error];
if (error) {
NSLog(@"服務(wù)器綁定失敗");
}
// 開(kāi)始接收對(duì)方發(fā)來(lái)的消息
[_receiveSocket beginReceiving:nil];
}
- 在代理方法里獲取到對(duì)方發(fā)過(guò)來(lái)的消息,記錄下主機(jī)和端口,以便之后回復(fù)消息
#pragma mark - GCDAsyncUdpSocketDelegate
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)filterContext {
NSString *message = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
_destPort = [GCDAsyncUdpSocket portFromAddress:address];
_destHost = [GCDAsyncUdpSocket hostFromAddress:address];
NSLog(@"來(lái)自%@---%zd:%@",_destHost,_destPort,message);
}
- 調(diào)用
sendData:(NSData *)data toHost:(NSString *)host port:(uint16_t)port withTimeout:(NSTimeInterval)timeout tag:(long)tag
方法來(lái)發(fā)送消息
- (void)sendMessage:(NSString *)message
{
NSData *sendData = [message dataUsingEncoding:NSUTF8StringEncoding];
[_receiveSocket sendData:sendData toHost:_destHost port:_destPort withTimeout:60 tag:500];
}
3、python方式
python方式就比較簡(jiǎn)單了
- 初始化socket,綁定端口
socket = socket(AF_INET, SOCK_DGRAM)
socket.bind(('', port))
- 循環(huán)接收消息
while True:
message, address = socket.recvfrom(2048)
print address,message
- 發(fā)送消息
socket.sendto(message, address)
二、利用python實(shí)現(xiàn)Udp通信demo
創(chuàng)建兩個(gè)python文件,分別作為客戶端和服務(wù)端,然后同時(shí)運(yùn)行
客戶端
from socket import *
host = '127.0.0.1'
port = 12000
socket = socket(AF_INET, SOCK_DGRAM)
while True:
message = raw_input('input message ,print 0 to close :\n')
socket.sendto(message, (host, port))
if message == '0':
socket.close()
break
receiveMessage, serverAddress = socket.recvfrom(2048)
print receiveMessage,serverAddress
- 服務(wù)端
from socket import *
port = 12000
socket = socket(AF_INET, SOCK_DGRAM)
socket.bind(('', port))
print 'server is ready to receive'
count = 0
while True:
message, address = socket.recvfrom(2048)
print address,message
count = count + 1
if message == '0':
socket.close()
break
else:
message = raw_input('input message ,print 0 to close :\n')
socket.sendto(message, address)
- 客戶端打印
/usr/local/bin/python2.7 /Users/wangyong/Desktop/other/python/UDPClient.py
input message ,print 0 to close :
hello,服務(wù)端
hello,客戶端 ('10.208.61.53', 12000)
input message ,print 0 to close :
結(jié)束通信吧我們
好的 ('10.208.61.53', 12000)
input message ,print 0 to close :
0
Process finished with exit code 0
- 服務(wù)端打印
/usr/local/bin/python2.7 /Users/wangyong/Desktop/other/python/UDPServer.py
server is ready to receive
('10.208.61.53', 53500) hello,服務(wù)端
input message ,print 0 to close :
hello,客戶端
('10.208.61.53', 53500) 結(jié)束通信吧我們
input message ,print 0 to close :
好的
('10.208.61.53', 53500) 0
Process finished with exit code 0
三、iOS端基于UDP的簡(jiǎn)易聊天demo
1、UdpManager
Udp通信用C語(yǔ)言版和GCDAsyncUdpSocket
都可以,封裝在UdpManager
中
-
initSocketWithReceiveHandle:(dispatch_block_t)receiveHandle
:初始化socket相關(guān),receiveHandle是接收到消息后的回調(diào) -
sendMessage:(NSString *)message
:發(fā)送消息 -
messageArray
:消息列表,包括接收到的和發(fā)送出去的消息
+ (void)initSocketWithReceiveHandle:(dispatch_block_t)receiveHandle;
+ (void)sendMessage:(NSString *)message;
+ (NSMutableArray *)messageArray;
消息內(nèi)容用MessageModel
,其中role代表消息發(fā)送對(duì)象,為0即是接收到的消息,1為自己發(fā)送的消息
@interface MessageModel:NSObject
@property (nonatomic, copy) NSString *message;
@property(nonatomic,assign) NSInteger role;
@end
2、ViewController
控制器里調(diào)用UdpManager
初始化socket
[UdpManager initSocketWithReceiveHandle:^{
dispatch_async(dispatch_get_main_queue(), ^{
self.title = [NSString stringWithFormat:@"%@---%@",[[UdpManager shareManager] valueForKey:@"_destHost"],[[UdpManager shareManager] valueForKey:@"_destPort"]];
[self reloadData];
});
}];
在代理方法textFieldShouldReturn
即點(diǎn)擊鍵盤(pán)的發(fā)送按鈕時(shí)發(fā)送編輯好的消息
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
if (self.textField.text.length == 0) return YES;
[UdpManager sendMessage:self.textField.text];
[self reloadData];
self.textField.text = nil;
return YES;
}
發(fā)送或者接收到新消息后都會(huì)將消息添加到messageArray
里,并刷新頁(yè)面
- (void)sendMessage:(NSString *)message
{
NSData *sendData = [message dataUsingEncoding:NSUTF8StringEncoding];
[self.messageArray addObject:[[MessageModel alloc] initWithMessage:message role:1]];
#ifdef UseGCDUdpSocket
// 該函數(shù)只是啟動(dòng)一次發(fā)送 它本身不進(jìn)行數(shù)據(jù)的發(fā)送, 而是讓后臺(tái)的線程慢慢的發(fā)送 也就是說(shuō)這個(gè)函數(shù)調(diào)用完成后,數(shù)據(jù)并沒(méi)有立刻發(fā)送,異步發(fā)送
[_receiveSocket sendData:sendData toHost:_destHost port:_destPort withTimeout:60 tag:500];
#else
sendto(_listenfd, [sendData bytes], [sendData length], 0, (struct sockaddr *)&_addr, sizeof(struct sockaddr));
#endif
}
UI就不多做介紹了,控制器里只有一個(gè)顯示接收和發(fā)送消息內(nèi)容列表的tableView
及一個(gè)編輯消息的輸入框textField
。大概就這些內(nèi)容,只是個(gè)簡(jiǎn)易的demo,只實(shí)現(xiàn)了接收發(fā)送文字消息的功能,并沒(méi)有做更多優(yōu)化
3、測(cè)試
分別用模擬器和真機(jī)運(yùn)行,或者可以配合剛才的python程序測(cè)試.
test_host就直接用電腦ip即可
然后手機(jī)先發(fā)送消息到模擬器上,模擬器就可以根據(jù)記錄下的手機(jī)的主機(jī)和端口回復(fù)消息了。這里手機(jī)連外網(wǎng)也是可以的
-
效果圖如下
image