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

tianmaxk 8年前
   <p>其实写这个socket一开始我是拒绝的。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/0f7ad2c5290593677e8b862a509f10bb.jpg"></p>    <p>因为大家学C 语言和linux基础时肯定都有接触,客户端和服务端的通信也都了解过,加上现在很多开放的第三方库都不需要我们来操作底层的通信。</p>    <p>但是来了!!!</p>    <p>但是!还是想写。底层的东西最好了解下。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/1d6bdc33a535c0eb7563cf9e69034237.jpg"></p>    <p>好了 正经了!!!!</p>    <h2>效果</h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/72971a2e04cc1472cdbad428626a7088.gif"></p>    <p style="text-align:center">效果.gif</p>    <p>由于5M的上传限制GIF可能看不清 我再截两张图吧</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/508f263682b3ace9332c27406620c2aa.png"></p>    <p style="text-align:center">服务器</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/3419d91cf312a4630d8835b0866fc920.png"></p>    <p style="text-align:center">客户端A</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/a20be1166725dccb2761fca829100c05.png"></p>    <p style="text-align:center">客户端B</p>    <h2>模型图</h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c2780407e05a3bd15bcc8444b3caafea.jpg"></p>    <p>做了个逗比模型图:arrow_down:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f79efd4df584829381307c732a31d4d5.png"></p>    <p style="text-align:center">模型</p>    <h2>分析</h2>    <p>由上图可以了解到服务器和客户端需要做哪些工作</p>    <h2>服务器</h2>    <p>抽象一点分为:</p>    <ul>     <li>1.创建线程等待接收客户端的连接</li>     <li>2.接收并解析客户端发来的消息</li>     <li>3.给客户端发送消息<br> 具体一点:</li>     <li>1.创建socket. 绑定端口.开始监听.</li>     <li>2.创建线程.等待接收客户端连接.</li>     <li>3.接收客户端发来的消息</li>     <li>4.解析消息内容      <ul>       <li>a.设置用户名</li>       <li>b.发送消息给指定客户端</li>      </ul> </li>    </ul>    <h2>客户端</h2>    <p>抽象一点分为:</p>    <ul>     <li>1.连接服务器</li>     <li>2.给服务器发送消息</li>     <li>3.接收服务器消息</li>     <li>4.解析消息内容<br> 具体一点:</li>     <li>1.创建socket.绑定端口.连接服务器</li>     <li>2.发送消息      <ul>       <li>a.设置用户名</li>       <li>b.给指定用户发消息:按服务器格式拼接字符串</li>      </ul> </li>     <li>3.接收消息      <ul>       <li>a.普通消息</li>       <li>b.用户列表:保存至用户列表</li>      </ul> </li>    </ul>    <h2>UI方面</h2>    <p>服务器:其实不用什么UI放个控件展示下日志就是了</p>    <p>客户端:比较简单,一个俗套聊天室的界面,直接storyboard里拖拖控件设置约束了</p>    <p>DEMO而已别太当真</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/17fc90c0bf2b06360e18db643d2c49e9.jpg"></p>    <h2>代码部分</h2>    <h2>服务器</h2>    <p>要使用scoket需要引用这三个头文件</p>    <pre>  <code class="language-objectivec">#include <netinet/in.h>  #include <sys/socket.h>  #include <arpa/inet.h></code></pre>    <p>只有一个model,用来绑定用户名和socket</p>    <pre>  <code class="language-objectivec">@interface ClientModel : NSObject  @property(nonatomic,assign)int clientSocket;  @property(nonatomic,copy)NSString *clientName;  @end</code></pre>    <p>只有一个文件全给你</p>    <pre>  <code class="language-objectivec">#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</code></pre>    <h2>客户端</h2>    <p>由于客户端设计的就比较简单,所以代码量也很少,全给你.</p>    <pre>  <code class="language-objectivec">#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</code></pre>    <p> </p>