Wifi通讯-(3)WIFI通讯服务端搭建

WIFI通讯服务端搭建

  • 服务端的职责主要做两件事,一:监听socket网络中的端口 二:将某一个客户端的发送过来的消息转发给其他客户端

    • 客户端是无法直接和客户端通讯的,需要经过服务端的转发
    • 其他一些开发中细节笔者将会在代码注释中写明
  • [1.0-准备工作]

  • [1.1-服务端监听网络端口]

    • [1.1.1-关于端口号的说明]
  • [1.2-服务端接收客户端消息并转发]

  • [1.3-完整代码及示例演示]

1.0-准备工作

  • 笔者首先将上一小节的代码封装成一个wifi工具类,方便服务端和客户端快速获取自己的wifi名称和ip地址

#import "WIFITools.h"

//系统配置框架(网络配置也在该框架中)
#import <SystemConfiguration/CaptiveNetwork.h>

#include <arpa/inet.h>
#include <netdb.h>
#include <net/if.h>
#include <ifaddrs.h>
#import <dlfcn.h>

@implementation WIFITools


//只对真机wifi连接有效,如果是模拟器和真机4G网络则都为nil
+(NSString *)currentWifiSSID
{
    //1.获取网络底层监视的所有接口列表,返回的是一个BSD接口名称
    NSArray *ifs = (__bridge id)CNCopySupportedInterfaces();
    
    id info = nil;
    for (NSString *ifnam in ifs) {
        
        //2.通过BSD接口名称获取网络信息,返回的是一个字典。其中包含 1.wifi名称字符串  2.mac地址 3.wifi名称二进制数据
        info = (__bridge id)CNCopyCurrentNetworkInfo((__bridge CFStringRef)ifnam);
        if (info && [info count]) {
            break;
        }
    }
    
    //3.获取字典
    NSDictionary *dctySSID = (NSDictionary *)info;
    
    //4.字典的SSID键对应的值就是wifi的名称
    //注意:  1. 模拟器获取不到wifi名称 返回为nil  2.真机的话如果没有连接wifi而是使用4G,返回的也是nil
    NSString *ssid = [dctySSID objectForKey:@"SSID"];//wifi名称
    
//     NSString *bssid = [dctySSID objectForKey:@"BSSID"];//mac地址,苹果返回的是一个无效的mac地址,与手机实际mac地址不一致,主要用于保护用户隐私
    
    return ssid;
    
}


//获取本机在当前wifi中的ip地址,模拟器真机均可获取
+(NSString *)localWiFiIPAddress
{
    BOOL success;
    struct ifaddrs * addrs;
    const struct ifaddrs * cursor;
    
    success = getifaddrs(&addrs) == 0;
    if (success) {
        cursor = addrs;
        while (cursor != NULL) {
            // the second test keeps from picking up the loopback address
            if (cursor->ifa_addr->sa_family == AF_INET && (cursor->ifa_flags & IFF_LOOPBACK) == 0)
            {
                NSString *name = [NSString stringWithUTF8String:cursor->ifa_name];
                if ([name isEqualToString:@"en0"])  // Wi-Fi adapter
                    return [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)cursor->ifa_addr)->sin_addr)];
            }
            cursor = cursor->ifa_next;
        }
        freeifaddrs(addrs);
    }
    return nil;
}

@end

  • 笔者搭建了一个简单的界面,主要功能就是服务端开始监听网络端口,并且将接收到的服务端的消息转发之后顺便显示在界面tableView中

1.1-服务端监听网络端口

  • 1.创建服务端socket
  • 2.开始监听

//服务端监听按钮点击事件
- (IBAction)wifiButtonClick:(UIButton*)sender {
    
    //1.创建服务端socket
    if (!self.serviceSocket) {
        self.serviceSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    }
    
    NSError *error = nil;
    
    //2.开始监听端口
    //注意:笔者这里随便写的一个端口。实际开发中,端口号不能随便乱写,硬件工程师会给我们一份wifi通讯协议文档,文档中会写明服务端的ip地址及端口号。  我这里默认情况下,ip地址用的就是mac电脑当前连接wifi的ip地址,已经在界面初始化时获取,端口号我写的是1234.
    [self.serviceSocket acceptOnPort:1234 error:&error];
    
    if (error==nil) {
        NSLog(@"监听成功");
    }
    else
    {
        NSLog(@"%@",error.description);
    }
    
}

1.1.1-关于端口号的说明

  • 端口号
    • 总范围:0~65535
    • 周知端口:0~1023 通常由Unix系统占用
    • 注册端口:1025~49151 一般由系统自动分配
    • 1024端口一般不固定分配给某个服务,在英文中的解释是“Reserved”(保留)。之前,我们曾经提到过动态端口的范围是从1024~65535,而1024正是动态端口的开始。该端口一般分配给第一个向系统发出申请的服务,在关闭服务的时候,就会释放1024端口,等待其他服务的调用

1.2-服务端接收客户端消息并转发

  • 均在Socket代理中完成

  • 1.服务端将监听到的客户端添加到数组,否则会因为局部变量而导致被释放

  • 2.服务端接听到客户端加入之后,开始读取客户端的数据(该方法调用一次只能读取一次)

  • 3.服务端接收到客户端发送过来的数据之后,转发给数组中其他的客户端

  • 4.服务端接收到客户端数据之后,继续读取客户端数据,这样可以保证一直都能读取到客户端数据


#pragma mark - GCDAsyncSocketDelegate
//服务端监听到客户端的连接
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
{
    //1.将连接的客户端添加到数组,防止局部变量被释放
    if (!self.clientArr) {
        self.clientArr = [NSMutableArray array];
    }
    [self.clientArr addObject:newSocket];
    
    //2给客户端发送一个消息,可以让客户端在连接服务端时快速知道自己是否连接服务端成功
    //注意:有的wifi通讯在这里会有一个握手的过程,也就是服务端会发给客户端一个用于验证的密文,然后客户端验证通过之后才能继续通讯,否则服务端就会踢掉客户端。主要是保证只能让自己的APP连接自己的产品,防止被其他程序连接
    NSString *str = @"服务器:欢迎来到黑马程序员";
    
    //线程延迟发送消息,防止线程阻塞
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [newSocket writeData:[str dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
    });
    
    
    //2.开始读取客户端的数据(类似于服务器实时监听客户端发送的数据)
    //注意:该行代码调用一次只能接收一次数据
    [newSocket readDataWithTimeout:-1 tag:0];
}

//服务端接收数据成功
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"服务端接发送数据:%@",str);
    //添加到数组用于界面显示
    [self.tableArr addObject:data];
    //1.服务器的作用主要是数据的转发,所以这里我们将数据转发给其他客户端
    for (GCDAsyncSocket *socket in self.clientArr) {
        //1.1过滤掉发送消息者本身
        if (socket != sock) {
            
            //1.2给客户端发送数据
            [socket writeData:data withTimeout:-1 tag:0];
        }
    }
    [self.tableView reloadData];
    
    //2.接收到客户端数据之后,继续读取客户端数据
    //注意在这个方法中调用该方法可以保证一直都能读取到客户端数据,如果不调用,每一个客户端的数据就只能读取一次
    [sock readDataWithTimeout:-1 tag:0];
    
}

//服务端发送数据成功
-(void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
    NSLog(@"服务端发送数据");
}

1.3-完整代码及示例演示

  • 整个项目Demo将在稍后给出下载链接

//
//  ServiceViewController.m
//  wifi通讯
//
//  Created by 晓坤张 on 2017/8/12.
//  Copyright © 2017年 晓坤张. All rights reserved.
//

#import "ServiceViewController.h"

#import "GCDAsyncSocket.h"

#import "WIFITools.h"

@interface ServiceViewController ()<UITableViewDelegate,UITableViewDataSource>

//显示wifi名称文本
@property (weak, nonatomic) IBOutlet UILabel *wifiNameLabel;

//显示ip地址文本
@property (weak, nonatomic) IBOutlet UILabel *ipAddressLabel;

//开始连接按钮(监听端口)
@property (weak, nonatomic) IBOutlet UIButton *wifiButton;

//服务端socket(在socket网络通讯中,万物皆socket,类似于OC中的NSObject)
@property(nonatomic,strong)GCDAsyncSocket *serviceSocket;

//客户端数组(防止因为客户端是局部变量而被释放!)
@property(nonatomic,strong)NSMutableArray *clientArr;

//显示所有客户端的数据
@property (weak, nonatomic) IBOutlet UITableView *tableView;
//存放客户端的消息用于显示
@property(nonatomic,strong)NSMutableArray <NSData *>*tableArr;

@end

@implementation ServiceViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //界面初始化操作
    self.wifiNameLabel.text = [NSString stringWithFormat:@"wifi名称:%@",[WIFITools currentWifiSSID]];
    self.ipAddressLabel.text = [NSString stringWithFormat:@"ip地址:%@",[WIFITools localWiFiIPAddress]];
    
    self.tableArr = [NSMutableArray array];
    // Do any additional setup after loading the view.
}

//服务端监听按钮点击事件
- (IBAction)wifiButtonClick:(UIButton*)sender {
    
    //1.创建服务端socket
    if (!self.serviceSocket) {
        self.serviceSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    }
    
    NSError *error = nil;
    
    //2.开始监听端口
    //注意:笔者这里随便写的一个端口。实际开发中,端口号不能随便乱写,硬件工程师会给我们一份wifi通讯协议文档,文档中会写明服务端的ip地址及端口号。  我这里默认情况下,ip地址用的就是mac电脑当前连接wifi的ip地址,已经在界面初始化时获取,端口号我写的是1234.
    [self.serviceSocket acceptOnPort:1234 error:&error];
    
    if (error==nil) {
        NSLog(@"监听成功");
    }
    else
    {
        NSLog(@"%@",error.description);
    }
    
}


#pragma mark - GCDAsyncSocketDelegate
//服务端监听到客户端的连接
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
{
    //1.将连接的客户端添加到数组,防止局部变量被释放
    if (!self.clientArr) {
        self.clientArr = [NSMutableArray array];
    }
    [self.clientArr addObject:newSocket];
    
    //2给客户端发送一个消息,可以让客户端在连接服务端时快速知道自己是否连接服务端成功
    //注意:有的wifi通讯在这里会有一个握手的过程,也就是服务端会发给客户端一个用于验证的密文,然后客户端验证通过之后才能继续通讯,否则服务端就会踢掉客户端。主要是保证只能让自己的APP连接自己的产品,防止被其他程序连接
    NSString *str = @"服务器:欢迎来到黑马程序员";
    
    //线程延迟发送消息,防止线程阻塞
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [newSocket writeData:[str dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
    });
    
    
    //2.开始读取客户端的数据(类似于服务器实时监听客户端发送的数据)
    //注意:该行代码调用一次只能接收一次数据
    [newSocket readDataWithTimeout:-1 tag:0];
}

//服务端接收数据成功
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"服务端接发送数据:%@",str);
    //添加到数组用于界面显示
    [self.tableArr addObject:data];
    //1.服务器的作用主要是数据的转发,所以这里我们将数据转发给其他客户端
    for (GCDAsyncSocket *socket in self.clientArr) {
        //1.1过滤掉发送消息者本身
        if (socket != sock) {
            
            //1.2给客户端发送数据
            [socket writeData:data withTimeout:-1 tag:0];
        }
    }
    [self.tableView reloadData];
    
    //2.接收到客户端数据之后,继续读取客户端数据
    //注意在这个方法中调用该方法可以保证一直都能读取到客户端数据,如果不调用,每一个客户端的数据就只能读取一次
    [sock readDataWithTimeout:-1 tag:0];
    
}

//服务端发送数据成功
-(void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
    NSLog(@"服务端发送数据");
}

#pragma mark -UITableViewDelegate

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.tableArr.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"serviceCell"];
    
    UILabel *nameLabel = (UILabel *)[cell.contentView viewWithTag:1];
    UILabel *msgLabel = (UILabel *)[cell.contentView viewWithTag:2];
    
    //读取数据  笔者这里定义的数据协议格式是这样   客户端的设备名称:数据   通过分割:来获取对于数据,实际中应当根据wifi协议来。另外,笔者这里演示的字符串通讯,实际中wifi协议有可能是更加底层的一些通讯格式  例如 二进制字节流  Ascii码等
    NSData *data = self.tableArr[indexPath.row];
    NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSArray *arr = [str componentsSeparatedByString:@":"];
    //显示数据
    nameLabel.text = [NSString stringWithFormat:@"设备名称:%@",arr[0]];
    msgLabel.text = [NSString stringWithFormat:@" 数据:%@",arr[1]];
    
    return cell;
    
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容