TCP通信server端搭建(C++)

因为工作需要,需要将我们计算的某种Pose数据发送给远端的client,远端的client规定了Pose的格式。

搭建TCP server的函数大致如下:

    ///jin
    void createTCPServerClient(){
        // 创建address
        // 结构体socketaddr_in用于描述Internet socket address,而且是ipv4的,区别于socketaddr_in6
        struct sockaddr_in server_add;
        memset(&server_add, '0', sizeof(sockaddr_in));
        char serv_add[] = "192.168.71.40";
        server_add.sin_family = AF_INET;//代表使用ipv4进行通信,一般不用作修改
        /* Convert Internet host address from numbers-and-dots notation in CP
   into binary data in network byte order.  */
        // 把字符串型的ip地址转换成in_addr_t(实际上还是uint32_t),只不过网络字节序
        server_add.sin_addr.s_addr = inet_addr(serv_add);
        // htons是将整型变量从主机字节顺序转变成网络字节顺序, 就是整数在地址空间存储方式变为高位字节存放在内存的低地址处。
        // s代表short
        server_add.sin_port = htons(9012);
        // 创建socket,代表了一个硬件的端口吧,参数0代表协议一般不做修改
        // 返回该socket的文件描述符
        serverfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if(serverfd_ < 0){
            LOG(INFO) << "Failed to create socket!";
            return;
        }else{
            LOG(INFO) << "Socket is created!";
            // 获取address的地址,强制转换成sockaddr*类型,并与socket绑定
            if(bind(serverfd_, (struct sockaddr*)&server_add, sizeof(server_add)) < 0){
                LOG(INFO) << "Failed to bind!";
                return;
            } else{
                LOG(INFO) << "Bind successful!";
                // 使得socket进入监听模式,开始接收client的连接请求,所有连接请求会进入一个队列
                // 这也标志着这个socket是一个server,而不是client
                if(listen(serverfd_, 10)){
                    LOG(INFO) << "Failed to listen!";
                    return;
                }else{
                    LOG(INFO) << "Start listening...";
                }
            }
        }
        struct sockaddr_in client_add;
        memset(&client_add, 0, sizeof(sockaddr_in));
        uint32_t size = sizeof(sockaddr_in);
        while(true){
            // isConnected_用于在server这一端标记连接状态
            LOG(INFO) << "Connect status in while: " << isConnected_;
            if(!isConnected_){
                LOG(INFO) << "Waiting for accept: ";
                // 使得socket接受队列中的连接请求,后面两个参数用于保存client的地址,
                connectionfd_ = accept(serverfd_, (struct sockaddr*)&client_add, (socklen_t*)&size);//第2/3个参数用来返回对方的套接字和地址
                // 返回client的文件描述符
                if(connectionfd_ < 0){
                    LOG(INFO) << "Failed to accept!";
                }else{
                    LOG(INFO) << "Connected with connectionfd_ : " << connectionfd_;
                    // 这里是非常有必要的,因为有两处同时对变量进行修改
                    isConnectedMutex_.lock();
                    isConnected_ = true;
                    isConnectedMutex_.unlock();
                }
            }
            sleep(1);
        }

    }

其中的while循环是为了保证断线重连,建议放在独立的一个线程中进行:

tcp_server_thread_ = std::thread(std::bind(&PoseManagerModule::createTCPServerClient, this));//创建新线程,用于等待client连接

我这里是为了发送odometry数据,而且是自定义了格式: 

    odom_count_++;// 无论连接与否都计数
    // 判断是否已经连接
    if(isConnected_){
        SensorOdomMsg bosh_odom_msg;
        bosh_odom_msg.timestamp = odo_timestamp * double(1e-6);// us->s
        bosh_odom_msg.odomNumber = odom_count_;
        bosh_odom_msg.x = p.x();
        bosh_odom_msg.y = p.y();
        bosh_odom_msg.yaw = p.yaw();
        bosh_odom_msg.velocitySet = false;
        // 往client进行写入操作,connectionfd_是server端对client的文件描述符
        // 后面的两个参数是写入的字符的起始地址和总字节数,会严格按照struct定义的成员变量的顺序进行以字符串的形式发送
        // 如果写入字节数<=0,说明写入不成功,已经断开了,需要重新连接
        if(write(connectionfd_, &bosh_odom_msg, sizeof(bosh_odom_msg)) <= 0){
            LOG(INFO) << "Write error!";
            // 关闭这个文件描述符(socket)
            close(connectionfd_);
            isConnectedMutex_.lock();
            // 置为false,上面的断线重连起作用
            isConnected_ = false;
            isConnectedMutex_.unlock();
            LOG(INFO) << "isConnected status: " << isConnected_;
        }
        if(odom_count_%1000 == 0){
            LOG(INFO) << "Sequence " << odom_count_ << " time : " << bosh_odom_msg.timestamp;
        }
    }

 其中,SensorOdomMsg的定义如下:

// __attribute__((packed))的作用是不对齐,因为在传输的过程中。某个成员变量如果用三个字节就可以表示,但是为了对齐会用四个字节,这里强制不对齐,更加紧凑,是与client端的解析对应的
struct __attribute__((packed)) SensorOdomMsg{
        double timestamp{0.};
        uint32_t odomNumber{0};
        uint64_t epoch{0};
        double x{0.};
        double y{0.};
        double yaw{0.};
        double v_x{0.};
        double v_y{0.};
        double omega{0.};
        bool velocitySet{false};
    };

最后,再举例说一下server该怎么向上面的client发起连接请求并接收和解析数据:

#include <sys/socket.h>
//#include <sys/types.h>
#include <netinet/in.h>
//#include <netdb.h>
#include <stdio.h>
#include <string.h>
//#include <stdlib.h>
#include <unistd.h>
//#include <errno.h>
//#include <arpa/inet.h>

//#define SEND_BUF_SIZE 1024

int main(int argc, char *argv[])
{
//    if (argc != 2)
//    {
//        printf("\n Usage: %s <ip of server> \n",argv[0]);
//        return 1;
//    }

    char recvBuff[70] = {0};
    int sockfd = 0, n = 0;
    // 创建socket,返回文件描述符
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("\n Error : Could not create socket \n");
        return 1;
    }

    struct sockaddr_in serv_addr;
    memset(&serv_addr, '0', sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(5000);

    //将 ip地址 转换
//    char tmp[] = "192.168.71.40";
//    if (inet_pton(AF_INET, tmp, &serv_addr.sin_addr) <= 0)//presentation to net in AF_INET
//    {
//        printf("\n inet_pton error occured\n");
//        return 1;
//    }
    // 用inet_addr()、inet_pton,或者下面这种htonl都是可以的,建议使用inet_addr()比较好理解吧
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // 向server申请建立连接,sockfd为本地client的描述符,后面两个参数为client的地址,通过地址建立连接
    // 猜测,返回值估计是server的文件描述符,这里不需要写,
    if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    {
        printf("\n Error : Connect Failed \n");
        return 1;
    }


    //连上之后,就可以通信了

//    char sendBuff[SEND_BUF_SIZE] = {0};
//    const char *src = "Client====";
//    strncpy(sendBuff, src, SEND_BUF_SIZE - 1);
//    sendBuff[SEND_BUF_SIZE - 1] = '\0';
//    for (int i = 0; i < 10; ++i) {
//        write(sockfd, sendBuff, strlen(sendBuff));
//    }
    int count = 0;
    while (count++ < 5)
    {
        // 从client的文件描述符读取数据
        if ((n = read(sockfd, recvBuff, sizeof(recvBuff)-1)) > 0) {
            printf("%d\n", n);
            recvBuff[n] = 0;//ascii码的0代表空字符
//            printf("%s\n", recvBuff);
            // 把字符指针强制转换成double型,再打印显示
            printf("%f\n", *(reinterpret_cast<double*>(&recvBuff[36])));
        }
    }
    // 关闭client本地的socket,对方再write就不成功
    close(sockfd);

    if(n < 0)
    {
        printf("\n Read error \n");
    }

    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值