iOS 使用 socket 即时通信(非第三方库)
其实写这个socket一开始我是拒绝的。
因为大家学C 语言和linux基础时肯定都有接触,客户端和服务端的通信也都了解过,加上现在很多开放的第三方库都不需要我们来操作底层的通信。
但是来了!!!
但是!还是想写。底层的东西最好了解下。
好了 正经了!!!!
效果
由于5M的上传限制GIF可能看不清 我再截两张图吧
模型图
做了个逗比模型图️
分析
由上图可以了解到服务器和客户端需要做哪些工作
服务器
抽象一点分为:
- 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
iOS 使用 socket 即时通信(非第三方库)的更多相关文章
- IOS数据持久化存储之SQLite3第三方库FMDB的使用
SQLite是一种小型的轻量级的关系型数据库,在移动设备上使用是非常好的选择,无论是Android还是IOS,都内置了SQLite数据库,现在的版本都是SQLite3.在IOS中使用SQLite如果使 ...
- 最全面的iOS和Mac开源项目和第三方库汇总
标签: UI 下拉刷新 EGOTableViewPullRefresh – 最早的下拉刷新控件. SVPullToRefresh – 下拉刷新控件. MJRefresh – 仅需一行代码就可以为UIT ...
- 【原】iOS学习42即时通信之XMPP(1)
1. 即时通信 1> 概述 即时通讯(Instant Messaging)是目前Internet上最为流行的通讯方式,各种各样的即时通讯软件也层出不穷,服务提供商也提供了越来越丰富的通讯服务功能 ...
- 【原】iOS学习43即时通信之XMPP(2)
本篇是 即时通信之XMPP(2) 接上次 即时通信之XMPP(1) 1. 好友列表 1> 初始化好友花名册 // 获取管理好友的单例对象 XMPPRosterCoreDataStorage *r ...
- iOS开发--即时通讯常用第三方库
前言 自毕业到现在,从事iOS即时通讯开发已经1年半之久.主要负责Allure开发,目前已上架,可以在苹果商店搜素Allure.Allure模仿微信的交互和设计效果,已经实现微信的大部分功能. 在这里 ...
- ios开发经常使用到的第三方库
由于iOS SDK相对照较底层,所以开发人员就得受累多做一些体力活.只是幸运的是,有非常多第三方的类库能够用来简化非常多不必要的工作.经过作者团队的谨慎讨论.他们 评选出了10款可以极大提高iOS开发 ...
- ios开源项目(各种有用的第三方库)
状态栏:MTStatusBarOverlay 下拉刷新:EGOTableViewPullRefresh 网络应用:ASIHTTPRequest 等待特效:MBProgressHUD JSON解 ...
- Activity通信的第三方库——EventBus
1.可以实现Activity之间高效的通信. 2.较好地实现了监听器和事件之间的解耦. (ps:本人觉得它实际上是一个简易的观察者模式.) 3.用法: //事件接收 public void onEve ...
- iOS 推荐一个下载用的第三方库
AFNetworking有下载功能,但是下载功能比较基本,要实现复杂下载功能需要自己写一些代码.今天在github上找到了一个下载功能的开源项目,非常不错,链接如下:https://github.co ...
随机推荐
- 通过JSP+servlet实现文件上传功能
在TCP/IP中,最早出现的文件上传机制是FTP.它将文件由客户端到服务器的标准机制. 但是在JSP中不能使用FTP来上传文件,这是有JSP的运行机制所决定的. 通过为表单元素设置Method=&qu ...
- 从deque到std::stack,std::queue,再到iOS 中NSArray(CFArray)
从deque到std::stack,std::queue,再到iOS 中NSArray(CFArray) deque deque双端队列,分段连续空间数据结构,由中控的map(与其说map,不如说是数 ...
- 依赖注入之Autofac使用总结
依赖倒置?控制反转(IOC)? 依赖注入(DI)? 你是否还在被这些名词所困扰,是否看了大量理论文章后还是一知半解了? 今天我想结合实际项目,和正在迷惑中的新手朋友一起来学习和总结依赖注入Autofa ...
- solr学习笔记section2-solr单机(节点)简单的core操作
在上一节中我们已经成功部署和运行了一个solr应用,那么我们就可以通过这个正在运行的solr来创建一些文档,并进行搜索. 首先介绍一下core这个概念,core在solr中类似与关系型数据库中一张表的 ...
- swiper结合ajax的轮播图
Swiper 是什么:是纯JavaScript打造的滑动特效插件,能够实现触屏焦点图.触屏tab切换.触屏多图切换等常用效果. 开源.免费.稳定.应用广泛. 这就是swiper简单的介绍,由于是结合a ...
- wildfly10报错2:ID注释有错
13:55:56,612 INFO [org.jboss.modules] (main) JBoss Modules version 1.5.1.Final 13:55:56,891 INFO [or ...
- 各开放平台API接口通用SDK序列文章 前言
最近两年一直在做API接口相关的工作,在平时工作中以及网上看到很多刚接触API接口调用的新人一开始会感到很不适应,要看的文档一大堆,自己要调用的接口找不着,或都找着了不知道怎么去调用,记得包括自己刚开 ...
- matlab对文件目录进行自然排序
作者:tongqingliu 转载请注明出处: matlab对文件目录进行自然排序 比如我新建一个tmp文件夹,在该文件夹下新建以下txt文件进行测试 a1.txt a2.txt a3.txt a11 ...
- js将字符串转化成函数:eval(logOutCallbackFun+"()");
js将字符串转化成函数:eval(logOutCallbackFun+"()");
- [leetcode-508-Most Frequent Subtree Sum]
Given the root of a tree, you are asked to find the most frequent subtree sum. The subtree sum of a ...