其实写这个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 即时通信(非第三方库)的更多相关文章

  1. IOS数据持久化存储之SQLite3第三方库FMDB的使用

    SQLite是一种小型的轻量级的关系型数据库,在移动设备上使用是非常好的选择,无论是Android还是IOS,都内置了SQLite数据库,现在的版本都是SQLite3.在IOS中使用SQLite如果使 ...

  2. 最全面的iOS和Mac开源项目和第三方库汇总

    标签: UI 下拉刷新 EGOTableViewPullRefresh – 最早的下拉刷新控件. SVPullToRefresh – 下拉刷新控件. MJRefresh – 仅需一行代码就可以为UIT ...

  3. 【原】iOS学习42即时通信之XMPP(1)

    1. 即时通信 1> 概述 即时通讯(Instant Messaging)是目前Internet上最为流行的通讯方式,各种各样的即时通讯软件也层出不穷,服务提供商也提供了越来越丰富的通讯服务功能 ...

  4. 【原】iOS学习43即时通信之XMPP(2)

    本篇是 即时通信之XMPP(2) 接上次 即时通信之XMPP(1) 1. 好友列表 1> 初始化好友花名册 // 获取管理好友的单例对象 XMPPRosterCoreDataStorage *r ...

  5. iOS开发--即时通讯常用第三方库

    前言 自毕业到现在,从事iOS即时通讯开发已经1年半之久.主要负责Allure开发,目前已上架,可以在苹果商店搜素Allure.Allure模仿微信的交互和设计效果,已经实现微信的大部分功能. 在这里 ...

  6. ios开发经常使用到的第三方库

    由于iOS SDK相对照较底层,所以开发人员就得受累多做一些体力活.只是幸运的是,有非常多第三方的类库能够用来简化非常多不必要的工作.经过作者团队的谨慎讨论.他们 评选出了10款可以极大提高iOS开发 ...

  7. ios开源项目(各种有用的第三方库)

    状态栏:MTStatusBarOverlay  下拉刷新:EGOTableViewPullRefresh  网络应用:ASIHTTPRequest  等待特效:MBProgressHUD  JSON解 ...

  8. Activity通信的第三方库——EventBus

    1.可以实现Activity之间高效的通信. 2.较好地实现了监听器和事件之间的解耦. (ps:本人觉得它实际上是一个简易的观察者模式.) 3.用法: //事件接收 public void onEve ...

  9. iOS 推荐一个下载用的第三方库

    AFNetworking有下载功能,但是下载功能比较基本,要实现复杂下载功能需要自己写一些代码.今天在github上找到了一个下载功能的开源项目,非常不错,链接如下:https://github.co ...

随机推荐

  1. 走进BFC

    在解释 BFC 是什么之前,需要先介绍 Box.Formatting Context的概念. Box: CSS布局的基本单位: Box 是 CSS 布局的对象和基本单位, 直观点来说,就是一个页面是由 ...

  2. python学习-面向对象

    面向对象 编程方式的区别 过程式编程 函数式编程 面向对象式编程 面向对象编程 对象是类的一个实例 创建 class foo(): def __init__(self): #类的构造方法 pass d ...

  3. 使用JPA和Hibernate进行批量处理的最佳方式

    Tips 原文作者:Vlad Mihalcea 原文地址:The best way to do batch processing with JPA and Hibernate 在本文中,你将了解什么是 ...

  4. 几个常用的linux命令(操作服务器时会用到)

    目录 tmux 背景 安装 使用 启动一个tmux session 暂时离开当前session 回到之前的session 重命名session 创建window 创建pane ps scp 参考 tm ...

  5. springmvc 之 返回值

    springMVC对于controller处理方法返回值的可选类型 spring mvc 支持如下的返回方式:ModelAndView, Model, ModelMap, Map,View, Stri ...

  6. .net Kafka.Client多个Consumer Group对Topic消费不能完全覆盖研究总结(一)

    我们知道Kafka支持Consumer Group的功能,但是最近在应用Consumer Group时发现了一个Topic 的Partition不能100%覆盖的问题. 程序部署后,发现Kafka在p ...

  7. Docker Machine 详解

    笔者在<Docker Machine 简介>一文中简单介绍了 Docker Machine 及其基本用法,但是忽略的细节实在是太多了.比如 Docker 与 Docker Machine ...

  8. 有关ArrayList常用方法的源码解析

    我相信几乎所有的同学在大大小小的笔试.面试过程中都会被问及ArrayList与LinkedList之间的异同点.稍有准备的人这些问题早已烂熟于心,前者基于数组实现,后者基于链表实现:前者随机方法速度快 ...

  9. 后端对数组json_encode,前端遍历输出

    echo json_encode($get_city_lists); <script type="text/javascript"> function get_city ...

  10. 论MyBatis日志

    Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具: SLF4J Apache Commons Logging Log4j 2 Log4j JDK logging 具体选择哪个日志 ...