iOS开发-使用OC搭建自己的Socket 包括服务端和客服端

iOS开发-使用OC搭建自己的Socket 包括服务端和客服端

前言

  • iOS开发中需要使用到Socket通信的地方,socket分为UDP和TCP,这次分享的是基于UDP是实现的socket。

开发须知

  • 七层模型
    在这里插入图片描述
  • 计算机基础
  • IP地址(主机名) 本地地址127.0.0.1 主机名localhost,每台电脑都有存在一个http://www.ip138.com
  • 端口号
    • 和进程关联起来的(IOS App)
    • 有效端口号:0~65535
    • 其中0~1024由系统使用或保留
  • 传输协议
    • TCP ,UDP
  • 关于socket
  • socket编程有两种模型:SOCK_DGRAM/SOCK_STREAM
    • TCP:有连接/数据可靠/无边界/双工/C/S模型
    • UDP:无连接/数据不可靠/有边界/双工 /对等模型
  • TCP数据的无边界(STREAM)
    • 需要建立连接
    • 通过三次握手完成连接(connect(ack))
    • 数据没有限制
  • UDP(DGRAM)
    • 不需要建立链接(所以速度快)
    • 只管发送,不确认对方是接收(不可靠)
    • 每个数据包的大小限制在64K内
  • MSG_WAITALL:标记控制数据接收必须接受到指定字节长度才返回, 利用MSG_WAITALL接收指定长度的数据,达到数据分边界.
  • TCP客户端处理
    • 建立socket
    • 连接服务器
    • 发送数据
    • 循环接收数据
    • 关闭连接
  • UDP 客户端处理
    • 建立socket
    • 发送数据SendTo
    • 接收数据
    • 接收数据 recvFrom
    • 1.5.关闭连接

客服端

  • 使用GCDAsyncSocket 库辅助
  • chatC.h
#import <Foundation/Foundation.h>

@interface chatC : NSObject

- (void)buildClient;
- (void)sendData:(NSString*)content;

@end
  • chatC.m
#import "chatC.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>

@implementation chatC

static int fd;
- (void)buildClient {
    
    
    /*
     ipv4
     SOCK_STREAM  TCP
     SOCK_DGRAM   UDP
     SOCK_RAW     检测电脑流量
     
     套接字 fd 是一个文件
     */
    // 1.1.建立socket
    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1) {
    
    
        NSLog(@"socket fail");
    }
    //1.2.连接服务器
    
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;//AF_INET ipv4
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(8888);//终端输入 ns -l 8888
    
    //1 发送一个信号给服务端,在吗(ACK) 2服务端回一个(ACK)我在  3 客服端,那我们就开始吧
    int result = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
    if (result == -1) {
    
    
        NSLog(@"connect fail");
    }
    
    //1.3.发送数据
    [self sendData:@"123456"];
    //1.4.循环接收数据
    [NSThread detachNewThreadSelector:@selector(threadRecvData) toTarget:self withObject:nil];
    
    //1.5.关闭连接
}

- (void)threadRecvData {
    
    
    char buf[32];
    while (1) {
    
    
        /*
         0 阻塞
         MSG_WAITALL 等待缓存满了才不阻塞
         */
        ssize_t result = recv(fd, buf, 32, MSG_WAITALL);
        if (result <= 0) {
    
    
            NSLog(@"recv fail!");
            break;
        }
        buf[result] = 0;
        NSLog(@"%s", buf);
    }
    close(fd);
}

- (void)sendData:(NSString*)content {
    
    
    [NSThread detachNewThreadSelector:@selector(threadSendData:) toTarget:self withObject:content];
}

- (void)threadSendData:(NSString*)content {
    
    
    if (content.length == 0) {
    
    
        return;
    }
    //content = @"八点钟"; // content.length = 3;
    const char* contC =  [content UTF8String];
    
    ssize_t result =  send(fd, contC, strlen(contC), 0);
    if (result < 0) {
    
    
        NSLog(@"send fail");
    }
}

@end

  • chatcUDP.h
#import <Foundation/Foundation.h>

@interface chatcUDP : NSObject

- (void)buildClient;
- (void)sendData:(NSString*)content;

@end
  • chatcUDP.m
#import "chatcUDP.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>

static int fd;
static struct sockaddr_in addr;
@implementation chatcUDP

- (void)buildClient {
    
    
    // 1.1.建立socket
    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd == -1) {
    
    
        NSLog(@"socket fail");
    }
    
    //1.2.连接服务器
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(8888);//终端输入 ns -lu 8888
    
    //1.3.发送数据
    [self sendData:@"123456"];
    
    //1.4.循环接收数据
    [NSThread detachNewThreadSelector:@selector(threadRecvData) toTarget:self withObject:nil];
    
    //1.5.关闭连接
}

- (void)threadRecvData {
    
    
    char buf[32];
    while (1) {
    
    
       // ssize_t result = recv(fd, buf, 32, 0);
        socklen_t size =  (socklen_t)sizeof(addr);
        ssize_t result = recvfrom(fd, buf, 32, 0,  (struct sockaddr *)&addr, &size);
        if (result <= 0) {
    
    
            NSLog(@"recv fail!");
            break;
        }
        buf[result] = 0;
        NSLog(@"%s", buf);
    }
    close(fd);
}

- (void)sendData:(NSString*)content {
    
    
    [NSThread detachNewThreadSelector:@selector(threadSendData:) toTarget:self withObject:content];
}

- (void)threadSendData:(NSString*)content {
    
     //聊天时udp实现的 如果用户量特别高的时候,一般都用udp来做,udp可以自己做一个接受者
    if (content.length == 0) {
    
    
        return;
    }
    //content = @"八点钟"; // content.length = 3;
    const char* contC =  [content UTF8String];
    //因为udp是不需要连接的,所以不需要知道它是否失败
    ssize_t result = sendto(fd, contC, strlen(contC), 0, (struct sockaddr *)&addr, sizeof(addr));
}


@end

  • 应用
  • ViewController.m
#import "ViewController.h"
#import "chatC.h"
#import "chatcUDP.h"

@interface ViewController () <UITextFieldDelegate>
{
    
    
    chatC *_chatC;
    chatcUDP *_chatcUdp;
}
@property(nonatomic, strong) UITextField *textField;

@end

@implementation ViewController


- (void)viewDidLoad{
    
    
    
    [super viewDidLoad];
    [self.view addSubview:self.textField];
    NSLog(@"==%d", getpid());
//    _chatC = [chatC new];
//    [_chatC buildClient];
    
    _chatcUdp = [chatcUDP new];
    [_chatcUdp buildClient];
}


- (BOOL)textFieldShouldReturn:(UITextField *)textField{
    
    
    
   // [_chatC sendData:textField.text];
    [_chatcUdp sendData:textField.text];
    return YES;
}

- (UITextField *)textField {
    
    
    if(_textField == nil) {
    
    
        _textField = [[UITextField alloc]initWithFrame:CGRectMake(0, self.view.frame.size.height/2, self.view.frame.size.width, 20)];
        _textField.delegate = self;
    }
    return _textField;
}


@end

服务端

  • ChatS.h
#import <Foundation/Foundation.h>

@interface ChatS : NSObject

- (int)buildServer;

@end
  • ChatS.m
#import "ChatS.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/_select.h>
#include <sys/select.h>

/*
 S  a连接过来(fd(1))
 当客户端C连接上了服务端,S端这边会给客户端创建一个套接字fd,
 如果有2C,那么S端会生成两个 fd
 */

static int serverfd; // 服务端的套接字

static fd_set allfd; // fd_set (容器,看作是一个数组)存放当前所有的套字节

static fd_set changefds;  // fd(1) fd(2)发消息过来, 监听得到fd的变化,把变化放到这个容器里来

static int maxfd = -1; // allfd 里面最大的fd的值

//static int maxchangefd = -1;// changefds里面最大的fd的值

// select  poll
@implementation ChatS


- (int)buildServer{
    
    
    
    // 1 .建立socket
    // serverfd = 5;
    serverfd = socket(AF_INET, SOCK_STREAM, 0);
    if (serverfd == -1) {
    
    
        NSLog(@"socket fail");
        return 0;
    }
    
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("192.168.0.108");
    addr.sin_port = htons(9999);
    
    
    // 2 bind 绑定端口   进程和端口进行绑定
    int isreused  = 0;
    setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR, &isreused, sizeof(int));
    int r = bind(serverfd, (struct sockaddr *)&addr, sizeof(addr));
    
    if(r == -1){
    
    
        printf("bind fail!\n");
        return 0;
    }else{
    
    
        printf("bind success!\n");
    }
    
    // 3 linsten 监听  当前10代表最大监听的数量,多余的10就会拒绝
    /*
      一次能处理的任务数,假如一次有15个客服端连接上来了,那么他最多处理10个,其他5就会被拒绝
     */
    r = listen(serverfd, 10);
    if (r == -1) {
    
    
        printf("listen fail\n");
        return 0;
    }
    printf("listen success\n");
    // 4 select
    while (1) {
    
    
        
        FD_ZERO(&changefds);
        /*
         第一次的时候,当我们创建服务端的时候,本地只有serverfd这么一个套字节
          */
        FD_SET(serverfd, &changefds); //把服务端的fd添加到changefds //往容器set里面存值
        maxfd = maxfd<serverfd?serverfd:maxfd;
        
        for (int i = 0; i <= maxfd; i++) {
    
    
            
            if (FD_ISSET(i, &allfd)) {
    
    
                FD_SET(i, &changefds); // 添加客户端的fd到changefds里面
                maxfd = maxfd<i?i:maxfd;
            }
        }
        
    /*
     select 阻塞,当客户端有相应的时候(发消息或者建立连接),阻塞就接触
     有变化的fd 就保留在changefds;
     轮询 套接字的状态
     */
        // poll
        r = select(maxfd + 1, &changefds, 0, 0, 0);
      //  r = select(maxfd + 1, &changefds, 0, 0, 0); // 阻塞 客户端没有响应
        
        // 如果serverfd 还存在changefds 容器里面,那么说明serverfd有状态,这个状态代表有客户端来连接了
        if (FD_ISSET(serverfd, &changefds)) {
    
     // 这个if主要是查看是否有人来连接
            
            printf("有人来连接了\n");
            
            int fd =  accept(serverfd, 0, 0); // 接受连接(会给客服端生成一个对应的套接字fd)
            if(fd == -1){
    
    
                printf("连接失败\n");
                break;
            }
            maxfd = maxfd<fd?fd:maxfd;
            FD_SET(fd, &allfd);
        }
        
        // 处理客服端的事情
        // 没有提供直接读取fd_set changefds里面的对象
        
        char buf[256];
        for(int i = 0; i <= maxfd; i++){
    
     // 5 状态(客户的状态)
            
            //FD_ISSET(i, &allfd) fd是没有断开的
            if (FD_ISSET(i, &changefds) && FD_ISSET(i, &allfd)) {
    
    
                
                r = (int)recv(i, buf, 255, 0);
                if (r <= 0) {
    
    
                    printf("有人退出了\n");
                    // 把这个套字节从 allfd里面移除
                    FD_CLR(i, &allfd);
                }
                buf[r] =  0;
                printf("来自客户端的数据:%d,%s\n", r, buf);
                
            }
            
            // 消息处理
            // 广播数据 (对每一个连接上的客户端发送数据)
            for (int j = 0; j <= maxfd; j++) {
    
    
                
                if (FD_ISSET(j, &allfd)) {
    
    
                    
                    r = (int)send(j, buf,strlen(buf), 0);
                    printf("send:%d\n", r);
                }
                
            }
        }
        
        
    }
    
    // 6 close
    close(serverfd);
    
    return 0;
}

@end

两端测试

  • ViewController.m
#import "ViewController.h"
#import "chatC.h"
#import "chatcUDP.h"
#import "ChatS.h"

@interface ViewController () <UITextFieldDelegate>
{
    
    
    chatC *_chatC;
    chatcUDP *_chatcUdp;
    ChatS *chatS;
}

@property(nonatomic, strong) UITextField *textField;

@end

@implementation ViewController


- (void)viewDidLoad{
    
    
    
    [super viewDidLoad];
    [self.view addSubview:self.textField];
//    chatS = [ChatS new];
//    [chatS buildServer];
    
   // NSLog(@"==%d", getpid());
    _chatC = [chatC new];
    [_chatC buildClient];
    
 //   _chatcUdp = [chatcUDP new];
  //  [_chatcUdp buildClient];
}


- (BOOL)textFieldShouldReturn:(UITextField *)textField{
    
    
    
    [_chatC sendData:textField.text];
   // [_chatcUdp sendData:textField.text];
    return YES;
}

- (UITextField *)textField {
    
    
    if(_textField == nil) {
    
    
        _textField = [[UITextField alloc]initWithFrame:CGRectMake(0, self.view.frame.size.height/2, self.view.frame.size.width, 20)];
        _textField.delegate = self;
    }
    return _textField;
}


@end

猜你喜欢

转载自blog.csdn.net/weixin_41732253/article/details/110264432
今日推荐