现在市面上有很多免费的FTP软件:如FileZilla ,那如果想自己在代码中实现与ftp服务器的上传下载文件该如何实现那?
本质上ftp协议就是TCP基础上建立的一种协议,具体如下。
文件传输协议(FTP)作为网络共享文件的传输协议,在网络应用软件中具有广泛的应用。FTP的目标是提高文件的共享性和可靠高效地传送数据。
在传输文件时,FTP 客户端程序先与服务器建立连接,然后向服务器发送命令。服务器收到命令后给予响应,并执行命令。FTP 协议与操作系统无关,任何操作系统上的程序只要符合 FTP 协议,就可以相互传输数据。本文主要基于 LINUX 平台,对 FTP 客户端的实现原理进行详尽的解释并阐述如何使用 C 语言编写一个简单的 FTP 客户端。
相比其他协议,如 HTTP 协议,FTP 协议要复杂一些。与一般的 C/S 应用不同点在于一般的C/S 应用程序一般只会建立一个 Socket 连接,这个连接同时处理服务器端和客户端的连接命令和数据传输。而FTP协议中将命令与数据分开传送的方法提高了效率。
FTP 使用 2 个端口,一个数据端口和一个命令端口(也叫做控制端口)。这两个端口一般是21 (命令端口)和 20 (数据端口)。控制 Socket 用来传送命令,数据 Socket 是用于传送数据。每一个 FTP 命令发送之后,FTP 服务器都会返回一个字符串,其中包括一个响应代码和一些说明信息。其中的返回码主要是用于判断命令是否被成功执行了。
一般来说,客户端有一个 Socket 用来连接 FTP 服务器的相关端口,它负责 FTP 命令的发送和接收返回的响应信息。一些操作如“登录”、“改变目录”、“删除文件”,依靠这个连接发送命令就可完成。
对于有数据传输的操作,主要是显示目录列表,上传、下载文件,我们需要依靠另一个 Socket来完成。
如果使用被动模式,通常服务器端会返回一个端口号。客户端需要用另开一个 Socket 来连接这个端口,然后我们可根据操作来发送命令,数据会通过新开的一个端口传输。
如果使用主动模式,通常客户端会发送一个端口号给服务器端,并在这个端口监听。服务器需要连接到客户端开启的这个数据端口,并进行数据的传输。
下面对 FTP 的主动模式和被动模式做一个简单的介绍。
主动模式下,客户端随机打开一个大于 1024 的端口向服务器的命令端口 P,即 21 端口,发起连接,同时开放N +1 端口监听,并向服务器发出 “port N+1” 命令,由服务器从它自己的数据端口 (20) 主动连接到客户端指定的数据端口 (N+1)。
FTP 的客户端只是告诉服务器自己的端口号,让服务器来连接客户端指定的端口。对于客户端的防火墙来说,这是从外部到内部的连接,可能会被阻塞。
为了解决服务器发起到客户的连接问题,有了另一种 FTP 连接方式,即被动方式。命令连接和数据连接都由客户端发起,这样就解决了从服务器到客户端的数据端口的连接被防火墙过滤的问题。
被动模式下,当开启一个 FTP 连接时,客户端打开两个任意的本地端口 (N > 1024 和 N+1) 。
第一个端口连接服务器的 21 端口,提交 PASV 命令。然后,服务器会开启一个任意的端口 (P > 1024 ),返回如“227 entering passive mode (127,0,0,1,4,18)”。 它返回了 227 开头的信息,在括号中有以逗号隔开的六个数字,前四个指服务器的地址,最后两个,将倒数第二个乘 256 再加上最后一个数字,这就是 FTP 服务器开放的用来进行数据传输的端口。如得到 227 entering passive mode (h1,h2,h3,h4,p1,p2),那么端口号是 p1*256+p2,ip 地址为h1.h2.h3.h4。这意味着在服务器上有一个端口被开放。客户端收到命令取得端口号之后, 会通过 N+1 号端口连接服务器的端口 P,然后在两个端口之间进行数据传输。
FTP 每个命令都有 3 到 4 个字母组成,命令后面跟参数,用空格分开。每个命令都以 "\r\n"结束。
要下载或上传一个文件,首先要登入 FTP 服务器,然后发送命令,最后退出。这个过程中,主要用到的命令有 USER、PASS、SIZE、REST、CWD、RETR、PASV、PORT、QUIT。
USER: 指定用户名。通常是控制连接后第一个发出的命令。“USER gaoleyi\r\n”: 用户名为gaoleyi 登录。
PASS: 指定用户密码。该命令紧跟 USER 命令后。“PASS gaoleyi\r\n”:密码为 gaoleyi。
SIZE: 从服务器上返回指定文件的大小。“SIZE file.txt\r\n”:如果 file.txt 文件存在,则返回该文件的大小。
CWD: 改变工作目录。如:“CWD dirname\r\n”。
PASV: 让服务器在数据端口监听,进入被动模式。如:“PASV\r\n”。
PORT: 告诉 FTP 服务器客户端监听的端口号,让 FTP 服务器采用主动模式连接客户端。如:“PORT h1,h2,h3,h4,p1,p2”。
RETR: 下载文件。“RETR file.txt \r\n”:下载文件 file.txt。
STOR: 上传文件。“STOR file.txt\r\n”:上传文件 file.txt。
REST: 该命令并不传送文件,而是略过指定点后的数据。此命令后应该跟其它要求文件传输的 FTP 命令。“REST 100\r\n”:重新指定文件传送的偏移量为 100 字节。
QUIT: 关闭与服务器的连接。
客户端发送 FTP 命令后,服务器返回响应码。
响应码用三位数字编码表示:
第一个数字给出了命令状态的一般性指示,比如响应成功、失败或不完整。
第二个数字是响应类型的分类,如 2 代表跟连接有关的响应,3 代表用户认证。
第三个数字提供了更加详细的信息。
第一个数字的含义如下:
1 表示服务器正确接收信息,还未处理。
2 表示服务器已经正确处理信息。
3 表示服务器正确接收信息,正在处理。
4 表示信息暂时错误。
5 表示信息永久错误。
第二个数字的含义如下:
0 表示语法。
1 表示系统状态和信息。
2 表示连接状态。
3 表示与用户认证有关的信息。
4 表示未定义。
5 表示与文件系统有关的信息。
Socket 客户端编程主要步骤如下:
Socket 服务器端编程主要步骤如下:
下面让我们通过一个例子来对 FTP 客户端有一个深入的了解。本文实现的 FTP 客户端有下列功能:
经过测试可以正常上传下载数据,,测试代码如下:
main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ftp.h"
#define FTP_SERVER_IP "XXXXXXXX"
#define FTP_SERVER_USER "XXXXX"
#define FTP_SERVER_PASS "XXXXXX"
#define MAX_BUF_LEN 512
typedef struct{
char usr[32];
char passwd[32];
char ser_filepath[512];
char ser_filename[64];
char new_filename[64];
int control_sock;
}ftp_client_st;
ftp_client_st ftp_st;
int main (int argc , char * argv[])
{
char str[MAX_BUF_LEN] ={0};
int ret =-1;
//
while(1){
printf("*************\n");
//printf("Please input the ftp server ip: ");
memset(str,0,sizeof(str));
//scanf("%s",str); //从终端获取到服务器ip地址。
strcpy(str,FTP_SERVER_IP);
printf("input fpt server ip:%s\n",str);
/*连接到服务器*/
memset(&ftp_st,0,sizeof(ftp_client_st));
ftp_st.control_sock = connect_ftp_server(str,FTP_SERVER_PORT);
if(ftp_st.control_sock > 0){/*连接成功*/
ret = -1;
while(ret < 0){
strcpy(ftp_st.usr,FTP_SERVER_USER);
strcpy(ftp_st.passwd,FTP_SERVER_PASS);
printf("input usr:%s passwd:%s\n",ftp_st.usr,ftp_st.passwd);
ret = login_ftp_server(ftp_st.control_sock,ftp_st.usr,ftp_st.passwd);
if(ret < 0){
printf("\nUser or Passwd is wrong,input agin");
}
else{
//打印服务器当前目录和列表
while(1){
printf("Get list start:\n");
//ret = down_file_ftpserver(ftp_st.control_sock,"/","/list_mode",0,0,CMD_LIST); /*被动模式*/
ret = down_file_ftpserver(ftp_st.control_sock,"/","../list_passive",1,0,CMD_LIST); /*被动模式获取文件列表*/
// down_file_ftpserver(ftp_st.control_sock,"/down_test","list1",0,0,CMD_LIST);
//printf("\nInput down file dir (Input quit to quit):");
//memset(ftp_st.ser_filepath,0,sizeof(ftp_st.ser_filepath));
//scanf("%s",ftp_st.ser_filepath);
//if(strncmp(ftp_st.ser_filepath,"quit",4) ==0)
// goto err0;
#if 0
printf("\nInput down filename (Input quit to quit):");
memset(ftp_st.ser_filename,0,sizeof(ftp_st.ser_filename));
scanf("%s",ftp_st.ser_filename);
if(strncmp(ftp_st.ser_filename,"quit",4) ==0)
goto err0;
printf("\nInput new filename (Input quit to quit):");
memset(ftp_st.new_filename,0,sizeof(ftp_st.new_filename));
scanf("%s",ftp_st.new_filename);
if(strncmp(ftp_st.new_filename,"quit",4) ==0)
goto err0;
printf("input filename :%s; newfilename:%s; \n",ftp_st.ser_filename,ftp_st.new_filename);
printf("down file start:\n");
//ret = down_file_ftpserver(ftp_st.control_sock,ftp_st.ser_filename,ftp_st.new_filename,0,0,CMD_RETR);
ret = down_file_ftpserver(ftp_st.control_sock,ftp_st.ser_filename,ftp_st.new_filename,0,0,CMD_RETR);
#endif
down_file_ftpserver(ftp_st.control_sock,"/down_test/test_ftp.zip","../12.zip",1,0,CMD_RETR);
up_file_ftpserver(ftp_st.control_sock, "/down_test/12.zip", "../12.zip", 1, 0);
get_fsize_ftpserver(ftp_st.control_sock, "/down_test/12.zip");
goto err0;
}
}
}
}
}
err0:
quit_fpt_server(ftp_st.control_sock);
return 0;
}
fpt.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <sys/unistd.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include "ftp.h"
#define MAX_BUF 512
#define IP_LENGTH 16
//正常时服务器回复的响应码
#define ACK_USER_NUM "331"
#define ACK_PASS_NUM "230"
#define ACK_PASV_NUM "227"
#define ACK_CWD_NUM "250"
#define ACK_SIZE_NUM "213"
#define ACK_RETR_NUM "150"
#define ACK_REST_NUM "350"
#define ACK_QUIT_NUM "200"
#define ACK_LIST_NUM "125"
#define ACK_STOR_NUM "150"
#define ACK_CONNECT_NUM "220"
#define ACK_PORT_NUM "200"
/*ftp server info*/
typedef struct
{
//char szUserName[16];
//char szPassWd[32];
char server_path[128];
char server_filename[64];
char new_filename[128];
int data_sock;
char data_ip[32];
int data_port;
int client_server_sock;
int file_handle;
}FTP_DATA_INFO;
static int itoa(int value, char * str, int radix);
static int send_cmd(int ctrl_sock,eu_cmd_type typ, const char *val,const char *ack_num);
static int enter_passive_mode(int ctrl_sock,char *data_ip, int * data_port);
static int enter_active_mode(int ctrl_sock);
static int get_data_sock(const char* server_ip,const int port);
static int get_active_data_sock(int client_server_sock);
static int GetAddr(const char *ifname, char *addr, int flag);
static int close_st_info(FTP_DATA_INFO * info);
FTP_DATA_INFO server_info;
static int GetAddr(const char *ifname, char *addr, int flag)
{
struct sockaddr_in *sin;
struct ifreq ifr;
int sockfd;
if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
printf("socket create error!\n");
return - 1;
}
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_ifrn.ifrn_name, ifname, IFNAMSIZ);
if(ioctl(sockfd, flag, &ifr) < 0)
{
close(sockfd);
return - 1;
}
close(sockfd);
if(SIOCGIFHWADDR == flag)
{
memcpy((void *)addr, (const void *)&ifr.ifr_ifru.ifru_hwaddr.sa_data, 6);
}
else
{
sin = (struct sockaddr_in *)&ifr.ifr_ifru.ifru_addr;
snprintf((char *)addr, IP_LENGTH, "%s", inet_ntoa(sin->sin_addr));
}
return 0;
}
static int itoa(int value, char * str, int radix)
{
char temp[33];
char *tp = temp;
int i;
unsigned v;
int sign;
char *sp;
int num= 0;
if(radix > 36 || radix < 1)
return 0;
sign = (radix == 10 && value < 0); //十进制负数
if(sign)
v = -value;
else
v = (unsigned)value;
while(v || tp == temp) //转化操作
{
i = v % radix;
v = v / radix;
if(i < 10)
*tp++ = i + '0';
else
*tp++ = i + 'a' - 10;
}
if(str == 0)
str = (char*)malloc((tp - temp) + sign + 1);
sp = str;
if(sign) //是负数的话把负号先加入数组
*sp++ = '-';
while(tp > temp)
{
*sp++ = *--tp;
num++;
}
*sp = 0;
return num;
}
/*
* @brief 连接fpt服务器
* @param 无
* @return -1/成功建立的套接字
*/
int connect_ftp_server(const char* server_ip,const int port)
{
int control_sock =-1;
int ret =-1;
struct sockaddr_in server;
char read_buf[MAX_BUF]={0};
struct timeval tv_out;
memset(&server,0,sizeof(struct sockaddr_in));
if(server_ip == NULL){
printf("argc is NULL\n");
return -1;
}
control_sock = socket(AF_INET,SOCK_STREAM,0);
if(control_sock <0){
printf("socket failed\n");
return -1;
}
/*设置sock fd 接收超时时间*/
tv_out.tv_sec =0;
tv_out.tv_usec =500*1000;
setsockopt(control_sock, SOL_SOCKET, SO_RCVTIMEO,&tv_out,sizeof(tv_out));
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr = inet_addr(server_ip);
ret = connect(control_sock,(struct sockaddr *)&server,sizeof(server));
if(ret < 0){
printf("connect failed\n");
return -1;
}
ret =1;
/*接收服务端的应答消息*/
usleep(100*1000);
ret = read(control_sock,read_buf,sizeof(read_buf));
if(ret < 0){
printf("read error\n");
return -1;
}
printf("%s ret=%d \n",read_buf,ret);
if(strncmp(read_buf,ACK_CONNECT_NUM,3) == 0) /*成功*/
{
printf("Connect ftp ok\n");
return control_sock;
}
else
{
close(control_sock);
return -1;
}
}
/*
* @brief 被动模式连接获取服务器的data_sock
* @param 无
* @return -1/成功建立的套接字
*/
static int get_data_sock(const char* server_ip,const int port)
{
int data_sock =-1;
int ret =-1;
struct sockaddr_in server;
char read_buf[MAX_BUF]={0};
memset(&server,0,sizeof(struct sockaddr_in));
if(server_ip == NULL){
printf("argc is NULL\n");
return -1;
}
data_sock = socket(AF_INET,SOCK_STREAM,0);
if(data_sock <0){
printf("socket failed\n");
return -1;
}
/*设置为非阻塞*/
//int cflags = fcntl(data_sock,F_GETFL,0);
//fcntl(data_sock,F_SETFL,cflags|O_NONBLOCK);
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr = inet_addr(server_ip);
ret = connect(data_sock,(struct sockaddr *)&server,sizeof(server));
if(ret < 0){
printf("connect failed\n");
return -1;
}
/*无应答*/
return data_sock;
}
/*主动模式获取data sock
必须要在LIST等下载上传命令发送后
accept接受才能成功
*/
static int get_active_data_sock(int client_server_sock)
{
int data_sock =-1;
struct sockaddr_in client_name;
int len;
len = sizeof(client_name);
data_sock = accept(client_server_sock,(struct sockaddr *)&client_name ,&len);
if(data_sock <0)
{
printf("accept failed\n");
}
printf("data_sock = %d\n",data_sock);
return data_sock;
}
/*
* @brief 登陆fpt服务器
* @param 无
* @return -1/成功返回0
*/
int login_ftp_server(int ctrl_sock,const char *user_name, const char * passwd)
{
int ret =-1;
if((user_name == NULL) ||(passwd == NULL)){
printf("argc is NULL\n");
return -1;
}
ret = send_cmd(ctrl_sock,CMD_USER,user_name,ACK_USER_NUM);
if(ret < 0){
printf("send_cmd %d failed \n",CMD_USER);
return -1;
}
ret = send_cmd(ctrl_sock,CMD_PASS,passwd,ACK_PASS_NUM);
if(ret < 0){
printf("send_cmd %d failed \n",CMD_PASS);
return -1;
}
return 0;
}
/*
* @brief 给服务器发送指令
* @param
* @return 失败返回-1/成功返回0
SIZE 返回等到的文件大小
*/
static int send_cmd(int ctrl_sock,eu_cmd_type typ, const char *val,const char *ack_num)
{
int ret =-1;
char send_buf[MAX_BUF]={0};
char read_buf[MAX_BUF]={0};
char *pos= NULL;
char tmp[64] ={0};
if((typ == CMD_USER) ||(typ == CMD_PASS) || (typ == CMD_CWD)){
if((val == NULL) ||(ack_num == NULL)){
printf("argc is NULL\n");
return -1;
}
}
switch(typ){
case CMD_USER:
memset(send_buf,0,sizeof(send_buf));
sprintf(send_buf,"USER %s\r\n",val);
ret = write(ctrl_sock,send_buf,strlen(send_buf));
if(ret < 0){
printf("write failed\n");
return -1;
}
memset(read_buf,0,sizeof(read_buf));
ret = read(ctrl_sock,read_buf,sizeof(read_buf));
if(ret < 0){
printf("read failed\n");
return -1;
}
ret = strncmp(read_buf,ack_num,strlen(ack_num));
break;
case CMD_PASS:
memset(send_buf,0,sizeof(send_buf));
sprintf(send_buf,"PASS %s\r\n",val);
ret = write(ctrl_sock,send_buf,strlen(send_buf));
if(ret < 0){
printf("write failed\n");
return -1;
}
memset(read_buf,0,sizeof(read_buf));
ret = read(ctrl_sock,read_buf,sizeof(read_buf));
if(ret < 0){
printf("read failed\n");
return -1;
}
ret = strncmp(read_buf,ack_num,strlen(ack_num));
break;
case CMD_PASV: /*只发送命令,函数外面接收提取信息*/
memset(send_buf,0,sizeof(send_buf));
sprintf(send_buf,"PASV\r\n");
ret = write(ctrl_sock,send_buf,strlen(send_buf));
if(ret < 0){
printf("write failed\n");
return -1;
}
break;
case CMD_CWD:
memset(send_buf,0,sizeof(send_buf));
sprintf(send_buf,"CWD %s\r\n",val);
ret = write(ctrl_sock,send_buf,strlen(send_buf));
if(ret < 0){
printf("write failed\n");
return -1;
}
usleep(500*1000);
memset(read_buf,0,sizeof(read_buf));
ret = read(ctrl_sock,read_buf,sizeof(read_buf));
if(ret < 0){
printf("read failed\n");
return -1;
}
ret = strncmp(read_buf,ack_num,strlen(ack_num));
break;
case CMD_QUIT:
memset(send_buf,0,sizeof(send_buf));
sprintf(send_buf,"QUIT\r\n");
ret = write(ctrl_sock,send_buf,strlen(send_buf));
if(ret < 0){
printf("write failed\n");
return -1;
}
usleep(500*1000);
memset(read_buf,0,sizeof(read_buf));
ret = read(ctrl_sock,read_buf,sizeof(read_buf));
if(ret < 0){
printf("read failed\n");
return -1;
}
// ret = strncmp(read_buf,ack_num,strlen(ack_num));
case CMD_LIST:
memset(send_buf,0,sizeof(send_buf));
sprintf(send_buf,"LIST %s\r\n",val);
ret = write(ctrl_sock,send_buf,strlen(send_buf));
if(ret < 0){
printf("write failed\n");
return -1;
}
memset(read_buf,0,sizeof(read_buf));
usleep(100*1000); /*等待一会把266 也接收回来*/
ret = read(ctrl_sock,read_buf,sizeof(send_buf));
if(ret < 0){
printf("read failed\n");
return -1;
}
// ret = strncmp(read_buf,ack_num,strlen(ack_num));
break;
case CMD_STOR:
memset(send_buf,0,sizeof(send_buf));
sprintf(send_buf,"STOR %s\r\n",val);
ret = write(ctrl_sock,send_buf,strlen(send_buf));
if(ret < 0){
printf("write failed\n");
return -1;
}
usleep(50*1000);
memset(read_buf,0,sizeof(read_buf));
ret = read(ctrl_sock,read_buf,sizeof(read_buf));
if(ret < 0){
printf("read failed\n");
return -1;
}
// ret = strncmp(read_buf,ack_num,strlen(ack_num));
break;
case CMD_RETR:
memset(send_buf,0,sizeof(send_buf));
sprintf(send_buf,"RETR %s\r\n",val);
ret = write(ctrl_sock,send_buf,strlen(send_buf));
if(ret < 0){
printf("write failed\n");
return -1;
}
usleep(50*1000);
memset(read_buf,0,sizeof(read_buf));
ret = read(ctrl_sock,read_buf,sizeof(read_buf));
if(ret < 0){
printf("read failed\n");
return -1;
}
// ret = strncmp(read_buf,ack_num,strlen(ack_num));
break;
case CMD_SIZE_FTP:
memset(send_buf,0,sizeof(send_buf));
sprintf(send_buf,"SIZE %s\r\n",val);
ret = write(ctrl_sock,send_buf,strlen(send_buf));
if(ret < 0){
printf("write failed\n");
return -1;
}
usleep(50*1000);
memset(read_buf,0,sizeof(read_buf));
ret = read(ctrl_sock,read_buf,sizeof(read_buf));
if(ret < 0){
printf("read failed\n");
return -1;
}
/* 客户端接收服务器的响应码和信息,正常为 ”213 <size>” */
pos = strstr(read_buf,ack_num);
if(pos != NULL){
pos += strlen(ack_num) +1;
strcpy(tmp,pos);
ret = atoi(tmp);
}
else{
ret =-1;
}
break;
case CMD_PORT_FTP:
memset(send_buf,0,sizeof(send_buf));
sprintf(send_buf,"PORT %s\r\n",val);
ret = write(ctrl_sock,send_buf,strlen(send_buf));
if(ret < 0){
printf("write failed\n");
return -1;
}
usleep(50*1000);
memset(read_buf,0,sizeof(read_buf));
ret = read(ctrl_sock,read_buf,sizeof(read_buf));
if(ret < 0){
printf("read failed\n");
return -1;
}
ret = strncmp(read_buf,ack_num,strlen(ack_num));
break;
case CMD_MLSD:
memset(send_buf,0,sizeof(send_buf));
sprintf(send_buf,"MLSD\r\n");
ret = write(ctrl_sock,send_buf,strlen(send_buf));
if(ret < 0){
printf("write failed\n");
return -1;
}
break;
default:break;
}
printf("FTP server ack= %s\n",read_buf);
return ret;
}
/*
* @brief 进入主动模式,让服务器主动连接到客户端的端口
* @param 无
* @return -1/成功返回client_server_sock
*/
static int enter_active_mode(int ctrl_sock)
{
int data_sock,server_sock;
struct sockaddr_in name;
struct sockaddr_in client_name,loc_addr;
unsigned short server_port =0;
int ret =-1;
int len =0;
char send_buf[64] ={0};
char ip[20]={0};
unsigned short ip0,ip1,ip2,ip3,p1,p2;
//char read_buf[128] ={0};
struct timeval tv_out;
/*设置sock fd 接收超时时间*/
tv_out.tv_sec =3;
tv_out.tv_usec =0;
/*
if(GetAddr("eth0", ip, SIOCGIFADDR) != 0)
{
printf("get local ip failed\n");
return -1;
}
printf("local ip =%s\n",ip);
*/
memset(&name,0,sizeof(name));
memset(&client_name,0,sizeof(client_name));
/*通过ctrl_sock获取到本机的ip*/
len =sizeof(name);
if(getsockname(ctrl_sock,(struct sockaddr*)&name,&len) == -1)
{
printf("get sock name failed\n");
return -1;
}
sscanf(inet_ntoa(name.sin_addr),"%hu.%hu.%hu.%hu",&ip0,&ip1,&ip2,&ip3);
server_sock = socket(AF_INET,SOCK_STREAM,0);
if(server_sock <0){
printf("get sock failed\n");
return -1;
}
/*设置接收超时*/
setsockopt(server_sock, SOL_SOCKET, SO_RCVTIMEO,&tv_out,sizeof(tv_out));
name.sin_family = AF_INET;
name.sin_port = 0;
len = sizeof(name);
ret = bind(server_sock,(struct sockaddr *)&name,len);
if(ret < 0){
printf("bind error\n");
goto err0;
}
/*通过ctrl_sock获取到系统分配到的端口*/
len = sizeof(loc_addr);
memset(&loc_addr,0,len);
ret = getsockname(server_sock,(struct sockaddr *)&loc_addr,&len);
if(ret < 0)
{
printf("get sock name failed\n");
goto err0;
}
server_port = ntohs(loc_addr.sin_port);
p1 = server_port/256;
p2 = server_port%256;
ret = listen(server_sock,10);
if(ret < 0)
{
printf("listen error\n");
goto err0;
}
/*给服务器 命令 “PORT \r\n*/
/*将ip中的.更换为,*/
#if 0
&ip0 = strtok(ip,".");
&ip1 = strtok(NULL,".");
&ip2 = strtok(NULL,".");
&ip3 = strtok(NULL,".");
#endif
sprintf(send_buf,"%hu,%hu,%hu,%hu,%hu,%hu",ip0,ip1,ip2,ip3,p1,p2);
printf("send_buf =%s server_port=%d\n",send_buf,server_port);
ret = send_cmd(ctrl_sock,CMD_PORT_FTP, send_buf,ACK_PORT_NUM);
if(ret < 0){
printf("Send PORT failed\n");
goto err0;
}
return server_sock;
err0:
close(server_sock);
return -1;
}
/*
* @brief 进入被动模式,让服务器在数据端口监听数据
* @param 无
* @return -1/成功返回0
*/
static int enter_passive_mode(int ctrl_sock,char *data_ip, int * data_port)
{
int ret =-1;
char read_buf[MAX_BUF]={0};
char tmp_buf[64]={0};
unsigned char ip1,ip2,ip3,ip4,port1,port2;
//char *tmp;
if((data_ip == NULL) ||(data_port == NULL)){
printf("argc is NULL\n");
return -1;
}
ret = send_cmd(ctrl_sock,CMD_PASV,NULL,NULL);
if(ret < 0){
printf("send_cmd %d failed \n",CMD_PASV);
return -1;
}
usleep(100*1000);
ret = read(ctrl_sock,read_buf,sizeof(read_buf));
if(ret < 0){
printf("read failed\n");
return -1;
}
printf("rev =%d: %s\n",ret,read_buf);
if(strstr(read_buf,ACK_PASV_NUM) != NULL){
sscanf(strchr(read_buf,'(')+1,"%hhu,%hhu,%hhu,%hhu,%hhu,%hhu",&ip1,&ip2,&ip3,&ip4,&port1,&port2);
//printf("ip1=%d,ip2=%d,ip3=%d,ip4=%d,port1 =%d ,port2 = %d\n",ip1,ip2,ip3,ip4,port1,port2);
//snprintf(data_ip,sizeof(data_ip),"%hhu,%hhu,%hhu,%hhu",ip1,ip2,ip3,ip4);
//memset(data_ip,0,sizeof(data_ip));
//snprintf(data_ip,sizeof(data_ip),"%d.%d.%d.%d",ip1,ip2,ip3,ip4);
//printf("data_ip = %s\n",data_ip);
memset(data_ip,0,sizeof(data_ip));
memset(tmp_buf,0,sizeof(tmp_buf));
itoa(ip1,tmp_buf,10);
strcat(data_ip,tmp_buf);
strcat(data_ip,".");
memset(tmp_buf,0,sizeof(tmp_buf));
itoa(ip2,tmp_buf,10);
strcat(data_ip,tmp_buf);
strcat(data_ip,".");
memset(tmp_buf,0,sizeof(tmp_buf));
itoa(ip3,tmp_buf,10);
strcat(data_ip,tmp_buf);
strcat(data_ip,".");
memset(tmp_buf,0,sizeof(tmp_buf));
itoa(ip4,tmp_buf,10);
strcat(data_ip,tmp_buf);
//printf("data_ip1 = %s\n",data_ip);
*data_port = port1*256+port2;
}
return 0;
}
/*
* @brief 从ftp服务器上下载文件
* @param
ctrl_sock 控制服务器sock
connect_mode= 0 设置服务器被动模式下载,非0服务器主动模式下载
server_filepath_name 要下载文件的路径
newfilename 下载到本地的路径和文件名字
offset 设置下载文件偏移的位置,不偏移写0 ,可用于错误时续传。
* @return -1/成功返回未接收到的字节数
*/
int down_file_ftpserver(int ctrl_sock, char *server_filepath_name,
const char *newfilename,int connect_mode,int offset,eu_cmd_type typ)
{
int ret =-1,file_size=0;
char rec_buf[2048] ={0};
char stri[128]={0};
int read_size =0;
char * pos =NULL;
char *tmp = NULL;
int flags =O_CREAT|O_RDWR|O_TRUNC;;
close_st_info(&server_info);
if(server_filepath_name == NULL || newfilename == NULL){
printf("argc is NULL\n");
return -1;
}
/*提取出server_path file name*/
tmp = server_filepath_name;
if(tmp != NULL){
while(tmp != NULL){
tmp = strstr(tmp,"/");
// printf("tmp =%x :%s\n",tmp,tmp);
if(tmp != NULL){
pos = tmp;
tmp++; /*越过找到的"/"*/
}
}
if(pos !=NULL){
strncpy(server_info.server_path,server_filepath_name,pos-server_filepath_name+1);
strcpy(server_info.server_filename,pos+1);
}
else{
strcpy(server_info.server_filename,server_filepath_name);
}
}
printf("server path = %s ;file name =%s\n",server_info.server_path,server_info.server_filename);
if((typ !=CMD_RETR) && (typ !=CMD_LIST)){
printf("typ value is not CMD_RETR or CMD_LIST\n");
return -1;
}
if(connect_mode){ /*主动模式*/
server_info.client_server_sock =enter_active_mode(ctrl_sock);
if(server_info.client_server_sock< 0)
{
printf("get data_scok failed\n");
return -1;
}
}else{ /*被动模式*/
ret = enter_passive_mode(ctrl_sock,server_info.data_ip, &server_info.data_port);
if(ret < 0){
printf("set passive mode failed\n");
return -1;
}
printf("server_info.data_ip =%s, data_port =%d \n",server_info.data_ip,server_info.data_port);
server_info.data_sock = get_data_sock(server_info.data_ip,server_info.data_port);
if(server_info.data_sock < 0){
printf("get data sock failed\n");
return -1;
}
}
/*更改目录*/
if(strlen(server_info.server_path) !=0)
{
ret = send_cmd(ctrl_sock,CMD_CWD,server_info.server_path,ACK_CWD_NUM);
if(ret < 0){
printf("set passive mode failed\n");
goto err0;
}
}
if(typ ==CMD_RETR){/*发送下载文件命令*/
/*设置下载文件偏移的位置*/
if(offset >0){
flags =O_CREAT|O_RDWR|O_APPEND;
itoa(offset,stri,10);
ret = send_cmd(ctrl_sock,CMD_REST,stri,ACK_REST_NUM);
if(ret < 0){
printf("set file offsize failed\n");
goto err0;
}
}
ret = send_cmd(ctrl_sock,CMD_RETR,server_info.server_filename,ACK_RETR_NUM);
if(ret < 0){
printf("send RETR failed\n");
goto err0;
}
}
else if(typ ==CMD_LIST){
ret = send_cmd(ctrl_sock,CMD_LIST,server_filepath_name,ACK_LIST_NUM);
if(ret < 0){
printf("send LIST failed\n");
goto err0;
}
}
if(connect_mode){
server_info.data_sock= get_active_data_sock(server_info.client_server_sock);
if(server_info.data_sock <0)
{
printf("accept failed\n");
goto err0;
}
}
server_info.file_handle = open(newfilename,flags,0766);
if(server_info.file_handle < 0){
printf("open file failed\n");
goto err0;
}
if(offset >0){
lseek(server_info.file_handle,offset, SEEK_SET);
read_size += offset;
}
for(;;){
memset(rec_buf,0,sizeof(rec_buf));
ret = recv(server_info.data_sock,rec_buf,sizeof(rec_buf),0);
if(ret < 0)
{
printf("Read error\n");
goto err1;
}
else if(ret == 0)
{
ret = read_size;
goto err1;
}
else if(ret >0)
{
read_size += ret;
ret = write(server_info.file_handle,rec_buf,ret);
if(ret < 0)
{
printf("Write error\n");
goto err1;
}
//printf("read_buf =%s\n",rec_buf);
}
}
err1:
if(server_info.file_handle >0)
close(server_info.file_handle);
err0:
if(server_info.client_server_sock > 0)
close(server_info.client_server_sock);
if(server_info.data_sock > 0)
close(server_info.data_sock);
memset(rec_buf,0,sizeof(rec_buf));
/* 客户端接收服务器的响应码和信息,正常为 ”226 Transfer complete.” */
read(ctrl_sock,rec_buf,sizeof(rec_buf)); /*有时消息再发送完命令后里面就能收到,导致会阻塞在这个上面*/
printf("%s \n Download file end!!\n",rec_buf);
return ret;
}
/*
* @brief 上传文件从ftp服务器
* @param
ctrl_sock 控制服务器sock
connect_mode = 0 设置服务器被动模式,非0 服务器主动模式
server_filepath_name 上传到服务器上的文件路径和名称
srcfilename 本地要上传的路径和文件名字
offset 设置下载文件偏移的位置,不偏移写0 ,可用于错误时续传。
* @return -1/成功返回上传完成的字节数
*/
int up_file_ftpserver(int ctrl_sock, char *server_filepath_name,
const char *srcfilename,int connect_mode,int offset)
{
int ret =-1,file_size=0;
int file_handle =0;
char rec_buf[2048] ={0};
int read_size =0;
char stri[128]={0};
char *tmp = NULL;
char * pos =NULL;
close_st_info(&server_info);
if((server_filepath_name == NULL) ||(srcfilename == NULL)){
printf("argc is NULL\n");
return -1;
}
/*提取出server_path file name*/
tmp = server_filepath_name;
if(tmp != NULL){
while(tmp != NULL){
tmp = strstr(tmp,"/");
// printf("tmp =%x :%s\n",tmp,tmp);
if(tmp != NULL){
pos = tmp;
tmp++; /*越过找到的"/"*/
}
}
if(pos !=NULL){
strncpy(server_info.server_path,server_filepath_name,pos-server_filepath_name+1);
strcpy(server_info.server_filename,pos+1);
}
else{
strcpy(server_info.server_filename,server_filepath_name);
}
}
printf("server path = %s ;file name =%s\n",server_info.server_path,server_info.server_filename);
if(connect_mode){ /*主动模式*/
server_info.client_server_sock =enter_active_mode(ctrl_sock);
if(server_info.client_server_sock <= 0)
{
printf("get data_scok failed\n");
return -1;
}
}else{ /*被动模式*/
ret = enter_passive_mode(ctrl_sock,server_info.data_ip, &server_info.data_port);
if(ret < 0){
printf("set passive mode failed\n");
return -1;
}
printf("server_info.data_ip =%s, data_port =%d\n",server_info.data_ip,server_info.data_port);
server_info.data_sock = get_data_sock(server_info.data_ip,server_info.data_port);
if(server_info.data_sock < 0){
printf("get data sock failed\n");
return -1;
}
}
/*更改目录*/
if(strlen(server_info.server_path) !=0)
{
ret = send_cmd(ctrl_sock,CMD_CWD, server_info.server_path,ACK_CWD_NUM);
if(ret < 0){
printf("set passive mode failed\n");
goto err0;
}
}
/*设置上传文件偏移的位置*/
if(offset >0){
itoa(offset,stri,10);
ret = send_cmd(ctrl_sock,CMD_REST,stri,ACK_REST_NUM);
if(ret < 0){
printf("set file offsize failed\n");
goto err0;
}
}
/*发送上传文件命令*/
ret = send_cmd(ctrl_sock,CMD_STOR,server_info.server_filename,ACK_STOR_NUM);
if(ret < 0){
printf("send STOR failed\n");
goto err0;
}
if(connect_mode){
server_info.data_sock= get_active_data_sock(server_info.client_server_sock);
if(server_info.data_sock <0)
{
printf("accept failed\n");
goto err0;
}
}
server_info.file_handle = open(srcfilename,O_RDONLY);
if(server_info.file_handle < 0){
printf("open file failed\n");
goto err0;
}
if(offset >0){
lseek(server_info.file_handle,offset, SEEK_SET);
read_size += offset;
}
for(;;){
memset(rec_buf,0,sizeof(rec_buf));
ret = read(server_info.file_handle,rec_buf,sizeof(rec_buf));
if(ret < 0)
{
printf("read file error\n");
goto err1;
}
else if(ret == 0)
{
ret = read_size;
goto err1;
}
else if(ret >0)
{
//printf("read_buf =%s\n",rec_buf);
ret = write(server_info.data_sock,rec_buf,ret);
if(ret < 0)
{
printf("Write failed\n");
goto err1;
}else{
read_size += ret;
// lseek(file_handle,read_size, SEEK_SET);
}
}
}
err1:
if(server_info.file_handle >0)
close(server_info.file_handle);
err0:
if(server_info.client_server_sock > 0)
close(server_info.client_server_sock);
if(server_info.data_sock > 0)
close(server_info.data_sock);
memset(rec_buf,0,sizeof(rec_buf));
/* 客户端接收服务器的响应码和信息,正常为 ”226 Transfer complete.” */
read(ctrl_sock,rec_buf,sizeof(rec_buf));
printf("%s\n Up file end!!!\n",rec_buf);
return ret;
}
static int close_st_info(FTP_DATA_INFO * info)
{
if(info == NULL)
return -1;
if(info->file_handle >0)
close(info->file_handle);
if(info->client_server_sock > 0)
close(info->file_handle);
if(info->data_sock > 0)
close(info->file_handle);
memset(info,0,sizeof(FTP_DATA_INFO));
return 0;
}
int get_fsize_ftpserver(int ctrl_sock, char *server_filepath_name)
{
int ret =-1;
if(server_filepath_name ==NULL){
printf("argc is null\n");
return -1;
}
/*发送上传文件命令*/
ret = send_cmd(ctrl_sock,CMD_SIZE_FTP,server_filepath_name,ACK_SIZE_NUM);
if(ret < 0){
printf("send SZIE failed\n");
return -1;
}
//printf("file size =%d\n",ret);
return ret;
}
/*
* @brief 从ftp服务器退出
* @param
ctrl_sock 控制服务器sock
* @return -1/成功返回0
*/
int quit_fpt_server(int ctrl_sock)
{
int ret =-1;
close_st_info(&server_info);
ret = send_cmd(ctrl_sock,CMD_QUIT,NULL,ACK_QUIT_NUM);
if(ret < 0)
printf("quit fpt server error\n");
close(ctrl_sock);
return ret;
}
fpt.h
#ifndef __FTP_H__
#define __FTP_H__
#define FTP_SERVER_PORT 21
typedef enum
{
CMD_USER =0, /*用户名*/
CMD_PASS, /*密码*/
CMD_PASV, /*让服务器进入被动模式*/
CMD_CWD, /*切换工作目录*/
CMD_SIZE_FTP, /*获取文件大小*/
CMD_RETR, /*下载文件*/
CMD_REST, /*指定下载文件的偏移量*/
CMD_QUIT, /*退出命令*/
CMD_LIST, /*列表*/
CMD_STOR, /*上传文件*/
CMD_PORT_FTP,/*发送客户端端口给服务器*/
CMD_MLSD, /*列表*/
}eu_cmd_type;
int connect_ftp_server(const char* server_ip,const int port);
int login_ftp_server(int ctrl_sock,const char *user_name, const char * passwd);
int down_file_ftpserver(int ctrl_sock, char *server_filepath_name,
const char *newfilename,int connect_mode,int offset,eu_cmd_type typ);
int up_file_ftpserver(int ctrl_sock, char *server_filepath_name,
const char *srcfilename,int connect_mode,int offset);
int get_fsize_ftpserver(int ctrl_sock, char *server_filepath_name);
int quit_fpt_server(int ctrl_sock);
#endif
遇到的主要问题记录:
1、实现FTP主动模式的时候,开始的accpet一直无法接收到服务器的连接请求。
后来用wireshark跟踪FileZilla与服务器直接的通信数据才找到问题所在。原来accept要在
必须要在LIST等下载上传命令发送后服务器才会连接过来。
2、主要一定要关闭掉防火墙
3、ftp还有一些传送方法类型的选择,本代码中并没有进行设置,后续用到的时候再进行完善。
4、 代码中接收服务器回复的时候增加了不少延迟。后来我有在sock 上设置接收超时,延迟太多反正感觉不是太好。
5、最近更换了一个ftp服务器平台时,发现新问题。发现下载后的文件大小变大了。经过对比发现多了很多0D .
从网上查询发现时Linux系统的回车时\n. 而windows系统的回车时\r\n。 所以当ftp 采用ascii方式传播的时候是有着问题的。
解决方法是发送TYPE I\r\n命令将传输方式设置为二进制传输 。问题解决。
命令及响应码
| 命令 | 描述 |
| ABOR | 中断数据连接程序 |
| ACCT <account> | 系统特权帐号 |
| ALLO <bytes> | 为服务器上的文件存储器分配字节 |
| APPE <filename> | 添加文件到服务器同名文件 |
| CDUP <dir path> | 改变服务器上的父目录 |
| CWD <dir path> | 改变服务器上的工作目录 |
| DELE <filename> | 删除服务器上的指定文件 |
| HELP <command> | 返回指定命令信息 |
| LIST <name> | 如果是文件名列出文件信息,如果是目录则列出文件列表 |
| MODE <mode> | 传输模式(S=流模式,B=块模式,C=压缩模式) |
| MKD <directory> | 在服务器上建立指定目录 |
| NLST <directory> | 列出指定目录内容 |
| NOOP | 无动作,除了来自服务器上的承认 |
| PASS <password> | 系统登录密码 |
| PASV | 请求服务器等待数据连接 |
| PORT <address> | IP 地址和两字节的端口 ID |
| PWD | 显示当前工作目录 |
| QUIT | 从 FTP 服务器上退出登录 |
| REIN | 重新初始化登录状态连接 |
| REST <offset> | 由特定偏移量重启文件传递 |
| RETR <filename> | 从服务器上找回(复制)文件 |
| RMD <directory> | 在服务器上删除指定目录 |
| RNFR <old path> | 对旧路径重命名 |
| RNTO <new path> | 对新路径重命名 |
| SITE <params> | 由服务器提供的站点特殊参数 |
| SMNT <pathname> | 挂载指定文件结构 |
| STAT <directory> | 在当前程序或目录上返回信息 |
| STOR <filename> | 储存(复制)文件到服务器上 |
| STOU <filename> | 储存文件到服务器名称上 |
| STRU <type> | 数据结构(F=文件,R=记录,P=页面) |
| SYST | 返回服务器使用的操作系统 |
| TYPE <data type> | 数据类型(A=ASCII,E=EBCDIC,I=binary) |
| USER <username> | 系统登录的用户名 |
| 响应代码 | 解释说明 |
| 110 | 新文件指示器上的重启标记 |
| 120 | 服务器准备就绪的时间(分钟数) |
| 125 | 打开数据连接,开始传输 |
| 150 | 打开连接 |
| 200 | 成功 |
| 202 | 命令没有执行 |
| 211 | 系统状态回复 |
| 212 | 目录状态回复 |
| 213 | 文件状态回复 |
| 214 | 帮助信息回复 |
| 215 | 系统类型回复 |
| 220 | 服务就绪 |
| 221 | 退出网络 |
| 225 | 打开数据连接 |
| 226 | 结束数据连接 |
| 227 | 进入被动模式(IP 地址、ID 端口) |
| 230 | 登录因特网 |
| 250 | 文件行为完成 |
| 257 | 路径名建立 |
| 331 | 要求密码 |
| 332 | 要求帐号 |
| 350 | 文件行为暂停 |
| 421 | 服务关闭 |
| 425 | 无法打开数据连接 |
| 426 | 结束连接 |
| 450 | 文件不可用 |
| 451 | 遇到本地错误 |
| 452 | 磁盘空间不足 |
| 500 | 无效命令 |
| 501 | 错误参数 |
| 502 | 命令没有执行 |
| 503 | 错误指令序列 |
| 504 | 无效命令参数 |
| 530 | 未登录网络 |
| 532 | 存储文件需要帐号 |
| 550 | 文件不可用 |
| 551 | 不知道的页类型 |
| 552 | 超过存储分配 |
| 553 | 文件名不允许 |
参考资料:
(29条消息) FTP协议讲解_zhubao124的博客-CSDN博客_ftp协议
FTP:文件传输协议(指令及响应代码) - CTHON - 博客园 (cnblogs.com)
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru
我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的
几个月前,我读了一篇关于rubygem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:
只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您
我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur
前言作为一名程序员,自己的本质工作就是做程序开发,那么程序开发的时候最直接的体现就是代码,检验一个程序员技术水平的一个核心环节就是开发时候的代码能力。众所周知,程序开发的水平提升是一个循序渐进的过程,每一位程序员都是从“菜鸟”变成“大神”的,所以程序员在程序开发过程中的代码能力也是根据平时开发中的业务实践来积累和提升的。提高代码能力核心要素程序员要想提高自身代码能力,尤其是新晋程序员的代码能力有很大的提升空间的时候,需要针对性的去提高自己的代码能力。提高代码能力其实有几个比较关键的点,只要把握住这些方面,就能很好的、快速的提高自己的一部分代码能力。1、多去阅读开源项目,如有机会可以亲自参与开源
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o