最近在學習socket通信原理,寫篇文章將其記錄一下,tcp和udp的傳值的實現(xiàn)
這次是實現(xiàn)在客戶端發(fā)送數(shù)據(jù),分別使用udp和tcp傳輸數(shù)據(jù),之后服務器上顯示客戶端傳輸?shù)臄?shù)據(jù),使用的第三方框架是CocoaAsyncSocket
因為需要有服務器充當角色,沒有相應的服務器,使用mac自帶的apache服務器并沒有配置成功,之后參考網(wǎng)上的寫法
iOS上的app充當客戶端,mac上的app充當服務器(xcode新建project時選擇macOs中的app選項)
GitHub的相關地址為:
socketClient
socketServer
最終的效果圖為:
先在mac上運行socketServer項目,開啟服務器
控制臺輸出結果:

之后在iphone上運行socketClient項目
tcp測試時的效果圖:

點擊發(fā)送后
socketSercer項目的控制臺輸出為:

udp的效果圖和tcp一樣,不再復述
tcp實現(xiàn)
tcpServer的實現(xiàn)
對應的實現(xiàn)文件是:
socketClient項目中的tcpViewController,
socketServer項目中的TcpServer
首先,先新建一個project,選擇macOs的app,在mac上運行充當服務器
之后新建一個文件,繼承自NSObject,命名為TcpServer
使用的第三方框架CocoaAsyncSocket中,作者有英文介紹服務器方面代碼的寫法,相應的連接如下:
CocoaAsyncSocket說明文檔

參考作者提供的例子,在TcpServer中,我們在.h文件中,聲明一個對外的方法,
- (void)startTcpServer;
另外,聲明兩個屬性
@property (nonatomic, strong) GCDAsyncSocket *listenSocket;//監(jiān)聽socket
@property (nonatomic, strong) NSMutableArray *clientSocketArray;//這里用來保存socket,如果不保存的話,socket會被直接dealloc,然后就會出現(xiàn)服務器斷開連接的錯誤
在.m文件中,實現(xiàn)clientSocketArray懶加載
- (NSMutableArray *)clientSocketArray {
if (_clientSocketArray) {
return _clientSocketArray;
}
_clientSocketArray = [NSMutableArray array];
return _clientSocketArray;
}
實現(xiàn)startTcpServer方法
self.listenSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
NSError *error = nil;
//這里我使用的port端口為5556,也可以使用其他的端口,重要的是要和客戶端寫的保持一致
if (![self.listenSocket acceptOnPort:5556 error:&error]) {
NSLog(@"tcp服務開啟失敗,失敗的原因為%@",error);
} else {
NSLog(@"tcp服務開啟成功");
}
讓該類遵守協(xié)議GCDAsyncSocketDelegate,實現(xiàn)相應的協(xié)議方法
//當socket連接成功后,執(zhí)行這個方法,然后會產(chǎn)生一個新的socket來處理連接,如果想要處理連接,必須retain這個socket(使其不被釋放),這就是我們前面定義clientSocketArray數(shù)組的原因
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket {
//為了對連接進行處理,必須保證newSocket retain,不被釋放,我們使用數(shù)組將其加入
[self.clientSocketArray addObject:newSocket];
NSLog(@"執(zhí)行了這個方法");
//開始讀取數(shù)據(jù),timeOut為負值表示沒有時間限制,讀取數(shù)據(jù)完畢,執(zhí)行socket:didReadData:withTag: 這個協(xié)議方法
[newSocket readDataWithTimeout:-1 tag:self.clientSocketArray.count];
}
//讀取數(shù)據(jù)完成后,執(zhí)行這個方法
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
NSLog(@"當前的data為%@",data);
NSLog(@"當前的tag為%ld",tag);
//將NSData數(shù)據(jù)類型轉變?yōu)镹SString
NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"客戶端傳來的字符為:%@",result);
}
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
NSLog(@"當前服務器的IP地址為%@",host);
}
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
NSLog(@"服務器斷開了連接,錯誤為%@",err);
}
如果我們不使用clientSocketArray數(shù)組保存newSocket的話,在客戶端開始傳值時,server的控制臺會輸出以下信息

意思就是newSocket被回收,導致連接中斷
所以,clientSocketArray的作用很大
之后,在main.m文件中,
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
TcpServer *tcpServer = [[TcpServer alloc] init];
[tcpServer startTcpServer];
UdpServer *udpServer = [[UdpServer alloc] init];
[udpServer startUdpServer];
//開啟主運行循環(huán)
[[NSRunLoop mainRunLoop] run];
}
return NSApplicationMain(argc, argv);
}
運行之后的輸出結果為:

tcpClient的實現(xiàn)
新建一個projiect,選擇iOS中的app,運行在iphone手機上,命名為clientForSocketCommunication,新建文件tcpViewController,繼承于UIViewController,
在.h文件中,聲明相關的屬性
@property (nonatomic, strong) UITextField *textField;
@property (nonatomic, strong) GCDAsyncSocket *clientSocket;
@property (nonatomic, strong) UIButton *sendButton;
@property (nonatomic, strong) NSMutableArray *textArr;
在.m中實現(xiàn)相應的懶加載
- (UITextField *)textField {
if (_textField) {
return _textField;
}
_textField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width - 60, 60)];
_textField.backgroundColor = [UIColor grayColor];
_textField.textColor = [UIColor blackColor];
_textField.delegate = self;
return _textField;
}
- (NSMutableArray *)textArr {
if (_textArr) {
return _textArr;
}
_textArr = [NSMutableArray array];
return _textArr;
}
- (UIButton *)sendButton {
if (_sendButton) {
return _sendButton;
}
_sendButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 100, 50)];
[_sendButton setTitle:@"發(fā)送" forState:UIControlStateNormal];
[_sendButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[_sendButton addTarget:self action:@selector(sendMessage) forControlEvents:UIControlEventTouchUpInside];
return _sendButton;
}
在viewDidload中,實現(xiàn)相應的布局,并且調用開始連接的方法startConnection
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
self.navigationController.navigationBar.translucent = NO;
self.title = @"socket通信中tcp測試客戶端";
[self.view addSubview:self.textField];
[self.textField mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.view);
make.left.equalTo(self.view).offset(30);
make.top.equalTo(self.view).offset(60);
make.height.equalTo(@60);
}];
[self.view addSubview:self.sendButton];
[self.sendButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.view);
make.top.equalTo(self.textField.mas_bottom).offset(30);
make.width.greaterThanOrEqualTo(@0);
make.height.greaterThanOrEqualTo(@0);
}];
[self startConnection];
}
- (void)startConnection {
self.clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
NSError *error = nil;
//電腦上的網(wǎng)絡名稱和手機上的網(wǎng)絡ip地址必須一致才行,不然的話協(xié)議方法一直無法執(zhí)行,電腦和手機在同一個局域網(wǎng)內(nèi),我的mac電腦的局域網(wǎng)地址是192.168.31.61,使用的端口是5556,必須要和Server里面設置的監(jiān)聽端口保持一致,我用的是5556,也可以使用其他的
if (![self.clientSocket connectToHost:@"192.168.31.61" onPort:5556 error:&error]) {
NSLog(@"連接失敗,失敗的原因是%@",error);
}
}
實現(xiàn)按鈕的點擊方法,給server發(fā)送消息
- (void)sendMessage {
NSLog(@"點擊了該方法");
if (!self.textField.text || [self.textField.text length] == 0) {
NSLog(@"發(fā)送的信息不能為空");
return;
}
[self.textArr addObject:self.textField.text];
//將NSString的數(shù)據(jù)類型轉換為NSData的數(shù)據(jù)類型
NSData *data = [self.textField.text dataUsingEncoding:NSUTF8StringEncoding];
//將數(shù)據(jù)寫入套接字,并在完成時調用委托。
[self.clientSocket writeData:data withTimeout:-1 tag:self.textArr.count];
}
接下來實現(xiàn)相應的協(xié)議方法
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[self.textField resignFirstResponder];
return YES;
}
#pragma mark - GCDAsyncSocketDelegate
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
NSLog(@"連接成功,連接的ip地址為%@",host);
}
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
NSLog(@"斷開了連接,錯誤的原因是%@",err);
}
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
NSLog(@"調用了這個方法");
}
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
NSLog(@"當前發(fā)送的是第%ld個請求",tag);
}
完成之后,運行socketServer項目,然后運行socketClient,在文本框中輸入消息,點擊發(fā)送,在socketServer項目中的控制臺,即可得到相應的傳輸信息
udp實現(xiàn)
udpServer的實現(xiàn)
在socketServer項目中,新建一個文件UdpSever,繼承于NSObject
在.h文件中聲明一個對外的方法
- (void)startUdpServer;
并且聲明相應的屬性
@property (nonatomic, strong) GCDAsyncUdpSocket *listenSocket;
在.m中實現(xiàn)聲明的方法
- (void)startUdpServer {
self.listenSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];//隊列傳遞nil,會自動創(chuàng)建一個隊列,這里如果自定義隊列,一定不能是并發(fā)隊列
NSError *error = nil;的端口,但是必須和客戶端的保持一致,不然的話會接收不到數(shù)據(jù)
//設置socket對應的port,這里我使用的是5557這個端口,也可以使用其他
[self.listenSocket bindToPort:5557 error:&error];
if (error) {
NSLog(@"開啟udp服務失敗,失敗的原因是%@",error);
} else {
NSLog(@"開啟udp服務成功");
NSError *err = nil;
// [self.listenSocket receiveOnce:&err];//該方法表示只接受一次數(shù)據(jù),接收客戶端發(fā)過來的數(shù)據(jù)一次之后,就不再接收
[self.listenSocket beginReceiving:&err];//該方法表示多次接收數(shù)據(jù)
if (err) {
NSLog(@"接收數(shù)據(jù)失敗");
}
}
}
實現(xiàn)相應的協(xié)議方法
#pragma mark - GCDAsyncUdpSocketDelegate
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address {
NSLog(@"執(zhí)行了這個方法,address結果為%@",address);
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data
fromAddress:(NSData *)address
withFilterContext:(nullable id)filterContext {
NSLog(@"接受數(shù)據(jù)的ip地址為%@",address);
NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"從客戶端傳來的數(shù)據(jù)為%@",result);
}
之后,同樣的在main.m文件中添加以下代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
TcpServer *tcpServer = [[TcpServer alloc] init];
[tcpServer startTcpServer];
UdpServer *udpServer = [[UdpServer alloc] init];
[udpServer startUdpServer];
//開啟主運行循環(huán)
[[NSRunLoop mainRunLoop] run];
}
return NSApplicationMain(argc, argv);
}
運行程序,控制臺的輸出結果為:

udpClient的實現(xiàn)
在socketClient中,新建文件UdpViewController,繼承于UIViewController,
在.h文件中,聲明相應的屬性
@property (nonatomic, strong) UITextField *textField;
@property (nonatomic, strong) GCDAsyncUdpSocket *clientSocket;
@property (nonatomic, strong) UIButton *sendButton;
@property (nonatomic, strong) NSMutableArray *textArr;
在.m文件中,實現(xiàn)相應的懶加載
- (UITextField *)textField {
if (_textField) {
return _textField;
}
_textField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width - 60, 60)];
_textField.backgroundColor = [UIColor grayColor];
_textField.textColor = [UIColor blackColor];
_textField.delegate = self;
return _textField;
}
- (NSMutableArray *)textArr {
if (_textArr) {
return _textArr;
}
_textArr = [NSMutableArray array];
return _textArr;
}
- (UIButton *)sendButton {
if (_sendButton) {
return _sendButton;
}
_sendButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 100, 50)];
[_sendButton setTitle:@"發(fā)送" forState:UIControlStateNormal];
[_sendButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[_sendButton addTarget:self action:@selector(sendMessage) forControlEvents:UIControlEventTouchUpInside];
return _sendButton;
}
在viewDidload中實現(xiàn)相應的布局,并且開始調用startConnection方法
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
self.navigationController.navigationBar.translucent = NO;
self.title = @"socket通信中udp測試客戶端";
[self.view addSubview:self.textField];
[self.textField mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.view);
make.left.equalTo(self.view).offset(30);
make.top.equalTo(self.view).offset(60);
make.height.equalTo(@60);
}];
[self.view addSubview:self.sendButton];
[self.sendButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.view);
make.top.equalTo(self.textField.mas_bottom).offset(30);
make.width.greaterThanOrEqualTo(@0);
make.height.greaterThanOrEqualTo(@0);
}];
[self startConnection];
}
- (void)startConnection {
self.clientSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
NSError *error = nil;
//端口這里使用的是5557,也可以使用5558,都行,這里無所謂,重要的是下面發(fā)送數(shù)據(jù)時host和port一定要和服務器保持一致,下面的這部分代碼其實也可以不寫
[self.clientSocket bindToPort:5557 error:&error];
if (error) {
NSLog(@"綁定端口失敗,失敗的原因是%@",error);
}
}
實現(xiàn)點擊按鈕發(fā)送消息的方法
- (void)sendMessage {
if (!self.textField.text || [self.textField.text length] == 0) {
return;
}
NSLog(@"開始發(fā)送數(shù)據(jù)");
[self.textArr addObject:self.textField.text];
NSData *data = [self.textField.text dataUsingEncoding:NSUTF8StringEncoding];
//192.168.31.61是我mac電腦連接網(wǎng)路的地址,5557是服務器監(jiān)聽的port,一定要和服務器保持一致
[self.clientSocket sendData:data toHost:@"192.168.31.61" port:5557 withTimeout:-1 tag:self.textArr.count];
}
實現(xiàn)相應的協(xié)議方法
#pragma mark - delegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[self.textField resignFirstResponder];
return YES;
}
#pragma mark - GCDAsyncUdpSocketDelegate
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag {
NSLog(@"調用了該方法");
NSLog(@"這是發(fā)出的第%ld個請求",tag);
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error {
NSLog(@"tag為%ld的數(shù)據(jù)發(fā)送錯誤",tag);
NSLog(@"錯誤的原因為%@",error);
}
- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError * _Nullable)error {
NSLog(@"socket被關閉");
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError * _Nullable)error {
NSLog(@"并沒有連接,連接失敗");
}
先運行socketServer,再運行socketClient,點擊發(fā)送相應的消息,在socketServer控制臺中就會輸出相應的接收到的數(shù)據(jù)
總結
GitHub的相關地址為:
socketClient
socketServer
最終的效果圖為:
先在mac上運行socketServer項目,開啟服務器
控制臺輸出結果:

之后在iphone上運行socketClient項目
tcp測試時的效果圖:

點擊發(fā)送后
socketSercer項目的控制臺輸出為:

udp的效果圖和tcp一樣,不再復述