iOS 使用 socket 實現(xiàn)即時通信示例(非第三方庫)
其實寫這個socket一開始我是拒絕的。
因為大家學(xué)C 語言和linux基礎(chǔ)時肯定都有接觸,客戶端和服務(wù)端的通信也都了解過,加上現(xiàn)在很多開放的第三方庫都不需要我們來操作底層的通信。
但是!還是想寫。底層的東西最好了解下。
效果

由于5M的上傳限制GIF可能看不清 我再截兩張圖吧

服務(wù)器

客戶端A

客戶端B
模型圖

分析
由上圖可以了解到服務(wù)器和客戶端需要做哪些工作
服務(wù)器
抽象一點分為:
1.創(chuàng)建線程等待接收客戶端的連接
2.接收并解析客戶端發(fā)來的消息
3.給客戶端發(fā)送消息
具體一點:
1.創(chuàng)建socket. 綁定端口.開始監(jiān)聽.
2.創(chuàng)建線程.等待接收客戶端連接.
3.接收客戶端發(fā)來的消息
4.解析消息內(nèi)容
a.設(shè)置用戶名
b.發(fā)送消息給指定客戶端
客戶端
抽象一點分為:
1.連接服務(wù)器
2.給服務(wù)器發(fā)送消息
3.接收服務(wù)器消息
4.解析消息內(nèi)容
具體一點:
1.創(chuàng)建socket.綁定端口.連接服務(wù)器
2.發(fā)送消息
a.設(shè)置用戶名
b.給指定用戶發(fā)消息:按服務(wù)器格式拼接字符串
3.接收消息
a.普通消息
b.用戶列表:保存至用戶列表
UI方面
服務(wù)器:其實不用什么UI放個控件展示下日志就是了
客戶端:比較簡單,一個俗套聊天室的界面,直接storyboard里拖拖控件設(shè)置約束了
DEMO而已別太當(dāng)真
代碼部分
服務(wù)器
要使用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.
//創(chuàng)建socket
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
NSLog(@"創(chuàng)建失敗");
[self showLogsWithString:@"socket創(chuàng)建失敗"];
}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(@"監(jiān)聽失敗");
[self showLogsWithString:@"監(jiān)聽失敗"];
}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.
}
//創(chuàng)建線程接受客戶端
-(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, ^{
//創(chuàng)建新的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];
//接受客戶端數(shù)據(jù)
[self recvFromClinetWithSocket:client_socket];
}
}
});
}
//接受客戶端數(shù)據(jù)
- (void)recvFromClinetWithSocket:(int)client_socket{
while (1) {
//接受客戶端傳來的數(shù)據(jù)
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:@","];
//向客戶端推送當(dāng)前在線列表
[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];
//向客戶端推送當(dāng)前在線列表
for (ClientModel *client in self.clientArray) {
[self sendMsg:[NSString stringWithFormat:@"%@,上線了",name] toClient:client.clientSocket];
NSString *list = [self.clientNameArray componentsJoinedByString:@","];
//向客戶端推送當(dāng)前在線列表
[self sendMsg:[NSString stringWithFormat:@"list:%@",list] toClient:client.clientSocket];
}
//給當(dāng)前客戶端發(fā)送一條歡迎信息
NSString *msg = [NSString stringWithFormat:@"Welcome %@ !",name];
[self sendMsg:msg toClient:socket];
[self showLogsWithString:msg];
}else if (flag == 1){
[self sendMsg:@"注冊用戶名失敗,用戶名已經(jīng)存在,請重新設(shè)置用戶名" toClient:socket];
[self showLogsWithString:[NSString stringWithFormat:@"socket %d 注冊用戶名失敗,設(shè)置的用戶名已經(jīng)存在",socket]];
for (ClientModel *model in self.clientArray) {
[name isEqualToString:model.clientName];
}
}
}else{
[self.clientArray addObject:model];
[self.clientNameArray addObject:model.clientName];
//向客戶端推送當(dāng)前在線列表
//給當(dāng)前客戶端發(fā)送一條歡迎信息
NSString *msg = [NSString stringWithFormat:@"Welcome %@ !",name];
[self sendMsg:msg toClient:socket];
[self showLogsWithString:msg];
NSString *list = [self.clientNameArray componentsJoinedByString:@","];
//向客戶端推送當(dāng)前在線列表
[self sendMsg:[NSString stringWithFormat:@"list:%@",list] toClient:socket];
}
}
//給某人發(fā)消息
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;
//找出發(fā)送者
for (ClientModel *model in self.clientArray) {
if (socket == model.clientSocket) {
fromClientName = model.clientName;
break;
}
}
//給目標(biāo)發(fā)送信息
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:@"%@ 發(fā)送給 %@ 內(nèi)容是:%@",fromClientName,name,content]];
break;
}
}
}
}
//給客戶端發(fā)送信息
- (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
客戶端
由于客戶端設(shè)計的就比較簡單,所以代碼量也很少,全給你.
#import "ViewController.h"
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
@interface ViewController ()<UITableViewDelegate,UITableViewDataSource>
//服務(wù)器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(@"創(chuàng)建失敗");
}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, ^{
//創(chuàng)建新的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];
}
});
}
}
//從服務(wù)端接受消息
- (void)acceptFromServer{
while (1) {
//接受服務(wù)器傳來的數(shù)據(jù)
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(@"當(dāng)前在線用戶列表:%@",arrayStr);
}else{
//回到主線程 界面上顯示內(nèi)容
[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.
}
//設(shè)置用戶名
- (IBAction)clickSetUserName:(id)sender {
NSString *msg = [NSString stringWithFormat:@"name:%@",self.userNameField.text] ;
[self sendMsg:msg];
// [self showLogsWithString:msg];
[self.msgField becomeFirstResponder];
}
//發(fā)送信息
- (IBAction)clickSendMsg:(id)sender {
if ([self.msgField.text isEqualToString:@""] || ![self.userArray containsObject:self.userNameField.text] || [self.toName.text isEqualToString:self.userNameField.text]) {
[self showLogsWithString:@"請設(shè)置用戶名、檢查發(fā)送對象、消息不能為空"];
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 = @"";
}
//給客戶端發(fā)送信息
- (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地址:IMsocketDemo_jb51.rar
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
iOS之?dāng)?shù)據(jù)解析之XML解析詳解
本篇文章主要介紹了iOS之?dāng)?shù)據(jù)解析之XML解析詳解,XML解析常見的兩種方式:DOM解析和SAX解析,有興趣的可以了解一下。2016-12-12
iOS開發(fā)教程之UIRefreshControl使用的踩坑指南
UIRefreshControl是iOS6自帶的UITableView下拉刷新控件。下面這篇文章主要給大家介紹了關(guān)于iOS開發(fā)教程之UIRefreshControl使用的踩坑指南,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-04-04
iOS開發(fā)驗證判斷語句之正則表達(dá)式小結(jié)
最近在公司接手重構(gòu)一個項目,發(fā)現(xiàn)之前的開發(fā)在驗證格式這塊寫的太亂了,到處都有相關(guān)的驗證代碼,所以就有了這篇文章,供自己收藏也分享給有需要的朋友們參考借鑒,下面跟著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2016-12-12
iOS利用AVPlayer播放網(wǎng)絡(luò)音樂的方法教程
最近工作中遇到了一個需求,需要做一個在線音樂類的APP,通過一段時間的努力實現(xiàn)了,所以這篇文章主要給大家介紹了關(guān)于iOS利用AVPlayer播放網(wǎng)絡(luò)音樂的方法教程,文中介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。2017-05-05

