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