一、 IPMSG通信协议介绍 声明:下述协议内容略去了一些本程序中没有用到协议内容,最初的Ipmsg协议是用日文写的,下面协议内容由本人翻译自Mr.Kanazawa的英文文档。 IP信使传输协议(第9版草案) 1996/02/21 2003/01/14 修订 H.Shirouzu [email protected] 关于IP信使: IP信使使用TCP/UDP协议提供收发消息及文件(目录)。 特性: IP信使能够安装在任何一个安装了TCP/IP协议栈的操作系统上,使用在线用户的动态识别机制,可以和在线所有用户进行信息交换。 运行机制介绍: 使用TCP/UDP端口(默认端口为2425),消息的收发使用UDP协议,文件(文件夹)的收发使用TCP协议。 1、 命令字: 1) 基本命令字(32位命令字的低8位) IPMSG_NOOPERATION 不进行任何操作 IPMSG_BR_ENTRY 用户上线 IPMSG_BR_EXIT 用户退出 IPMSG_ANSENTRY 通报在线 IPMSG_SENDMSG 发送消息 IPMSG_RECVMSG 通报收到消息 IPMSG_GETFILEDATA 请求通过TCP传输文件 IPMSG_RELEASEFILES 停止接收文件 IPMSG_GETDIRFILES 请求传输文件夹 2) 选项位(32位命令字的高24位) IPMSG_SENDCHECKOPT 传送检查(需要对方返回确认信息) IPMSG_FILEATTACHOPT 传送文件选项 3) 附件类型命令(文件类型命令字的低8位) IPMSG_FILE_REGULAR 普通文件 IPMSG_FILE_DIR 目录文件 IPMSG_FILE_RETPARENT 返回上一级目录 2、 数据包格式(使用字符串): 1) 数据包格式(版本1的格式) 版本号(1):包编号:发送者姓名:发送者主机名:命令字:附加信息 2) 举例如下 “1:100:shirouzu:Jupiter:32:Hello” 3、 数据包处理总述: 1) 用户识别 当IPMSG启动时,命令IPMSG_BR_ENTRY被广播到网络中,向所有在线的用户提示一个新用户的到达(即表示“我来了”);所有在线用户将把该新上线用户添加到自己的用户列表中,并向该新上线用户发送IPMSG_ANSENTRY命令(即表示“我在线”);该新上线用户接收到IPMSG_ANSENTRY命令后即将在线用户添加到自己的用户列表中。 2) 收发消息 使用IPMSG_SENDMSG命令发送消息,消息内容添加在附加信息中;在接收消息时,如果对方要求回信确认(IPMSG_SENDCHECKOPT位打开),则需发送IPMSG_RECVMSG命令并将对方发送的数据包的编号放在附加信息中一同发送至发送消息方 3) 附加文件的扩充(添加于第9版) 带有IPMSG_FILEATTACHOPT位的IPMSG_SENDMSG命令可用来传输文件,文件属性及内容添加在附加信息中,文件内容添加在消息内容后并以’/0’与之分隔开。传输文件时以下信息将被添加到消息内容之后(包括格式):文件序号:文件名:大小(单位:字节):最后修改时间:文件属性[:附加属性=val1[,val2…][:附加信息=…]]:/a:文件序号… (文件大小、最后修改时间和文件属性为十六进制数,如果文件名中包含’:’则使用“::”代替)。 接收端开始接收文件时,请求传输文件命令IPMSG_GETFILEDATA将发送到发送端的TCP端口(和UDP的发送端口相同),并将发送端发送的包编号:文件序号:偏移量(全为十六进制格式)写到附加信息区一同发送,文件发送端接收到该请求信息并进行校验正确后即开始发送文件(不使用任何格式,亦不进行加密)。 当接收端接收到目录文件时,将发送附加信息区为发送端发送的包编号:文件序号:偏移量(全为十六进制格式)的IPMSG_GETDIRFILES命令,以用来请求传输目录文件;发送端则将头信息长度:文件名:文件大小:文件属性:文件内容添加到附加信息区(除了文件名和文件内容外,其余皆为十六进制),头信息长度是从头信息长度开始到文件内容前的‘:’分割符为止的字符个数。 当文件属性为IPMSG_FILE_DIR时,IPMsg能够自动识别其为目录,下一个文件的数据在该目录之后。 当文件属性为IPMSG_FILE_RETPARENT时,IPMsg识别其动作为返回上一级目录,在这种情况下,文件名为‘.’其属性为当前目录的值。 到此本程序所用到的协议已经叙述完毕,由于实现的功能较简单,故删减了一些原IPMSG协议的内容,完整的IPMSG协议请参照如下网址: http://linuxopen.cugb.edu.cn/Forum/read.php?tid=283 二、 程序实现(Linux下的socket编程) 程序源代码请到 http://linuxopen.cugb.edu.cn/Forum/read.php?tid=453下载,下面简要叙述一下程序设计结构和实现 1、数据结构定义 1) 在线用户信息数据结构,用于存放在线用户信息,并组成用户列表(使用双向链表实现) typedef struct ipmsguser { int user_num; /*在线用户编号*/ struct sockaddr_in user_addr; char user_name[MAX_USER_NAME_LENGTH]; char user_group[MAX_USER_GROUP_LENGTH]; char user_machine[HOSTNAME_LENGTH]; char append[MAXSOCKBUF]; char user_nick[MAX_USER_NAME_LENGTH]; struct ipmsguser * next; struct ipmsguser * pre; }ipmsg_user_list; 2) 发送的UDP数据包结构(按照数据包格式设计) typedef struct ipmsgdata { int version; unsigned int packetno; char sendername[MAX_USER_NAME_LENGTH]; char *ip; char senderhost[HOSTNAME_LENGTH]; unsigned int com; char append[MAX_APPEND_LEN]; char sendfileheader[MAX_SEND_FILE_HEADER_LEN]; }ipmsgdata; 3) 传输文件的首部信息结构(按照文件传输协议定义) typedef struct transfileheader { unsigned int fileID; char filename[PATH_MAX]; unsigned long long size; unsigned int mtime; unsigned long fileattr; }transfileheader; 4) 传输目录的首部信息结构(按照目录传输协议定义) typedef struct transfolderheader { int headersize; char filename[PATH_MAX]; unsigned long long filesize; unsigned long fileattr; time_t filemtime; //文件/文件夹的修改和创建时间 time_t filectime; }transfolderheader; 5) 发送多个文件/目录时用于记录信息的结构(用于向发送文件/目录线程传送的参数) typedef struct sendfilearg { int filenum; int flags; /*flags用来记录发送到的目的地的形式:1为序号,2为all,3为IP,4为用户名*/ char filename[MAXFILESEND][PATH_MAX]; //文件/文件夹名 unsigned long fileattr[MAXFILESEND]; /*发送的文件的属性,为标志量,*为0时表示对应的文件名为文件,为1表示对应为目录*/ char to[20]; //目的地 }sendfilearg; 2、函数说明 int command(char *cmd);//处理用户输入的命令 void ipmsgswitch(ipmsgdata * data, char * buff);//解析接收到UDP数据 void ipmsgsendbcast(struct sockaddr_in *dest, unsigned int com, ipmsgdata *data);//发送UDP数据 ipmsg_user_list *adduser(struct sockaddr_in *useraddrs, struct ipmsguser *q, ipmsgdata *data);//添加用户于用户列表 char user_exist(struct sockaddr_in *useraddr, ipmsgdata *data); //判断用户是否存在于用户列表 void *watch_net(void *arg); //监控网络线程的回调函数,用于接收UDP数据 void del_user(struct sockaddr_in *useraddr, ipmsgdata *data);//删除用户 void del_all_user();//清空用户列表 ipmsg_user_list *search_user(int *usernum, struct in_addr *useraddr, char *username);//查找某个用户于用户列表 void *getfiles(void *arg);//接收文件/目录线程的回调函数 void *sendfiles(void *arg);//发送文件/目录线程的回调函数 int senddir(char dir[], int socksendfd);//遍历目录树并通过TCP发送目录内容 3、总体设计 使用多线程编程技术,一个线程监控网络,用来实时更新用户列表和显示消息;一个线程用来和用户交互,处理用户输入的命令;当接收文件时还会生成一个线程,用于接收文件/目录并存盘;当发送文件/目录时也会生成一个线程,用于发送文件/目录;这就是多线程的好处,可以并行的完成几件事情。在使用多线程编写网络程序时,尤其要注意线程同步问题以及TCP和UDP协议的性质(即TCP为流式协议,UDP为用户数据报协议)。 4、 细节方面的简要说明 在编写网络程序时,处理字符串是经常碰到的问题,TCP流式协议的特性亦应引起高度重视,不过只要记住这一点,以后在这方面就不会遇到大麻烦,现介绍一下TCP协议的特性,首先TCP协议发送的是字符流,一个TCP包的长度不定,最大为程序员指定的大小,最小可能为0,而且应用层使用send发送的数据可能被内核重新装备再进行发送,所以可能出现这样的情况:多个send语句发送的内容组成了一个TCP包发送给对方,而recv语句则可能接收到0长度的数据,所以在处理接收到数据时应谨慎对待,因为每次recv到的数据并不是想象中的数据长度和数据格式,很可能错位,所以程序需要对数据进行全面的分析。 网络程序很多都使用多线程实现,因为线程只独立拥有栈空间共享堆空间,所以在使用库函数或系统调用时尤其要注意使用的函数是否是线程安全的,如果不是线程安全的,请使用对应的线程安全的函数或自己编写完成同样功能的线程安全的函数。这里介绍两个非线程安全的函数,他们在编写多进程程序时是可以安全的使用的:readdir、strtok,其中readdir为Linux/Unix的系统调用,strtok为标准C库函数,这两个函数都使用了静态变量(由static修饰),故为非线程安全的,而进程拥有独立的堆空间和独立的栈空间,故对进程而言他们是安全的,对应于readdir的线程安全函数为readdir_r,其为POSIX标准函数,在绝大多数UNIX/Linux系统上都可以使用,对应于strtok的线程安全函数我没有找到,但我根据strtok的glibc的实现自己写了个线程安全的函数(mystrtok),其取消对静态变量的依赖,故为线程安全的。 还有就是开发网络程序,特别需要注意的是编写安全的网络程序,写的网络程序不要成为对方攻击的目标,这里要提醒注意的是在使用一些容易引起内存溢出的函数时需要考虑是否有其他函数(不会引起溢出的)可用呢?这里列举几个,提起大家的注意,sprintf、strcpy、strcat这三个函数由于在操作时不检查是否越界,所以请考虑使用snprintf、strncpy、strncat代替之。 5、 有待改进的地方 还有很多地方有待改进,win版的IPMSG的功能还有很多,其命令字亦有不少,这里只使用其中较少的几个,主要是为了完成文件/目录的收发功能,而且程序肯定还有很多bug,尚需时日进行调试,这次发布的只是版本1,以后还会有版本2、版本3。 Linux下的IPMSG实现就介绍到这儿,具体代码请到论坛下载,如遇问题请回帖指出,如有不同意见欢迎交流。 |