您的位置:首页 > 理论基础 > 计算机网络

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实现一个简易服务器就可以了。代码如下:
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

代码中,我已进行了详细的注释。它的基本功能就是,客户端发送一个消息给服务器,服务器端会打印出信息(在终端)。服务器处理完数据之后,会把信息再返回给客户端,客户端接受到信息之后,会刷新表格,展示聊天数据。最终效果图如下:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息