`

IPMsg飞鸽传书网络协议解析手记

 
阅读更多

相信很多人都使用过飞鸽传书,这个小工具在局域网传输数据高效而便捷,自己在大二的时候就想看看飞鸽传书的源码,但那时候自己的水平有限,这几天有机会重写飞鸽传书,也对IPMSG的网络协议做了深入的研究,这里也要感谢IPMSG的作者公开源代码。

首先需要明确IPMSG的主要功能,IPMSG可以局域网通信、传输文件、传输文件夹,可以通过添加局域网外IP来实现网外的聊天与文件传输功能。我们先分析下IPMSG的聊天功能,IPMSG通过UDP协议实现聊天,当一个IPMSG的客户端运行开始,首先它向整个局域网广播上线报文,局域网内的其他IPMSG客户端收到上线报文后,回复该报文,回复报文中包含了该客户端的IP PORT 用户名 机器名。这样在上线客户端通过广播发送上线报文后,局域网内的其他所有IPMSG客户端都发送一个回复报文,这样,所有IPMSG的客户端都更新自己的在线用户列表。这样IPMSG的上线就算结束了,接下来,如果有客户端发送消息,而消息是通过UDP来完成的,客户端通过查询自己用户链表获取其他用户的网络地址信息,发送消息给其他用户。总结一下:

ipmsg可以用于收发消息和文件(夹)

使用UDP协议收发消息使用TCP协议收发文件(夹)

默认使用2425端口做数据传输(TCP/UDP)

包含以下功能

用户上下线识别

消息收发

文件传输文件夹传输

IPMSG的报文格式:版本号:包编号:发送者姓名:发送者主机名:命令字:附加信息

整个报文通过字符串的形式发送,IPMSG的版本号为1,而包编号必须是不重复的数字,这里可以是用比较简洁的方式,就是通过linux的库函数timer来完成,time 函数返回从1970 年1 月1 日0 点以来的秒数.所以每个运行timer()的结果都是不一样的,可以放心使用。报文中的命令字是指明这个报文是消息、上线通告、传输文件、传输文件夹还是其他的东西,附加信息在不同的命令字下是不一样的,如果命令字是消息,那么附加信息就是消息内容,如果命令字是传输文件,那么附加信息就是文件的信息了,我们来看一下命令字,这是IPMSG最为重要的内容。

/*@(#)Copyright (C) H.Shirouzu 1996-1998 ipmsg.hVer1.34 */

#ifndef IPMSG_H
#define IPMSG_H

/* IP Messenger Communication Protocol version 1.0 define */
/* macro */
#define GET_MODE(command)(command & 0x000000ffUL)
#define GET_OPT(command)(command & 0xffffff00UL)

/* header */
#define IPMSG_VERSION0x0001
#define IPMSG_DEFAULT_PORT0x0979

/* command */
#define IPMSG_NOOPERATION0x00000000UL

#define IPMSG_BR_ENTRY0x00000001UL
#define IPMSG_BR_EXIT0x00000002UL
#define IPMSG_ANSENTRY0x00000003UL
#define IPMSG_BR_ABSENCE0x00000004UL

#define IPMSG_BR_ISGETLIST0x00000010UL
#define IPMSG_OKGETLIST0x00000011UL
#define IPMSG_GETLIST0x00000012UL
#define IPMSG_ANSLIST0x00000013UL
#define IPMSG_FILE_MTIME0x00000014UL
#define IPMSG_FILE_CREATETIME0x00000016UL
#define IPMSG_BR_ISGETLIST20x00000018UL

#define IPMSG_SENDMSG0x00000020UL
#define IPMSG_RECVMSG0x00000021UL
#define IPMSG_READMSG0x00000030UL
#define IPMSG_DELMSG0x00000031UL

/* option for all command */
#define IPMSG_ABSENCEOPT0x00000100UL
#define IPMSG_SERVEROPT0x00000200UL
#define IPMSG_DIALUPOPT0x00010000UL
#define IPMSG_FILEATTACHOPT0x00200000UL

/* file types for fileattach command */
#define IPMSG_FILE_REGULAR0x00000001UL
#define IPMSG_FILE_DIR0x00000002UL
#define IPMSG_LISTGET_TIMER0x0104
#define IPMSG_LISTGETRETRY_TIMER0x0105

#define HS_TOOLS"HSTools"
#define IP_MSG"IPMsg"
#define NO_NAME"no_name"
#define URL_STR"://"
#define MAILTO_STR"mailto:"
#endif /* IPMSG_H */

报文中的命令字是一个32位无符号整数,包含命令(最低字节)和选项(高三字节)两部分
常用基本命令(带有BR标识的为广播命令),下边是一些重要的命令字。
IPMSG_NOOPERATION不进行任何操作
IPMSG_BR_ENTRY用户上线
IPMSG_BR_EXIT用户退出
IPMSG_ANSENTRY通报在线
IPMSG_SENDMSG发送消息
IPMSG_RECVMSG通报收到消息
IPMSG_GETFILEDATA请求通过TCP传输文件
IPMSG_RELEASEFILES停止接收文件
IPMSG_GETDIRFILES请求传输文件夹
在IPMSG上线时,首先发送的是IPMSG_NOOPERATION,默认是不做任何处理,然后上线通告报文IPMSG_BR_ENTRY。
用户列表通过链表来实现,看看结构体:

typedef struct use_date
{
char use_name[USE_NAME_LEN]; //用户名
char host_name[HOST_NAME_LEN]; //机器名
int id;//节点ID。
long int host_ip; //存储IP信息,避免重复添加
struct sockaddr_in inet; //存储网络信息
struct use_data *next;
}IPMSG_USE;

每次IPMSG在收到上线通告报文后,都要查找相同ip的节点是否已经存在,只要和结构体成员host_ip比较就可以了,这样整个用户列表当中的成员是不会重复的。报文的发送主要依靠下边的函数实现,这里推荐下边的这种写法,特别是对与命令比较多的情况下,使用下边的好处就在与结构非常的清晰。

mode: 命令 msg: 附加信息 struct sockaddr *p:网络信息 fd:网络套接字描述符

int msg_send(const int mode,const char *msg,const struct sockaddr *p,int fd)
{
int udp_fd=fd;
int broadcast_en=1;
char msg_buf[SND_BUF_LEN];
char *use="test",*group="sunplusapp";
socklen_t broadcast_len=sizeof(broadcast_en);
long int msg_id=time((time_t *)NULL);
struct sockaddr_in udp_addr;
struct sockaddr client;
bzero(msg_buf,SND_BUF_LEN);
bzero(&udp_addr,sizeof(struct sockaddr_in));
udp_addr.sin_family=AF_INET;
udp_addr.sin_port=htons(IPMSG_UDP_PORT);
inet_pton(AF_INET,BR_IP,&udp_addr.sin_addr.s_addr);

//下边的if 与else if :对于上线通告 下线等使用广播地址,其他的则否
if( (p==NULL)&&(mode!=IPMSG_NOOPERATION)&&(mode!=IPMSG_BR_ENTRY)&&(mode!=IPMSG_BR_EXIT))
{
printf("p is NULL,only mode = IPMSG_NOOPERATICNA IPMSG_BR_ENTRY IPMSG_EXIT is allowed p=NULL /n");
return 1;
}
else if( (p!=NULL)&&(mode!=IPMSG_NOOPERATION)&&(mode!=IPMSG_BR_ENTRY)&&(mode!=IPMSG_BR_EXIT))
client=*p;

//打开广播
if( setsockopt(udp_fd,SOL_SOCKET,SO_BROADCAST,&broadcast_en,broadcast_len)<0 )
{
perror("setsockopt error");
exit(1);
}
switch (mode)
{
case IPMSG_NOOPERATION:
sprintf(msg_buf,"1:%d:%s:%s:%d:%s",msg_id,use,group,mode,NULL);
sendto(udp_fd,msg_buf,strlen(msg_buf),0,(struct sockaddr *)&udp_addr,sizeof(struct sockaddr));
break;
case IPMSG_BR_ENTRY:
sprintf(msg_buf,"1:%d:%s:%s:%d:%s",msg_id,use,group,mode,msg);
sendto(udp_fd,msg_buf,strlen(msg_buf),0,(struct sockaddr *)&udp_addr,sizeof(struct sockaddr));
break;
case IPMSG_BR_EXIT:
sprintf(msg_buf,"1:%d:%s:%s:%d:%s",msg_id,use,group,mode,msg);
sendto(udp_fd,msg_buf,strlen(msg_buf),0,(struct sockaddr *)&udp_addr,sizeof(struct sockaddr));
break;
case IPMSG_ANSENTRY:
sprintf(msg_buf,"1:%d:%s:%s:%d:%s",msg_id,use,group,mode,msg);
sendto(udp_fd,msg_buf,strlen(msg_buf),0,&client,sizeof(struct sockaddr));
break;
case IPMSG_SENDMSG:
sprintf(msg_buf,"1:%d:%s:%s:%d:%s",msg_id,use,group,mode,msg);
sendto(udp_fd,msg_buf,strlen(msg_buf),0,&client,sizeof(struct sockaddr));
break;
case IPMSG_SENDMSG_OPT:
sprintf(msg_buf,"1:%d:%s:%s:%d:%s",msg_id,use,group,mode,msg);
sendto(udp_fd,msg_buf,strlen(msg_buf),0,&client,sizeof(struct sockaddr));
break;
case IPMSG_RECVMSG:
sprintf(msg_buf,"1:%d:%s:%s:%d:%s",msg_id,use,group,mode,msg);
sendto(udp_fd,msg_buf,strlen(msg_buf),0,&client,sizeof(struct sockaddr));
break;
case IPMSG_GETFILEDATA:
sprintf(msg_buf,"1:%d:%s:%s:%d:%s",msg_id,use,group,mode,msg);
sendto(udp_fd,msg_buf,strlen(msg_buf),0,&client,sizeof(struct sockaddr));
break;
case IPMSG_RELEASEFILES:
sprintf(msg_buf,"1:%d:%s:%s:%d:%s",msg_id,use,group,mode,msg);
sendto(udp_fd,msg_buf,strlen(msg_buf),0,&client,sizeof(struct sockaddr));
break;
case IPMSG_GETDIRFILES:
sprintf(msg_buf,"1:%d:%s:%s:%d:%s",msg_id,use,group,mode,msg);
sendto(udp_fd,msg_buf,strlen(msg_buf),0,&client,sizeof(struct sockaddr));
break;
default:
printf("no match mode !/n");
break;
}
broadcast_en=0;

// 关掉广播
if( setsockopt(udp_fd,SOL_SOCKET,SO_BROADCAST,&broadcast_en,broadcast_len)<0 )
{
perror("setsockopt error");
exit(1);
}
printf("msg send ok ! /n");
return 0;
}

通过上边的报文就可以实现消息的传递,可以发起文件、文件夹的传输,传输文件时,首先需要通过UDP报文联络,在UDP报文联络好之后,随即发起TCP文件传输,文件传输是不带格式的。IPMSG的一个难点就是文件夹的传输。今天就写这里,而且也做到这里。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics