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

写在前面
弄了下个人站...防止内容再次被锁定...所有东西都在这里面
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

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

推荐阅读更多精彩内容