美文网首页iOS开发实践IosiOS网络编程
iOS 使用 socket 即时通信(非第三方库)

iOS 使用 socket 即时通信(非第三方库)

作者: gwk_iOS | 来源:发表于2017-01-18 12:03 被阅读2145次

    写在前面
    弄了下个人站...防止内容再次被锁定...所有东西都在这里面
    welcome~
    个人博客

    其实写这个socket一开始我是拒绝的。



    因为大家学C 语言和linux基础时肯定都有接触,客户端和服务端的通信也都了解过,加上现在很多开放的第三方库都不需要我们来操作底层的通信。

    但是来了!!!

    但是!还是想写。底层的东西最好了解下。


    好了 正经了!!!!

    效果

    xiaoguo.gif

    由于5M的上传限制GIF可能看不清 我再截两张图吧

    服务器 客户端A 客户端B

    模型图


    做了个逗比模型图️


    模型

    分析

    由上图可以了解到服务器和客户端需要做哪些工作

    服务器

    抽象一点分为:

    • 1.创建线程等待接收客户端的连接
    • 2.接收并解析客户端发来的消息
    • 3.给客户端发送消息
      具体一点:
    • 1.创建socket. 绑定端口.开始监听.
    • 2.创建线程.等待接收客户端连接.
    • 3.接收客户端发来的消息
    • 4.解析消息内容
      • a.设置用户名
      • b.发送消息给指定客户端

    客户端

    抽象一点分为:

    • 1.连接服务器
    • 2.给服务器发送消息
    • 3.接收服务器消息
    • 4.解析消息内容
      具体一点:
    • 1.创建socket.绑定端口.连接服务器
    • 2.发送消息
      • a.设置用户名
      • b.给指定用户发消息:按服务器格式拼接字符串
    • 3.接收消息
      • a.普通消息
      • b.用户列表:保存至用户列表

    UI方面

    服务器:其实不用什么UI放个控件展示下日志就是了
    客户端:比较简单,一个俗套聊天室的界面,直接storyboard里拖拖控件设置约束了
    DEMO而已别太当真


    代码部分

    服务器

    要使用scoket需要引用这三个头文件

    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    

    只有一个model,用来绑定用户名和socket

    @interface ClientModel : NSObject
    @property(nonatomic,assign)int clientSocket;
    @property(nonatomic,copy)NSString *clientName;
    @end
    

    只有一个文件全给你

    #import "ViewController.h"
    
    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #import "ClientModel.h"
    static int const kMaxConnectCount = 5;
    
    @interface ViewController()
    @property (weak) IBOutlet NSTextField *textField;
    //@property (nonatomic,assign)int client_socket; //客户端socket
    @property (unsafe_unretained) IBOutlet NSTextView *textView;
        @property (nonatomic,strong)NSMutableArray *clientArray;
        @property (nonatomic,strong)NSMutableArray *clientNameArray;
    @end
    
    @implementation ViewController
    
    - (NSMutableArray *)clientArray {
        if (!_clientArray) {
            _clientArray = [NSMutableArray array];
        }
        return _clientArray;
    }
    - (NSMutableArray *)clientNameArray {
        if (!_clientNameArray) {
            _clientNameArray = [NSMutableArray array];
        }
        return _clientNameArray;
    }
        
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        //创建socket
        int server_socket = socket(AF_INET, SOCK_STREAM, 0);
        if (server_socket == -1) {
            NSLog(@"创建失败");
            [self showLogsWithString:@"socket创建失败"];
    
        }else{
            //绑定地址和端口
            struct sockaddr_in server_addr;
            server_addr.sin_len = sizeof(struct sockaddr_in);
            server_addr.sin_family = AF_INET;
            server_addr.sin_port = htons(1234);
            server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
            bzero(&(server_addr.sin_zero), 8);
            
            int bind_result = bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));
            if (bind_result == -1) {
                NSLog(@"绑定端口失败");
                [self showLogsWithString:@"绑定端口失败"];
    
            }else{
                if (listen(server_socket, kMaxConnectCount)==-1) {
                    NSLog(@"监听失败");
                    [self showLogsWithString:@"监听失败"];
    
                }else{
                    for (int i = 0; i < kMaxConnectCount; i++) {
                        //接受客户端的链接
                        [self acceptClientWithServerSocket:server_socket];
                    }
                }
            }
        }
    }
    
    
    - (void)setRepresentedObject:(id)representedObject {
        [super setRepresentedObject:representedObject];
    
        // Update the view, if already loaded.
    }
    
    //创建线程接受客户端
    -(void)acceptClientWithServerSocket:(int)server_socket{
        struct sockaddr_in client_address;
        socklen_t address_len;
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(queue, ^{
            //创建新的socket
            while (1) {
                int client_socket = accept(server_socket, (struct sockaddr*)&client_address,&address_len );
                if (client_socket == -1) {
                    [self showLogsWithString:@"接受客户端链接失败"];
                    NSLog(@"接受客户端链接失败");
                }else{
                    NSString *acceptInfo = [NSString stringWithFormat:@"客户端 in,socket:%d",client_socket];
                    [self showLogsWithString:acceptInfo];
                    
                    //接受客户端数据
                    [self recvFromClinetWithSocket:client_socket];
                }
            }
        });
    }
    
    //接受客户端数据
    - (void)recvFromClinetWithSocket:(int)client_socket{
        while (1) {
            //接受客户端传来的数据
            char buf[1024] = {0};
            long iReturn = recv(client_socket, buf, 1024, 0);
            if (iReturn>0) {
                NSLog(@"客户端来消息了");
                NSString *str = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];
                [self showLogsWithString:[NSString stringWithFormat:@"客户端来消息了:%@",str]];
                [self checkRecvStr:str andClientSocket:client_socket];
            }else if (iReturn == -1){
                NSLog(@"读取消息失败");
                [self showLogsWithString:@"读取消息失败"];
                break;
            }else if (iReturn == 0){
                NSLog(@"客户端走了");
                [self showLogsWithString:[NSString stringWithFormat:@"客户端 out socket:%d",client_socket]];
                NSMutableArray *array = [NSMutableArray arrayWithArray:self.clientArray];
                for (ClientModel *model in array) {
                    if (model.clientSocket == client_socket) {
                        [self.clientNameArray removeObject:model.clientName];
                        [self.clientArray removeObject:model];
                    }
                }
                
                close(client_socket);
                
                break;
            }
        }
    }
        
        //检查接受到的字符串
    - (void)checkRecvStr:(NSString*)str andClientSocket:(int)socket{
        if ([str hasPrefix:@"name:"]) {
            NSString *name = [str substringFromIndex:5];
            
            ClientModel *model = [[ClientModel alloc] init];
            model.clientSocket = socket;
            model.clientName = name;
    
            
            if (self.clientArray.count > 0) {
                int flag = 999;
                //用户名不能相同
                int i = 0;
    
                for (ClientModel *client in self.clientArray) {
                    
                    //改名
                    if (client.clientSocket == socket) {
                        NSString *oldName = self.clientNameArray[i];
                        self.clientNameArray[i] = name;
                        self.clientArray[i] = model;
    
                        for (ClientModel *oldclient in self.clientArray) {
                            [self sendMsg:[NSString stringWithFormat:@"%@ 改名 %@",oldName,name] toClient:oldclient.clientSocket];
                            [self showLogsWithString:[NSString stringWithFormat:@"%@ 改名 %@",oldName,name]];
                            NSString *list = [self.clientNameArray componentsJoinedByString:@","];
                            //向客户端推送当前在线列表
                            [self sendMsg:[NSString stringWithFormat:@"list:%@",list] toClient:oldclient.clientSocket];
                        }
                        
                        flag = 2;
                        
                    }else{
                        if ([client.clientName isEqualToString:model.clientName]) {
                            //用户名已存在
                            flag = 1;
                            break;
                        }
                    }
                    i++;
    
                }
                if (flag != 1 & flag != 2) {
                    [self.clientArray addObject:model];
                    [self.clientNameArray addObject:model.clientName];
                    //向客户端推送当前在线列表
                    for (ClientModel *client in self.clientArray) {
                        [self sendMsg:[NSString stringWithFormat:@"%@,上线了",name] toClient:client.clientSocket];
                        NSString *list = [self.clientNameArray componentsJoinedByString:@","];
                        //向客户端推送当前在线列表
                        [self sendMsg:[NSString stringWithFormat:@"list:%@",list] toClient:client.clientSocket];
                    }
                    
                    //给当前客户端发送一条欢迎信息
                    NSString *msg = [NSString stringWithFormat:@"Welcome %@ !",name];
                    [self sendMsg:msg toClient:socket];
                    [self showLogsWithString:msg];
    
                }else if (flag == 1){
                    [self sendMsg:@"注册用户名失败,用户名已经存在,请重新设置用户名" toClient:socket];
                    [self showLogsWithString:[NSString stringWithFormat:@"socket %d 注册用户名失败,设置的用户名已经存在",socket]];
                    
                    for (ClientModel *model in self.clientArray) {
                        
                        [name isEqualToString:model.clientName];
                    }
                    
                    
    
                }
            }else{
                [self.clientArray addObject:model];
                [self.clientNameArray addObject:model.clientName];
                //向客户端推送当前在线列表
                //给当前客户端发送一条欢迎信息
                NSString *msg = [NSString stringWithFormat:@"Welcome %@ !",name];
                [self sendMsg:msg toClient:socket];
                [self showLogsWithString:msg];
                
                NSString *list = [self.clientNameArray componentsJoinedByString:@","];
                //向客户端推送当前在线列表
                [self sendMsg:[NSString stringWithFormat:@"list:%@",list] toClient:socket];
                
            }
        
        }
        //给某人发消息
        else if  ([str hasPrefix:@"to:"]){
            NSRange nameRange = [str rangeOfString:@"*"];
            NSString *name = [str substringWithRange:NSMakeRange(3, nameRange.location-3)];
            NSString *content = [str substringFromIndex:nameRange.location+1];
            NSString *fromClientName;
            //找出发送者
            for (ClientModel *model in self.clientArray) {
                if (socket == model.clientSocket) {
                    fromClientName = model.clientName;
                    break;
                }
            }
            
            //给目标发送信息
            for (ClientModel *model in self.clientArray) {
                if ([name isEqualToString:model.clientName]) {
                    NSString *msg = [NSString stringWithFormat:@"%@ to you\n%@",fromClientName,content];
                    [self sendMsg:msg toClient:model.clientSocket];
                    
                    [self showLogsWithString:[NSString stringWithFormat:@"%@ 发送给 %@ 内容是:%@",fromClientName,name,content]];
                    break;
                    
                }
            }
            
        }
    }
        
    //给客户端发送信息
    - (void)sendMsg:(NSString*)msg toClient:(int)socket{
        char *buf[1024] = {0};
        const char *p1 = (char*)buf;
        p1 = [msg cStringUsingEncoding:NSUTF8StringEncoding];
        send(socket, p1, 1024, 0);
    }
    
        //在界面上显示日志
    - (void)showLogsWithString:(NSString*)str {
        dispatch_async(dispatch_get_main_queue(), ^{
            NSString *newStr = [NSString stringWithFormat:@"\n%@",str];
            self.textView.string = [self.textView.string stringByAppendingString:newStr];
        });
    }
        
    @end
    

    客户端

    由于客户端设计的就比较简单,所以代码量也很少,全给你.

    #import "ViewController.h"
    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    @interface ViewController ()<UITableViewDelegate,UITableViewDataSource>
    //服务器socket
    @property (nonatomic,assign)int server_socket;
    
    //UI
    @property (weak, nonatomic) IBOutlet UITextField *userNameField;
    @property (weak, nonatomic) IBOutlet UITextView *chatView;
    @property (weak, nonatomic) IBOutlet UITextField *msgField;
    @property (weak, nonatomic) IBOutlet UILabel *toName;
    @property (weak, nonatomic) IBOutlet UIView *onlineUserView;
    @property (nonatomic,strong)UITableView *onlineTable;
    
    //user列表
    @property (nonatomic,strong)NSMutableArray *userArray;
    
    @end
    
    @implementation ViewController
    - (NSMutableArray *)userArray {
        if (!_userArray) {
            _userArray = [NSMutableArray array];
        }
        return _userArray;
    }
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self.userNameField becomeFirstResponder];
        self.userNameField.text = @"";
        self.msgField.text = @"";
        //添加table用户列表
        self.onlineTable = [[UITableView alloc] initWithFrame:self.onlineUserView.frame style:UITableViewStylePlain];
        self.onlineTable.delegate = self;
        self.onlineTable.dataSource = self;
        [self.view addSubview:self.onlineTable];
        
        int server_socket = socket(AF_INET, SOCK_STREAM, 0);
        if (server_socket == -1) {
            NSLog(@"创建失败");
        }else{
            //绑定地址和端口
            struct sockaddr_in server_addr;
            server_addr.sin_len = sizeof(struct sockaddr_in);
            server_addr.sin_family = AF_INET;
            server_addr.sin_port = htons(1234);
            server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
            bzero(&(server_addr.sin_zero), 8);
            
            //接受客户端的链接
            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
            dispatch_async(queue, ^{
                //创建新的socket
                int aResult = connect(server_socket, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in));
                if (aResult == -1) {
                    NSLog(@"链接失败");
                }else{
                    self.server_socket = server_socket;
                    [self acceptFromServer];
                }
            });
        }
    }
    
    //从服务端接受消息
    - (void)acceptFromServer{
        while (1) {
            //接受服务器传来的数据
            char buf[1024];
            long iReturn = recv(self.server_socket, buf, 1024, 0);
            if (iReturn>0) {
                NSString *str = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];
    
                //筛选前缀
                if ([str hasPrefix:@"list:"]) {
                    NSString *arrayStr = [str substringFromIndex:5];
                    NSArray *list = [arrayStr componentsSeparatedByString:@","];
                    self.userArray = [NSMutableArray arrayWithArray:list];
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [self.onlineTable reloadData];
                    });
                    NSLog(@"当前在线用户列表:%@",arrayStr);
                }else{
                    //回到主线程 界面上显示内容
                    [self showLogsWithString:str];
                }
                 
            }else if (iReturn == -1){
                NSLog(@"接受失败-1");
                break;
            }
        }
    }
        
    //在界面上显示日志
    - (void)showLogsWithString:(NSString*)str {
        dispatch_async(dispatch_get_main_queue(), ^{
            NSString *newStr = [NSString stringWithFormat:@"\n%@",str];
            self.chatView.text = [self.chatView.text stringByAppendingString:newStr];
        });
    }
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    //设置用户名
    - (IBAction)clickSetUserName:(id)sender {
        NSString *msg = [NSString stringWithFormat:@"name:%@",self.userNameField.text] ;
        [self sendMsg:msg];
    //    [self showLogsWithString:msg];
        [self.msgField becomeFirstResponder];
    }
    
    //发送信息
    - (IBAction)clickSendMsg:(id)sender {
        if ([self.msgField.text isEqualToString:@""] || ![self.userArray containsObject:self.userNameField.text] || [self.toName.text isEqualToString:self.userNameField.text]) {
            [self showLogsWithString:@"请设置用户名、检查发送对象、消息不能为空"];
            return;
        }
        NSString *msg = [NSString stringWithFormat:@"to:%@*%@",self.toName.text,self.msgField.text];
        [self sendMsg:msg];
        NSString *displayMsg = [NSString stringWithFormat:@"to:%@\n%@",self.toName.text,self.msgField.text];
        [self showLogsWithString:displayMsg];
        self.msgField.text = @"";
    
    }
        
    //给客户端发送信息
    - (void)sendMsg:(NSString*)msg {
        char *buf[1024] = {0};
        const char *p1 = (char*)buf;
        p1 = [msg cStringUsingEncoding:NSUTF8StringEncoding];
        send(self.server_socket, p1, 1024, 0);
    }
    
    #pragma mark - TableViewDelegate & dataSource
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
        return self.userArray.count;
    }
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
        static NSString *cellId = @"onlinetableviewcellid";
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
        if (cell == nil) {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
        }else{
            NSLog(@"cell重用了");
        }
        cell.textLabel.text = self.userArray[indexPath.row];
        return cell;
    }
        
    //点击cell
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
        self.toName.text = self.userArray[indexPath.row];
        [self.msgField becomeFirstResponder];
    }
    @end
    

    Demo地址

    https://github.com/gongxiaokai/IMsocketDemo
    demo系列求点赞加星

    求关注
    iOS开发实战-时光记账Demo 网络版
    iOS开发实战-时光记账Demo 本地数据库版
    Objective-C MapKit的使用-LBS简单的租车主界面demo
    swift3.0 coreData的使用-日记本demo

    相关文章

      网友评论

      • 心语风尚:port 与host 根据什么配置 随意写吗
      • 心语风尚:一直不明白 host 与 端口是 想写什么就什么吗 不可能吧 怎么确定
        心语风尚:绑定端口失败. 什么原因
        心语风尚:为什么你的127.0.0.1在我电脑也可以 我没有改电脑的
        gwk_iOS:@心语风尚 我的host 是127.0.0.1 是本机地址,两个模拟器都在本机的网络下 端口 自己随便指定就行,不要跟常用的端口冲突就可以
      • Chinws:留个联系方式呗 有些问题想问你 大神
      • 雪_晟:IP换成了我自己的还是连接失败了,需要设置哪里吗
      • 黑幕居士:就凭这个文章风格,赞一个
      • 心语风尚:怎么做到 所有网络都可以收发信息
      • 起个名字叫布谷:服务器这边可以用OC写么?我看你这个等于是Mac上的应用开发了,服务器部分。这边能讲讲么?
        gwk_iOS:@Simple丶WQZ 服务器都是一个道理 看服务器的环境,即时通信主要就是socket的配置,地址的绑定,监听 建立连接 收发消息 等一些内容。
      • 成长路上多迷惑: 老哥,我运行你的demo, 模拟器可以连接成功。 真机连接失败。 要改哪里啊
        gwk_iOS:@成长路上多迷惑 去证书选择那边新增你自己的账号team
        成长路上多迷惑:@gwk_ios 证书清什么?。不用证书吗
        gwk_iOS:证书清一下 。
      • 国王or乞丐:写的很好,支持大神
        gwk_iOS:感谢支持:clap: :clap: :clap:
      • 爱阿爸的阿龙龙:最近也在研究即时通讯!
        gwk_iOS:@爱阿爸的阿龙龙868 :clap::clap::clap:
      • 2e1547f17500::smile: 看在这些表情的份上,给你点个赞
        gwk_iOS:感谢支持:clap:
      • StevenJ:好奇问下,看简书好像大部分人都还在用objc,为什么不用swift呢??
        gwk_iOS:@StevenJ 目前大部分的项目还是用OC比较多,直接去转swift工程量是很大的,虽然swift很便捷,swift3.0可以算得上稳定版本,加入了服务端的开发,但改动太大了,只能说这个语言的选择还是要看整个项目前提架构 现在swift他爹也走了 堪忧啊
        GiantAxe77:swift 之父都跳槽了,谁还用
        Schorem:公司没用swift啊, 大家得同步
      • 90年代的小伙子:我就想知道你们这些逗逼的图片哪里找的
        gwk_iOS:@娶个名字真难 哈哈 表情到处都有😝
      • 松树李树:楼主好牛
        gwk_iOS:@LS_Developer 🤗🤗
      • f75dfaf80e43:挺有需要的,我想问下,你那个MyServer是用OC写的服务器 这样理解对吗
        gwk_iOS:@奔小康 是可以的,linux上也是也可搭建oc的环境
        f75dfaf80e43:@gwk_ios 你写的那个mac app,相当于服务器吧.如果说我要搭建服务器,是不是也可以用OC去实现呢
        gwk_iOS:@奔小康 是的 相当于 mac下的 app 直接运行就行
      • LambZhou:刚好要做socket 即时通讯,可以拿来参考,谢啦~
        国王or乞丐:建议使用GCD的,因为这个会的还比较多,如果真的遇到问题还有人可以帮你看看,并且GCD也简单很多

      本文标题:iOS 使用 socket 即时通信(非第三方库)

      本文链接:https://www.haomeiwen.com/subject/rgivbttx.html