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 設置組播
相關函數可以通過man 7 ip查看
發送方:
1. 創建組播組,setsockopt(sd,IPPROTO_IP,IP_MULTICAST_IF,&mreq,sizeof(mreq))
接收方:
1. 加入組播組,setsockopt(sd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq)
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