美文网首页
NetworkExtension4-Server开发

NetworkExtension4-Server开发

作者: 梦即是幻 | 来源:发表于2020-08-28 16:40 被阅读0次

    环境

    • Xcode 11.6
    • iOS 13
    • MacOS 10.15

    导航

    1-总览

    2-Client开发

    3-Tunnel开发

    4-Server开发

    5-App和Extension通信

    完整代码在此,熟悉的小伙伴可以直接试试。

    UI界面

    这篇,我们将构建一个运行在mac上的Server作为代理服务器,接收Client发送过来的UDP数据,再转发。

    也会有一个简单的界面,如下:

    image

    添加一个新的Target:Server,macOS App使用SwiftUI,

    然后还必须勾选Network的2个权限:

    image

    ContentView代码:

    import SwiftUI
    
    struct ContentView: View {
        @State var items: [YYListView.Model] = []
    
        var body: some View {
            VStack {
                HStack {
                    Text("接收数据中...")
                    Button(action: clean) {
                        Text("clean")
                    }
                }.fixedSize()
                YYListView(items: $items)
            }.onAppear(perform: starServer)
        }
    
        private func clean() {
            items.removeAll()
        }
    
        private func starServer() {
            YYServer.startUDPServer(8899) { str in
                self.items.append(.init(text: str))
            }
        }
    }
    

    YYListView:

    import SwiftUI
    
    extension YYListView {
        struct Model: Identifiable {
            var id = UUID()
            var text: String
        }
    }
    
    struct YYListView: View {
        @Binding var items: [Model]
    
        var body: some View {
            List(items) { item in
                Text(item.text)
            }
        }
    }
    

    可以看到,核心方法就是starServer了。

    大概意思就是开启一个端口8899的服务器,并设置回调,将收到的字符串添加到items数组,刷新UI。

    private func starServer() {
      YYServer.startUDPServer(8899) { str in
            self.items.append(.init(text: str))
        }
    }
    

    Socket编程

    先来把服务器写好,这里打算使用C的Socket来创建服务器,简单强大,而且mac上也可以用,直接上代码吧:

    udp_server.h

    #ifndef udp_server_h
    #define udp_server_h
    
    #include <stdio.h>
    
    typedef void (*data_handler_t)(char *, long);
    
    #define ANET_OK      0
    #define ANET_ERR    -1
    
    void udp_server_start(int port);
    void set_data_handler(data_handler_t handler);
    
    #endif /* udp_server_h */
    

    对外提供开启服务器和设置数据监听的api。

    udp_server.c

    #include "udp_server.h"
    #include <stdio.h>
    #include <ctype.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <signal.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <string.h>
    #include <netdb.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <time.h>
    #include <arpa/inet.h>
    #include <pthread/pthread.h>
    #include <net/if.h>
    
    #define BUFF_LEN 1500
    
    data_handler_t datahandler;
    
    void set_data_handler(data_handler_t handler) {
        datahandler = handler;
    }
    
    void handle_udp_datagram(int fd) {
        char buf[BUFF_LEN];
        long count;
        struct sockaddr_in client;
        socklen_t len;
        while (1) {
            bzero(buf, sizeof(buf));
            len = sizeof(client);
            count = recvfrom(fd, buf, BUFF_LEN, 0, (struct sockaddr *)&client, &len);
            if (count == ANET_ERR) {
                printf("recieve data failed:[%s]\n", strerror(errno));
                return;
            }
            
            if (datahandler != NULL) {
                datahandler(buf, count);
            }
            
            sendto(fd, buf, count, 0, (struct sockaddr*)&client, len);
        }
    }
    
    static int upd_server(int port) {
        ///AF_INET:IPV4;SOCK_DGRAM:UDP
        int fd = socket(AF_INET, SOCK_DGRAM, 0);
        if (fd < 0) {
            printf("creating socket failed:[%s]\n", strerror(errno));
            return ANET_ERR;
        }
        
        struct sockaddr_in sa;
        bzero(&sa, sizeof(sa));
        sa.sin_family = AF_INET;
        ///IP地址,需要进行网络序转换,INADDR_ANY:本地地址
        sa.sin_addr.s_addr = htonl(INADDR_ANY);
        sa.sin_port = htons(port);
        
        int ret = bind(fd, (struct sockaddr *)&sa, sizeof(sa));
        if (ret < 0) {
            printf("bind failed:[%s]\n", strerror(errno));
            close(fd);
            return ANET_ERR;
        }
        
        return fd;
    }
    
    void udp_server_start(int port) {
        int fd = upd_server(port);
        if (fd > 0) {
            handle_udp_datagram(fd);
            close(fd);
        }
    }
    

    简单起见,这里直接使用recvfrom循环阻塞接受数据,而且先把收到的数据直接发送信息给client,走通流程。

    Objc桥接

    服务器写好了,现在我们要在Swift里面使用,虽然有些麻烦,不过Swift是可以调用C的;

    但是这种时候,我还是喜欢先用Objc封装一下,毕竟这是Objc的优势之一,而且封装好后,直接放入Objc项目也很方便。

    YYServer.h

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    typedef void (^DataHandler)(NSString *data);
    
    @interface YYServer : NSObject
    
    + (void)startUDPServer:(NSInteger)port dataHandler:(DataHandler)handler;
    
    @end
    
    NS_ASSUME_NONNULL_END
    

    YYServer.m

    #import "YYServer.h"
    #include "udp_server.h"
    
    int port = 8899;
    
    DataHandler dataHandler;
    
    @implementation YYServer
    
    void data_handler(char *udpbuf, long len) {
        if (dataHandler) {
            NSData *udpData = [NSData dataWithBytes:udpbuf length:len];
            NSString *data = udpData.description;
            NSLog(@"data_handler ------- %@", data);
            dispatch_async(dispatch_get_main_queue(), ^{
                dataHandler(data);
            });
        }
    }
    
    + (void)startUDPServer:(NSInteger)port dataHandler:(DataHandler)handler {
        dataHandler = handler;
        set_data_handler(data_handler);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            udp_server_start((int)port);
        });
    }
    
    @end
    

    这里需要注意,因为我们的server会循环阻塞的接收数据,所以不能在主线程启动,当然,这个是应该优化的- -

    测试

    现在应该能跑通了,来测试一下。

    先运行Server,端口8899,然后看一下电脑连上wifi的ip地址,比如我这里172.20.49.36,

    然后在Client的ConfigView中,将下面的hostname和port设置成自己的,

    @ObservedObject var viewModel =
            ConfigViewModel(config: .init(hostname: "172.20.49.36", port: "8899"))
    

    还有YYVPNManager中的这2个ID也必须设置成自己的,

    extension YYVPNManager {
        public static let groupID = "group.com.yy.Client"
        public static let bundleIDTunnel = "com.yy.Client.Tunnel"
    }
    

    运行Client,将手机和电脑连上同一个wifi,点击Start,顺利的话,应该就能看到第一张图的内容了😄~~

    不顺利的话,一般都是Tunnel进程或Server权限问题,用Console调试一下吧😂。。

    ok之后你会发现,手机里的所有应用都无法获取数据了,一直在loading,直到超时😂😂😂,

    这是因为我们现在的服务器是直接把客户端请求的包原路返回了,漏掉了去请求真正服务器这步,

    这个后面再优化,现在调通后,还剩下怎么把Tunnel进程中的数据发送给主App了,我们下篇继续~

    参考链接

    相关文章

      网友评论

          本文标题:NetworkExtension4-Server开发

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