UNP——socket套接字分析

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 設置組播

相關函數可以通過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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章