XMPP介绍三:Socket
2016-01-23 19:38
507 查看
本人录制技术视频地址:https://edu.csdn.net/lecturer/1899 欢迎观看。
一、 什么是SocketSocket实质上提供了进程通信的端点。进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。正如打电话之前,双方必须各自拥有一台电话机一样。在网间网内部,每一个Socket用一个半相关描述:(协议,本地地址,本地端口)。一个完整的Socket有一个本地唯一的Socket号,由操作系统分配。最重要的是,Socket是面向客户/服务器模型而设计的,针对客户和服务器程序提供不同的Socket系统调用。客户随机申请一个Socket(相当于一个想打电话的人可以在任何一台入网电话上拨号呼叫),系统为之分配一个Socket号;服务器拥有全局公认的Socket,任何客户都可以向它发出连接请求和信息请求(相当于一个被呼叫的电话拥有一个呼叫方知道的电话号码)。Socket利用客户/服务器模式巧妙地解决了进程之间建立通信连接的问题。服务器Socket半相关为全局所公认非常重要。读者不妨考虑一下,两个完全随机的用户进程之间如何建立通信?假如通信双方没有任何一方的Socket固定,就好比打电话的双方彼此不知道对方的电话号码,要通话是不可能的。二、 Socket在网络层级之间的作用
从图中可以看出,Socket是应用层与运输层之间的桥梁。再进一步进行分析,上图中的 "Socket抽象层" 在iOS中的体现,如下图:
三、Socket工作流程现在我们就定位到Socket这一层,看看它的具体工作流程。
1. TCP服务端先创建一个socket (调用socket()方法)。2. 绑定一个端口号 (调用bind()方法)。3. 开启监听 (调用listen()方法)。4. 一直阻塞直到有客户端连接 (调用accept()方法)。5. 当客户端发起连接请求的时候 (调用connect()方法)。6. 假设第五步中的连接操作完成,客户端接着发送请求,服务器端会 (调用read()方法)。7. 服务器端读取请求信息之后,进行相应的处理工作,并将处理结果写会客户端 (调用write()方法)。8. 客户端在接收到服务端的回应数据后,(调用read()方法) 获取数据。9. 最终客户端想断开连接请求的时候 (调用close()方法)。主语第五步操作,这里就实现了典型的 "三次握手" 操作,流程图如下:
四、Socket代码实现由于socket通讯是在客户端与服务端之间进行的,所以我们应该先考虑搭建服务器,但是我们这里只是为了简单的实现其功能,所以直接用python实现一个简易服务器就可以了。代码如下:
代码说明:因为我们这里实现的是socket级别的通讯,所以需要考虑到一下几点:1. 服务器的端口号。2. 通信的契约(即客户端发送什么数据过来,服务端可以识别)。在上述代码中,我们可以看出:1. 我们定义的服务端socket的端口号是12345。2. 我们定义的通信契约格式是以 "冒号" 分割的。 "iam:XXX" 表示登陆契约, "msg:XXX" 表示聊天契约。紧接着,我们就可以启动这个服务:
处理完服务端之后,我们开始着手客户端的代码:
代码中,我已进行了详细的注释。它的基本功能就是,客户端发送一个消息给服务器,服务器端会打印出信息(在终端)。服务器处理完数据之后,会把信息再返回给客户端,客户端接受到信息之后,会刷新表格,展示聊天数据。最终效果图如下:
一、 什么是SocketSocket实质上提供了进程通信的端点。进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。正如打电话之前,双方必须各自拥有一台电话机一样。在网间网内部,每一个Socket用一个半相关描述:(协议,本地地址,本地端口)。一个完整的Socket有一个本地唯一的Socket号,由操作系统分配。最重要的是,Socket是面向客户/服务器模型而设计的,针对客户和服务器程序提供不同的Socket系统调用。客户随机申请一个Socket(相当于一个想打电话的人可以在任何一台入网电话上拨号呼叫),系统为之分配一个Socket号;服务器拥有全局公认的Socket,任何客户都可以向它发出连接请求和信息请求(相当于一个被呼叫的电话拥有一个呼叫方知道的电话号码)。Socket利用客户/服务器模式巧妙地解决了进程之间建立通信连接的问题。服务器Socket半相关为全局所公认非常重要。读者不妨考虑一下,两个完全随机的用户进程之间如何建立通信?假如通信双方没有任何一方的Socket固定,就好比打电话的双方彼此不知道对方的电话号码,要通话是不可能的。二、 Socket在网络层级之间的作用
从图中可以看出,Socket是应用层与运输层之间的桥梁。再进一步进行分析,上图中的 "Socket抽象层" 在iOS中的体现,如下图:
三、Socket工作流程现在我们就定位到Socket这一层,看看它的具体工作流程。
1. TCP服务端先创建一个socket (调用socket()方法)。2. 绑定一个端口号 (调用bind()方法)。3. 开启监听 (调用listen()方法)。4. 一直阻塞直到有客户端连接 (调用accept()方法)。5. 当客户端发起连接请求的时候 (调用connect()方法)。6. 假设第五步中的连接操作完成,客户端接着发送请求,服务器端会 (调用read()方法)。7. 服务器端读取请求信息之后,进行相应的处理工作,并将处理结果写会客户端 (调用write()方法)。8. 客户端在接收到服务端的回应数据后,(调用read()方法) 获取数据。9. 最终客户端想断开连接请求的时候 (调用close()方法)。主语第五步操作,这里就实现了典型的 "三次握手" 操作,流程图如下:
四、Socket代码实现由于socket通讯是在客户端与服务端之间进行的,所以我们应该先考虑搭建服务器,但是我们这里只是为了简单的实现其功能,所以直接用python实现一个简易服务器就可以了。代码如下:
from twisted.internet.protocol import Protocol, Factory from twisted.internet import reactor class IphoneChat(Protocol): def connectionMade(self): #self.transport.write("""connected""") self.factory.clients.append(self) print "clients are ", self.factory.clients def connectionLost(self, reason): self.factory.clients.remove(self) def dataReceived(self, data): #print "data is ", data a = data.split(':') if len(a) > 1: command = a[0] content = a[1] msg = "" if command == "iam": self.name = content msg = self.name + " has joined" elif command == "msg": msg = self.name + ": " + content print msg for c in self.factory.clients: c.message(msg) def message(self, message): self.transport.write(message + '\n') factory = Factory() factory.protocol = IphoneChat factory.clients = [] reactor.listenTCP(12345, factory) print "Iphone Chat server started" reactor.run()
代码说明:因为我们这里实现的是socket级别的通讯,所以需要考虑到一下几点:1. 服务器的端口号。2. 通信的契约(即客户端发送什么数据过来,服务端可以识别)。在上述代码中,我们可以看出:1. 我们定义的服务端socket的端口号是12345。2. 我们定义的通信契约格式是以 "冒号" 分割的。 "iam:XXX" 表示登陆契约, "msg:XXX" 表示聊天契约。紧接着,我们就可以启动这个服务:
处理完服务端之后,我们开始着手客户端的代码:
// // ViewController.m // A01_聊天室 // // Created by LIFEI on 15-12-24. // Copyright (c) 2015年 apple. All rights reserved. // #import "ViewController.h" @interface ViewController ()<NSStreamDelegate,UITableViewDataSource,UITableViewDelegate,UITextFieldDelegate>{ NSInputStream *_inputStream; NSOutputStream *_outputSteam; } @property (weak, nonatomic) IBOutlet UITableView *tableView; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomConstraint; /** * 存储所有聊天数据的数组 */ @property (strong, nonatomic) NSMutableArray *msgs; @end @implementation ViewController #pragma mark - Init Relative - (NSMutableArray *)msgs { if (!_msgs) { _msgs = [NSMutableArray array]; } return _msgs; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(kbWillShow:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(kbWillHide:) name:UIKeyboardWillHideNotification object:nil]; } #pragma mark - Keyboard Notifications - (void)kbWillShow:(NSNotification *)notification { //显示的时候改变bottomContraint CGFloat kbHeight = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; self.bottomConstraint.constant = kbHeight; } - (void)kbWillHide:(NSNotification *)notification { self.bottomConstraint.constant = 0; } #pragma mark - Events - (IBAction)connectToServer:(id)sender { //ios里实现sokcet的连接,使用C语言 // 1.与服务器通过三次握手建立连接 NSString *host = @"127.0.0.1"; int port = 12345; // 2.定义输入输出流 CFReadStreamRef readStream; CFWriteStreamRef writeStream; // 3.分配输入输出流的内存空间 CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream); // 4.把C语言的输入输出流转成OC对象 _inputStream = (__bridge NSInputStream *)(readStream); _outputSteam = (__bridge NSOutputStream *)(writeStream); // 5.设置代理,监听数据接收的状态 _outputSteam.delegate = self; _inputStream.delegate = self; // 把输入输入流添加到主运行循环(RunLoop) // 主运行循环是监听网络状态 [_outputSteam scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; [_inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; // 6.打开输入输出流 [_inputStream open]; [_outputSteam open]; } - (IBAction)loginBtnClick:(id)sender { //发送登录请求 使用输出流 //拼接登录的指令 iam:zhangsan NSString *loginStr = @"iam:zhangsan"; [self sendDataToHost:loginStr]; } #pragma mark - Public Methods - (void)sendDataToHost:(NSString *)str { //uint8_t * 字符数组 NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding]; [_outputSteam write:data.bytes maxLength:data.length]; } - (void)readData { //定义缓冲区 这个缓冲区只能存储1024字节 uint8_t buf[1024]; // 读取数据 // len为服务器读取到的实际字节数 NSInteger len = [_inputStream read:buf maxLength:sizeof(buf)]; // 把缓冲区里的实现字节数转成字符串 NSString *receiverStr = [[NSString alloc] initWithBytes:buf length:len encoding:NSUTF8StringEncoding]; NSLog(@"%@",receiverStr); //把数据添加到msgs数组 [self.msgs addObject:receiverStr]; //刷新表格 [self.tableView reloadData]; } #pragma mark - NSStreamDelegate - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode { //代理的回调是在主线程 NSLog(@"%@",[NSThread currentThread]); // NSStreamEventOpenCompleted = 1UL << 0, // NSStreamEventHasBytesAvailable = 1UL << 1, // NSStreamEventHasSpaceAvailable = 1UL << 2, // NSStreamEventErrorOccurred = 1UL << 3, // NSStreamEventEndEncountered = 1UL << 4 switch (eventCode) { case NSStreamEventOpenCompleted: NSLog(@"%@",aStream); NSLog(@"成功连接建立,形成输入输出流的传输通道"); break; case NSStreamEventHasBytesAvailable: NSLog(@"有数据可读"); [self readData]; break; case NSStreamEventHasSpaceAvailable: NSLog(@"可以发送数据"); break; case NSStreamEventErrorOccurred: NSLog(@"有错误发生,连接失败"); break; case NSStreamEventEndEncountered: NSLog(@"正常的断开连接"); //把输入输入流关闭,而还要从主运行循环移除 [_inputStream close]; [_outputSteam close]; [_inputStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; [_outputSteam removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; break; default: break; } } #pragma mark - UITableViewDelegate/Datasource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.msgs.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *ID = @"ChatCell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; cell.textLabel.text = self.msgs[indexPath.row]; return cell; } - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { [self.view endEditing:YES]; } #pragma mark - TextFieldDelegate - (BOOL)textFieldShouldReturn:(UITextField *)textField { //发送聊天数据 // 1.有数据才发送 NSString *txt = textField.text; if (txt.length == 0) return YES; // 发送数据 NSString *msg = [@"msg:" stringByAppendingString:txt]; [self sendDataToHost:msg]; return YES; } @end
代码中,我已进行了详细的注释。它的基本功能就是,客户端发送一个消息给服务器,服务器端会打印出信息(在终端)。服务器处理完数据之后,会把信息再返回给客户端,客户端接受到信息之后,会刷新表格,展示聊天数据。最终效果图如下: