本文主要是介绍UNP——socket套接字分析以及IPC_UDS,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1. socket流程
发送方:
- int socket(int domain, int type, int protocol);
- ssize_t sendto(int socket, const void *buffer, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len);
- close
接收方:
- int socket(int domain, int type, int protocol);
- int bind(int socket, const struct sockaddr *address, socklen_t address_len);
- ssize_t recvfrom(int socket, void *restrict buffer, size_t length, int flags, struct sockaddr *restrict address, socklen_t *restrict address_len);
- 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等
- 大端:
- 小端
- MSB,LSB
最高有效位和最低有效位,最右的0A为MSB,0D为LSB - 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 groupaddress */struct in_addr imr_address; /* IP address of localinterface */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,该设置项优先级高于上边的网卡ipint 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 40int 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",得用strcpysndr_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_IFstruct ip_mreqn mreq;inet_pton(AF_INET,MULTIGROUP,&mreq.imr_multiaddr); //多播ipinet_pton(AF_INET,"0.0.0.0",&mreq.imr_address); //本机ipmreq.imr_ifindex = if_nametoindex("eno1");//通过ip ad sh查看网络索引号,或者通过函数if_nametoindex将名字转换成索引号/* struct ip_mreqn {struct in_addr imr_multiaddr; /* IP multicast groupaddress *//* struct in_addr imr_address; /* IP address of localinterface *//* 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 ipstruct 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 RCVPORTinet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr); // 把ip地址从char* 转换成 二进制数,sin_addr类型为in_addr为uint32_tsd = 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 ipsa_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网络通信的区别在于
- UDS里,bind使用的struct sockaddr类型为struct sockaddr_un(AF_UNIX),而一般socket用的是sockaddr_in(AF_INET)
- bind的时候,创建S_IFSOCK的文件。该文件仅用于向客户端进程告知套接字名字,该文件不能打开,也不能由应用程序用于通信,当关闭套接字时,并不自动删除该文件,所以我们必须确保在应用程序终止前,对该文件执行解除链接操作(unlink(path)),或删除该文件。
- 发送数据时,指定接收方绑定的路径名,操作系统根据该路径名可以直接找到对应的接收方,并将原始数据直接拷贝到接收方的内核缓冲区中,并上报给接收方进程进行处理。同样的接收方可以从收到的数据包中获取到发送方的路径名,并通过此路径名向其发送数据。
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; //1strcpy(sun.sun_path, filepath); //2bind(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_addressserver_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_addressaddress.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_bufif((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: 4xili271948@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
这篇关于UNP——socket套接字分析以及IPC_UDS的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!