8118|11

291

帖子

5

TA的资源

纯净的硅(中级)

楼主
 

没有IP和端口号,可以进行socket通信吗? [复制链接]

 


在使用socket通信时,无论是本机内部通信,还是两台机器通信,也无论是TCP的方式,还是UDP的方式,一般都要指定IP和端口号。在Linux开发中,如果是同一台设备内部通信,也可以不需要IP和端口号,这就是Unix域socket通信,它实际上是通过文件的方式实现通信,从而不再需要IP和端口号。本篇就来介绍了Unix域socket的使用示例。

 

Unix域socket和普通的socket使用起来区别不大,也有TCP和UDP两种传输方式,在介绍Unix域socket之前,再来看下TCP和UDP两种模式下的socket通信模型。

 

1 Unix域socket基础知识

在使用IP和端口号的socket通信中,会用到sockaddr和sockaddr_in结构体,两个结构体一样大,都是16个字节,而且都有family属性,不同的是:

  • sockaddr用其余14个字节来表示sa_data

  • sockaddr_in把14个字节拆分成sin_port, sin_addr和sin_zero,分别表示端口、ip地址。sin_zero用来填充字节使sockaddr_in和sockaddr保持一样大小

include <netinet/in.h>
struct sockaddr {
    unsigned short sa_family;   // 2 bytes address family, AF_xxx
    char           sa_data[14]; // 14 bytes of protocol address
};
 
// IPv4 AF_INET sockets:
struct sockaddr_in {
    short          sin_family;  // 2 bytes e.g. AF_INET, AF_INET6
    unsigned short sin_port;    // 2 bytes e.g. htons(3490)
    struct in_addr sin_addr;    // 4 bytes see struct in_addr, below
    char           sin_zero[8]; // 8 bytes zero this if you want to
};
​
struct in_addr {
    unsigned long s_addr; // 4 bytes load with inet_pton()
};

sockaddr和sockaddr_in包含的数据都是一样的,但他们在使用上有区别:

  • 在赋值时,把类型、ip地址、端口填充sockaddr_in结构体

  • 在bind时,强制转换成sockaddr

使用Uinx域的socket时,也会用到sockaddr,另外,还需要用到sockaddr_un结构体:

struct sockaddr_un {
    sa_family_t sun_family;              /* AF_UNIX */
    char        sun_path[UNIX_PATH_MAX]; /* pathname */
};

类比sockaddr_in,此结构体只有协议类型和路径名。

2 编程测试

本篇的测试实例要实现的功能是Unix域socket的客户端与服务端实现通信,先实现一对一的通信功能,客户端和服务端分别使用一个线程,两者通信成功后,每隔一段时间客户端向服务端发送一条消息。

分别使用UDP和TCP两种方式实现上述功能。

下面先来看下需要用到的头文件和宏定义:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <thread>
#include <string>
#include "printUtil.h"
​
#define UNIX_UDP_SOCKET_ADDR "unixUDP.socket"
#define UNIX_TCP_SOCKET_ADDR "unixTCP.socket"
#define BUF_SIZE 100
​
using namespace std;

2.1 UDP方式

2.1.1 客户端代码

Unix域socket的UDP客户端程序,对照UDP方式的socket通信模型,因为UDP是无连接的,作为客户端,只需要创建一个socket,然后向需要发送的地址调用sento即可发送消息了,代码如下。

需要注意的是,UDP通信时,socket的参数选用SOCK_DGRAM,数据报。

void UdpClientThread()
{
    int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        PRINT("create socket fail\n");
        return;
    }
    PRINT("create socketfd:%d\n", sockfd);
​
    struct sockaddr_un addr;
    memset (&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, UNIX_UDP_SOCKET_ADDR);
​
    while(1)
    {
        static int i = 0;
        std::string str("helloUDP" + std::to_string(++i));
        sendto(sockfd, str.c_str(), str.length(), 0, (struct sockaddr *)&addr, sizeof(addr));
        sleep(1);
    }
}

总结下Unix域socket的UDP客户端程序的流程:

  • 创建socket

  • sendto发送消息给指定地址的UDP服务端

2.1.2 服务端代码

Unix域socket的UDP服务端程序,对照UDP方式的socket通信模型,因为UDP是无连接的,作为服务端,只需要先创建一个socket,然后再绑定到要接收消息的地址上,然后就可以使用recvfrom来接收消息了,代码如下。

需要注意的是,UDP通信时,socket的参数选用SOCK_DGRAM,数据报。

void UdpServerThread()
{
    int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        PRINT("create socket fail\n");
        return;
    }
    PRINT("create socketfd:%d\n", sockfd);
​
    struct sockaddr_un addr;
    memset (&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, UNIX_UDP_SOCKET_ADDR);
​
    if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)))
    {
        PRINT("bind fail\n");
        return;
    }
​
    size_t size = 0;
    char buf[BUF_SIZE] = {0};
    while(1)
    {
        size = recvfrom(sockfd, buf, BUF_SIZE, 0, NULL, NULL);
        //size = read(sockfd, buf, BUF_SIZE);
        if (size > 0)
        {
            PRINT("recv:%s\n", buf);
        }
    }
}

总结下Unix域socket的UDP服务端程序的流程:

  • 创建socket

  • bind到指定的地址(文件)

  • recvfrom/read接收UDP客户端的消息

2.2 TCP方式

2.2.1 客户端代码

Unix域socket的TCP客户端程序,对照TCP方式的socket通信模型,因为TCP是有连接的,作为客户端,需要先创建一个socket,然后连接到要发送消息的地址上(注意需要服务端先建立,TCP客户端才能连接上),连上后,调用send或write就可发送消息了,代码如下。

需要注意的是,TCP通信时,socket的参数选用SOCK_STREAM,数据流。

void TcpClientThread()
{
    //------------socket
    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        PRINT("create socket fail\n");
        return;
    }
    PRINT("create socketfd:%d\n", sockfd);
​
    struct sockaddr_un addr;
    memset (&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, UNIX_TCP_SOCKET_ADDR);
​
    sleep(2);//wait server ready
​
    //------------connect
    if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)))
    {
        PRINT("connect fail\n");
        return;
    }
    PRINT("connect ok\n");
​
    while(1)
    {
        static int i = 0;
        std::string str("helloTCP" + std::to_string(++i));
        //------------send
        send(sockfd, str.c_str(), str.length(), 0);
        //write(sockfd, str.c_str(), str.length());
        sleep(1);
    }
}
​

总结下Unix域socket的TCP客户端程序的流程:

  • 创建socket

  • connect连接到指定的地址(文件)

  • send/write发送消息给TCP服务端

2.2.1 服务端代码

Unix域socket的TCP服务端程序,对照TCP方式的socket通信模型,因为TCP是有连接的,作为服务端,需要先创建一个socket,然后绑定到要接收消息的地址上,接下来就是监听TCP客户端的连接,等客户端来连接后,就可以使用recv或read来接收消息了,代码如下。

需要注意的是,TCP通信时,socket的参数选用SOCK_STREAM,数据流。

void TcpServerThread()
{
    //------------socket
    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        PRINT("create socket fail\n");
        return;
    }
    PRINT("create socketfd:%d\n", sockfd);
​
    struct sockaddr_un addr;
    memset (&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, UNIX_TCP_SOCKET_ADDR);
​
    //------------bind
    if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)))
    {
        PRINT("bind fail\n");
        return;
    }
    PRINT("bind ok\n");
​
    //------------listen
    if (listen(sockfd, 5))
    {
        PRINT("listen fail\n");
        return;
    }
    PRINT("listen ok\n");
​
    //------------accept
    int clientfd = accept(sockfd, NULL, NULL);
    PRINT("accept clientfd:%d\n", clientfd);
    if (clientfd > 0)
    {
        size_t size = 0;
        char buf[BUF_SIZE] = {0};
        while(1)
        {
            //------------recv
            size = recv(clientfd, buf, BUF_SIZE, 0);
            //size = read(clientfd, buf, BUF_SIZE);
            if (size > 0)
            {
                PRINT("recv:%s\n", buf);
            }
            sleep(1);
        }
    }
    PRINT("end\n");
}

总结下Unix域socket的TCP服务端程序的流程:

  • 创建socket

  • bind到指定的地址(文件)

  • listen监听TCP客户端的连接请求

  • accept接受TCP客户端的连接

  • recv/read接收TCP客户端的消息

2.3 一种打印技巧

为了在打印调试信息时,每条信息能把对应的函数名打印出来,这里写了一个PRINT宏定义来进行打印,可以对原本的printf打印,增加函数名的打印功能。

printUtil.h

#ifndef __PRINTUTIL_H_
#define __PRINTUTIL_H_
​
#define FIRST(...) FIRST_HELPER(__VA_ARGS__, throwaway)
#define FIRST_HELPER(first, ...) first
​
#define REST(...) REST_HELPER(NUM(__VA_ARGS__), __VA_ARGS__)
#define REST_HELPER(qty, ...) REST_HELPER2(qty, __VA_ARGS__)
#define REST_HELPER2(qty, ...) REST_HELPER_##qty(__VA_ARGS__)
#define REST_HELPER_ONE(first)
#define REST_HELPER_TWOORMORE(first, ...) , __VA_ARGS__
#define NUM(...) \
    SELECT_10TH(__VA_ARGS__, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\
                TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway)
#define SELECT_10TH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, ...) a10
​
//自定义打印格式
#define PRINT(...) printf("[%s] " FIRST(__VA_ARGS__), __func__ REST(__VA_ARGS__))
​
#endif

 

2.4 测试结果

将上述的UDP和TCP方式的客户端和服务端程序,分别以独立线程的方式调用起来:

int main()
{
    unlink(UNIX_UDP_SOCKET_ADDR);
    unlink(UNIX_TCP_SOCKET_ADDR);
​
    thread th1(UdpServerThread);
    thread th2(UdpClientThread);
​
    thread th3(TcpServerThread);
    thread th4(TcpClientThread);
​
    th1.join();
    th2.join();
​
    th3.join();
    th4.join();
​
    PRINT("hello\n");
}

测试结果如下:

[UdpServerThread] create socketfd:3
[TcpServerThread] create socketfd:5
[TcpClientThread] create socketfd:6
[TcpServerThread] bind ok
[TcpServerThread] listen ok
[UdpClientThread] create socketfd:4
[UdpServerThread] recv:helloUDP1
[UdpServerThread] recv:helloUDP2
[TcpClientThread] connect ok
[TcpServerThread] accept clientfd:7
[TcpServerThread] recv:helloTCP1
[UdpServerThread] recv:helloUDP3
[TcpServerThread] recv:helloTCP2
[UdpServerThread] recv:helloUDP4
[TcpServerThread] recv:helloTCP3
[UdpServerThread] recv:helloUDP5
[TcpServerThread] recv:helloTCP4
[UdpServerThread] recv:helloUDP6
[TcpServerThread] recv:helloTCP5
[UdpServerThread] recv:helloUDP7
[TcpServerThread] recv:helloTCP6
[UdpServerThread] recv:helloUDP8
[UdpServerThread] recv:helloUDP9
[TcpServerThread] recv:helloTCP7

另外,程序运行后,会在本地生成对应的Unix域socket通信文件,本例程序中,就会生成unixUDP.socket和unixTCP.socket这两个文件。

3 总结

本篇介绍了Unix域的Socket通信实例,包括UDP和TCP两种方式,使用流程总结下来如下图:

使用Unix域的Socket通信(同一台机器内部通信),不再需要IP和端口号,只需要指定一个文件即可实现sokect通信

此帖出自ARM技术论坛

最新回复

还是需要通用的IP和Port,毕竟需要各个系统之间的连接  详情 回复 发表于 2023-6-5 09:29
点赞 关注
 

回复
举报

7067

帖子

11

TA的资源

版主

沙发
 

使用Unix域的Socket通信(同一台机器内部通信),不再需要IP和端口号,只需要指定一个文件即可实现sokect通信。

大佬写的帖子,拜读后收获满满!

此帖出自ARM技术论坛
 
 
 

回复

2942

帖子

4

TA的资源

五彩晶圆(中级)

板凳
 

这是当年X window留下的遗产,那时X windows是使用socket地,由于在本机内部使用socket使得系统的开销较大,而且效率很低。于是就出现了这个本机内部socket的方式。目前linux的桌面发展较慢的一个原因就是历史包袱校重,android抛弃了很多linux的包袱发展的就很好。

此帖出自ARM技术论坛
 
 
 

回复

6565

帖子

9

TA的资源

版主

4
 

太高级了,没接触过问个局外的,单台设备为什么进行Socket通信

此帖出自ARM技术论坛

点评

统一用户层API  详情 回复 发表于 2022-10-25 20:07
 
个人签名

在爱好的道路上不断前进,在生活的迷雾中播撒光引

 
 

回复

7671

帖子

2

TA的资源

五彩晶圆(高级)

5
 
秦天qintian0303 发表于 2022-10-25 16:23 太高级了,没接触过问个局外的,单台设备为什么进行Socket通信

统一用户层API

此帖出自ARM技术论坛
 
个人签名

默认摸鱼,再摸鱼。2022、9、28

 
 

回复

7671

帖子

2

TA的资源

五彩晶圆(高级)

6
 

UNIX domain不太能算作socket了,它也还是IPC的一种封装成socket的样子。

此帖出自ARM技术论坛
 
个人签名

默认摸鱼,再摸鱼。2022、9、28

 
 

回复

10

帖子

0

TA的资源

一粒金砂(中级)

7
 
不同主机通信肯定需要ip和端口号,不然无法找到对应的主机,无法通信!
此帖出自ARM技术论坛

点评

是的,不过这篇讲的是同一台机器内部的通信,可以使用unix域socket,不需要ip和端口  详情 回复 发表于 2022-12-15 22:03
 
 
 

回复

291

帖子

5

TA的资源

纯净的硅(中级)

8
 
honghong__ 发表于 2022-12-14 22:27 不同主机通信肯定需要ip和端口号,不然无法找到对应的主机,无法通信!

是的,不过这篇讲的是同一台机器内部的通信,可以使用unix域socket,不需要ip和端口

此帖出自ARM技术论坛
 
 
 

回复

8

帖子

1

TA的资源

一粒金砂(中级)

9
 

同一台机器内部的通信,可以使用unix域socket, 但不同主机通信肯定需要ip和端口号,不然无法找到对应的主机,无法通信!

此帖出自ARM技术论坛
 
 
 

回复

18

帖子

0

TA的资源

一粒金砂(中级)

10
 

太高级了,没接触过问个局外的,单台设备为什么进行Socket通信

此帖出自ARM技术论坛
 
 
 

回复

331

帖子

0

TA的资源

一粒金砂(高级)

11
 

学习了。。。。。

此帖出自ARM技术论坛
 
 
 

回复

44

帖子

0

TA的资源

一粒金砂(中级)

12
 
还是需要通用的IP和Port,毕竟需要各个系统之间的连接
此帖出自ARM技术论坛
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/6 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表