UNP——socket套接字分析以及IPC_UDS

3 篇文章 0 订阅
2 篇文章 0 订阅

1. socket流程

发送方:

  1. int socket(int domain, int type, int protocol);
  2. ssize_t sendto(int socket, const void *buffer, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len);
  3. close

接收方:

  1. int socket(int domain, int type, int protocol);
  2. int bind(int socket, const struct sockaddr *address, socklen_t address_len);
  3. ssize_t recvfrom(int socket, void *restrict buffer, size_t length, int flags, struct sockaddr *restrict address, socklen_t *restrict address_len);
  4. close

注意,服务器需要绑定port,而一般客户端不需要,因为服务端的port一般是固定且众所周知的,而客户端是内核随机指定,不需要bind。rpc服务器除外。

2 注意点及相关函数分析

2.1 sockaddr与sockaddr_in

bind,sendto,recvfrom等函数都用到了sockaddr类型参数,sockaddr_in通过man 7 ip查看

struct sockaddr {
  unsigned short sa_family; /* address family, AF_xxx */
  char sa_data[14]; /* 14 bytes of protocol address */
  };

sa_data[14]包含套接字中的目标地址和端口信息,而sockaddr_in中,则将port,addr分隔开
一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数

struct sockaddr_in {
               sa_family_t    sin_family; /* address family: AF_INET */
               in_port_t      sin_port;   /* port in network byte order */
               struct in_addr sin_addr;   /* internet address */
           };

           /* Internet address. */
           struct in_addr {
               uint32_t       s_addr;     /* address in network byte order */
           };

2.2 htons htonl ntohs ntohl

htons为 host to net short 主机字节顺序转换为网络字节顺序,且参数为uint16_t
ntohl为 net to host long相反,参数为uint32_t

网络字节序 Network Order

TCP/IP各层协议将字节序定义为Big-Endian,因此TCP/IP协议中使用的字节序通常称之为网络字节序。

主机序 Host Orader

它遵循Little-Endian规则。所以当两台主机之间要通过TCP/IP协议进行通信的时候就需要调用相应的函数进行主机序(Little-Endian)和网络序(Big-Endian)的转换。

这里要区分几个概念,大小端,最有效位优先MSBF,最有效位MSB,LSB等

  1. 大端:
    在这里插入图片描述
  2. 小端
    在这里插入图片描述
  3. MSB,LSB
    最高有效位和最低有效位,最右的0A为MSB,0D为LSB
  4. MSBF 等同于大端, MSB在内存的最前,即MSB在低地址,为大端

2.3 inet_pton,inet_ntop

上述两个函数将IP地址在“点分十进制”和“二进制整数”之间转换
inet_ntop将点分ip转换成二进制整数,供sockaddr_in中sin_addr使用

const char * inet_ntop(int af, const void * restrict src, char * restrict dst,
         socklen_t size);

inet_pton则相反,将sockaddr_in中的sin_addr转换成点分ip地址,如"172.16.9.6"

int inet_pton(int af, const char * restrict src, void * restrict dst);

注意,不能使用atoi或者itoa,这两个函数是转换convert ASCII string to integer或者反向,而"172.16.9.6"非一般字符串,为点分的字符串

int atoi(const char *str);

2.4 setsockopt

	   #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int getsockopt(int sockfd, int level, int optname,
                      void *optval, socklen_t *optlen);
       int setsockopt(int sockfd, int level, int optname,
                      const void *optval, socklen_t optlen);

setsockopt设置socket options,通过man 7 socket、IP、UDP、TCP等查看其属性,如果设置broadcast,则man 7 socket,其有SO_BROADCAST等属性,设置其flag,注意有些属性的,如man 7 ip,IP_ADD_MEMBERSHIP加入组播组,其参数为结构体,如下,所以要设置optlen

 struct ip_mreqn {
               struct in_addr imr_multiaddr; /* IP multicast group
                                                address */
               struct in_addr imr_address;   /* IP address of local
                                                interface */
               int            imr_ifindex;   /* interface index */
           };
2.4.1 设置组播

需要注意点:
A 发送组播到sendto 235.2.3.5(1990)
B 接受recvfrom ,本地port 也必须是1990才能收到
相关函数可以通过man 7 ip查看
发送方:
1. 设置组播组接口属性,setsockopt(sd,IPPROTO_IP,IP_MULTICAST_IF,&mreq,sizeof(mreq))
如果不调用该函数表示使用默认接口,如果调用,可以使用 addr.imr_ifindex = if_nametoindex(“eth0”) 设置接口为eth0,且可以设置ip地址等。
在这里插入图片描述
接收方:
1. 加入组播组,setsockopt(sd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq)
在这里插入图片描述
需要注意的点在于如下结构体,Linux系统多播发送和接收,这是别人文章引用

/* Internet address.  */
typedef uint32_t in_addr_t;
struct in_addr
{
    in_addr_t s_addr;
};

// ip_mreq是一个旧的数据结构,但目前仍然可用
struct ip_mreq
{
    /* IP multicast address of group.  */
    struct in_addr imr_multiaddr;

    // 设置加入多播组的的网卡ip, 注意这里并不表示socket同该网卡绑定
    // 该socket仍然能够接收到不是该网卡的数据包,该设置仅仅表示该ip
    // 对应的网卡能够接收对应多播组的数据包
    struct in_addr imr_interface;
};

// ip_mreqn是从Linux 2.2之后可用的新的数据结构,相比ip_mreq,其多了一个imr_ifindexy
struct ip_mreqn {
    struct in_addr imr_multiaddr;
    // 设置加入多播组的的网卡ip, 注意这里并不表示socket同该网卡绑定
    // 该socket仍然能够接收到不是该网卡的数据包,该设置仅仅表示该ip
    // 对应的网卡能够接收对应多播组的数据包
    struct in_addr imr_address;   
    // 设置加入多播组的网卡的index,该设置项优先级高于上边的网卡ip
    int            imr_ifindex; 
};

别人文章描述,懒得码字了
TCP中bind()的意思是将该socket绑定到某个IP:PORT上,组播的意思是socket只接收该组播组的数据包。如果这里绑定的是INADDR_ANY, 这个socket就讲接收到能听到的所有组播组IP相同端口的数据包, 即组播IP不同,端口相同时,组播串线的情况。
如果这里绑定的是某个具体组播组的IP, 这里内核向socket发送数据的时候就会过滤掉不是该组播组的数据。

IP_ADD_MEMBERSHIP意思是加入组播组。 根据IGMP协议,主机将会向组播管理端发送一个报文,报告本机要加入某个交换机,交换机就会向相应的端口转发这个组播组的数据包。 group.imr_multiaddr.s_addr为组播组IP。
group.imr_interface.s_addr一般写要加入组播组主机的接口/网卡的 IP, 以确定收取哪一个子网的组播信息,会通过该接口接收组播包。如果设为INADDR_ANY(0.0.0.0),则使用默认的IPV4组播接口

之前项目中使用dlt-receive时,由于没设置组播imr_interface的ip,即没有选择网络接口号,从默认的ipv4组播接口收不到数据

    case DLT_CLIENT_MODE_UDP_MULTICAST:

        if ((client->sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
        {
            dlt_vlog(LOG_ERR,
                     "%s: ERROR: socket error: %s\n",
                     __func__,
                     strerror(errno));

            return DLT_RETURN_ERROR;
        }

        /* allow multiple sockets to use the same PORT number */
        if (setsockopt(client->sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0)
        {
            dlt_vlog(LOG_ERR,
                     "%s: ERROR: Reusing address failed: %s\n",
                     __func__,
                     strerror(errno));

            return DLT_RETURN_ERROR;
        }

        memset(&client->receiver.addr, 0, sizeof(client->receiver.addr));
        client->receiver.addr.sin_family = AF_INET;
        client->receiver.addr.sin_addr.s_addr = htonl(INADDR_ANY);
        client->receiver.addr.sin_port = htons(client->port);

        /* bind to receive address */
        if (bind(client->sock, (struct sockaddr*) &client->receiver.addr, sizeof(client->receiver.addr)) < 0)
        {
            dlt_vlog(LOG_ERR,
                     "%s: ERROR: bind failed: %s\n",
                     __func__,
                     strerror(errno));

            return DLT_RETURN_ERROR;
        }

        mreq.imr_interface.s_addr = htonl(INADDR_ANY);
        if (client->hostip)
        {
            mreq.imr_interface.s_addr = inet_addr(client->hostip);
        }
        if (client->servIP == NULL)
        {
            dlt_vlog(LOG_ERR,
                     "%s: ERROR: server address not set\n",
                     __func__);

            return DLT_RETURN_ERROR;
        }

        char delimiter[] = ",";
        char* servIP = strtok(client->servIP, delimiter);

        while(servIP != NULL) {
            mreq.imr_multiaddr.s_addr = inet_addr(servIP);
            if (mreq.imr_multiaddr.s_addr == (in_addr_t)-1)
            {
                dlt_vlog(LOG_ERR,
                         "%s: ERROR: server address not not valid %s\n",
                         __func__,
                         servIP);

                return DLT_RETURN_ERROR;
            }

            if (setsockopt(client->sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq)) < 0)
            {
                dlt_vlog(LOG_ERR,
                         "%s: ERROR: setsockopt add membership failed: %s\n",
                         __func__,
                         strerror(errno));

                return DLT_RETURN_ERROR;
            }
            servIP = strtok(NULL, delimiter);
        }
        receiver_type = DLT_RECEIVE_UDP_SOCKET;

        break;
2.4.2 设置广播

发送方和接收方均需要设置SO_BROADCAST,通过man 7 socket查看
在这里插入图片描述

2.5 可变长数组

下面例子中使用了可变长数组,发送和接收并不知道传输数据的长度

typedef struct msg_st
{
    uint32_t math;
    uint32_t chinese;
    uint8_t name[];  //可变长度数组
}test_st

此时,sizeof(test_st)为8,name[]数组并不占据空间大小,只有当malloc时,才确定

ize = sizeof(struct msg_st)+strlen(argv[2]);
    sndr_l = (struct msg_st*)malloc(size);

此时,分配了strlen(argv[2])的长度给name[]

代码分析

sndercopy.c


#include "protocopy.h"
#include <sys/types.h> 
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <net/if.h>
#define IPSTRSIZE 40


int main(int argc, char* argv[])

{
    int sd, size;
    struct sockaddr_in laddr,raddr;
    struct msg_st* sndr_l; //这里要使用msg_st*,因为使用了可变数组,数组长度未知,需要使用malloc动态申请内存
    socklen_t raddrlen;

    int flag = 1;
    if(argc < 3)
    {
        fprintf(stderr,"Usage....\n");
        exit(1);
    }
    if(strlen(argv[2])>NAMEMAX)
    {
        fprintf(stderr,"NAMEMAX error....\n");
        exit(1);
    }

    size = sizeof(struct msg_st)+strlen(argv[2]);
    sndr_l = (struct msg_st*)malloc(size); //动态申请内存长度为sizeof(msg_st)加上可变数组大小

    strcpy(sndr_l->name,argv[2]); //不能写sndr_l.name = "Alan",得用strcpy
    sndr_l->math = htonl(rand()%100);
    sndr_l->chinese = htonl(rand()%100);
    raddr.sin_family = AF_INET;
    raddr.sin_port = htons(atoi(RCVPORT));
    inet_pton(AF_INET,argv[1],&raddr.sin_addr);
    sd = socket(AF_INET,SOCK_DGRAM,0);


    //创建广播
    //int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
    if(setsockopt(sd,SOL_SOCKET,SO_BROADCAST,&flag,sizeof(flag))<0)//注意SO_BROADCAST对应的是flag,不同的opt可能有不同的参数类型,可能是结构体
    {
        perror("setsockopt_SO_BROADCAST");
        exit(1);
    }



    //创建组播组 IP_MULTICAST_IF
    struct ip_mreqn mreq;
    inet_pton(AF_INET,MULTIGROUP,&mreq.imr_multiaddr); //多播ip
    inet_pton(AF_INET,"0.0.0.0",&mreq.imr_address); //本机ip
    mreq.imr_ifindex = if_nametoindex("eno1");//通过ip ad sh查看网络索引号,或者通过函数if_nametoindex将名字转换成索引号
 /*   struct ip_mreqn {
               struct in_addr imr_multiaddr; /* IP multicast group
                                                address */
 /*              struct in_addr imr_address;   /* IP address of local
                                                interface */
 /*              int            imr_ifindex;   /* interface index */
  //         };
    if(setsockopt(sd,IPPROTO_IP,IP_MULTICAST_IF,&mreq,sizeof(mreq))<0)
    {
        perror("setsockopt_IP_MULTICAST_IF");
        exit(1);
    }




        //man 7 udp IP_ADD_MEMBERSHIP加入多播组等,man 7 socket Socket options--setsockopt或者getsockopt--SO_BROADCAST,
       //socket level  set to SOL_SOCKET for all sockets.
    if(sendto(sd,sndr_l,size,0,(void*)&raddr,sizeof(raddr))<0) //send是用在流式传输SOCK_STREAM
    {
        perror("sendto");
        exit(1);
    }

    fputs("ok",stderr);
    close(sd);

    free(sndr_l);
    exit(0);
}

recvercopy.c


#include "protocopy.h"
#include <sys/types.h> 
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <net/if.h>
#define IPSTRSIZE 40
int main()

{
    int sd,size;
    int flag = 1;
    socklen_t raddr_len;

    char raddr_addr[IPSTRSIZE];
    struct sockaddr_in laddr, raddr;    //man 7 ip
    struct msg_st* recvbuffer;
    size = sizeof(struct msg_st) + NAMEMAX;
    recvbuffer =(struct msg_st*) malloc(size);

    laddr.sin_family = AF_INET;
    laddr.sin_port = htons(atoi(RCVPORT)); //htons home to Network short ,atoi RCVPORT
    inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);  // 把ip地址从char* 转换成 二进制数,sin_addr类型为in_addr为uint32_t

    sd = socket(AF_INET,SOCK_DGRAM,0);

    if(setsockopt(sd,SOL_SOCKET,SO_BROADCAST,&flag,sizeof(flag))<0)
    {
        perror("setsockopt");
        exit(1);
    }

    struct ip_mreqn mreq;
    inet_pton(AF_INET,MULTIGROUP,&mreq.imr_multiaddr);
    inet_pton(AF_INET,"0.0.0.0",&mreq.imr_address);
    mreq.imr_ifindex = if_nametoindex("eno1");


    if(setsockopt(sd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq))<0)
    {
        perror("setsockopt_IP_ADD_MEMBERSHIP");
        exit(1);
    }
    raddr_len = sizeof(raddr); //初始化给个大小
    if(sd < 0)
    {
        perror("sd error");
        exit(1);
    }
//       int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    /*struct sockaddr_in {                                                man 7 ip
               sa_family_t    sin_family; /* address family: AF_IN
ET */
    /*           in_port_t      sin_port;   /* port in network byte 
order */
     /*          struct in_addr sin_addr;   /* internet address */
    //       };

        /* Internet address. */
 //   struct in_addr {
  //      uint32_t       s_addr;     /* address in network byte order */
  //  };

    if(bind(sd,(void*)&laddr,sizeof(laddr))<0)
    {
        perror("bind()");
        exit(1);
    }
    while(1)
    {
        recvfrom(sd ,recvbuffer,size,0,(void*)&raddr,&raddr_len);
        inet_ntop(AF_INET,&raddr.sin_addr,raddr_addr,IPSTRSIZE);
        fprintf(stderr,"-----Message From %s: %d----\n",raddr_addr,ntohs(raddr.sin_port));
        fprintf(stderr,"NAME = %s\n",recvbuffer->name);
        fprintf(stderr,"MATH = %d\n",ntohl(recvbuffer->math)); //多字节,跨网络要用ntohl,htonl等
        fprintf(stderr,"MATH = %d\n",ntohl(recvbuffer->chinese));
    }
    close(sd);
    free(recvbuffer);
    exit(0);
}

protocopy.h

#ifndef PROTOCOPY_H__
#define PROTOCOPY_H__

#define MULTIGROUP "235.2.3.5"
#define RCVPORT "1990"
#define NAMEMAX (512-8-8)
typedef unsigned int   uint32_t;
typedef unsigned  char   uint8_t;
struct msg_st
{
    uint32_t math;
    uint32_t chinese;
    uint8_t name[];  //可变长度数组
}__attribute__((packed));  //位对齐




#endif
ThinkStation-P310:~/Videos/lxz$ ./snderbroadcast 255.255.255.255 helloworld222222
ThinkStation-P310:~/Videos/lxz$ $ ./snderbroadcast 235.2.3.5 helloworld2222223333333
ThinkStation-P310:~/Videos/lxz$ $ ./snderbroadcast 127.0.0.1 helloworld22222233333334444444
-----Message From 10.64.4.49: 40728----
NAME = helloworld222222
MATH = 83
MATH = 86
-----Message From 127.0.0.1: 52268----
NAME = helloworld2222223333333
MATH = 83
MATH = 86
-----Message From 127.0.0.1: 46572----
NAME = helloworld22222233333334444444
MATH = 83
MATH = 86
^C

3. UDS进程间通信

下列代码源自https://blog.csdn.net/qq_22863733/article/details/80629920
注意,UDS与socket网络通信的区别在于

  1. UDS里,bind使用的struct sockaddr类型为struct sockaddr_un(AF_UNIX),而一般socket用的是sockaddr_in(AF_INET)
  2. bind的时候,创建S_IFSOCK的文件。该文件仅用于向客户端进程告知套接字名字,该文件不能打开,也不能由应用程序用于通信,当关闭套接字时,并不自动删除该文件,所以我们必须确保在应用程序终止前,对该文件执行解除链接操作(unlink(path)),或删除该文件。
  3. 发送数据时,指定接收方绑定的路径名,操作系统根据该路径名可以直接找到对应的接收方,并将原始数据直接拷贝到接收方的内核缓冲区中,并上报给接收方进程进行处理。同样的接收方可以从收到的数据包中获取到发送方的路径名,并通过此路径名向其发送数据。

在这里插入图片描述

 struct sockaddr_in {
     sa_family_t    sin_family; /* address family: AF_INET */
     in_port_t      sin_port;   /* port in network byte order */
     struct in_addr sin_addr;   /* internet address */
 };

 /* Internet address. */
 struct in_addr {
     uint32_t       s_addr;     /* address in network byte order */
 };
struct sockaddr_un {
sa_family_t     sun_family;     /* AF_UNIX */
char    sun_path[UNIX_PATH_MAX];        /* 路径名 */
};
 struct sockaddr_un sun;
 sun.sun_family = AF_LOCAL;    //1
 strcpy(sun.sun_path, filepath); //2
 bind(sockfd, (struct sockaddr*)&sun, sizeof(sun));

3.1 代码实例

server


#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
	int server_sockfd, client_sockfd;
	int server_len, client_len;
	struct sockaddr_un server_address;
	struct sockaddr_un client_address;
	int i,byte,ch_send;
	char recv_buf[128];
	char send_buf[128] = "ACK";
	
	unlink("server_socket");	//解除原有server_socket对象链接
	server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);//创建socket,指定通信协议为AF_UNIX,数据方式SOCK_STREAM
	//配置server_address
	server_address.sun_family = AF_UNIX;
	strcpy(server_address.sun_path, "server_socket");
	server_len = sizeof(server_address);
	
	bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
 
	listen(server_sockfd, 5);
	
	printf("server waiting for  client connect\n");
	client_len = sizeof(client_address);
	//accept函数接收客户端求情,存储客户端地址信息、客户端地址大小
	client_sockfd = accept(server_sockfd,(struct sockaddr *)&client_address, (socklen_t *)&client_len);
	printf("the server wait form client data\n");
	  
	for(i=0,ch_send=0;i<5;i++,ch_send++)
	{
		//从client_sockfd读取客户端发来的消息
		if((byte=read(client_sockfd, recv_buf, sizeof(recv_buf)))==-1)
		{
			perror("read");
			exit(EXIT_FAILURE);
		}
		printf("the massage receiver from client is: %s\n",recv_buf);
		sleep(1);
		//向客户端发送消息
		if((byte=write(client_sockfd,&ch_send,sizeof(ch_send)))==-1)
		{
		   	perror("write");
			exit(EXIT_FAILURE);
		}
    }
	close(client_sockfd);
	unlink("server socket");
	exit(0);
}

client

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
	int sockfd;
	int len;
	struct sockaddr_un address;
	int result;
	int i,byte;
	char send_buf[128];
    int ch_recv;
 
	if((sockfd = socket(AF_UNIX, SOCK_STREAM, 0))==-1)//创建socket,指定通信协议为AF_UNIX,数据方式SOCK_STREAM
	{
		perror("socket");
		exit(EXIT_FAILURE);
	}
	
	//配置server_address
	address.sun_family = AF_UNIX;
	strcpy(address.sun_path, "server_socket");
	len = sizeof(address);
 
	result = connect(sockfd, (struct sockaddr *)&address, len);
 
	if(result == -1) 
	{
		printf("ensure the server is up\n");
        	perror("connect");
        	exit(EXIT_FAILURE);
    	}
	for(i=0;i<5;i++)
	{
		sprintf(send_buf,"client massage %d",i);//用sprintf事先把消息写到send_buf
		if((byte=write(sockfd, send_buf, sizeof(send_buf)))==-1)
		{
			perror("write");
			exit(EXIT_FAILURE);
		}
		if((byte=read(sockfd,&ch_recv,sizeof(ch_recv)))==-1)
		{
			perror("read");
			exit(EXIT_FAILURE);
		}
		printf("receive from server data is: %d\n",ch_recv);
	}	
	close(sockfd);
    return 0;
}

结果

xili271948@er04180p:~/tcp$ ./uds_client 
receive from server data is: 0
receive from server data is: 1
receive from server data is: 2
receive from server data is: 3
receive from server data is: 4

xili271948@er04180p:~/tcp$ ./uds_server 
server waiting for  client connect
the server wait form client data
the massage receiver from client is: client massage 0
the massage receiver from client is: client massage 1
the massage receiver from client is: client massage 2
the massage receiver from client is: client massage 3
the massage receiver from client is: client massage 4
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值