Socket AF_UNIX 實作 -【C 語言學習筆記】

最近碰到一個狀況, 只有 Python 有提供工具包函式庫,但我的主程式是 C 語言,因此用 Socket 方式,在 Python 與 C 程式間作溝通,傳遞資料。

Socket 實作

Socket 不是新鮮的東西,所以本篇只是記錄跟備份網路找到的優質開源程式碼,並保存自己做的修改調整,參考來源是:

https://gist.github.com/marcom04/22860f1168330605cac3c448982b0393

因為我是本地端通訊,不是不同機器,所以用 AF_UNIX 就可以,傳輸速率也優於 AF_INET,更適合我正在開發的 GNSS + IMU 的演算法。我是用 Python Server 對接 C Client ,以 Python Server 程式碼為例,只需要修改下面註解 # Modified 的部分:

#!/usr/bin/env python3

""" server.py - Echo server for sending/receiving C-like structs via socket
References:
- Ctypes: https://docs.python.org/3/library/ctypes.html
- Sockets: https://docs.python.org/3/library/socket.html
"""

import socket
import sys
import random
from ctypes import *


""" This class defines a C-like struct """
class Payload(Structure):
    _fields_ = [("id", c_uint32),
                ("counter", c_uint32),
                ("temp", c_float)]


def main():
    # Modified
    server_addr = ('./FILE_NAME') 
    ssock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 
    print("Socket created")

    try:
        # bind the server socket and listen
        ssock.bind(server_addr)
        print("Bind done")
        ssock.listen(3)
        print("Server listening on port {:d}".format(PORT))

        while True:
            csock, client_address = ssock.accept()
            print("Accepted connection from {:s}".format(client_address[0]))

            buff = csock.recv(512)
            while buff:
                print("\nReceived {:d} bytes".format(len(buff)))
                payload_in = Payload.from_buffer_copy(buff)
                print("Received contents id={:d}, counter={:d}, temp={:f}".format(payload_in.id,
                                                            payload_in.counter,
                                                            payload_in.temp))
                print("Sending it back.. ", end='')
                nsent = csock.send(payload_in)
                print("Sent {:d} bytes".format(nsent))
                buff = csock.recv(512)

            print("Closing connection to client")
            print("----------------------------")
            csock.close()

    except AttributeError as ae:
        print("Error creating the socket: {}".format(ae))
    except socket.error as se:
        print("Exception on socket: {}".format(se))
    except KeyboardInterrupt:
        ssock.close()
    finally:
        print("Closing socket")
        ssock.close()


if __name__ == "__main__":
    main()

搭配的 C Client 端程式碼如下,修改下面註解 # Modified 的部分:

/* client.c */

#include <sys/socket.h>
#include <arpa/inet.h> //inet_addr
#include <unistd.h>    //write
#include <time.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#pragma pack(1)

typedef struct payload_t {
    uint32_t id;
    uint32_t counter;
    float temp;
} payload;

#pragma pack()


void sendMsg(int sock, void* msg, uint32_t msgsize)
{
    if (write(sock, msg, msgsize) < 0)
    {
        printf("Can't send message.\n");
        close(sock);
        exit(1);
    }
    printf("Message sent (%d bytes).\n", msgsize);
    return;
}

int main()
{
    # Modified
    int BUFFSIZE = sizeof(payload);
    char buff[BUFFSIZE];
    int sock;
    int nread;
    float mintemp = -10.0;
    float maxtemp = 30.0;
    time_t t;

    srand((unsigned) time(&t));

  # Modified
  struct sockaddr_un server_address; 
    server_address.sun_family = AF_UNIX;
    strcpy(server_address.sun_path, "./FILE_NAME");

    if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
        printf("ERROR: Socket creation failed\n");
        return 1;
    }

    if (connect(sock, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) {
        printf("ERROR: Unable to connect to server\n");
        return 1;
    }

    payload data;
    for(int i = 0; i < 5; i++) {
        data.id = 1;
        data.counter = i;
        data.temp = mintemp + rand() / (RAND_MAX / (maxtemp - mintemp + 1.0) + 1.0);

        printf("\nSending id=%d, counter=%d, temp=%f\n", data.id, data.counter, data.temp);
        sendMsg(sock, &data, sizeof(payload));

        bzero(buff, BUFFSIZE);
        nread = read(sock, buff, BUFFSIZE);
        printf("Received %d bytes\n", nread);
        payload *p = (payload*) buff;
        printf("Received id=%d, counter=%d, temp=%f\n",
                p->id, p->counter, p->temp);
    }
    
    // close the socket
    close(sock);
    return 0;

}

至此就完成一個 Python-C Socket 互傳 Payload 資料的架構,再按自己需要替換 Payload的內容,或是刪掉回傳部分,改成單向傳輸。

另外備份參考來源的 Client 端 Python 程式碼以及 Server 端 C 程式碼。

Client 端 Python 語言程式碼:

#!/usr/bin/env python3

""" client.py - Echo client for sending/receiving C-like structs via socket
References:
- Ctypes: https://docs.python.org/3/library/ctypes.html
- Sockets: https://docs.python.org/3/library/socket.html
"""

import socket
import sys
import random
from ctypes import *


""" This class defines a C-like struct """
class Payload(Structure):
    _fields_ = [("id", c_uint32),
                ("counter", c_uint32),
                ("temp", c_float)]


def main():
    server_addr = ('localhost', 2300)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    try:
        s.connect(server_addr)
        print("Connected to {:s}".format(repr(server_addr)))

        for i in range(5):
            print("")
            payload_out = Payload(1, i, random.uniform(-10, 30))
            print("Sending id={:d}, counter={:d}, temp={:f}".format(payload_out.id,
                                                              payload_out.counter,
                                                              payload_out.temp))
            nsent = s.send(payload_out)
            # Alternative: s.sendall(...): coontinues to send data until either
            # all data has been sent or an error occurs. No return value.
            print("Sent {:d} bytes".format(nsent))

            buff = s.recv(sizeof(Payload))
            payload_in = Payload.from_buffer_copy(buff)
            print("Received id={:d}, counter={:d}, temp={:f}".format(payload_in.id,
                                                               payload_in.counter,
                                                               payload_in.temp))
    except AttributeError as ae:
        print("Error creating the socket: {}".format(ae))
    except socket.error as se:
        print("Exception on socket: {}".format(se))
    finally:
        print("Closing socket")
        s.close()


if __name__ == "__main__":
    main()

Server 端 C 語言程式碼:

/* server.c */

#include <sys/socket.h>
#include <arpa/inet.h> //inet_addr
#include <unistd.h>    //write

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#pragma pack(1)

typedef struct payload_t {
    uint32_t id;
    uint32_t counter;
    float temp;
} payload;

#pragma pack()


int createSocket(int port)
{
    int sock, err;
    struct sockaddr_in server;

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("ERROR: Socket creation failed\n");
        exit(1);
    }
    printf("Socket created\n");

    bzero((char *) &server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = htons(port);
    if (bind(sock, (struct sockaddr *)&server , sizeof(server)) < 0)
    {
        printf("ERROR: Bind failed\n");
        exit(1);
    }
    printf("Bind done\n");

    listen(sock , 3);

    return sock;
}

void closeSocket(int sock)
{
    close(sock);
    return;
}

void sendMsg(int sock, void* msg, uint32_t msgsize)
{
    if (write(sock, msg, msgsize) < 0)
    {
        printf("Can't send message.\n");
        closeSocket(sock);
        exit(1);
    }
    printf("Message sent (%d bytes).\n", msgsize);
    return;
}

int main()
{
    int PORT = 2300;
    int BUFFSIZE = 512;
    char buff[BUFFSIZE];
    int ssock, csock;
    int nread;
    struct sockaddr_in client;
    int clilen = sizeof(client);

    ssock = createSocket(PORT);
    printf("Server listening on port %d\n", PORT);

    while (1)
    {
        csock = accept(ssock, (struct sockaddr *)&client, &clilen);
        if (csock < 0)
        {
            printf("Error: accept() failed\n");
            continue;
        }

        printf("Accepted connection from %s\n", inet_ntoa(client.sin_addr));
        bzero(buff, BUFFSIZE);
        while ((nread=read(csock, buff, BUFFSIZE)) > 0)
        {
            printf("\nReceived %d bytes\n", nread);
            payload *p = (payload*) buff;
            printf("Received contents: id=%d, counter=%d, temp=%f\n",
                    p->id, p->counter, p->temp);

            printf("Sending it back.. ");
            sendMsg(csock, p, sizeof(payload));
        }
        printf("Closing connection to client\n");
        printf("----------------------------\n");
        closeSocket(csock);
    }

    closeSocket(ssock);
    printf("bye");
    return 0;
}

另外 C 語言程式碼可以參考另外一個來源,寫的比較簡單:

https://blog.csdn.net/weixin_39258979/article/details/80931464

Server 端:

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/un.h> //define the sockaddr_un structure
int num_reverse(int num);

int main()
{
    /* 断开之前的socket文件 */
    unlink("server_socket");
    
    /* 创建一个socket */
    int server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    
    struct sockaddr_un server_addr;
    server_addr.sun_family = AF_UNIX;
    strcpy(server_addr.sun_path, "server_socket");
    
    /* 与本地文件进行绑定 */
    bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    
    /* 监听 */
    if(listen(server_sockfd, 5)<0);
    {
	    perror("Listen failed");
    }
    
    int client_sockfd;
    struct sockaddr_un client_addr;
    socklen_t len = sizeof(client_addr);
    
    while(1)
    {
        printf("server waiting:\n");
        /* 接受一个客户端的链接 */
        client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len);
        
        /*数据交换 */
        read(client_sockfd, &num, 4);
        printf("get an integer from client: %d\n", num);
        num=num_reverse(num);
        write(client_sockfd, &num, 4);
        
        /* 关闭socket */
        close(client_sockfd);
    }
    
    return 0;
}

	int num_reverse(int num)
	{
	    int S=0,sum=0;
	    
	    while(num)
	    {
	        S=num%10;
	        sum=10*sum+S;
	        num = num / 10;
	    }
	    
	    return sum;
	}

Client 端:

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/un.h>

int main()
{
    /* 创建一个socket */
    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    
    struct sockaddr_un address;
    address.sun_family = AF_UNIX;
    strcpy(address.sun_path, "server_socket");
    
    /*从键盘读取需要转置的整数*/
    int num;
    printf("Please enter the num to reverse:\n");

    /* 链接至服务端 */
    int result = connect(sockfd, (struct sockaddr *)&address, sizeof(address));
    if(result == -1)
    {
        perror("connect failed: ");
        exit(1);
    }
    
    /* 数据处理 */
    write(sockfd, &num, 4);//一个int 4个字节
    read(sockfd, &num, 4);
    printf("get an integer from server: %d\n", num);
    
    /* 关闭socket */
    close(sockfd);
    
    return 0;
}  

應用

實際應用可以參考下面這兩篇文章,例如你需要使用 Python NMEA 函式庫時,可以用 Python-C Socket把處理完的 GNSS 資料送往 IMU 的 C 主程式 (因為 IMU 頻率高,故以它為主體),當然在下面的文章內也有 C 語言直接處理的教學,所以直接用 IMU 作主程序,另起一個 Thread 處理 GNSS 數據也可以,全部在 C 語言環境完成。

我的案例是還要另外接收車子 CANBUS 的車速與檔位數據,但解碼 CANBUS protocol 與處理 DBC 檔案的函式庫 Python 比較好用,所以需要一個 Python-C 的資料共享架構,關於 CANBUS 的處理待以後再記錄。

https://mapostech.com/c-gnss-nmea/
https://mapostech.com/c-gnss-nmea/
https://mapostech.com/imu/
Socket
Photo by Nur Andi Ravsanjani Gusma on Pexels.com
上一篇:
下一篇:

在〈Socket AF_UNIX 實作 -【C 語言學習筆記】〉中有 1 則留言

留言功能已關閉。