美文网首页
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