socket——报式套接字编程实例及注意事项

数据报格式定义
注:

  1. 需要使用__attribute__((packed))取消字节对齐,避免不同端字节对齐方式差异引起的问题。
  2. 数据报以结构体为单位,采用udp协议;流式套接字以字节流为单位进行传输,采用tcp协议。
  3. 网络通信中,封装的数据报结构体不能含有指针类型。因为主动端与被动端大概率不在一台主机上,这样的话,在主动端malloc了一块0x2000的内存地址,但在被动端这块地址可能存储的是其他数据。
  4. 上述问题的解决方案是可以使用变长结构体。
#ifndef DGRAM_H__
#define DGRAM_H__

#define NAME_SIZE 128

struct dgram_t {
    char name[NAME_SIZE];
    int chinese;
    int math;
}__attribute__((packed));


#endif // !

主动端(客户)
主动端也就是主动发送消息的一端。
主动端的端口号及IP地址是无需指定的。如果在主动端建立一个socket,对于报式套接字来说,我们只需要指定目的地址及端口号即可,因为主动端一定会先像被动端发送数据,因此被动端的IP地址和端口号一定要首先指定,否则主动端不知道向谁发送数据。
一旦主动端携带自己的IP和PORT向被动端发送数据后,被动端就知道了主动端的IP及PORT,这样就可以知道该向哪个主动端发送数据,因此主动端端口及IP无需指定。
【注】
一定要注意到字节序大小端问题,可使用htonl、htons、ntohl以及ntohs来解决大小端问题。这里的h代表host,n代表network,用哪个函数取决(1)是本地接收网络数据还是将本地数据发送到网络(2)发送/接收的数据是短整型还是长整型(端口号用uint8_t即可,但ip地址要用uint32_t)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include "dgram.h"

#define RCVPORT "8080"

static void dgram_initialize(struct dgram_t **dgram)
{
    // *dgram = (struct dgram_t*)malloc(sizeof(struct dgram_t));
    strcpy((*dgram)->name, "cuiyanran");
    (*dgram)->chinese = 100;
    (*dgram)->math = 97;
}

int main(int argc, char *argv[])
{
    int sfd;
    struct sockaddr_in raddr;
    struct dgram_t dgram;
    strcpy(dgram.name, "cuiyanran");
    dgram.chinese = htonl(100);
    dgram.math = htonl(97);    
    // socket()

    if(argc < 2)
    {
        perror("usage...");
        exit(1);
    }

    sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sfd < 0)
    {
        perror("socket()");
        exit(1);
    }

    // bind()
    raddr.sin_family = AF_INET;
    raddr.sin_port = htons(atoi(RCVPORT));
    inet_pton(AF_INET, argv[1], &raddr.sin_addr);

    // sendto()
    // dgram_initialize(&dgram);

    if(sendto(sfd, &dgram, sizeof(dgram), 0, (void *)&raddr, sizeof(raddr)) < 0)
    {
        perror("sendto()");
        close(sfd);
        exit(1);
    }

    // close()
    close(sfd);

    exit(0);
}

被动端(服务器)
被动端持续接收来自主动端的数据,需要注意的一点是命名规范问题。为避免混乱,可以用laddr代表local address,使用raddr代表remote address。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include "dgram.h"

#define PORT   "8080"
#define IP_LEN 32

int main()
{
    int sfd, sock_len, raddr_len;
    struct sockaddr_in laddr, raddr;
    struct dgram_t dgram;

    char ip_string[IP_LEN];

    if((sfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("socket()");
        exit(1);
    }

    laddr.sin_family = AF_INET;
    laddr.sin_port = htons(atoi(PORT));
    inet_pton(AF_INET, "0.0.0.0", &laddr.sin_addr); 
    bind(sfd, (void *)&laddr, sizeof(laddr));

    raddr_len = sizeof(raddr);

    while(1)
    {
        if(recvfrom(sfd, &dgram, sizeof(dgram), 0, (void *)&raddr, &raddr_len) < 0)
        {
            perror("recvfrom()");
            exit(1);
        }
        inet_ntop(AF_INET, &raddr.sin_addr, ip_string, IP_LEN);
        printf("--MASSAGE FROM %s:%d--\n", ip_string, ntohs(raddr.sin_port));
        printf("NAME = %s\n", dgram.name);
        printf("MATH = %d\n", ntohl(dgram.math));
        printf("CHINESE = %d\n", ntohl(dgram.chinese));
    }

    close(sfd);
    exit(0);
}

变长结构体解决数据报大小固定问题
变长结构体定义

// 注意变长结构体中变长数组的位置!!!!
/*
在一个结构体的最后 ,申明一个长度为空的数组,就可以使得这个结构体是可变长的。
对于编译器来说,此时长度为0的数组并不占用空间,因为数组名本身不占空间
*/
struct dgram_t {
    uint32_t chinese;
    uint32_t math;
    uint8_t name[1];
}__attribute__((packed));

主动端
【注】因为结构体变长,因此我们最初未知动态申请的内存大小。故而在发送报文时,需要指定发送消息的具体大小,并且采用结构体指针的方式传递数据。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include "dgram.h"

#define RCVPORT "8080"

int main(int argc, char *argv[])
{
    int sfd, size;
    struct sockaddr_in raddr;
    struct dgram_t *sbufp;
    // socket()

    if(argc < 3)
    {
        perror("usage...");
        exit(1);
    }

    if(strlen(argv[2]) > NAME_MAX)
    {
        fprintf(stderr, "argv[1]) > NAME_MAX");
        exit(1);
    }

    sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sfd < 0)
    {
        perror("socket()");
        exit(1);
    }

    // bind()
    raddr.sin_family = AF_INET;
    raddr.sin_port = htons(atoi(RCVPORT));
    inet_pton(AF_INET, argv[1], &raddr.sin_addr);

    size = sizeof(struct dgram_t) + sizeof(argv[2]);
    sbufp = (struct dgram_t *)malloc(size);
    if(sbufp == NULL)
    {
        perror("malloc()");
        exit(1);
    }
    strcpy(sbufp->name, argv[2]);
    sbufp->chinese = htonl(100);
    sbufp->math = htonl(97);    


    // sendto()
    // dgram_initialize(&sbufp);

    // 注意这里的size!!!
    if(sendto(sfd, sbufp, size, 0, (void *)&raddr, sizeof(raddr)) < 0)
    {
        perror("sendto()");
        close(sfd);
        exit(1);
    }

    // close()
    close(sfd);

    exit(0);
}

被动端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include "dgram.h"

#define PORT   "8080"
#define IP_LEN 32

int main()
{
    int size, sfd, sock_len, raddr_len;
    struct sockaddr_in laddr, raddr;
    struct dgram_t *rbufp;

    char ip_string[IP_LEN];

    size = sizeof(struct dgram_t) + NAME_MAX - 1;
    rbufp = (struct dgram_t *)malloc(size);

    if(rbufp == NULL)
    {
        perror("malloc()");
        exit(1);
    }

    if((sfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("socket()");
        exit(1);
    }

    laddr.sin_family = AF_INET;
    laddr.sin_port = htons(atoi(PORT));
    inet_pton(AF_INET, "0.0.0.0", &laddr.sin_addr); 
    bind(sfd, (void *)&laddr, sizeof(laddr));

    raddr_len = sizeof(raddr);

    while(1)
    {
        if(recvfrom(sfd, rbufp, size, 0, (void *)&raddr, &raddr_len) < 0)
        {
            perror("recvfrom()");
            exit(1);
        }
        inet_ntop(AF_INET, &raddr.sin_addr, ip_string, IP_LEN);
        printf("--MASSAGE FROM %s:%d--\n", ip_string, ntohs(raddr.sin_port));
        printf("NAME = %s\n", rbufp->name);
        printf("MATH = %d\n", ntohl(rbufp->math));
        printf("CHINESE = %d\n", ntohl(rbufp->chinese));
    }

    close(sfd);
    exit(0);
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值