TCP/UDP/LOCAL Socket C++的实现

来源:互联网 发布:java json传值到前台 编辑:程序博客网 时间:2024/06/07 11:11

前言

        本篇文章主要是记录了Native层Socket的实现,包括TCP、UDP和LOCAL。一般来说,我们使用Socket通信都是通过调用Java层的Api来实现,而Java层Api最终也会走到Native层来具体实现,而且其实不管是http、tcp、udp还是bluetooth抑或是WifiDirect都是在Socket的基础上封装而来,只不过协议不同而已。虽然在开发中通过调用Java层Api可以很方便的使用Socket,但是一些特殊情况下还是得用底层Socket的实现,比如需要写一些安全性较高的网络连接的时候,就需要自己在Native层实现网络的请求。

实现

// JNI#include <jni.h>// NULL#include <stdio.h>// va_list, vsnprintf#include <stdarg.h>// errno#include <errno.h>// strerror_r, memset#include <string.h>// socket, bind, getsockname, listen, accept, recv, send, connect#include <sys/types.h>#include <sys/socket.h>// sockaddr_un#include <sys/un.h>// htons, sockaddr_in#include <netinet/in.h>// inet_ntop#include <arpa/inet.h>// close, unlink#include <unistd.h>// offsetof#include <stddef.h>// Max log message length#define MAX_LOG_MESSAGE_LENGTH 256// Max data buffer size#define MAX_BUFFER_SIZE 80/** * Logs the given message to the application. * * @param env JNIEnv interface. * @param obj object instance. * @param format message format and arguments. */static void LogMessage(        JNIEnv *env,        jobject obj,        const char *format,        ...) {    // Cached log method ID    static jmethodID methodID = NULL;    // If method ID is not cached    if (NULL == methodID) {        // Get class from object        jclass clazz = env->GetObjectClass(obj);        // Get the method ID for the given method        methodID = env->GetMethodID(clazz, "logMessage",                                    "(Ljava/lang/String;)V");        // Release the class reference        env->DeleteLocalRef(clazz);    }    // If method is found    if (NULL != methodID) {        // Format the log message        char buffer[MAX_LOG_MESSAGE_LENGTH];        va_list ap;        va_start(ap, format);        vsnprintf(buffer, MAX_LOG_MESSAGE_LENGTH, format, ap);        va_end(ap);        // Convert the buffer to a Java string        jstring message = env->NewStringUTF(buffer);        // If string is properly constructed        if (NULL != message) {            // Log message            env->CallVoidMethod(obj, methodID, message);            // Release the message reference            env->DeleteLocalRef(message);        }    }}/** * Throws a new exception using the given exception class * and exception message. * * @param env JNIEnv interface. * @param className class name. * @param message exception message. */static void ThrowException(        JNIEnv *env,        const char *className,        const char *message) {    // Get the exception class    jclass clazz = env->FindClass(className);    // If exception class is found    if (NULL != clazz) {        // Throw exception        env->ThrowNew(clazz, message);        // Release local class reference        env->DeleteLocalRef(clazz);    }}/** * Throws a new exception using the given exception class * and error message based on the error number. * * @param env JNIEnv interface. * @param className class name. * @param errnum error number. */static void ThrowErrnoException(        JNIEnv *env,        const char *className,        int errnum) {    char buffer[MAX_LOG_MESSAGE_LENGTH];    // Get message for the error number    if (-1 == strerror_r(errnum, buffer, MAX_LOG_MESSAGE_LENGTH)) {        strerror_r(errno, buffer, MAX_LOG_MESSAGE_LENGTH);    }    // Throw exception    ThrowException(env, className, buffer);}/** * Constructs a new TCP socket. * * @param env JNIEnv interface. * @param obj object instance. * @return socket descriptor. * @throws IOException */static int NewTcpSocket(JNIEnv *env, jobject obj) {    // Construct socket    LogMessage(env, obj, "Constructing a new TCP socket...");    int tcpSocket = socket(PF_INET, SOCK_STREAM, 0);    // Check if socket is properly constructed    if (-1 == tcpSocket) {        // Throw an exception with error number        ThrowErrnoException(env, "java/io/IOException", errno);    }    return tcpSocket;}/** * Binds socket to a port number. * * @param env JNIEnv interface. * @param obj object instance. * @param sd socket descriptor. * @param port port number or zero for random port. * @throws IOException */static void BindSocketToPort(        JNIEnv *env,        jobject obj,        int sd,        unsigned short port) {    struct sockaddr_in address;    // Address to bind socket    memset(&address, 0, sizeof(address));    address.sin_family = PF_INET;    // Bind to all addresses    address.sin_addr.s_addr = htonl(INADDR_ANY);    // Convert port to network byte order    address.sin_port = htons(port);    // Bind socket    LogMessage(env, obj, "Binding to port %hu.", port);    if (-1 == bind(sd, (struct sockaddr *) &address, sizeof(address))) {        // Throw an exception with error number        ThrowErrnoException(env, "java/io/IOException", errno);    }}/** * Gets the port number socket is currently binded. * * @param env JNIEnv interface. * @param obj object instance. * @param sd socket descriptor. * @return port number. * @throws IOException */static unsigned short GetSocketPort(        JNIEnv *env,        jobject obj,        int sd) {    unsigned short port = 0;    struct sockaddr_in address;    socklen_t addressLength = sizeof(address);    // Get the socket address    if (-1 == getsockname(sd, (struct sockaddr *) &address, &addressLength)) {        // Throw an exception with error number        ThrowErrnoException(env, "java/io/IOException", errno);    } else {        // Convert port to host byte order        port = ntohs(address.sin_port);        LogMessage(env, obj, "Binded to random port %hu.", port);    }    return port;}/** * Listens on given socket with the given backlog for * pending connections. When the backlog is full, the * new connections will be rejected. * * @param env JNIEnv interface. * @param obj object instance. * @param sd socket descriptor. * @param backlog backlog size. * @throws IOException */static void ListenOnSocket(        JNIEnv *env,        jobject obj,        int sd,        int backlog) {    // Listen on socket with the given backlog    LogMessage(env, obj,               "Listening on socket with a backlog of %d pending connections.",               backlog);    if (-1 == listen(sd, backlog)) {        // Throw an exception with error number        ThrowErrnoException(env, "java/io/IOException", errno);    }}/** * Logs the IP address and the port number from the * given address. * * @param env JNIEnv interface. * @param obj object instance. * @param message message text. * @param address adress instance. * @throws IOException */static void LogAddress(        JNIEnv *env,        jobject obj,        const char *message,        const struct sockaddr_in *address) {    char ip[INET_ADDRSTRLEN];    // Convert the IP address to string    if (NULL == inet_ntop(PF_INET,                          &(address->sin_addr),                          ip,                          INET_ADDRSTRLEN)) {        // Throw an exception with error number        ThrowErrnoException(env, "java/io/IOException", errno);    } else {        // Convert port to host byte order        unsigned short port = ntohs(address->sin_port);        // Log address        LogMessage(env, obj, "%s %s:%hu.", message, ip, port);    }}/** * Blocks and waits for incoming client connections on the * given socket. * * @param env JNIEnv interface. * @param obj object instance. * @param sd socket descriptor. * @return client socket. * @throws IOException */static int AcceptOnSocket(        JNIEnv *env,        jobject obj,        int sd) {    struct sockaddr_in address;    socklen_t addressLength = sizeof(address);    // Blocks and waits for an incoming client connection    // and accepts it    LogMessage(env, obj, "Waiting for a client connection...");    int clientSocket = accept(sd,                              (struct sockaddr *) &address,                              &addressLength);    // If client socket is not valid    if (-1 == clientSocket) {        // Throw an exception with error number        ThrowErrnoException(env, "java/io/IOException", errno);    } else {        // Log address        LogAddress(env, obj, "Client connection from ", &address);    }    return clientSocket;}/** * Block and receive data from the socket into the buffer. * * @param env JNIEnv interface. * @param obj object instance. * @param sd socket descriptor. * @param buffer data buffer. * @param bufferSize buffer size. * @return receive size. * @throws IOException */static ssize_t ReceiveFromSocket(        JNIEnv *env,        jobject obj,        int sd,        char *buffer,        size_t bufferSize) {    // Block and receive data from the socket into the buffer    LogMessage(env, obj, "Receiving from the socket...");    ssize_t recvSize = recv(sd, buffer, bufferSize - 1, 0);    // If receive is failed    if (-1 == recvSize) {        // Throw an exception with error number        ThrowErrnoException(env, "java/io/IOException", errno);    } else {        // NULL terminate the buffer to make it a string        buffer[recvSize] = NULL;        // If data is received        if (recvSize > 0) {            LogMessage(env, obj, "Received %d bytes: %s", recvSize, buffer);        } else {            LogMessage(env, obj, "Client disconnected.");        }    }    return recvSize;}/** * Send data buffer to the socket. * * @param env JNIEnv interface. * @param obj object instance. * @param sd socket descriptor. * @param buffer data buffer. * @param bufferSize buffer size. * @return sent size. * @throws IOException */static ssize_t SendToSocket(        JNIEnv *env,        jobject obj,        int sd,        const char *buffer,        size_t bufferSize) {    // Send data buffer to the socket    LogMessage(env, obj, "Sending to the socket...");    ssize_t sentSize = send(sd, buffer, bufferSize, 0);    // If send is failed    if (-1 == sentSize) {        // Throw an exception with error number        ThrowErrnoException(env, "java/io/IOException", errno);    } else {        if (sentSize > 0) {            LogMessage(env, obj, "Sent %d bytes: %s", sentSize, buffer);        } else {            LogMessage(env, obj, "Client disconnected.");        }    }    return sentSize;}/** * Connects to given IP address and given port. * * @param env JNIEnv interface. * @param obj object instance. * @param sd socket descriptor. * @param ip IP address. * @param port port number. * @throws IOException */static void ConnectToAddress(        JNIEnv *env,        jobject obj,        int sd,        const char *ip,        unsigned short port) {    // Connecting to given IP address and given port    LogMessage(env, obj, "Connecting to %s:%uh...", ip, port);    struct sockaddr_in address;    memset(&address, 0, sizeof(address));    address.sin_family = PF_INET;    // Convert IP address string to Internet address    if (0 == inet_aton(ip, &(address.sin_addr))) {        // Throw an exception with error number        ThrowErrnoException(env, "java/io/IOException", errno);    } else {        // Convert port to network byte order        address.sin_port = htons(port);        // Connect to address        if (-1 == connect(sd, (const sockaddr *) &address, sizeof(address))) {            // Throw an exception with error number            ThrowErrnoException(env, "java/io/IOException", errno);        } else {            LogMessage(env, obj, "Connected.");        }    }}void Java_com_apress_echo_EchoClientActivity_nativeStartTcpClient(        JNIEnv *env,        jobject obj,        jstring ip,        jint port,        jstring message) {    // Construct a new TCP socket.    int clientSocket = NewTcpSocket(env, obj);    if (NULL == env->ExceptionOccurred()) {        // Get IP address as C string        const char *ipAddress = env->GetStringUTFChars(ip, NULL);        if (NULL == ipAddress)            goto exit;        // Connect to IP address and port        ConnectToAddress(env, obj, clientSocket, ipAddress,                         (unsigned short) port);        // Release the IP address        env->ReleaseStringUTFChars(ip, ipAddress);        // If connection was successful        if (NULL != env->ExceptionOccurred())            goto exit;        // Get message as C string        const char *messageText = env->GetStringUTFChars(message, NULL);        if (NULL == messageText)            goto exit;        // Get the message size        jsize messageSize = env->GetStringUTFLength(message);        // Send message to socket        SendToSocket(env, obj, clientSocket, messageText, messageSize);        // Release the message text        env->ReleaseStringUTFChars(message, messageText);        // If send was not successful        if (NULL != env->ExceptionOccurred())            goto exit;        char buffer[MAX_BUFFER_SIZE];        // Receive from the socket        ReceiveFromSocket(env, obj, clientSocket, buffer, MAX_BUFFER_SIZE);    }    exit:    if (clientSocket > 0) {        close(clientSocket);    }}void Java_com_apress_echo_EchoServerActivity_nativeStartTcpServer(        JNIEnv *env,        jobject obj,        jint port) {    // Construct a new TCP socket.    int serverSocket = NewTcpSocket(env, obj);    if (NULL == env->ExceptionOccurred()) {        // Bind socket to a port number        BindSocketToPort(env, obj, serverSocket, (unsigned short) port);        if (NULL != env->ExceptionOccurred())            goto exit;        // If random port number is requested        if (0 == port) {            // Get the port number socket is currently binded            GetSocketPort(env, obj, serverSocket);            if (NULL != env->ExceptionOccurred())                goto exit;        }        // Listen on socket with a backlog of 4 pending connections        ListenOnSocket(env, obj, serverSocket, 4);        if (NULL != env->ExceptionOccurred())            goto exit;        // Accept a client connection on socket        int clientSocket = AcceptOnSocket(env, obj, serverSocket);        if (NULL != env->ExceptionOccurred())            goto exit;        char buffer[MAX_BUFFER_SIZE];        ssize_t recvSize;        ssize_t sentSize;        // Receive and send back the data        while (1) {            // Receive from the socket            recvSize = ReceiveFromSocket(env, obj, clientSocket,                                         buffer, MAX_BUFFER_SIZE);            if ((0 == recvSize) || (NULL != env->ExceptionOccurred()))                break;            // Send to the socket            sentSize = SendToSocket(env, obj, clientSocket,                                    buffer, (size_t) recvSize);            if ((0 == sentSize) || (NULL != env->ExceptionOccurred()))                break;        }        // Close the client socket        close(clientSocket);    }    exit:    if (serverSocket > 0) {        close(serverSocket);    }}/** * Constructs a new UDP socket. * * @param env JNIEnv interface. * @param obj object instance. * @return socket descriptor. * @throws IOException */static int NewUdpSocket(JNIEnv *env, jobject obj) {    // Construct socket    LogMessage(env, obj, "Constructing a new UDP socket...");    int udpSocket = socket(PF_INET, SOCK_DGRAM, 0);    // Check if socket is properly constructed    if (-1 == udpSocket) {        // Throw an exception with error number        ThrowErrnoException(env, "java/io/IOException", errno);    }    return udpSocket;}/** * Block and receive datagram from the socket into * the buffer, and populate the client address. * * @param env JNIEnv interface. * @param obj object instance. * @param sd socket descriptor. * @param address client address. * @param buffer data buffer. * @param bufferSize buffer size. * @return receive size. * @throws IOException */static ssize_t ReceiveDatagramFromSocket(        JNIEnv *env,        jobject obj,        int sd,        struct sockaddr_in *address,        char *buffer,        size_t bufferSize) {    socklen_t addressLength = sizeof(struct sockaddr_in);    // Receive datagram from socket    LogMessage(env, obj, "Receiving from the socket...");    ssize_t recvSize = recvfrom(sd, buffer, bufferSize, 0,                                (struct sockaddr *) address,                                &addressLength);    // If receive is failed    if (-1 == recvSize) {        // Throw an exception with error number        ThrowErrnoException(env, "java/io/IOException", errno);    } else {        // Log address        LogAddress(env, obj, "Received from", address);        // NULL terminate the buffer to make it a string        buffer[recvSize] = NULL;        // If data is received        if (recvSize > 0) {            LogMessage(env, obj, "Received %d bytes: %s", recvSize, buffer);        }    }    return recvSize;}/** * Sends datagram to the given address using the given socket. * * @param env JNIEnv interface. * @param obj object instance. * @param sd socket descriptor. * @param address remote address. * @param buffer data buffer. * @param bufferSize buffer size. * @return sent size. * @throws IOException */static ssize_t SendDatagramToSocket(        JNIEnv *env,        jobject obj,        int sd,        const struct sockaddr_in *address,        const char *buffer,        size_t bufferSize) {    // Send data buffer to the socket    LogAddress(env, obj, "Sending to", address);    ssize_t sentSize = sendto(sd, buffer, bufferSize, 0,                              (const sockaddr *) address,                              sizeof(struct sockaddr_in));    // If send is failed    if (-1 == sentSize) {        // Throw an exception with error number        ThrowErrnoException(env, "java/io/IOException", errno);    } else if (sentSize > 0) {        LogMessage(env, obj, "Sent %d bytes: %s", sentSize, buffer);    }    return sentSize;}void Java_com_apress_echo_EchoClientActivity_nativeStartUdpClient(        JNIEnv *env,        jobject obj,        jstring ip,        jint port,        jstring message) {    // Construct a new UDP socket.    int clientSocket = NewUdpSocket(env, obj);    if (NULL == env->ExceptionOccurred()) {        struct sockaddr_in address;        memset(&address, 0, sizeof(address));        address.sin_family = PF_INET;        // Get IP address as C string        const char *ipAddress = env->GetStringUTFChars(ip, NULL);        if (NULL == ipAddress)            goto exit;        // Convert IP address string to Internet address        int result = inet_aton(ipAddress, &(address.sin_addr));        // Release the IP address        env->ReleaseStringUTFChars(ip, ipAddress);        // If conversion is failed        if (0 == result) {            // Throw an exception with error number            ThrowErrnoException(env, "java/io/IOException", errno);            goto exit;        }        // Convert port to network byte order        address.sin_port = htons(port);        // Get message as C string        const char *messageText = env->GetStringUTFChars(message, NULL);        if (NULL == messageText)            goto exit;        // Get the message size        jsize messageSize = env->GetStringUTFLength(message);        // Send message to socket        SendDatagramToSocket(env, obj, clientSocket, &address,                             messageText, messageSize);        // Release the message text        env->ReleaseStringUTFChars(message, messageText);        // If send was not successful        if (NULL != env->ExceptionOccurred())            goto exit;        char buffer[MAX_BUFFER_SIZE];        // Clear address        memset(&address, 0, sizeof(address));        // Receive from the socket        ReceiveDatagramFromSocket(env, obj, clientSocket, &address,                                  buffer, MAX_BUFFER_SIZE);    }    exit:    if (clientSocket > 0) {        close(clientSocket);    }}void Java_com_apress_echo_EchoServerActivity_nativeStartUdpServer(        JNIEnv *env,        jobject obj,        jint port) {    // Construct a new UDP socket.    int serverSocket = NewUdpSocket(env, obj);    if (NULL == env->ExceptionOccurred()) {        // Bind socket to a port number        BindSocketToPort(env, obj, serverSocket, (unsigned short) port);        if (NULL != env->ExceptionOccurred())            goto exit;        // If random port number is requested        if (0 == port) {            // Get the port number socket is currently binded            GetSocketPort(env, obj, serverSocket);            if (NULL != env->ExceptionOccurred())                goto exit;        }        // Client address        struct sockaddr_in address;        memset(&address, 0, sizeof(address));        char buffer[MAX_BUFFER_SIZE];        ssize_t recvSize;        ssize_t sentSize;        // Receive from the socket        recvSize = ReceiveDatagramFromSocket(env, obj, serverSocket,                                             &address, buffer, MAX_BUFFER_SIZE);        if ((0 == recvSize) || (NULL != env->ExceptionOccurred()))            goto exit;        // Send to the socket        sentSize = SendDatagramToSocket(env, obj, serverSocket,                                        &address, buffer, (size_t) recvSize);    }    exit:    if (serverSocket > 0) {        close(serverSocket);    }}/** * Constructs a new Local UNIX socket. * * @param env JNIEnv interface. * @param obj object instance. * @return socket descriptor. * @throws IOException */static int NewLocalSocket(JNIEnv *env, jobject obj) {    // Construct socket    LogMessage(env, obj, "Constructing a new Local UNIX socket...");    int localSocket = socket(PF_LOCAL, SOCK_STREAM, 0);    // Check if socket is properly constructed    if (-1 == localSocket) {        // Throw an exception with error number        ThrowErrnoException(env, "java/io/IOException", errno);    }    return localSocket;}/** * Binds a local UNIX socket to a name. * * @param env JNIEnv interface. * @param obj object instance. * @param sd socket descriptor. * @param name socket name. * @throws IOException */static void BindLocalSocketToName(        JNIEnv *env,        jobject obj,        int sd,        const char *name) {    struct sockaddr_un address;    // Name length    const size_t nameLength = strlen(name);    // Path length is initiall equal to name length    size_t pathLength = nameLength;    // If name is not starting with a slash it is    // in the abstract namespace    bool abstractNamespace = ('/' != name[0]);    // Abstract namespace requires having the first    // byte of the path to be the zero byte, update    // the path length to include the zero byte    if (abstractNamespace) {        pathLength++;    }    // Check the path length    if (pathLength > sizeof(address.sun_path)) {        // Throw an exception with error number        ThrowException(env, "java/io/IOException", "Name is too big.");    } else {        // Clear the address bytes        memset(&address, 0, sizeof(address));        address.sun_family = PF_LOCAL;        // Socket path        char *sunPath = address.sun_path;        // First byte must be zero to use the abstract namespace        if (abstractNamespace) {            *sunPath++ = NULL;        }        // Append the local name        strcpy(sunPath, name);        // Address length        socklen_t addressLength =                (offsetof(struct sockaddr_un, sun_path))                + pathLength;        // Unlink if the socket name is already binded        unlink(address.sun_path);        // Bind socket        LogMessage(env, obj, "Binding to local name %s%s.",                   (abstractNamespace) ? "(null)" : "",                   name);        if (-1 == bind(sd, (struct sockaddr *) &address, addressLength)) {            // Throw an exception with error number            ThrowErrnoException(env, "java/io/IOException", errno);        }    }}/** * Blocks and waits for incoming client connections on the * given socket. * * @param env JNIEnv interface. * @param obj object instance. * @param sd socket descriptor. * @return client socket. * @throws IOException */static int AcceptOnLocalSocket(        JNIEnv *env,        jobject obj,        int sd) {    // Blocks and waits for an incoming client connection    // and accepts it    LogMessage(env, obj, "Waiting for a client connection...");    int clientSocket = accept(sd, NULL, NULL);    // If client socket is not valid    if (-1 == clientSocket) {        // Throw an exception with error number        ThrowErrnoException(env, "java/io/IOException", errno);    }    return clientSocket;}void Java_com_apress_echo_LocalEchoActivity_nativeStartLocalServer(        JNIEnv *env,        jobject obj,        jstring name) {    // Construct a new local UNIX socket.    int serverSocket = NewLocalSocket(env, obj);    if (NULL == env->ExceptionOccurred()) {        // Get name as C string        const char *nameText = env->GetStringUTFChars(name, NULL);        if (NULL == nameText)            goto exit;        // Bind socket to a port number        BindLocalSocketToName(env, obj, serverSocket, nameText);        // Release the name text        env->ReleaseStringUTFChars(name, nameText);        // If bind is failed        if (NULL != env->ExceptionOccurred())            goto exit;        // Listen on socket with a backlog of 4 pending connections        ListenOnSocket(env, obj, serverSocket, 4);        if (NULL != env->ExceptionOccurred())            goto exit;        // Accept a client connection on socket        int clientSocket = AcceptOnLocalSocket(env, obj, serverSocket);        if (NULL != env->ExceptionOccurred())            goto exit;        char buffer[MAX_BUFFER_SIZE];        ssize_t recvSize;        ssize_t sentSize;        // Receive and send back the data        while (1) {            // Receive from the socket            recvSize = ReceiveFromSocket(env, obj, clientSocket,                                         buffer, MAX_BUFFER_SIZE);            if ((0 == recvSize) || (NULL != env->ExceptionOccurred()))                break;            // Send to the socket            sentSize = SendToSocket(env, obj, clientSocket,                                    buffer, (size_t) recvSize);            if ((0 == sentSize) || (NULL != env->ExceptionOccurred()))                break;        }        // Close the client socket        close(clientSocket);    }    exit:    if (serverSocket > 0) {        close(serverSocket);    }}

完整代码传送门:https://github.com/qq542391099/NativeSocket


原创粉丝点击