iOS socket网络编程

代码下载

服务端代码下载地址

客户端代码下载地址

相关概念

  • socket是一个针对TCP和UDP编程的接口,你可以借助它建立TCP连接等。socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。Socket的出现只是使得程序员更方便地使用TCP/IP协议栈而已,是对TCP/IP协议的抽象,从而形成了我们知道的一些最基本的函数接口。
  • socket连接:socket连接就是所谓的长连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉;但是由于各种环境因素可能会是连接断开,比如说:服务器端或客户端主机down了,网络故障,或者两者之间长时间没有数据传输,网络防火墙可能会断开该连接以释放网络资源。所以当一个socket连接中没有数据的传输,那么为了维持连接需要发送心跳消息~~具体心跳消息格式是开发者自己定义的。
  • TCP连接的三次握手:

    第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;

    第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

    第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

    握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”。

  • socket建立网络连接的步骤

    建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。

    套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。

    1。服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。

    2。客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

    3。连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

BSD socket

BSD socket:完全由c语言实现,并且可以在Objective-C代码中使用。

优点:不同平台中易于移植

缺点:

无法访问操作系统内建的网络特性(比如系统范围的VPN)。

更糟糕的是初始化socket连接并不会自动打开设备的Wi-Fi或是蜂窝网络,无线网络会智能的关闭以节省电池电量,任何通信连接都会失败,除非其他网络进程激活了无线网。

CFNetwork对BSD Socket的分装可以激活设备的无线网,所以几乎所有场景都建议使用CFNetwork而不是BSD Socket.

最常用的API:

1.int socket(int, int, int):创建并初始化新的socket,如果成功放回一个文件描述符,如果失败返回0。

2.int bind(int, const struct sockaddr *, socklen_t):为指定的地址与端口号分配socket。

3.int listen(int, int) __DARWIN_ALIAS(listen):用于服务端监听客户端

4.int accept(int, struct sockaddr * __restrict, socklen_t * __restrict):接受连接请求,将客户端地址存储到clientAddress中。

5.int connect(int, const struct sockaddr *, socklen_t):连接到指定的服务器。

6.ssize_t send(int, const void *, size_t, int):在socket上最多发送XX数据。

7.ssize_t recv(int, void *, size_t, int):在socket上最多读取XX数据。

……

服务端逻辑:如果接收到客户端发送过来”1”,那么回复”你猜!”;客户端发送过来”2”,那么回复”谢谢!”;客户端发送过来”3”,那么回复”对不起!”;客户端发送过来”4”,那么回复”好的!”;客户端发送过来其他的内容,那么一半概率原样回复,一半概率回复”不知道你在说什么!”。

创建服务端:

1.创建socket

    struct sockaddr_in server_addr;
    server_addr.sin_len = sizeof(struct sockaddr_in);
    server_addr.sin_family = AF_INET;//AF_INET互联网地址簇
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = inet_addr(address);
    bzero(&server_addr.sin_zero, 8);

    //创建socket
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);//SOCK_STREAM 有链接

    if (server_socket == -1) {
        perror("socket error!");

        return 1;
    }

2.绑定socket:将创建的socket绑定到本地的IP地址和端口,此socket是半相关的,只是负责侦听客户端的连接请求,并不能用于和客户端通信

    int bind_result = bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));
    if (bind_result == -1) {
        perror("bind error!");

        return 1;
    }

3.listen侦听

   if (listen(server_socket, 5)) {
        perror("listen error!");

        return 1;
    }

4.接受客户端连接

    int client_socket;
    socklen_t address_len;
    struct sockaddr_in client_address;
    for (; ; ) {
        address_len = sizeof(client_address);
        client_socket = accept(server_socket, (struct sockaddr*)&client_address, &address_len);
        //1.使用多线程
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            //这个函数进行数据操作
            str_echo(client_socket);
        });
    }

5.接收和发送数据

    char buf[1024];

    while (1) {
        bzero(buf, 1024);
        long byte_num = recv(socket, buf, 1024, 0);
        if (byte_num < 0) {
            return;
        }
        buf[byte_num] = ‘\0‘;
        printf("client said:%s\n", buf);

        char *result;
        NSString *str = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];
        if ([str isEqualToString:@"1"]) {
            result = "你猜!";
        }
        else if ([str isEqualToString:@"2"])
        {
            result = "谢谢!";
        }
        else if ([str isEqualToString:@"3"])
        {
            result = "对不起!";
        }
        else if ([str isEqualToString:@"4"])
        {
            result = "好的!";
        }
        else
        {
            if (arc4random()%2 == 0) {
                result = "不知道你在说什么!";
            }
            else
            {
                result = buf;
            }
        }

        send(socket, result, 1024, 0);
    }

创建客户端:

    struct sockaddr_in server_addr;
    server_addr.sin_len = sizeof(struct sockaddr_in);
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(11332);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    bzero(&(server_addr.sin_zero), 8);

    int server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
        perror("socket error");

        return 1;
    }

    char receive_msg[1024];
    char reply_msg[1024];

    if (connect(server_socket, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in)) == 0) {
        //connect 成功之后,其实系统将你创建的socket绑定到一个系统分配的端口上,且其为全相关,包含服务器端的信息,可以用来和服务器端进行通信。

        while (1) {
            bzero(reply_msg, 1024);
            printf("replay:");
            scanf("%s", reply_msg);
            if (send(server_socket, reply_msg, 1024, 0) == -1) {
                perror("send error!");

                return 1;
            }

            bzero(receive_msg, 1024);
            long byte_num = recv(server_socket, receive_msg, 1024, 0);
            receive_msg[byte_num] = ‘\0‘;
            printf("server said:%s\n", receive_msg);
        }
    }

    return 0;

NSStream 读写文件

Cocoa Streams包含三个相关的类: NSStream、NSInputStream 和NSOutputStream。

NSStream:是个抽象类,定义了一些基本属性和方法;

NSInputStream:是NSStream的子类,可通过它从NSData、File和socket中读取数据流;

NSOutputStream:也是NSStream的子类,可通过它将数据流写入NSData、File和socket。

还可以给stream对象设置Delegate(NSStreamDelegate),如果没有精确了给stream指定Delegate,那么默认将Delegate设置为其自己。

NSStreamDelegate只有一个方法
  • (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode

NSStreamEventNone :改默认值不代表任何事件

NSStreamEventOpenCompleted :socket被成功打开

NSStreamEventHasBytesAvailable :有可以读取的字节

NSStreamEventHasSpaceAvailable :缓存中可以写入字节

NSStreamEventErrorOccurred :操作出现错误CFReadStreamCopyError()和CFWriteStreamCopyError()会提供更多错误细节

NSStreamEventEndEncountered :socket达到字节流的末尾

通过NSStream对象读数据,可以分为如下步骤完成:

a) 从数据源创建和初始化一个NSStream对象;

b) 配置run loop,并打开stream对象;

c) 响应NSInputStream事件(NSStreamDelegate);

d) 关闭NSInputStream对象。

- (void)viewDidLoad {
    [super viewDidLoad];

    NSString *documentStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    NSString *dataPath = [documentStr stringByAppendingPathComponent:@"data.txt"];

    NSFileManager *manager = [NSFileManager defaultManager];
    if ([manager fileExistsAtPath:dataPath]) {
        self.read = YES;
        NSLog(@"string:%@", [NSString stringWithContentsOfFile:dataPath encoding:NSUTF8StringEncoding error:nil]);

        NSInputStream *inStream = [[NSInputStream alloc] initWithFileAtPath:dataPath];
        [inStream setDelegate:self];
        [inStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [inStream open];
    }
    else
    {
        if ([manager createFileAtPath:dataPath contents:nil attributes:nil]) {
            self.read = NO;
            NSOutputStream *outStream = [[NSOutputStream alloc] initToFileAtPath:dataPath append:YES];
            [outStream setDelegate:self];
            [outStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
            [outStream open];
        }
        else
        {
            NSLog(@"文件创建失败!");
        }
    }
}

#pragma mark - <NSStreamDelegate>代理方法
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
    if (self.read) {
        NSInputStream *inStream = (NSInputStream *)aStream;
        switch (eventCode) {
            case NSStreamEventHasBytesAvailable:
            {
                uint8_t data[1024];
                [inStream read:data maxLength:1024];
                printf("%s", data);
                [inStream close];
                [inStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
                inStream = nil;
            }
                break;
            case NSStreamEventEndEncountered:
            {
                [inStream close];
                [inStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
                inStream = nil;
            }
                break;

            default:
                break;
        }
    }
    else{
        NSOutputStream *outStream = (NSOutputStream *)aStream;
        switch (eventCode) {
            case NSStreamEventHasSpaceAvailable:
            {
                uint8_t data[] = "{name:‘张三‘, age:10}";
                [outStream write:data maxLength:strlen((char *)data)];
                [outStream close];
                [outStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
                outStream = nil;
            }
                break;
            case NSStreamEventEndEncountered:
            {
                [outStream close];
                [outStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
                outStream = nil;
            }
                break;

            default:
                break;
        }
    }
}

NSStream Socket

NSStream类不支持在IOS平台上创建socket连接,而CFStream支持在IOS平台的socket行为。所以若知道远程主机的DNS或者是IP地址,可以使用CFStreamCreatePairWithSocketToHost函数来创建socket连接,通过该函数创建了CFStream类型为全双工的socket连接,接着可以利用toll-free bridge,将CFStream对象转换为NSStream对象。

通过NSStream对象进行Socket通信,与通过NSStream进行IO操作的步骤基本一样:

a) 创建NSStream对象,通过CFStreamCreatePairWithSocketToHost函数创建CFReadStreamRef 和CFWriteStreamRef 对象;继而将两者转换为NSInputStream 和NSOutputStream 对象;

b) 配置run loop,并打开NSInputStream 和NSOutputStream对象;

c) 响应事件,在Delegate中响应不同的信号;

d) 关闭NSStream对象。

服务端:服务端依然使用上面例子的服务端

客户端的实现:点击1~5这五个按钮,分别从服务端返回不同的数据,进而展示出来。

由于实现相对简单,就不加阐述说明了,实现代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];

    [self startSocket:@"127.0.0.1" andPort:11332];
}

- (void)startSocket:(NSString *)address andPort:(int)port
{
    CFReadStreamRef readRef;
    CFWriteStreamRef writeRef;

    CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)address, port, &readRef, &writeRef);

    NSInputStream *inputStream = (__bridge NSInputStream *)readRef;
    NSOutputStream *outputStream = (__bridge NSOutputStream *)writeRef;

    inputStream.delegate = self;
    outputStream.delegate = self;

    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

    [inputStream open];
    [outputStream open];

    self.outputStream = outputStream;
}

- (IBAction)sendMsg:(UIButton *)sender {
    if (sender.currentTitle > 0) {
        const char *output = sender.currentTitle.UTF8String;
        [self.outputStream write:(const uint8_t *)output maxLength:strlen(output)];
    }
}

- (void)showMessage:(NSString *)msg
{
    if (!self.msgLabel) {
        UIFont *font = [UIFont systemFontOfSize:14];
        UIColor *color = [UIColor blackColor];
        UIColor *backColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.2];

        UILabel *label = [[UILabel alloc] init];
        label.textColor = color;
        label.font = font;
        label.textAlignment = NSTextAlignmentCenter;
        label.backgroundColor = backColor;
        self.msgLabel = label;
    }

    CGFloat screenW = [UIScreen mainScreen].bounds.size.width;
    CGFloat screenH = [UIScreen mainScreen].bounds.size.height;
    CGFloat W = screenW - 16;
    CGSize size = [msg boundingRectWithSize:CGSizeMake(W, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:self.msgLabel.font} context:nil].size;
    W = size.width;
    CGFloat H = size.height;
    CGFloat X = (screenW - W)/2;
    CGFloat Y = (screenH - H)/2;

    self.msgLabel.frame = CGRectMake(X, Y, W, H);
    self.msgLabel.text = msg;
    [self.view addSubview:self.msgLabel];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.msgLabel removeFromSuperview];
    });
}

#pragma mark - <NSStreamDelegate>代理方法
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
    switch (eventCode) {
        case NSStreamEventNone:
            break;
        case NSStreamEventOpenCompleted:
            break;
        case NSStreamEventHasBytesAvailable:
        {
            uint8_t buf[1024];
            NSInteger len = 0;
            NSInputStream *inputStream = (NSInputStream *)aStream;
            len = [inputStream read:buf maxLength:1024];
            if (len) {
                [self showMessage:[NSString stringWithCString:(const char *)buf encoding:NSUTF8StringEncoding]];
            }
            break;
        }
        case NSStreamEventHasSpaceAvailable:
            break;
        case NSStreamEventErrorOccurred:
        {
            [aStream close];
            break;
        }
        case NSStreamEventEndEncountered:
        {
            [aStream close];
            [aStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
            aStream = nil;
            break;
        }

        default:
            break;
    }
}
时间: 2024-10-17 04:49:22

iOS socket网络编程的相关文章

(转)iOS 各种网络编程总结--进程、线程、Socket、HTTP、TCP/IP、TCP和UDP

######################################################### 进程与线程 进程和线程都是由操作系统分配和调度的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性. 进程是一块包含了某些资源的内存区域.操作系统利用进程把它的工作划分为一些功能单元.进程中所包含的一个或多个执行单元称为线程(thread).进程还拥有一个私有的虚拟地址空间,该空间仅能被它所包含的线程访问. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更

socket 网络编程快速入门(一)教你编写基于UDP/TCP的服务(客户端)通信

因为UNIX和Win的socket大同小异,为了方便和大众化,这里先介绍Winsock编程. socket 网络编程的难点在入门的时候就是对基本函数的了解和使用,因为这些函数的结构往往比较复杂,参数大部分都是结构体,令人难以记忆和理解. 但是一旦我们知道这些函数包括其参数的具体含义,socket网络编程也就变得不是那么复杂.这里不赘述 具体函数的详细含义,网络上有很多的文章,同时笔者建议大家参考 MSDN,对返回值,参数等会有更好的理解. 以下均为单线程的简单实例,多线程的请关注下一篇文章. (

Socket网络编程--网络爬虫(1)

我们这个系列准备讲一下--网络爬虫.网络爬虫是搜索引擎系统中十分重要的组成部分,它负责从互联网中搜集网页,采集信息,这些网页信息用于建立索引从而为搜索引擎提供支持,它决定着整个引擎系统的内容是否丰富,信息是否即时,因此其性能的优劣直接影响着搜索引擎的效果.网络爬虫的基本工作原理: (1)从一个初始URL集合中挑选一个URL,下载该URL对应的页面: (2)解析该页面,从该页面中抽取出其包含的URL集合,接下来将抽取的URL集合再添加到初始URL集合中: (3)重复前两个过程,直到爬虫达到某种停止

windows下的socket网络编程(入门级)

windows下的socket网络编程 clinet.c 客户端 server.c 服务器端 UDP通信的实现 代码如下 已经很久没有在windows下编程了,这次因为需要做一个跨平台的网络程序,就先写了个简单的winSocket网路通信的例子,以便以后用到的时候有个参考. windows下使用winsock编程与linux/unix的区别在于windows下需要先有一个初始化的操作,结束的时候需要一个清理的操作.还有windows下编译的时候需要连接ws32_lib库. 大致过程如下 1.初始

Linux Socket 网络编程

Linux下的网络编程指的是socket套接字编程,入门比较简单.在学校里学过一些皮毛,平时就是自学玩,没有见识过真正的socket编程大程序,比较遗憾.总感觉每次看的时候都有收获,但是每次看完了之后,过段时间不看,重新拾起这些知识的时候又要从头开始,所以,在这里做个笔记也算是做个模板,以后可以直接从某一个阶段开始接着玩... 1. socket套接字介绍 socket机制其实就是包括socket, bind, listen, connect, accept等函数的方法,其通过指定的函数实现不同

linux下C语言socket网络编程简例

转自:http://blog.csdn.net/kikilizhm/article/details/7858405 这里给出在linux下的简单socket网络编程的实例,使用tcp协议进行通信,服务端进行监听,在收到客户端的连接后,发送数据给客户端:客户端在接受到数据后打印出来,然后关闭.程序里有详细的说明,其中对具体的结构体和函数的实现可以参考其他资料. 程序说明: 这里服务器的端口号和ip地址使用固定的设置,移植时可以根据具体情况更改,可以改写为参数传递更好,这里为了方便,使用固定的. 移

ios开发 网络编程浅析(一)

iphone包含了很多框架和库,从底层的套接字到不同层次的封装,可以方便地给程序添加网络功能. (1)BSD套接字.最底层的套接字,这是Unix网络开发常用的API.如果从其他系统移植程序,而程序用的是BSD套接字,那么网络部分可以继续使用这些API. (2)CFNetwork framework .CFNetwork 也是比较底层的, 是对BSD套接字的一个扩展 .它是一个C语言的库,它是基于BSD套接字,提供了对网络协议的抽象.这些抽象使得用户更容易地操作套接字.处理网络的各种连接..它集成

Socket网络编程 详细过程(转)

我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览器浏览网页时,浏览器的进程怎么与web服务器通信的?当你用QQ聊天时,QQ进程怎么与服务器或你好友所在的QQ进程通信?这些都得靠socket?那什么是socket?socket的类型有哪些?还有socket的基本函数,这些都是本文想介绍的.本文的主要内容如下: 1.网络中进程之间如何通信? 2.Socket是什么? 3.socket的基本操作 3.1.socket()函数 3.2.bind()函数 3.3.listen().co

Socket网络编程初探

MarkdownPad Document Socket网络编程初探 客户端/服务器架构 即C/S架构,其实web服务在某种意义上也算是C/S架构 一个特点是服务器端持续运行对外提供服务 为何学习socket一定要先学习互联网协议: C/S架构的软件是基于网络进行通信的 网络的核心就是一堆协议,即标准,想要开发一款基于网络通信的软件就必须遵循这些标准 socket是处在应用层和传输层中间的一组接口 说到这,那么socket到底是个什么呢?Socket是应用层与TCP/IP协议族通信的中间软件抽象层