欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

iOS 使用 socket 实现即时通信示例(非第三方库)

程序员文章站 2024-02-15 20:05:22
其实写这个socket一开始我是拒绝的。 因为大家学c 语言和linux基础时肯定都有接触,客户端和服务端的通信也都了解过,加上现在很多开放的第三方库都不需要我们来操作底...

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

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

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

效果

iOS 使用 socket 实现即时通信示例(非第三方库)

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

iOS 使用 socket 实现即时通信示例(非第三方库)

服务器

iOS 使用 socket 实现即时通信示例(非第三方库)

客户端a

iOS 使用 socket 实现即时通信示例(非第三方库)

客户端b

模型图

iOS 使用 socket 实现即时通信示例(非第三方库)

分析

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

服务器

抽象一点分为:

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地址:imsocketdemo_jb51.rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。