iOS-Socket开发学习笔记-1

BSD socket API

/**
 socket 创建并初始化 socket,返回该 socket 的文件描述符,如果描述符为 -1 表示创建失败。

 @param addressFamily   是 IPv4(AF_INET) 或 IPv6(AF_INET6)。
 @param type 表示        socket 的类型,通常是流stream(SOCK_STREAM) 或数据报文datagram(SOCK_DGRAM)
 @param protocol        参数通常设置为0,以便让系统自动为选择我们合适的协议,对于 stream socket 来说会是 TCP 协议(IPPROTO_TCP),而对于 datagram来说会是   UDP 协议(IPPROTO_UDP)。
 @return                返回该 socket 的文件描述符
 *//
int socket(int addressFamily, int type, int protocol);

/**
 服务器端侦听客户端的请求

 @param socketFileDescriptor 服务端socket
 @param backlogSize 客户端连接请求缓冲区队列的大小
 @return 0 成功或者其他 错误代号,(不是非0即真)
 */
int listen(int socketFileDescriptor, int backlogSize) __DARWIN_ALIAS(listen);


/**
 接受客户端连接请求并将客户端的网络地址信息保存到 clientAddress 中。
 
 当客户端连接请求被服务器接受之后,客户端和服务器之间的链路就建立好了,两者就可以通信了

 @param socketFileDescriptor 服务器的socket描述字
 @param address 指向struct sockaddr *的指针,用于返回客户端的协议地址
 @param addressStructLength address结构体数据长度
 @return 由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。
 */
int accept(int socketFileDescriptor, struct sockaddr * __restrict address, socklen_t * __restrict addressStructLength)
__DARWIN_ALIAS_C(accept);

/**
 客户端向特定网络地址的服务器发送连接请求,连接成功返回0,失败返回 -1。
 当服务器建立好之后,客户端通过调用该接口向服务器发起建立连接请求。对于 UDP 来说,该接口是可选的,如果调用了该接口,表明设置了该 UDP socket 默认的网络地址。对 TCP socket来说这就是传说中三次握手建立连接发生的地。
 注意:该接口调用会阻塞当前线程,直到服务器返回。

 @param socketFileDescriptor 客户端sockets
 @param serverAddress 向数据结构sockaddr的指针,其中包括目的端口和IP地址,服务器的"结构体"地址;提示:C 语言中没有对象
 @param serverAddressLength 结构体数据长度
 @return 0 成功或者其他 错误代号,(不是非0即真)
 */
int connect(int socketFileDescriptor, const struct sockaddr *serverAddress, socklen_t serverAddressLength) __DARWIN_ALIAS_C(connect);


/**
 从 socket 中读取数据,读取成功返回成功读取的字节数,否则返回 -1。
 一旦连接建立好之后,就可以通过 send或者receive 接口发送或接收数据了。注意调用 connect 设置了默认网络地址的 UDP socket 也可以调用该接口来发送数据。

 @param socketFileDescriptor 客户端sockets
 @param buf 接受数据的buffer
 @param bufferLength buffer长度
 @param flags 接收方式,0表示阻塞,必须等待服务器返回数据
 @return 如果成功,则返回读入的字节数,失败则返回SOCKET_ERROR
 */
ssize_t recv(int socketFileDescriptor, void *buf, size_t bufferLength, int flags) __DARWIN_ALIAS_C(recv);


/**
 通过 socket 发送数据,发送成功返回成功发送的字节数,否则返回 -1。
 一旦连接建立好之后,就可以通过 send/receive 接口发送或接收数据了。注意调用 connect 设置了默认网络地址的 UDP socket 也可以调用该接口来接收数据。

 @param socketFileDescriptor    指定发送端套接字描述符
 @param buf                     存放应用程序要发送数据的缓冲区
 @param bufferLength            发送的数据的字节数
 @param flags                   发送方式,0表示阻塞,必须等待服务器返回数据
 @return                        如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR
 */
ssize_t send(int socketFileDescriptor, const void *buf, size_t bufferLength, int flags) __DARWIN_ALIAS_C(send);


/**
 将 socket 与特定主机地址与端口号绑定,成功绑定返回0,失败返回 -1。
 
 成功绑定之后,根据协议(TCPUDP)的不同,我们可以对 socket 进行不同的操作:
 UDP:因为 UDP 是无连接的,绑定之后就可以利用 UDP socket 传送数据了。
 TCP:而 TCP 是需要建立端到端连接的,为了建立 TCP 连接服务器必须调用 listen(int socketFileDescriptor, int backlogSize) 来设置服务器的缓冲区队列以接收客户端的连接请求,backlogSize 表示客户端连接请求缓冲区队列的大小。当调用 listen 设置之后,服务器等待客户端请求,然后调用 accept 来接受客户端的连接请求。

 @param socketFileDescriptor    主机socket
 @param addressToBind           数据结构sockaddr的指针,其中包括目的端口和IP地址,服务器的"结构体"地址;提示:C 语言中没有对象
 @param addressStructLength     sockaddr结构体数据长度
 @return                        0 成功/其他 错误代号,(不是非0即真)
 */
int bind(int socketFileDescriptor, const struct sockaddr *addressToBind, socklen_t addressStructLength) __DARWIN_ALIAS(bind);


/**
 服务器端侦听客户端的请求

 @param socketFileDescriptor 服务端socket
 @param backlogSize 客户端连接请求缓冲区队列的大小
 @return 0 成功/其他 错误代号,(不是非0即真)
 */
int listen(int socketFileDescriptor, int backlogSize) __DARWIN_ALIAS(listen);


/**
 接受客户端连接请求并将客户端的网络地址信息保存到 clientAddress 中。
 
 当客户端连接请求被服务器接受之后,客户端和服务器之间的链路就建立好了,两者就可以通信了

 @param socketFileDescriptor 服务器的socket描述字
 @param address 指向struct sockaddr *的指针,用于返回客户端的协议地址
 @param addressStructLength address结构体数据长度
 @return 由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。
 */
int accept(int socketFileDescriptor, struct sockaddr * __restrict address, socklen_t * __restrict addressStructLength)
__DARWIN_ALIAS_C(accept);

一、客户端操作

  • 1、绑定地址和端口操作
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);// 结构体长度
server_addr.sin_family = AF_INET; //sin_family指代协议族,在socket编程中只能是AF_INET
server_addr.sin_port = htons(1234);//存储端口号(使用网络字节顺序),在linux下,端口号的范围0~65535,同时0~1024范围的端口号已经被系统使用或保留。
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");//存储IP地址,使用in_addr这个数据结构
bzero(&(server_addr.sin_zero), 8);//是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节, 初始值应该使用函数 bzero() 来全部置零
  • 2、接受客户端的链接
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(queue, ^{
            //创建新的socket
            int aResult = connect(server_socket, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in));
            if (aResult == -1) {
                NSLog(@"链接失败");
            }else{
                self.server_socket = server_socket;
                [self acceptFromServer];
            }
        });
  • 3、从服务端接受消息
- (void)acceptFromServer {
    while (1) {
        //接受服务器传来的数据
        char buf[1024];
        long iReturn = recv(self.server_socket, buf, 1024, 0);
        if (iReturn > 0) {
            NSString *str = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];

            //筛选前缀
            if ([str hasPrefix:@"list:"]) {
                NSString *arrayStr = [str substringFromIndex:5];
                NSArray *list = [arrayStr componentsSeparatedByString:@","];
                self.userArray = [NSMutableArray arrayWithArray:list];
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self.onlineTable reloadData];
                });
                NSLog(@"当前在线用户列表:%@",arrayStr);
            }else{
                //回到主线程 界面上显示内容
                [self showLogsWithString:str];
            }
             
        }else if (iReturn == -1){
            NSLog(@"接受失败-1");
            break;
        }
    }
}
  • 4、给客户端发送信息
- (void)sendMsg:(NSString*)msg {
    char *buf[1024] = {0};
    const char *p1 = (char*)buf;
    p1 = [msg cStringUsingEncoding:NSUTF8StringEncoding];
    send(self.server_socket, p1, 1024, 0);
}

二、服务端操作

  • 1、绑定地址和端口
    //创建socket
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
        NSLog(@"创建失败");
        [self showLogsWithString:@"socket创建失败"];

    } else {
        //绑定地址和端口
        struct sockaddr_in server_addr;
        server_addr.sin_len = sizeof(struct sockaddr_in);
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(1234);
        server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        bzero(&(server_addr.sin_zero), 8);
        
        int bind_result = bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));
        if (bind_result == -1) {
            NSLog(@"绑定端口失败");
            [self showLogsWithString:@"绑定端口失败"];

        } else {
            if (listen(server_socket, kMaxConnectCount)==-1) {
                NSLog(@"监听失败");
                [self showLogsWithString:@"监听失败"];

            } else {
                for (int i = 0; i < kMaxConnectCount; i++) {
                    //接受客户端的链接
                    [self acceptClientWithServerSocket:server_socket];
                }
            }
        }
    }
  • 2、创建线程接受客户端
- (void)acceptClientWithServerSocket:(int)server_socket {
    struct sockaddr_in client_address;
    socklen_t address_len;
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        //创建新的socket
        while (1) {
            int client_socket = accept(server_socket, (struct sockaddr*)&client_address,&address_len );
            if (client_socket == -1) {
                [self showLogsWithString:@"接受客户端链接失败"];
                NSLog(@"接受客户端链接失败");
            }else{
                NSString *acceptInfo = [NSString stringWithFormat:@"客户端 in,socket:%d",client_socket];
                [self showLogsWithString:acceptInfo];
                
                //接受客户端数据
                [self recvFromClinetWithSocket:client_socket];
            }
        }
    });
}
  • 3、接受客户端数据
- (void)recvFromClinetWithSocket:(int)client_socket{
    while (1) {
        //接受客户端传来的数据
        char buf[1024] = {0};
        long iReturn = recv(client_socket, buf, 1024, 0);
        if (iReturn > 0) {
            NSLog(@"客户端来消息了");
            NSString *str = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];
            [self showLogsWithString:[NSString stringWithFormat:@"客户端来消息了:%@",str]];
            [self checkRecvStr:str andClientSocket:client_socket];
        } else if (iReturn == -1) {
            NSLog(@"读取消息失败");
            [self showLogsWithString:@"读取消息失败"];
            break;
        } else if (iReturn == 0) {
            NSLog(@"客户端走了");
            [self showLogsWithString:[NSString stringWithFormat:@"客户端 out socket:%d",client_socket]];
            NSMutableArray *array = [NSMutableArray arrayWithArray:self.clientArray];
            for (ClientModel *model in array) {
                if (model.clientSocket == client_socket) {
                    [self.clientNameArray removeObject:model.clientName];
                    [self.clientArray removeObject:model];
                }
            }
            
            close(client_socket);
            
            break;
        }
    }
}

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

推荐阅读更多精彩内容

  • 研究IPv6 socket编程原因: Supporting IPv6 in iOS 9 WWDC2015苹果宣布在...
    li大鹏阅读 7,318评论 7 15
  • 大纲 一.Socket简介 二.BSD Socket编程准备 1.地址 2.端口 3.网络字节序 4.半相关与全相...
    y角阅读 2,424评论 2 11
  • socket通信原理 socket又被叫做套接字,它就像连接到两端的插座孔一样,通过建立管道,将两个不同的进程之间...
    jiodg45阅读 1,140评论 0 1
  • 网络模型 物理层 物理层表示的是比特流传输,通常包括串口/COM口、并行/LPT口、USB、网线接口、电话线接口;...
    秋风弄影阅读 715评论 0 2
  • 一、基本socket函数Linux系统是通过提供套接字(socket)来进行网络编程的。网络的socket数据传输...
    WB莫遥燚阅读 1,471评论 0 0