Socket

socket又称为套接字,是应用层和传输层中间的软件抽象层,在网络中两个应用通过双向通信连接实现数据交互,向网络中另一个应用发送请求或应答其他网络请求,区别于WebSocketsocket是一组接口,WebSocket是协议。通信及关闭通信过程需要建立三次握手连接和四次挥手协议。

三次握手建立连接

  • 第一次握手:客户端向服务端发送一个SYN(同步序列编号)包到服务端,并进入syn_send状态,等待服务器进行确认
  • 第二次握手:服务器收到并确认客户端的SYN包,同时服务端也发送一个SYN包(SYN+ACK确认字符)包,此时服务器进入syn_recv状态
  • 第三次握手:客户端接收到SYN+ACK包之后,向服务器发送确认包SYN+1,此时客户端与服务端进入到确立状态

完成三次握手之后两端便可以通讯了。

握手过程:

build_connect.png

有聚有散,四次挥手协议

  • 第一次挥手:客户端向服务端发送FIN+ACK包,告诉服务端需要要结束连接
  • 第二次挥手:服务端收到断开连接消息后,返回ACK包表示确认,确认序号为收到的序号加1,表示同意断开
  • 第三次挥手:服务端数据传输完成后关闭客户端的连接,并向客户端发送FIN+ACK包通知客户端关闭连接
  • 第四次挥手:客户端发送FIN+ACK包确认,FIN+1,服务端接收数据后断开连接,客户端等待一段时间,没有消息后即断开连接

完成之后TCP连接断开,以上为客户端主动发起断开连接的请求,服务器发起过程同上。

挥手过程:

bye.png

socket连接示意图:

socket.png

五层协议体系结构

framework.png

一、建立socket链接,Mac端终端使用nc命令做端口监听,oc作为客户端建立socket连接。

nc/netcat(选项)(参数)

-g<网关>:设置路由器跃程通信网关,最多设置8个;
-G<指向器数目>:设置来源路由指向器,其数值为4的倍数;
-h:在线帮助;
-i<延迟秒数>:设置时间间隔,以便传送信息及扫描通信端口;
-l:使用监听模式,监控传入的资料;
-n:直接使用ip地址,而不通过域名服务器;
-o<输出文件>:指定文件名称,把往来传输的数据以16进制字码倾倒成该文件保存;
-p<通信端口>:设置本地主机使用的通信端口;
-r:指定源端口和目的端口都进行随机的选择;
-s<来源位址>:设置本地主机送出数据包的IP地址;
-u:使用UDP传输协议;
-v:显示指令执行过程;
-w<超时秒数>:设置等待连线的时间;
-z:使用0输入/输出模式,只在扫描通信端口时使用。

1、服务端 端口监听
nc -l 6666
2、永久监听TCP端口
nc -lk port
3、临时监听UDP
nc -lu port
4、永久监听UDP
nc -luk port
5、连接服务端
nc -v 127.0.0.1 666
6、端口扫描
nc -v -w 1 127.0.0.1 -z 1-1000

客户端代码:

#import "ViewController.h"
#import <sys/socket.h>
#import <arpa/inet.h>//inet_addr

#define connect_host @"127.0.0.1"
#define connect_port 6666
@interface ViewController (){
    int clientSocket;
    dispatch_queue_t queSerial;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //button
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.frame = CGRectMake((self.view.frame.size.width-200)/2, 100, 200, 30);
    button.backgroundColor = [UIColor grayColor];
    [button setTitle:@"发送普通文本" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(btn:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
    button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.frame = CGRectMake((self.view.frame.size.width-200)/2, 150, 200, 30);
    button.backgroundColor = [UIColor grayColor];
    [button setTitle:@"发送图片" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(btn:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
    
    button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.frame = CGRectMake((self.view.frame.size.width-200)/2, 200, 200, 30);
    button.backgroundColor = [UIColor grayColor];
    [button setTitle:@"发送文本加图片" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(btn:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
    
    [self initSocket];
}
-(void)btn:(UIButton *)button{
    if ([button.titleLabel.text isEqualToString:@"发送普通文本"]) {
        [self sendMessage:@"发送普通文本"];
    }else if ([button.titleLabel.text isEqualToString:@"发送图片"]) {
        UIImage *image = [UIImage imageNamed:@"hibo"];
        NSData *data = UIImagePNGRepresentation(image);
        NSLog(@"height:%f",image.size.height);
        NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@",dataString);
//        [self sendMessage:dataString];
    }else if ([button.titleLabel.text isEqualToString:@"发送文本加图片"]) {
        
    }
}
//发起socket连接
-(BOOL)initSocket{
    /*
     第一个参数:adress_family,协议簇 AF_INET:IPV4
     第二个参数:数据格式->SOCK_STREAM(TCP)/SOCK_DGRAM(UDP)
     第三个参数:protocal IPPROTO_TCP,如果为0会根据第二个参数选择合适的协议
     返回值:>0成功 -1失败
     */
    clientSocket = socket(AF_INET, SOCK_STREAM, 0);
    NSLog(@"clientsocket:%d",clientSocket);
    if (clientSocket>0) {
        NSLog(@"socket create success");
    }else{
        NSLog(@"socket create error");
    }
    /*
     连接
     第一个参数:客户端socket
     第二个参数:指向数据结构,socketAddr的指针,其中包括目的端口和IP地址
     第三个参数:结构体数据长度
     返回值:0成功 其他错误
     */
    struct sockaddr_in addr4 = {0};
    addr4.sin_family = AF_INET;//ipv4
    addr4.sin_len = sizeof(addr4);
    addr4.sin_addr.s_addr = inet_addr(connect_host.UTF8String);
    addr4.sin_port = htons(connect_port);//是将整型变量从主机字节顺序转变成网络字节顺序, 就是整数在地址空间存储方式变为高位字节存放在内存的低地址处
    int flag = connect(clientSocket, (const struct sockaddr *)&addr4, sizeof(addr4));
    if (!flag) {
        [self receiveMessage];
    }else{
        clientSocket = 0;
        NSLog(@"连接失败");
    }
    return flag;
}
//接收消息
-(void)receiveMessage{
    if (!queSerial) {
        queSerial=dispatch_queue_create("jrQueueSerial", DISPATCH_QUEUE_SERIAL);
    }
    dispatch_async(queSerial, ^{
        uint8_t buffer[1024];
        ssize_t recvLen = recv(self->clientSocket, buffer, sizeof(buffer), 0);
        NSData *data = [NSData dataWithBytes:buffer length:recvLen];
        NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        if (recvLen>0) {
            NSLog(@"%@:%@",str,[NSThread currentThread]);
            NSLog(@"%@",str);
            [self receiveMessage];
        }else{
            NSLog(@"连接断开");
            self->clientSocket = 0;
        }
    });
}
//发送消息
-(BOOL)sendMessage:(NSString *)message{
    if (clientSocket==0) {
        BOOL flag = [self initSocket];
        if (!flag)return NO;
    }
    ssize_t sendLen = send(clientSocket, message.UTF8String, strlen(message.UTF8String), 0);
    NSLog(@"发送消息长度:%zd",sendLen);
    return sendLen>=0;
}
@end

1、在终端执行命令监听端口6666:

nc -l 127.0.0.1 6666

进入等待连接状态。

2、执行以上oc代码进行socket连接:

client_socket.png

3、以上连接成功,由服务端发送一条消息,客户端接收打印如下:

client_receive.png

注:以上代码中图片发送及文本+图片发送未完成。

二、oc模拟服务端代替Mac终端命令,一步步实现服务端的三次握手及通信。主要使用方法:

1、创建一个socket:

socket(AF_INET, SOCK_STREAM, 0);

第一个参数:address_family,协议簇 AF_INET对应的IPV4
第二个参数:数据格式可选两种SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)
第三个参数:protocal IPPROTO_TCP,设置为0会根据第二个参数选择相应的协议;
返回值:sockaddr -1失败 其他成功为socket标号1、2、3、 4,指示为当前socket

2、端口绑定:

bind(sockaddr, (const struct sockaddr *)&addr4, sizeof(addr4));

第一个参数:创建的socket描述号;
第二个参数:对应socketaddr_in(对应IPV4)的结构体包含了端口号;
第三个参数:socketaddr_in结构体长度。

3、监听端口:

listen(sockaddr, 5);

第一个参数:创建的socket标号;
第二个参数:可以排队的最大连接个数。

4、获取连接的socket标号和以上使用的标号不同

accept(sockaddr, (struct sockaddr *)&aptsockaddr, &addrLen);

第一个参数:创建的socket描述号;
第二个参数:可以排队的最大连接个数;
返回值:接收后的socket标号。

5、接收客户端消息:

recv(aptsocket, buffer, len, 0);

第一个参数:accept返回的标号理解为当前socket
第二个参数:接收字符的缓存变量;
第三个参数:一般设置0
返回值:接收到的数据长度。

6、发送消息:

send(aptsocket, message.UTF8String, strlen(message.UTF8String), 0);

第一个参数:accept返回的标号理解为当前socket
第二个参数:发送的消息字符char *型数据;
第三个参数:一般设置0
返回值:发送的数据长度。

服务端代码:

/*
 ipv6
 struct sockaddr_in6 addr6 = {0};
 bzero(&addr6, sizeof(addr6));
 addr6.sin6_len = sizeof(addr6);
 addr6.sin6_family = AF_INET6;
 addr6.sin6_port = htons(connect_port);
 
 htons将主机的无符号短整形数转换成网络字节顺序
 htonl将主机的无符号长整形数转换成网络字节顺序
 */

#import "ViewController.h"
#import <sys/socket.h>
#import <arpa/inet.h>//inet_addr

#define connect_host @"127.0.0.1"
#define connect_port 6666

@interface ViewController ()
{
    int sockaddr;//创建的socket地址
    int aptsocket;//同意后返回的socket地址
    dispatch_queue_t queSerial;//接收消息的队列
    BOOL socket_flag;//socket标识
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //button
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.frame = CGRectMake((self.view.frame.size.width-200)/2, 100, 200, 30);
    button.backgroundColor = [UIColor grayColor];
    [button setTitle:@"回复文本" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(btn:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
    button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.frame = CGRectMake((self.view.frame.size.width-200)/2, 150, 200, 30);
    button.backgroundColor = [UIColor grayColor];
    [button setTitle:@"关闭链接" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(btn:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
    button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.frame = CGRectMake((self.view.frame.size.width-200)/2, 200, 200, 30);
    button.backgroundColor = [UIColor grayColor];
    [button setTitle:@"创建链接" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(btn:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
    //创建服务端socket
    [self initServerSocket];
}
-(void)btn:(UIButton *)button{
    if ([button.titleLabel.text isEqualToString:@"回复文本"]) {
        [self sendMessage:@"回复:你好啊!!"];
    }else if ([button.titleLabel.text isEqualToString:@"关闭链接"]) {
        [self closeSocket];
    }else if ([button.titleLabel.text isEqualToString:@"创建链接"]) {
        [self initServerSocket];
    }
}
//初始化socket
-(BOOL)initServerSocket{
    socket_flag = YES;
    //1、创建一个socket
    sockaddr = socket(AF_INET, SOCK_STREAM, 0);
    NSLog(@"sockaddr:%d",sockaddr);
    if (sockaddr==-1) {
        NSLog(@"创建失败");
        return NO;
    }
    //ipv4
    struct sockaddr_in addr4 = {0};
    //参数说明:s 要置零的数据的起始地址; n 要置零的数据字节个数。
    memset(&addr4, 0, sizeof(addr4));
    addr4.sin_len = sizeof(addr4);
    addr4.sin_family = AF_INET;
    addr4.sin_port = htons(connect_port);//将主机的无符号短整形数转换成网络字节顺序
    addr4.sin_addr.s_addr = INADDR_ANY;//就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”
    //2、端口绑定
    int error = bind(sockaddr, (const struct sockaddr *)&addr4, sizeof(addr4));
    if (error!=0) {
        NSLog(@"绑定失败");
        return NO;
    }
    //3、监听端口
    error = listen(sockaddr, 5);//开始监听第二个参数可以排队的最大连接个数
    if (error!=0) {
        NSLog(@"监听失败");
        return NO;
    }
    //4、轮询
    if (!queSerial) {
        queSerial = dispatch_queue_create("receive_queue", DISPATCH_QUEUE_SERIAL);
    }
    dispatch_async(queSerial, ^{
        [self receiveMessage];
    });
    return YES;
}
//轮询接收消息
-(void)receiveMessage{
    while (true) {
        NSLog(@"currentThread:%@",[NSThread currentThread]);
        struct sockaddr_in aptsockaddr;
        socklen_t addrLen = sizeof(aptsockaddr);
        //4、获取连接的socket
        aptsocket = accept(sockaddr, (struct sockaddr *)&aptsockaddr, &addrLen);
        NSLog(@"aptsocket:%d",aptsocket);
        if (aptsocket != -1) {
            NSLog(@"accept success address:%ss, port:%d",inet_ntoa(aptsockaddr.sin_addr),ntohs(aptsockaddr.sin_port));
            char buffer[1024];
            ssize_t recvLen;
            size_t len = sizeof(buffer);
            do{
                //5、接收客户端的消息
                recvLen = recv(aptsocket, buffer, len, 0);
                NSString *str = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding];
                NSLog(@"receive:%@",str);
                NSLog(@"buffer:%s",buffer);
                [self sendMessage:@"回复你:哈哈"];
            }while(socket_flag);
        }
        close(aptsocket);
        break;//链接失败后跳出轮询
    }
}
//发送消息
-(BOOL)sendMessage:(NSString *)message{
    if (sockaddr==0) {
        NSLog(@"链接已断开");
    }
    ssize_t sendLen = send(aptsocket, message.UTF8String, strlen(message.UTF8String), 0);
    NSLog(@"发送消息长度:%zd",sendLen);
    return sendLen>=0;
}
//断开链接
-(void)closeSocket{
    NSLog(@"关闭链接");
    socket_flag = NO;
}

@end

通过以上过程,对socket通信能有一个初步了解,可以利用socket通信搭建一套简单的聊天系统。

长连接和短连接的区别
长连接:建立连接->数据传输……保持连接……数据传输->关闭连接
短连接:建立连接->数据传输->关闭连接……建立连接->数据传输->关闭连接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268