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 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网络通信的区别在于
- 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; //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