专注·精彩 阅读(35) 评论(0)

一、概念

  • Socket 字面意思又称“套接字”

  • 网络上的两个程序(如,客户端和服务器端)通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。

  • 应用程序一般是先通过Socket来建立一个通信连接,再向网络发出请求或响应网络请求。

  

  说明:

    ☞ 客户端向服务器端发送网络请求前,必须要先在底层建立一个通信连接(通信管道),才能发送网络请求。

客户端向服务器端发送http请求,服务器返回数据,这个过程就是一个数据交换的过程。

客户端与服务器端进行数据交换,需要先建立一个双向的通信连接(即一条线、一个通道)

    ☞ 客户端和服务端 两端都有一个Socket,通过Socket建立一个连接(双向通信管道),有了管道就可以进行数据传输。

    ☞ Socket 就是通信管道的两个端口,可以理解为管道的入口/出口

二、网络通信的要素

  网络上的请求就是通过Socket来建立连接然后互相通信

  1. IP地址(网络上主机设备的唯一标识)——>寻找服务器主机

  2. 端口号定位程序 ——> 寻找程序

    • 用于标示进程的逻辑地址,不同进程的标示
    • 有效端口:0~65535,其中0~1024由系统使用或者保留端口,开发中建议使用1024以上的端口

  3. 传输协议(就是用什么样的方式进行交互)

    • 通讯的规则
    • 常见协议:TCP、UDP

三、传输协议 TCP/UDP

  TCP和UDP:数据传输的两种方式,即把数据从一端传到另一端的两种方式

  1. TCP(传输控制协议) —>要建立连接(如:发送HTTP请求,客户端向服务端发送网络请求)

☞ 建立连接,形成传输数据的通道

☞ 在连接中进行大数据传输(数据大小不受限制)

☞ 通过三次握手完成连接,是可靠协议,安全送达

        说明:在建立通信连接(打通管道)之前有三次握手,目的是为了数据的安全性和可靠性(让数据安全可靠的传输到对方)。

        举例:打电话 (理解三次握手)

第一次握手:拿起电话,进行拨号。这个拨号的过程称为第一次握手。【开始准备连接】

第二次握手:拨通了,对方""了一声(响应了一声),我听到了,称为第二次握手。【说明我连接你 没问题】

第三次握手:我听到了对方""了一声(响应了一声),我也习惯性的""了一声,对方听到了。【说明你连接我 没问题

如果这三个过程都没有问题,就可以确定通话连接建立成功。

    ☞ 必须建立连接,效率会稍低。(每次请求都要建立连接)

 

  2. UDP(用户数据报协议)—>不建立连接 (如:广播用这个,不断的发送数据包)

    ☞ 将 数据 目的 封装成数据包中,不需要建立连接

    ☞ 每个数据报的大小限制在64KB之内

    ☞ 因为无需连接,因此是不可靠协议

      举例:看老师广播讲课,网络卡主了,再看到的是最新的视频内容,不能接着看,可能错过了一些内容。

    ☞ 不需要建立连接,速度快 (省掉了三次握手操作)

四、Socket通信流程图

☞ bind():绑定端口 (80、3306)

☞ listen():监听端口(服务器监听客户端有没有连接到这个端口来)

☞ accept():如果有连接到这个端口,就接收这个连接。(通信管道打通,接下来就可以传输数据了)

☞ write():发请求/写请求/发数据

☞ read():读请求/读数据

  • HTTP底层就是Socket通信,通过Socket建立连接(通信管道),实现数据传输,连接的方式(数据传输的方式)是TCP。
  • HTTP是一个TCP的传输协议(方式),它是一个可靠、安全的协议。

五、体验Socket  

  实现Socket服务端监听:

1使用C语言实现。

2使用 CocoaAsyncSocket 第三方框架(OC),内部是对C的封装。

 

  telnet命令:是连接服务器上的某个端口对应的服务。

  telnet命令:telnet host port 

    如:telnet www.baidu.com 80  (IP地址和域名一样,都能找到主机。)

 

1. 案例:写个10086服务,体验客户端与服务端的Socket通信

☞ 自己写一个服务端,用终端代替客户端来演示

☞ 掌握:通过Socket对象在服务器里怎么去接收数据和返回数据。

  

完整代码:

/// ----- MyServiceListener.h -----
@interface MyServiceListener : NSObject
//开启服务
- (void)start;
@end

/// ----- MyServiceListener.m -----
#import "MyServiceListener.h"
#import "GCDAsyncSocket.h"
/**
 *  服务的监听者(服务端监听客户端连接)
 */
@interface MyServiceListener()<GCDAsyncSocketDelegate>
/** 保存服务端的Socket对象 */
@property (nonatomic, strong) GCDAsyncSocket *serviceSocket;
/** 保存客户端的所有Socket对象 */
@property (nonatomic, strong) NSMutableArray *clientSocketArr;

@end

@implementation MyServiceListener
- (GCDAsyncSocket *)serviceSocket {
    if (!_serviceSocket) {
        //1.创建一个Socket对象
                //serviceSocket 服务端的Socket只监听 有没有客户端请求连接
               //队列:代理的方法在哪个队列里调用 (子线程的队列)
        _serviceSocket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
    }
    return _serviceSocket;
}

- (NSMutableArray *)clientSocketArr {
    if(!_clientSocketArr) {
        _clientSocketArr = [NSMutableArray array];
    }
    return _clientSocketArr;
}

- (void)start {
    //开启10086服务:5288
    //2.绑定端口 + 开启监听
    NSError *error = nil;
    //框架里的这个方法做了两件事情:绑定端口和开启监听
    [self.serviceSocket acceptOnPort:5288 error:&error];
    if (!error) {
        NSLog(@"10086服务开启成功!");
    } else {
        //失败的原因是端口被其它程序占用
        NSLog(@"10086服务开启失败:%@", error);
    }    
}

#pragma mark -- 实现代理的方法 如果有客户端的Socket连接到服务器,就会调用这个方法。
- (void)socket:(GCDAsyncSocket *)serviceSocket didAcceptNewSocket:(GCDAsyncSocket *)clientSocket {
    static NSInteger index = 1;
    NSLog(@"客户端【%ld】已连接到服务器!", index++);
    //1.保存客户端的Socket(客户端的Socket被释放了,连接就会关闭)
    [self.clientSockets addObject:clientSocket];
    
    //提供服务(客户端一连接到服务器,就打印下面的内容)
    NSMutableString *serviceStr = [[NSMutableString alloc]init];
    [serviceStr appendString:@"========欢迎来到10086在线服务========\n"];
    [serviceStr appendString:@"请输入下面的数字选择服务...\n"];
    [serviceStr appendString:@" [0] 在线充值\n"];
    [serviceStr appendString:@" [1] 在线投诉\n"];
    [serviceStr appendString:@" [2] 优惠信息\n"];
    [serviceStr appendString:@" [3] special services\n"];
    [serviceStr appendString:@" [4] 退出\n"];
    [serviceStr appendString:@"=====================================\n"];
    // 服务端给客户端发送数据
    [clientSocket writeData:[serviceStr dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
    
    //2.监听客户端有没有数据上传 (参数1:超时时间,-1代表不超时)
    /**
     *  timeout: 超时时间,-1 代表不超时
     *  tag:标识作用,现在不用就写0
     */
    [clientSocket readDataWithTimeout:-1 tag:0];
}

#pragma mark -- 服务器端 读取 客户端请求(发送)的数据。在服务端接收客户端数据,这个方法会被调用
- (void)socket:(GCDAsyncSocket *)clientSocket didReadData:(NSData *)data withTag:(long)tag {
    //1.获取客户端发送的数据
    NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
    NSInteger index = [self.clientSocketArr indexOfObject:clientSocket];
    NSLog(@"接收到客户端【%ld】发送的数据:%@", index + 1, str);
    //把字符串转成数字
    NSInteger num = [str integerValue];
    NSString *responseStr = nil;
    //服务器对应的处理的结果
    switch (num) {
        case 0:
            responseStr = @"在线充值服务暂停中...\n";
            break;
        case 1:
            responseStr = @"在线投诉服务暂停中...\n";
            break;
        case 2:
            responseStr = @"优惠信息没有\n";
            break;
        case 3:
            responseStr = @"没有特殊服务\n";
            break;
        case 4:
            responseStr = @"恭喜你退出成功!\n";
            break;
        default:
            break;
    }
    
    //2.服务端处理请求,返回数据(data)给客户端
    [clientSocket writeData:[responseStr dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
    //写完数据后 判断
    if (num == 4) {
        //移除客户端,就会关闭连接
        [self.clientSockets removeObject:clientSocket];
    }

    //由于框架内部的实现,每次读完数据后,都要调用一次监听数据的方法(保证能接收到客户端第二次上传的数据)
    [clientSocket readDataWithTimeout:-1 tag:0];
    
}
@end

/// ----- ViewController.m -----
#import "ViewController.h"
#import "MyServiceListener.h"
@interface ViewController ()

@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    //1.创建一个服务监听对象
    MyServiceListener *listener = [[MyServiceListener alloc]init];
    //2.开始监听
    [listener start];
    //3.开启主运行循环,让服务不能停(服务器一般要永久开启)
    [[NSRunLoop mainRunLoop] run];
    
}
@end