iOS 用原生代码写一个简单的socket链接

socket简介(摘取自百度百科)

描述

网络上的两个程序经过一个双向的通讯链接实现数据的交换,这个链接的一端称为一个socket。 创建网络通讯链接至少要一对端口号(socket)。程序员

socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员作网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通讯的能力。编程

在链接成功时,应用程序两端都会产生一个Socket实例,操做这个实例,完成所需的会话。对于一个网络链接来讲,套接字是平等的,并无差异,不由于在服务器端或在客户端而产生不一样级别。无论是Socket仍是ServerSocket它们的工做都是经过SocketImpl类及其子类完成的。 bash

image.png

链接过程

根据链接启动的方式以及本地套接字要链接的目标,套接字之间的链接过程能够分为三个步骤:服务器监听,客户端请求,链接确认。服务器

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

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

(3)链接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的链接请求,它就响应客户端套接字的请求,创建一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,链接就创建好了。而服务器端套接字继续处于监听状态,继续接收其余客户端套接字的链接请求。 socket

image.png

iOS写一个原生socket

1. 导入头文件以及宏定义
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>

//htons : 将一个无符号短整型的主机数值转换为网络字节顺序,不一样cpu 是不一样的顺序 (big-endian大尾顺序 , little-endian小尾顺序)
#define SocketPort htons(8040) //端口
//inet_addr是一个计算机函数,功能是将一个点分十进制的IP转换成一个长整数型数
#define SocketIP inet_addr("127.0.0.1") // ip
复制代码
2. 建立socket

函数原型:async

int socket(int domain, int type, int protocol);函数

函数使用:工具

//属性,用于接收socket建立成功后的返回值
@property (nonatomic, assign) int clinenId;

_clinenId = socket(AF_INET, SOCK_STREAM, 0);
    
if (_clinenId == -1) {
    NSLog(@"建立socket 失败");
    return;
}
复制代码

参数说明:
domain:协议域,又称协议族(family)。经常使用的协议族有*AF_INET(ipv4)、AF_INET6(ipv6)、*AF_LOCAL(或称AF_UNIX,Unix域Socket)、AF_ROUTE等。协议族决定了socket的地址类型,在通讯中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名做为地址。

type:指定Socket类型。经常使用的socket类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。流式Socket(SOCK_STREAM)是一种面向链接的Socket,针对于面向链接的TCP服务应用。数据报式Socket(SOCK_DGRAM)是一种无链接的Socket,对应于无链接的UDP服务应用。

protocol:指定协议。经常使用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。

注意:type和protocol不能够随意组合,如SOCK_STREAM不能够跟IPPROTO_UDP组合。当第三个参数为0时,会自动选择第二个参数类型对应的默认协议。

返回值:若是调用成功就返回新建立的套接字的描述符,若是失败就返回INVALID_SOCKET(Linux下失败返回-1)。套接字描述符是一个整数类型的值。

3. 建立链接

函数原型:

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函数使用: 3.1 建立socketAddr

/**
     __uint8_t    sin_len;          假如没有这个成员,其所占的一个字节被并入到sin_family成员中
     sa_family_t    sin_family;     通常来讲AF_INET(地址族)PF_INET(协议族)
     in_port_t    sin_port;         // 端口
     struct    in_addr sin_addr;    // ip
     char        sin_zero[8];       没有实际意义,只是为了&emsp;跟SOCKADDR结构在内存中对齐
     */
    struct sockaddr_in socketAddr;
    socketAddr.sin_family   = AF_INET;//当前这个是ipv4
    socketAddr.sin_port     = SocketPort; //这里定义了一个宏
    
    struct in_addr  socketIn_addr;
    socketIn_addr.s_addr    = SocketIP; // 也是宏

    socketAddr.sin_addr     = socketIn_addr;
复制代码

3.2 链接

int result = connect(_clinenId, (const struct sockaddr *)&socketAddr, sizeof(socketAddr));
    if (result != 0) {
        NSLog(@"链接socket 失败");
        return;
    }
    NSLog(@"链接成功");
复制代码

参数说明: sockfd:标识一个已链接套接口的描述字,就是咱们刚刚建立的那个_clinenId。

addr指针,指向目的套接字的地址。

addrlen:接收返回地址的缓冲区长度。

***返回值:***成功则返回0,失败返回非0,错误码GetLastError()。

4. 发送消息

函数原型:

ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);

函数使用:

const char *msg = @"消息内容".UTF8String;
    //send() 等同于 write() 多提供了一个参数来控制读写操做
    ssize_t sendLen = send(self.clinenId, msg, strlen(msg), 0);
    NSLog(@"发送了:%ld字节",sendLen);
复制代码

参数说明:
sockfd:一个用于标识已链接套接口的描述字。
buff:包含待发送数据的缓冲区。
nbytes:缓冲区中数据的长度。
flags:调用执行方式。
返回值:若是成功,则返回发送的字节数,失败则返回SOCKET_ERROR,一个中文UTF8 编码对应 3 个字节。因此上面发送了3*4字节。

5. 接收数据

函数原型:

ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);

函数使用:

uint8_t buffer[1024];
    //recv() 等同于 read() 多提供了一个参数来控制读写操做
    ssize_t recvLen = recv(self.clinenId, buffer, sizeof(buffer), 0);
    NSLog(@"接收到了:%ld字节",recvLen);
    if (recvLen==0) {
        NSLog(@"这次传输长度为0 若是下次还为0 请检查链接");
    }
    // 接收到的数据转换
    NSData *recvData  = [NSData dataWithBytes:buffer length:recvLen];
    NSString *recvStr = [[NSString alloc] initWithData:recvData encoding:NSUTF8StringEncoding];
    NSLog(@"%@",recvStr);
复制代码

参数说明: sockfd:一个用于标识已链接套接口的描述字。
buff:包含待发送数据的缓冲区。
nbytes:缓冲区中数据的长度。
flags:调用执行方式。
返回值:若是成功,则返回读入的字节数,失败则返回SOCKET_ERROR。

完整代码

#pragma mark - 建立socket创建链接
- (IBAction)socketConnetAction:(UIButton *)sender {
    
    // 1: 建立socket
    int socketID = socket(AF_INET, SOCK_STREAM, 0);
    self.clinenId= socketID;
    if (socketID == -1) {
        NSLog(@"建立socket 失败");
        return;
    }
    
    // 2: 链接socket
    struct sockaddr_in socketAddr;
    socketAddr.sin_family = AF_INET;
    socketAddr.sin_port   = SocketPort;
    struct in_addr socketIn_addr;
    socketIn_addr.s_addr  = SocketIP;
    socketAddr.sin_addr   = socketIn_addr;
    
    int result = connect(socketID, (const struct sockaddr *)&socketAddr, sizeof(socketAddr));

    if (result != 0) {
        NSLog(@"链接失败");
        return;
    }
    NSLog(@"链接成功");
    
    // 调用开始接受信息的方法
    // while 若是主线程会形成堵塞
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self recvMsg];
    });
    
}


#pragma mark - 发送消息

- (IBAction)sendMsgAction:(id)sender {
    //3: 发送消息
    if (self.sendMsgContent_tf.text.length == 0) {
        return;
    }
    const char *msg = self.sendMsgContent_tf.text.UTF8String;
    ssize_t sendLen = send(self.clinenId, msg, strlen(msg), 0);
    NSLog(@"发送 %ld 字节",sendLen);
}

#pragma mark - 接受数据
- (void)recvMsg{
    // 4. 接收数据
    while (1) {
        uint8_t buffer[1024];
        ssize_t recvLen = recv(self.clinenId, buffer, sizeof(buffer), 0);
        NSLog(@"接收到了:%ld字节",recvLen);
        if (recvLen == 0) {
            continue;
        }
        // buffer -> data -> string
        NSData *data = [NSData dataWithBytes:buffer length:recvLen];
        NSString *str= [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@---%@",[NSThread currentThread],str);
    }
}
复制代码

调试

首先 运行效果

image.png

方式一,简单快捷
  1. 打开命令行工具输入 nc -lk 8040
  2. 点击链接socket
    image.png
  3. 命令行工具随便输入字符 回车
    image.png
    4.模拟器随便输入字符 发送
    image.png
方式二 本身写一个本地socket服务端

听起来好想很牛逼,其实跟上面写客户端差很少。 多了bind(),listen(),accept()三步。

头文件、宏、属性

#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>

#define SocketPort htons(8040)
#define SocketIP inet_addr("127.0.0.1")

@property (nonatomic, assign) int serverId;
@property (nonatomic, assign) int client_socket;
复制代码
  1. socket()
self.serverId = socket(AF_INET, SOCK_STREAM, 0);
    if (self.serverId == -1) {
        NSLog(@"建立socket 失败");
        return;
    }
    NSLog(@"建立socket 成功");
复制代码
  1. bind()
struct sockaddr_in socketAddr;
    socketAddr.sin_family   = AF_INET;
    socketAddr.sin_port     = SocketPort;
    struct in_addr  socketIn_addr;
    socketIn_addr.s_addr    = SocketIP;
    socketAddr.sin_addr     = socketIn_addr;
    bzero(&(socketAddr.sin_zero), 8);
    
    // 2: 绑定socket
    int bind_result = bind(self.serverId, (const struct sockaddr *)&socketAddr, sizeof(socketAddr));
    if (bind_result == -1) {
        NSLog(@"绑定socket 失败");
        return;
    }

    NSLog(@"绑定socket成功");
复制代码
  1. listen()
// 3: 监听socket
    int listen_result = listen(self.serverId, kMaxConnectCount);
    if (listen_result == -1) {
        NSLog(@"监听失败");
        return;
    }
    
    NSLog(@"监听成功");
复制代码
  1. accept()
dispatch_async(dispatch_get_global_queue(0, 0), ^{
        struct sockaddr_in client_address;
        socklen_t address_len;
        // accept函数
        int client_socket = accept(self.serverId, (struct sockaddr *)&client_address, &address_len);
        self.client_socket = client_socket;
        
        if (client_socket == -1) {
            NSLog(@"接受 %u 客户端错误",address_len);           
        }else{
            NSString *acceptInfo = [NSString stringWithFormat:@"客户端 in,socket:%d",client_socket];
            NSLog(@"%@",acceptInfo);
           //开始接受消息
            [self receiveMsgWithClietnSocket:client_socket];
        }
    });
复制代码
  1. recv()
while (1) {
        // 5: 接受客户端传来的数据
        char buf[1024] = {0};
        long iReturn = recv(clientSocket, buf, 1024, 0);
        if (iReturn>0) {
            NSLog(@"客户端来消息了");
            // 接收到的数据转换
            NSData *recvData  = [NSData dataWithBytes:buf length:iReturn];
            NSString *recvStr = [[NSString alloc] initWithData:recvData encoding:NSUTF8StringEncoding];
            NSLog(@"%@",recvStr);
            
        }else if (iReturn == -1){
            NSLog(@"读取消息失败");
            break;
        }else if (iReturn == 0){
            NSLog(@"客户端走了");
            
            close(clientSocket);
            
            break;
        }
    }
复制代码
  1. send()
const char *msg = @"给客户端发消息".UTF8String;
    ssize_t sendLen = send(self.client_socket, msg, strlen(msg), 0);
    NSLog(@"发送了:%ld字节",sendLen);
复制代码
  1. close()
int close_result = close(self.client_socket);
    
    if (close_result == -1) {
        NSLog(@"socket 关闭失败");
        return;
    }else{
        NSLog(@"socket 关闭成功");
    }
复制代码

那么这篇文章就到这里,写这篇文章的主要目的是为了让后面学习GCDAsyncSocket时,加深印象、深刻理解其实现原理。

相关文章
相关标签/搜索