Linux网络编程——基于tcp/ip的模拟聊天(文件传输)工具

开发平台:Linux
开发工具:Ubuntu, sourceInsight4.0
项目介绍: 本项目基于TCP/IP协议创建一个网络通信系统,可以实现客户之间的聊天通信以及文件传输,同时利用进程实现多客户群聊,多个客户也可同时从服务器下载文件实现文件共享,客户可向服务器发送ls命令获取服务器端的文件目录,并发送get+filename获取文件,也可发送put+filename上传文件到服务器。
protocol.h

#ifndef __PROTOCOL_H__
#define __PROTOCOL_H__

#define FTP_ROOT_DIR   "/home/gec/tftp"


//命令号, 命令参数


//命令号->  整数
enum CMD_NO 
{
	FTP_CMD_LS = 1024, //
	FTP_CMD_GET,
	FTP_CMD_PUT,
	FTP_CMD_BYE,
	
	FTP_CMD_NUM // 命令个数
};

//出错码
enum resp_result
{
	RESP_SUCCESS = 0, //成功
	RESP_PACK_ERROR, //失败,包的长度不对
	RESP_PACK_NOEND, //包没有结束,可以再次收包
	RESP_PACK_NOFILE //没有可以获取的文件
};


//参数: 参数长度, 参数内容



/*
	0xc0 : 包头

	pkg_len ;//4bytes, 小端模式,整个数据包的长度
				 4(pkg_len) + 4 (cmd_no) + arg_1 +...
	cmd_no // 4bytes, 小端模式

	arg_1;
		arg_1_len; //4bytes , 小端模式
		arg_1_data; len长度
	arg_2:
		arg_2_len; //4bytes,小端模式
		arg_2_data;
	....

	0xC0:包尾
*/

// cmd: ls

//0xc0  ___包长度______    __命令号()__             0xc0
// 0xc0  0x80 0x00 0x00 0x00 


//cmd: get
//0xc0 ____包长度(4)___ ___命令号(4)___   ___arg_1_Len(4)_   ___filename(r)___ 0xc0




#if 0
unsigned char cmd[1024];
int i = 0;

int pkg_len = 1024;

cmd[0] = 0xc0;

cmd[1] = pkg_len & 0xff;
cmd[2] = ( pkg_len >> 8) & 0xff;
cmd[3] = (pkg_len >> 16) & 0xff;
cmd[4] = (pkg_len >> 24) & 0xff;
#endif



//CMD_RESP
/*
	0xc0: 包头

	pkg_len: 整个数据包的长度,4bytes, 小端模式
	cmd_no :  命令号,4bytes, 小端模式,表示回复哪条命令
	resp_len: 回复内容的长度,4bytes, 小端模式
			result  + resp_conent
			1 + x
	result:  执行成功或失败,1 bytes , 0表示成功,其他表示失败
	resp_conent: 回复内容
			成功:
				ls: 文件名字  各文件名之间用空格分隔
				get: 文件大小,4bytes, 小端模式

			失败:
				 出错码

	0xc0:包尾
*/


//ls 的回复
// 0xc0  --pkg_Len(4)---   ---cmd_No(4)---  --resp_len(4)--  --result(1)--  --filenames(r, 名字以空格分开)--- 0xc0


//get 的回复
//0xc0  ---pkg_len(4)---  ----cmd_no(4)---  ---resp_len(4)---   ---result(1)--  --file_size(4, 小端模式)--  0xc0

#endif

tcp_client.c

#include           /* See NOTES */	
#include 
#include 	   
#include 	   
#include 			   
#include 
#include 
#include 
#include 
#include 
#include 
#include "protocol.h"


int connect_server(char *ip,int port)
{
/**1.创建套接字**/
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(-1 == sockfd)
	{
		perror("socket error");
		return -1;
	}
	
/**2.发起链接请求**/
	int ret;
	struct sockaddr_in sAddr;
	memset(&sAddr,0,sizeof(sAddr));
	sAddr.sin_family = AF_INET;
	sAddr.sin_port = htons(port);
	sAddr.sin_addr.s_addr = inet_addr(ip);
	ret = connect(sockfd,(struct sockaddr*)&sAddr,sizeof(sAddr));
	if(-1 ==ret)
	{
		perror("conncet error");
		return -1;
	}
	//
	return sockfd;
}

void send_cmd(int sockfd,unsigned char *cmd,int len)
{
	//
	int ret = send(sockfd,cmd,len,0);
	if(-1 == ret)
	{
		perror("send cmd error");
		return;
	}
	//
}

void recv_ls_val(int sockfd)
{
	/**
		服务器回复数据,事先会回复一个数据包
			0xc0  		 	L E N S   			 E R R N 			  S I Z E 			 0xc0 
						回复数据包长度		回复的验证信息			后续正文大小
		紧接着服务器会回复正文
			size个字节的数据
	**/
	
	int i=0;
	unsigned char ch = 0;
	unsigned char cmd[500] = {0};
/*************************************接收回复数据包*******************************************************/	
	//命令以0xc0开头,所以如果收到的不是0xc0则舍弃不要
	do
	{
		//
		read(sockfd,&ch,1);
		perror("read ls");
	}while(ch != 0xc0);
	//
	//排除连续的0xc0
	while(ch == 0xc0)
	{
		read(sockfd,&ch,1);
	}//确保读取的是数据包的第一个字节
	//
	while(ch != 0xc0)//读到的是数据包的内容,读到包尾结束
	{
		cmd[i++] = ch;
		read(sockfd,&ch,1);
	}
/***************************************数据包接收完成*****************************************************/	
	
	
/******************************************解析数据包******************************************************/	
	//解析包长
	int cmd_len = cmd[0] | cmd[1]<<8 | cmd[2]<<16 | cmd[3]<<24;
	if(cmd_len != i)
	{
		printf("read value error,the pack len is no right\n");
		//send_error(connfd);
		return;
	}
	
	//解析验证信息
	int err_no = cmd[4] | cmd[5]<<8 | cmd[6]<<16 | cmd[7]<<24;
	if(err_no == RESP_PACK_ERROR)
	{
		printf("cmd error\n");
		return;
	}
	
	int data_len = cmd[8] | cmd[9]<<8 | cmd[10]<<16 | cmd[11]<<24;
	printf("recv data len:%d\n",data_len);
/*****************************************解析完毕**********************************************************/	



/****************************************接收回复正文*******************************************************/
	memset(cmd,0,300);
	printf("data_len %d\n",data_len);
	recv(sockfd,cmd,data_len,0);
	printf("recv data is:%s\n",cmd);
	
}

void send_ls(int sockfd)
{
	/*
		ls 命令的数据包:
		0xc0  		 L E N S				C M D S        0xc0
				4个字节表示包的长度		4个字节表示命令
	*/
	int i = 0;
	int pak_len = 4+4;
	unsigned char cmd[50] ={0};
	
/**********************************组合数据包***************************************************/
	/**帧头**/
	cmd[i++] = 0xc0;
	
	/**包长**/
	cmd[i++] = pak_len & 0xff;
	cmd[i++] = pak_len>>8 & 0xff;
	cmd[i++] = pak_len>>16 & 0xff;
	cmd[i++] = pak_len>>24 & 0xff;

	/**命令**/
	cmd[i++] = FTP_CMD_LS & 0xff;
	cmd[i++] = FTP_CMD_LS>>8 & 0xff;
	cmd[i++] = FTP_CMD_LS>>16 & 0xff;
	cmd[i++] = FTP_CMD_LS>>24 & 0xff;
	
	/**帧尾**/
	cmd[i++] = 0xc0;
/******************************数据包组合完毕***************************************************/
	
	/**发送命令**/
	send_cmd(sockfd,cmd,i); //将数据包发送出去后,等待服务器的处理结果
	
	/**接收返回结果**/
	recv_ls_val(sockfd);

}


void recv_get_val(int sockfd,int fd)
{
	/**
		服务器回复数据,事先会回复一个数据包
			0xc0  		 	L E N S   			 E R R N 			  S I Z E 			 0xc0 
						回复数据包长度		回复的验证信息			后续正文大小
		紧接着服务器会回复正文
			size个字节的数据
	**/
	
	int i=0;
	unsigned char ch = 0;
	unsigned char cmd[300] = {0};
/*************************************接收回复数据包*******************************************************/	
	//命令以0xc0开头,所以如果收到的不是0xc0则舍弃不要
	do
	{
		//
		read(sockfd,&ch,1);
	}while(ch != 0xc0);
	
	//排除连续的0xc0
	while(ch == 0xc0)
	{
		read(sockfd,&ch,1);
	}//确保读取的是数据包的第一个字节
	
	while(ch != 0xc0)//读到的是数据包的内容,读到包尾结束
	{
		cmd[i++] = ch;
		read(sockfd,&ch,1);
	}
/***************************************数据包接收完成*****************************************************/	
	
	
/******************************************解析数据包******************************************************/	
	//解析包长
	int cmd_len = cmd[0] | cmd[1]<<8 | cmd[2]<<16 | cmd[3]<<24;
	if(cmd_len != i)
	{
		printf("read value error,the pack len is no right\n");
		//send_error(connfd);
		return;
	}
	
	//解析验证信息
	int err_no = cmd[4] | cmd[5]<<8 | cmd[6]<<16 | cmd[7]<<24;
	if(err_no == RESP_PACK_ERROR)
	{
		printf("cmd error\n");
		return;
	}
	
	int data_len = cmd[8] | cmd[9]<<8 | cmd[10]<<16 | cmd[11]<<24;
	printf("recv data len:%d\n",data_len);
/*****************************************解析完毕**********************************************************/	
	
/*****************************************接收文件数据******************************************************/
	unsigned char *p = (unsigned char *)malloc(data_len+1);
	int ret=0;
	while(1)
	{
		
		ret += read(sockfd,p+ret,data_len);
		if(ret == data_len)
		{
			break;
		}
		else if(ret == -1 || ret == 0)
		{
			perror("read error");
			return;
		}
		
	}
	
	write(fd,p,data_len);//将正文写入本地文件
	
/********************************************判断文件是否发送完成*******************************************************/
	if(err_no == RESP_PACK_NOEND) //文件内容没有发完,会在后续发送
	{
		
		recv_get_val(sockfd,fd); //再次接收后续包
	}
}


void send_get(int sockfd,char *file)
{
	/**
		get命令包格式:
			0xc0     L E N S      C M D S   	    S I Z E   			A R G S . . . .			0xc0
					包长(4bytes)  命令号(6bytes)    参数长度(4bytes)	参数正文(size bytes)
	**/
	int arg_len = strlen(file);
	int pak_len = 		4	+		4	+				4	+ 			arg_len;
	unsigned char cmd[50] = {0}; //保存命令数据包
	int i=0;
/****************************************封装命令包****************************************************/
	/*包头*/
	cmd[i++] = 0Xc0;
	
	/*包长 小端模式*/
	cmd[i++] = pak_len & 0xff;
	cmd[i++] = pak_len>>8 & 0xff;
	cmd[i++] = pak_len>>16 & 0xff;
	cmd[i++] = pak_len>>24 & 0xff;
	
	/*命令号  小端模式*/
	cmd[i++] = FTP_CMD_GET & 0xff;
	cmd[i++] = FTP_CMD_GET>>8 & 0xff;
	cmd[i++] = FTP_CMD_GET>>16 & 0xff;
	cmd[i++] = FTP_CMD_GET>>24 & 0xff;
	
	/*参数长度*/
	cmd[i++] = arg_len & 0xff;
	cmd[i++] = arg_len>>8 & 0xff;
	cmd[i++] = arg_len>>16 & 0xff;
	cmd[i++] = arg_len>>24 & 0xff;
	
	/*参数正文*/
	int j=0;
	for(j=0;j>8 & 0xff;
		cmd[i++] = pak_len>>16 & 0xff;
		cmd[i++] = pak_len>>24 & 0xff;
		
		/*回复码*/
		if(ret < file_len) //文件没完
		{
			cmd[i++] = RESP_PACK_NOEND & 0xff;
			cmd[i++] = RESP_PACK_NOEND>>8 & 0xff;
			cmd[i++] = RESP_PACK_NOEND>>16 & 0xff;
			cmd[i++] = RESP_PACK_NOEND>>24 & 0xff;
		}
		else
		{
			cmd[i++] = RESP_SUCCESS & 0xff;
			cmd[i++] = RESP_SUCCESS>>8 & 0xff;
			cmd[i++] = RESP_SUCCESS>>16 & 0xff;
			cmd[i++] = RESP_SUCCESS>>24 & 0xff;
		}
		
		/*正文长度*/
		cmd[i++] = size & 0xff;
		cmd[i++] = size>>8 & 0xff;
		cmd[i++] = size>>16 & 0xff;
		cmd[i++] = size>>24 & 0xff;
		
		/*帧尾*/
		cmd[i++] = 0xc0;
		
		send_cmd(sockfd,cmd,i); //回复数据包
		
		send(sockfd,buf,size,0); //回复正文
	}
} 

void send_put(int sockfd,char *file)
{
	/**
		get命令包格式:
			0xc0     L E N S      C M D S   	    S I Z E   			A R G S . . . .			0xc0
					包长(4bytes)  命令号(6bytes)    参数长度(4bytes)	参数正文(size bytes)
	**/
	int arg_len = strlen(file);
	int pak_len = 		4	+		4	+				4	+ 			arg_len;
	unsigned char cmd[50] = {0}; //保存命令数据包
	int i=0;
/****************************************封装命令包****************************************************/
	/*包头*/
	cmd[i++] = 0Xc0;
	
	/*包长 小端模式*/
	cmd[i++] = pak_len & 0xff;
	cmd[i++] = pak_len>>8 & 0xff;
	cmd[i++] = pak_len>>16 & 0xff;
	cmd[i++] = pak_len>>24 & 0xff;
	
	/*命令号  小端模式*/
	cmd[i++] = FTP_CMD_PUT & 0xff;
	cmd[i++] = FTP_CMD_PUT>>8 & 0xff;
	cmd[i++] = FTP_CMD_PUT>>16 & 0xff;
	cmd[i++] = FTP_CMD_PUT>>24 & 0xff;
	
	/*参数长度*/
	cmd[i++] = arg_len & 0xff;
	cmd[i++] = arg_len>>8 & 0xff;
	cmd[i++] = arg_len>>16 & 0xff;
	cmd[i++] = arg_len>>24 & 0xff;
	
	/*参数正文*/
	int j=0;
	for(j=0;j

tcp_server.c

#include    
#include        /* See NOTES */	
#include 	   
#include 		   
#include 			   
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "protocol.h"

#define FTP_DIR "/home/gec/tftp"

void send_pack(int connfd,unsigned char *cmd,int len)
{
	//
	printf("%d %x\n",connfd,cmd[0]);
	int ret = send(connfd,cmd,len,0);
	if(-1 == ret)
	{
		perror("send cmd error");
		return;
	}
	//
}

void send_error(int connfd)
{
	int i = 0;
	int pak_len = 4+4;
	unsigned char cmd[50] ={0};
	
	/**帧头**/
	cmd[i++] = 0xc0;
	
	/**包长**/
	cmd[i++] = pak_len & 0xff;
	cmd[i++] = pak_len>>8 & 0xff;
	cmd[i++] = pak_len>>16 & 0xff;
	cmd[i++] = pak_len>>24 & 0xff;

	/**错误码**/
	cmd[i++] = RESP_PACK_ERROR & 0xff;
	cmd[i++] = RESP_PACK_ERROR>>8 & 0xff;
	cmd[i++] = RESP_PACK_ERROR>>16 & 0xff;
	cmd[i++] = RESP_PACK_ERROR>>24 & 0xff;
	
	/**帧尾**/
	cmd[i++] = 0xc0;
	
	/**发送回复数据**/
	send_pack(connfd,cmd,i);
}

void repa_ls(int connfd)
{
	int i = 0;
	int pak_len = 0;
	unsigned char file[500] = {0};
	unsigned char cmd[500] = {0};
	DIR *dirp = opendir(FTP_DIR);
	if(NULL == dirp)
	{
		perror("open dir error");
		return ;
	}
	//
	struct dirent *dir;
	while((dir = readdir(dirp)) != NULL)
	{
		sprintf(file+strlen(file),"%s ",dir->d_name);
		printf("%d %s\n",strlen(file),dir->d_name);
	}
	//
	/**帧头**/
	cmd[i++] = 0xc0;
	
	/**包长**/
	pak_len = 4 + 4 + 4;
	cmd[i++] = pak_len & 0xff;
	cmd[i++] = pak_len>>8 & 0xff;
	cmd[i++] = pak_len>>16 & 0xff;
	cmd[i++] = pak_len>>24 & 0xff;

	/**错误码**/
	cmd[i++] = RESP_SUCCESS & 0xff;
	cmd[i++] = RESP_SUCCESS>>8 & 0xff;
	cmd[i++] = RESP_SUCCESS>>16 & 0xff;
	cmd[i++] = RESP_SUCCESS>>24 & 0xff;
	
	/**回复数据长度**/
	cmd[i++] = strlen(file) & 0xff;
	cmd[i++] = strlen(file)>>8 & 0xff;
	cmd[i++] = strlen(file)>>16 & 0xff;
	cmd[i++] = strlen(file)>>24 & 0xff;
	printf("recv ls data size %d\n",strlen(file));
	printf("%s\n",file);
	/**帧尾**/
	cmd[i++] = 0xc0;
	send_pack(connfd,cmd,i); //回复命令的处理结果
	
	send_pack(connfd,file,strlen(file));//回复命令数据
	//
}


void send_file_data(int connfd,int fd,int file_len)
{
	/*
		L E N S    E R R N   S I Z E
	*/
	
	unsigned char cmd[50] = {0};
	unsigned char buf[256] = {0};
	int pak_len;
	int size;
	int i;
	int ret = 0;
	while(1)
	{
		size = read(fd,buf,256);
		if(size == 0 || size==-1)
		{
			perror("read error");
			break;
		}
		pak_len = 4 + 4 + 4;
		ret += size;
		/*帧头*/
		i = 0;
		cmd[i++] = 0xc0;
		
		/*包长 小端模式*/
		cmd[i++] = pak_len & 0xff;
		cmd[i++] = pak_len>>8 & 0xff;
		cmd[i++] = pak_len>>16 & 0xff;
		cmd[i++] = pak_len>>24 & 0xff;
		
		/*回复码*/
		if(ret < file_len) //文件没完
		{
			cmd[i++] = RESP_PACK_NOEND & 0xff;
			cmd[i++] = RESP_PACK_NOEND>>8 & 0xff;
			cmd[i++] = RESP_PACK_NOEND>>16 & 0xff;
			cmd[i++] = RESP_PACK_NOEND>>24 & 0xff;
		}
		else
		{
			cmd[i++] = RESP_SUCCESS & 0xff;
			cmd[i++] = RESP_SUCCESS>>8 & 0xff;
			cmd[i++] = RESP_SUCCESS>>16 & 0xff;
			cmd[i++] = RESP_SUCCESS>>24 & 0xff;
		}
		
		/*正文长度*/
		cmd[i++] = size & 0xff;
		cmd[i++] = size>>8 & 0xff;
		cmd[i++] = size>>16 & 0xff;
		cmd[i++] = size>>24 & 0xff;
		
		/*帧尾*/
		cmd[i++] = 0xc0;
		
		send_pack(connfd,cmd,i); //回复数据包
		
		send(connfd,buf,size,0); //回复正文
	}
} 


void repa_get(int connfd,unsigned char *cmd)
{
/************************************解析get参数*******************************************************/
	int i = 8;
	int arg_len =  cmd[8] | cmd[9]<<8 | cmd[10]<<16 | cmd[11]<<24;
	char *file = (char *)malloc(arg_len+1);
	memset(file,0,arg_len+1);
	for(i=0;i>8 & 0xff;
		cmd[i++] = pak_len>>16 & 0xff;
		cmd[i++] = pak_len>>24 & 0xff;
		
		/*回复码*/
		cmd[i++] = RESP_PACK_NOFILE & 0xff;
		cmd[i++] = RESP_PACK_NOFILE>>8 & 0xff;
		cmd[i++] = RESP_PACK_NOFILE>>16 & 0xff;
		cmd[i++] = RESP_PACK_NOFILE>>24 & 0xff;
		
		/*帧尾*/
		cmd[i++] = 0xc0;
		send_pack(connfd,cmd,i);
	}
	else
	{
		
		int file_len = lseek(fd,0,SEEK_END); //获取文件大小
		lseek(fd,0,SEEK_SET);
		send_file_data(connfd,fd,file_len); //回复文件内容
		
		close(fd);
	}
	
}
void recv_get_val1(int connfd,int fd)
{
	/**
		服务器回复数据,事先会回复一个数据包
			0xc0  		 	L E N S   			 E R R N 			  S I Z E 			 0xc0 
						回复数据包长度		回复的验证信息			后续正文大小
		紧接着服务器会回复正文
			size个字节的数据
	**/
	
	int i=0;
	unsigned char ch = 0;
	unsigned char cmd[300] = {0};
/*************************************接收回复数据包*******************************************************/	
	//命令以0xc0开头,所以如果收到的不是0xc0则舍弃不要
	do
	{
		//
		read(connfd,&ch,1);
	}while(ch != 0xc0);
	
	//排除连续的0xc0
	while(ch == 0xc0)
	{
		read(connfd,&ch,1);
	}//确保读取的是数据包的第一个字节
	
	while(ch != 0xc0)//读到的是数据包的内容,读到包尾结束
	{
		cmd[i++] = ch;
		read(connfd,&ch,1);
	}
/***************************************数据包接收完成*****************************************************/	
	
	
/******************************************解析数据包******************************************************/	
	//解析包长
	int cmd_len = cmd[0] | cmd[1]<<8 | cmd[2]<<16 | cmd[3]<<24;
	if(cmd_len != i)
	{
		printf("read value error,the pack len is no right\n");
		//send_error(connfd);
		return;
	}
	
	//解析验证信息
	int err_no = cmd[4] | cmd[5]<<8 | cmd[6]<<16 | cmd[7]<<24;
	if(err_no == RESP_PACK_ERROR)
	{
		printf("cmd error\n");
		return;
	}
	
	int data_len = cmd[8] | cmd[9]<<8 | cmd[10]<<16 | cmd[11]<<24;
	printf("recv data len:%d\n",data_len);
/*****************************************解析完毕**********************************************************/	
	
/*****************************************接收文件数据******************************************************/
	unsigned char *p = (unsigned char *)malloc(data_len+1);
	int ret=0;
	while(1)
	{
		
		ret += read(connfd,p+ret,data_len);
		if(ret == data_len)
		{
			break;
		}
		else if(ret == -1 || ret == 0)
		{
			perror("read error");
			return;
		}
		
	}
	
	write(fd,p,data_len);//将正文写入本地文件
	
/********************************************判断文件是否发送完成*******************************************************/
	if(err_no == RESP_PACK_NOEND) //文件内容没有发完,会在后续发送
	{
		
		recv_get_val1(connfd,fd); //再次接收后续包
	}

}

void repa_put(int connfd,unsigned char *cmd)
{
	int i = 8;
	int arg_len =  cmd[8] | cmd[9]<<8 | cmd[10]<<16 | cmd[11]<<24;
	char *file = (char *)malloc(arg_len+1);
	memset(file,0,arg_len+1);
	for(i=0;i 0)
		{
			close(connfd);
			continue;
		}
		else if(pid == 0)
		{
			//
			recv_cmd(connfd);
			exit(0);
		}
		
	}
/**7.处理并退出**/	
	close(sockfd);
	close(connfd);
	
	return 0;
}

你可能感兴趣的:(C语言,网络编程)