美文网首页
iOS~网络基础~Socket

iOS~网络基础~Socket

作者: 派大星的博客 | 来源:发表于2019-01-15 19:36 被阅读15次

参考文档
https://en.wikipedia.org/wiki/Network_socket#Socket_states_in_the_client-server_model
https://en.wikipedia.org/wiki/Berkeley_sockets
http://blog.csdn.net/zapldy/article/details/5813984


Socket 是一组接口,它把复杂的TCP/IP协议族隐藏在Socket接口后面。

image.png

1、对比 Socket 在哪里


image.png

Socket是什么呢?: Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面。对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议.

image.png
image.png

先从Server端说起.
Server服务器端先初始化Socket,
Server然后与端口绑定(bind),
Server对端口进行监听(listen),
Server调用accept阻塞,等待客户端连接.

Client在这时如果有个客户端初始化一个Socket,
Client然后连接服务器(connect)

=====>
如果连接成功,这时客户端与服务器端的连接就建立了.
=====>


Client客户端发送数据请求,

Server服务器端接收请求并处理请求,然后把回应数据发送给客户端,

Client客户端读取数据,


最后关闭连接,一次交互结束.

QQ20180129-191303@2x.png QQ20180129-191337@2x.png pasted-image.png

Socket 编程

导入头文件.png

Flow diagram of client-server transaction using sockets with the Transmission Control Protocol (TCP).

This list is a summary of functions or methods provided by the

Berkeley sockets API library:
  • socket() creates a new socket of a certain socket type, identified by an integer number, and allocates system resources to it.
  • bind() is typically used on the server side, and associates a socket with a socket address structure, i.e. a specified local port number and IP address.
  • listen() is used on the server side, and causes a bound TCP socket to enter listening state.
  • connect() is used on the client side, and assigns a free local port number to a socket. In case of a TCP socket, it causes an attempt to establish a new TCP connection.
  • accept() is used on the server side. It accepts a received incoming attempt to create a new TCP connection from the remote client, and creates a new socket associated with the socket address pair of this connection.
  • send() and recv(), or write() and read(), or sendto() and recvfrom(), are used for sending and receiving data to/from a remote socket.
  • close() causes the system to release resources allocated to a socket. In case of TCP, the connection is terminated.
  • gethostbyname() and gethostbyaddr() are used to resolve host names and addresses. IPv4 only.
  • select() is used to suspend, waiting for one or more of a provided list of sockets to be ready to read, ready to write, or that have errors.
  • poll() is used to check on the state of a socket in a set of sockets. The set can be tested to see if any socket can be written to, read from or if an error occurred.
  • getsockopt() is used to retrieve the current value of a particular socket option for the specified socket.
  • setsockopt() is used to set a particular socket option for the specified socket.
//
//  main.m

#import <Foundation/Foundation.h>
#import <arpa/inet.h>

static const short SERVER_PORT = 1234;  // 端口
static const int MAX_Q_LEN = 64;        // 最大队列长度
static const int MAX_MSG_LEN = 4096;    // 最大消息长度

// 字符串截断函数
void change_enter_to_tail_zero(char * const buffer, int pos) {
    for (int i = pos - 1; i >= 0; i--) {
        if (buffer[i] == '\r') {
            buffer[i] = '\0';
            break;
        }
    }
}

// 处理客户端的内容。
void handle_client_connection(int clientSocketFD) {
    bool clientConnected = true;
    while (clientConnected) {
        char buffer[MAX_MSG_LEN + 1]; // C语言的数组结束符是'\0' 所以数组的实际长度是 MAX_MSG_LEN + 1
        
        /**
         不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。

         param 客户端套接字文件句柄
         param buffer 存放接受信息的缓冲区
         param 1 让内存进行分配buffer-1个字节空间(注意:计量单位是字节)
         param 0 一般设置为0
         return 如果是-1表示没有成功接收到客户端套接字的信息,否则就是成功接收到了信息
         */
        ssize_t bytesToRecv = recv(clientSocketFD, buffer, sizeof buffer - 1, 0); //buffer - 1 是因为 bytesToRecv的信息存贮空间大小是 MAX_MSG_LEN
        if (bytesToRecv > 0) {
            buffer[bytesToRecv] = '\0'; //把buffer字符数组最后一个设置为\0 “字符串结束符” 注意:数组角标是0开始的。
            change_enter_to_tail_zero(buffer, (int)bytesToRecv); //1.字符串的截断。通过在字符数组后面添加 '\0'即可。
            printf("%s\n", buffer);
            if (!strcmp(buffer, "bye")) { //通过匹配 bye 字符串来关闭链接。
                clientConnected = false;
            }
            
        /**
             不论是客户端还是服务器应用程序都用send函数来向TCP连接的另一端发送数据

             param 客户发送端套接字的描述符
             param buffer存放要发送数据的缓冲去
             param 实际要发送数据的字节数
             param 0 一般做保留
             return 如果是-1表示没有成功发送到信息,否则就是成功发送信息
             */
            ssize_t bytesToSend = send(clientSocketFD, buffer,
                                       bytesToRecv, 0);
            if (bytesToSend > 0) {
                printf("Echo message has been sent.\n");
            }
        }
        else { //如果接受到内容,说明链接失败
            printf("client socket closed!\n");
            clientConnected = false;
        }
    }
    close(clientSocketFD); //关闭客户端Soket
}


int main() {
   
    /**
     创建一个socket对象,什么是socket:网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket;
     param AF_INET 指定使用IPV4协议
     param SOCK_STREAM 指定使用TCP协议
     param AF_UNIX 指定使用本地的socket
     return 如果返回的是-1,表示创建失败,否则表示创建成功,该值是一个socket的描述符 一般用命名使用FD作为结尾,起源于Unix “everything is a file”的设计哲学。
     */
    int serverSocketFD = socket(AF_INET, SOCK_STREAM, 0); //创建一个服务端socket对象
    if (serverSocketFD < 0) { // 创建socket文件句柄失败
        NSLog(@"%d", serverSocketFD);
        perror("无法创建套接字!!!\n");
        exit(1);
    }
    
    // 宏资料的地址:https://opensource.apple.com/source/xnu/xnu-1228/libkern/libkern/_OSByteOrder.h
    /*
     #define ntohs(x)    __DARWIN_OSSwapInt16(x) // 16位整数 网络字节序转主机字节序
     #define htons(x)    __DARWIN_OSSwapInt16(x) // 16位整数 主机字节序转网络字节序
     
     #define ntohl(x)    __DARWIN_OSSwapInt32(x)  //32位整数 网络字节序转主机字节序
     #define htonl(x)    __DARWIN_OSSwapInt32(x) //32位整数 主机字节序转网络字节序
     */
    // 定义一个socket地址
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET; // 指定IPV4
    serverAddr.sin_port = htons(SERVER_PORT); // 设置端口
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY 设置监听所有的地址。这里我们使用127.0.0.1.(使用telent命令进行测试后,1234端口被占用,可以通过 kill pid 来关闭占用该接口的进程。)
    
    // 3. 把socket address接口绑定到系统的socket句柄上。
    int ret = bind(serverSocketFD, (struct sockaddr *)&serverAddr, sizeof serverAddr);
    if (ret < 0) {
        perror("无法将套接字绑定到指定的地址!!!\n");
        close(serverSocketFD);
        exit(1);
    }
    
    /**
     serverSocketFD监听通过sreverAddr进入socket的TCP链接(该TCP前面通过SOCK_STREAM指定了。)

     @param serverSocketFD serverSocket的文件表述
     @param MAX_Q_LEN 监听队列上的最大监听队列长度
     @return 是否成功开启监听
     */
    ret = listen(serverSocketFD, MAX_Q_LEN);
    if (ret < 0) {
        perror("无法开启监听!!!\n");
        close(serverSocketFD);
        exit(1);
    }
    
    //5.设定一个死循环,让服务端一直处于开启状态。
    while(true) {
        struct sockaddr_in clientAddr; //创建一个socket客户端地址
        socklen_t clientAddrLen = sizeof clientAddr; // 设定socket客户端地址长度
        
        /**
         该函数为每一个TCP链接创建一个新的套接字,之后从监听队列上移除该链接请求。
         param serverSocketFD 服务端的socket文件句柄
         param &clientAddr 客户端socket的地址
         return 客户端的socket文件对象句柄,注意该客户端socket对象是通过sockaddr_in和accept俩进行创建的。
         */
        int clientSocketFD = accept(serverSocketFD, (struct sockaddr *)&clientAddr, &clientAddrLen);
        
        if (clientSocketFD < 0) {
            perror("接受客户端连接时发生错误!!!\n");
        } else {
            //在子线程中异步执行客户端的socket。
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                handle_client_connection(clientSocketFD);
            });
        }
    }
    return 0;
}

相关文章

网友评论

      本文标题:iOS~网络基础~Socket

      本文链接:https://www.haomeiwen.com/subject/rjeydqtx.html