XMPP介绍三:Socket

本人录制技术视频地址:https://edu.csdn.net/lecturer/1899 欢迎观看。
一、 什么是Socket
Socket实质上提供了进程通信的端点。进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。正如打电话之前,双方必须各自拥有一台电话机一样。
在网间网内部,每一个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

代码中,我已进行了详细的注释。它的基本功能就是,客户端发送一个消息给服务器,服务器端会打印出信息(在终端)。服务器处理完数据之后,会把信息再返回给客户端,客户端接受到信息之后,会刷新表格,展示聊天数据。最终效果图如下:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋恨雪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值