旭++

张旭的C++学习笔记
posts - 5, comments - 8, trackbacks - 0, articles - 0
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

2007年12月10日

 

      从上周三,我才真正了解到我要做的东西(FTPserver)是一个涉及知识极其广泛的复杂程序。而并非之前以为的,单纯的C\S模式文件传输程序。

      首先按照老师的提示,配了ftp服务器和web服务器,熟悉了一些ftp命令和配置方法。但是对ftp的通信方式和具体过程还是一无所知。

      接下来找来RFC-FTP协议来看,同时参考了telnet协议和http协议。结果还是一头雾水,搞不清楚协议中规定的东西到底应该怎样去实现。

      我打算转变思路,动手做试验。模拟了一个服务器端程序,这个程序只能send一条信息和recv一条信息并且打印,然后用linux自带的ftp客户端程序去连接模拟服务器。我想他们之间肯定是有通信来往的,这样做可以观察客户端到底传给服务器怎样形式的信息。经过反复测试。终于搞明白了客户端和服务器之间严格按照RFC-959协议的规定一问一答的通讯方式。

      接下来,我就没有着急动手继续写程序,而是去网上搜索了不少关于ftp协议的文档和讨论。了解了双socket-(控制通道,数据通道)。了解了主动传输和被动传输的区别。

      看了这些材料以后,我已经大概对整个ftp整个传输过程有一个了解。接下来继续完善了模拟服务器,能够用ftp客户端软件登陆和退出(只做肯定回应)。这样控制通道的通信已经可以正常对话了。剩下的一大问题就是数据通道的通信方式了。

      做到这里,我遇到了两个棘手的问题。

      Listen()监听函数是如何作用的?按我的理解,listen函数是这道题目中“控制最大同时访问数”的关键。我在编码过程中对这个地方做了试验:当我在while(1)中执行accept时,当接收到客户端connect请求时,fork()建立子进程去处理,父进程继续循环等待accept(),之前listen函数backlog为2。但结果是我仍然可以使用三个以上的ftp客户端进程去同时访问服务器,并保持连接不断开或者阻塞。

      两台电脑相互通信,如何取得适合对方访问的自己IP地址?在客户端要求使用被动传输模式的时候,服务器要建立临时的数据传输通道,并把建好的本机地址+新端口,以“msg…. (xxx,xxx,xxx,xxx,xxx,xxx) \r\n”的形式发送给客户端,客户端根据所给的地址和端口号和服务器的数据传输通道建立连接。在C/C++里,可以使用gethostname函数获取本机名称,根据获取的名称,使用gethostbyname函数可以获取本机的IP地址。但是这个IP地址永远是127.0.0.1(至少我的程序中是这样的)。但是这个地址传给对方,对方并不能按照这个地址找到服务器(除非客户端和服务器在同一台电脑上)。如果客户端和服务器在同一局域网中的不同电脑上,那服务器应该传给客户端类似192.168.x.x的地址。如果在internet范围内则需要传送互联网上的IP地址。而真正的ftp服务器和客户端之间的通信,是可以发送适合对方访问自己的IP地址给对方的。而目前所了解到的函数,gethostname和gethostbyname函数并不能完成这项要求。

在网上查了很多资料,关于以上两个问题,有不少存在和我类似情况的提问,不过回答不甚详细。

posted @ 2007-12-10 22:58 张旭 阅读(2281) | 评论 (1)编辑 收藏

2007年12月2日

在编写socket ftp之前,我对fork函数进行了学习。
先看这段范例代码:
#include <unistd.h>


#include 
<sys/types.h>


main () 





   pid_t pid; 


        pid
=fork(); 


        
if (pid < 0


                printf(
"error in fork!"); 


        
else if (pid == 0


                printf(
"i am the child process, my process id is %dn",getpid()); 


        
else 


                printf(
"i am the parent process, my process id is %dn",getpid()); 



}
 

这段代码写了一个使用fork函数创建子进程,父子进程同时运行而产生交错的,不一样的运行结果。
运行结果如下:
[root@localhost c]# ./a.out
i am the child process, my process id is 4286
i am the parent process, my process id is 4285 
      fork在英文中是叉子,分叉的意思,在函数fork中,取后面的意思。很形象的表示程序从这里分叉,fork函数创建了子进程,子进程和父进程同时(其实是cpu分时处理)开始运行分叉之后的程序。
      我把程序改写了一下: 

 

#include <unistd.h>
#include 
<sys/types.h>
main()
{
        pid_t pid;
        printf(
"\n[%d]not fork pid=%d\n",getpid(),pid);
        pid
=fork();
        printf(
"\n[%d]forked pid=%d\n",getpid(),pid);
        
if(pid<0)
        
{
                printf(
"error in fork!\n");
                getchar();
                exit(
1);
        }

        
else if(pid==0)
                printf(
"\n[%d]in child process,p_id=%d\n",getpid(),getpid());
        
else
        
{
                printf(
"\n[%d]in parent process,my pid=%d\n",getpid(),pid);
                printf(
"\n[%d]in parent process,my getpid=%d\n",getpid(),getpid());

        }

}


程序运行结果如下:
[hardy@localhost fork]$ ./fork

[3819]not fork

[3820]forked pid=0

[3820]in child process,p_id=3820

[3819]forked pid=3820

[3819]in parent process,my pid=3820

[3819]in parent process,my getpid=3819

可以清楚的看到 not fork只打印了一次,其中[3819]是父进程的进程号,创建fork以后,fork函数返回给父进程的值pid是子进程的进程号[3820],而在子进程中,pid值为零。也就是说子进程中,pid被置零。

引用网上一位网友的解释“其实就相当于链表,进程形成了链表,父进程pid(p 意味point)指向子进程的进程id, 因为子进程没有子进程,所以其pid为0. 

下面有一个很有意思的程序:
#include <sys/types.h>
#include 
<unistd.h>

int main()
{
        
int i;
        
for( i= 0; i< 3; i++)
        
{
                
int pid= fork();
                
if(pid== 0)
                
{
                        printf(
"son\n");
                }

                
else
                
{
                        printf(
"father\n");
                }

        }

        
return 0;
}


大家想想看最后将出现几个son 几个father呢?








对一下答案吧:
[hardy@localhost fork]$ ./fork
father
son
son
son
father
father
son
father
son
son
father
father
son
father

总共7个son7个father。你答对了么?

这道题需要在纸上画画才好理解
for            i=0         1           2
               father     father     father
                                           son
                              son       father
                                            son
               son       father        father
                                             son
                              son         father
                                             son
其中每一行分别代表一个进程的运行打印结果。
当产生子进程的时刻,子进程打印son,当子进程调用fork的生成子子进程,他就提升为father。
总结来说,father永远打印father,son在fork之前是son,fork之后就为father,同时生成新的son。
这个比喻就像真正的父子,孩子长大了生了小孩,孩子就成了父亲。而父亲永远是父亲。

posted @ 2007-12-02 15:20 张旭 阅读(11485) | 评论 (7)编辑 收藏

2007年12月1日

      今天花了一下午的时间学习李老师新写的智能猜数字程序,从执行效率上来看,在取值范围比我做的大1万倍的情况下,计算次数几乎相同。所以我学习的重点集中在,算法是如何优化的。
      首先回忆我自己的程序算法,是先创造一个结果全集(在取四位的情况下全集是1万),在根据提示逐步筛选出正确的答案。但是这种算法在全集总数巨大的时候(李老师的程序取8位,全集为1亿),那么程序的执行效率必然是大打折扣的。
      智能猜数字游戏中最关键的就是计算机产生假设的那个函数,李老师的相关函数是

char * generate_new_answer (RECORD * ar, OPTIONS * opt)

      我的疑问也主要集中在这部分,先说说我看明白的部分。

 1if (!ar->count)
 2    {   // First step work
 3//        i = size = (unsigned long) pow (opt->total_num, opt->select_num)+2;
 4  
 5        for (i=0, size = 1; i<opt->select_num;i++)//计算集合的总数 
 6        {
 7            size *= opt->total_num;
 8        }

 9        i = size = size + 2
10        
11        for (j=0; j<opt->select_num; j++)
12        {
13            ar->answer[ar->count  ][opt->select_num-j-1= opt->resource [(opt->total_num*10-j-1)%opt->total_num];
14            ar->answer[ar->count+1][j] = opt->resource [opt->total_num-1];
15        }

16        return (ar->answer[ar->count]);
17    }
 
      上面这一段是当还未进行猜测的时候触发的一段函数。
      5-9行是计算一下整个集合的数量,10选8的结果应该是10的8次方,1亿。其中变量i在之前静态声明。
      11-15行是生成第一次和第二次要猜测的answer。至于为什么要固定第一次和第二次的猜测答案。在我的程序中也有类似的设定,我的全集是(0000-9999)规定第一次猜测9876,第二次猜测2345。这样做的好处是可以最大限度的加速猜测速度。如果第一次猜0000,而第二次猜1111的话,排除掉的结果比较少。
      最后函数返回第一次的猜测结果。 
      
for (; i; i--)
    
{

    }

      上面这个循环是当有了猜测数后,开始进行排除工作,最后产生一个有效的新的答案。i是集合中剩余结果的数量。

 1ar->answer[ar->count][0++;
 2        for (j=0; j<opt->select_num; j++)
 3        {
 4            if (ar->answer[ar->count][j] > opt->resource [opt->total_num-1])
 5            {
 6                ar->answer[ar->count][j] = opt->resource [0];
 7                ar->answer[ar->count][j+1]++;
 8                ar->answer[ar->count][opt->select_num] = 0;
 9            }
 else
10            {
11                break;
12            }

13        }

      上面这段是进入循环之后的第一段代码,
      1行,猜测次数加1.
      2-13行从注释来看这是生成一个新的答案系列,可是我只能看出这是对本次对比筛选的答案的检查,看是否超出全集中的范围。
1  for (j=0; j<ar->count; j++)
2        {   // compare with all records
3            give_answer_tip (tip, ar->answer[j], ar->answer[ar->count], opt->select_num);
4            if (tip[0!= ar->tip[j][0|| tip[1!= ar->tip[j][1])
5            {
6                break;
7            }

8        }

      上面这段是进入循环之后的第二段代码,
      将这个结果和前几次的猜测做“猜数字判断”,如果和之前的猜测结果不完全相同的,则跳出。
      这一点不太明白。首先为什么要和之前的结果做比对。之前的结果是猜数和答案的关系。而新猜数和旧猜数之间的关系能说明什么呢?这不是惯常的思路。      
1if (j == ar->count)
2        {   // pass all records check, record new answer
3            memcpy (ar->answer[ar->count+1], ar->answer[ar->count], opt->select_num);
4            return (ar->answer[ar->count]);
5        }

      当上一个循环没有break时,也就是(当前数字和之前猜数)的比对结果和(之前猜数和答案)的比对结果都不一样的时候。这该数字为新的猜数。返回。

posted @ 2007-12-01 17:05 张旭 阅读(465) | 评论 (0)编辑 收藏

     摘要: 在TCP/IP网络结构中,为了保证网络安全,网络人员往往需要在路由器上添加防火墙,禁止非法用户用ftp等安全危害较大的TCP/IP协议访问主机。而有时系统维护人员需要用ftp将一些文件从中心机房主机传到前端网点主机上,比如应用程序的替换升级。如果每次传输文件时都要打开防火墙,未免显得有些繁琐,要是在自己的应用程序中增加一个专门的文件传输模块,那将是十分愉快的事情。   阅读全文

posted @ 2007-12-01 01:27 张旭 阅读(2169) | 评论 (0)编辑 收藏

     摘要: 今天重新把socket编程中的每一个函数的功能和说明都仔细的看了一遍,也有了更深一层的理解。在经历一次面试的失利之后,我觉得最大的问题就出在没有对学过的知识力求甚解,导致对概念不清楚。所以在看这部分知识的时候,倍加用心的研究。
socket编程中主要用到一个结构 sockaddr和以下几个函数,socket(),bind(),connect(),listen(),accept(),send(),recv()。  阅读全文

posted @ 2007-12-01 00:22 张旭 阅读(589) | 评论 (0)编辑 收藏