自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+
  • 博客(69)
  • 资源 (4)
  • 收藏
  • 关注

原创 python:excel导入hive

实现功能目前公司中的部分数据是crm人工处理的,处理后放在一个excel文件中。现需要将这些excel数据导入到大数据平台,供其他部门使用。本程序提供一个web页面,实现在web页面上传指定格式的excel文件,程序自动将该文件的数据导入到hive中。实现方案web交互:Flaskexcel解析:Pandas数据导入:Impyla 或 Hdfs使用Flask快速搭建web应用,实现接收文件、流程控制、web交互。将上传的文件保存到指定临时目录下,用pandas解析excel.

2021-08-03 17:27:02 1449

原创 漫谈:一个工作两年半的程序员的人生感悟

前言从2018年毕业之后,到今天2020年9月19日,不知不觉间我已经工作了两年零三个月了。先后入职了两家公司,在这两段工作经历中,能够感觉自己获得了极大的成长,这个成长不仅仅是技术上的,更多的是学习到了一个职场人应具备的心理素质和软实力。刚毕业那会,别人一眼就能看出我是一个初出学校的人,当时还沾沾自喜,觉得自己长得还挺年轻的,哈哈哈哈。可是,“像是一个学生”真的是对你的褒奖吗?其实不然,尽管这只是别人随口说说,可是“学生”这个词对于一个职场人而言,恰恰说明了这个人的专业性不足。给别人的第一印象

2020-09-21 16:04:39 7955 7

原创 大数据:缓慢变化维

create table ldltmp.test_scd_dwd( id int, name string)stored as parquet;create table ldltmp.test_scd_dws( id int, name string, start_date string, end_date string, is_current_flag tinyint)stored as parquet;create...

2020-09-01 22:02:15 1094

原创 java:神武防掉线挂机小程序

需求游戏中一段时间不操作,会自动掉线,本程序模拟鼠标点击操作,实现自动挂机、自动刷怪、副本挂机三个功能。实现定时点击屏幕固定位置,实现自动加血、自动打开副本宝箱、自动移动等功能。由于点击位置固定,所以需要将游戏窗口放在屏幕左上角。程序分为四个小模块:RobotUtils:鼠标点击工具类,模拟鼠标点击操作。Show:用于显示操作窗口。GuajiService:主要模块,根据不同模式使用不同策略定时点击不同位置。GuajiControler:程序启动入口。代码实现Robo

2020-08-23 19:39:40 1944

原创 Hudi:初识Hudi

是什么?Hudi是什么?可以说Hudi是一个数据湖或是数据库,但它又不是数据湖或是数据库。笔者理解为Hudi是不带计算功能Hive。众所周知,Hive是一个计算引擎,但是现在我们更多的是使用Spark基于Hive对HDFS中文件提供的Schema信息和元数据进行计算,而Hive作为计算引擎的功能逐渐被忽略,更多的是将Hive视作一个“数据库”(尽管它并不是),而Hudi则是完善了Hive的这部分功能。使用Hudi对HDFS或是其他存储系统中的文件进行管理,使用Hudi创建相应的表,一样可以使用H

2020-07-07 23:18:30 9811 4

原创 Java:爬取代理ip,并使用代理IP刷uv

前言很多网站对访问量的判断并不严格,只要页面被点击即视为有效访问,但是应该现在反爬虫越来越严格,为防止部分网站会对IP进行拉黑,所以这里写一个小程序爬取代理IP,并使用代理IP刷访问量。原本想把代理IP包装成一个bean类,但是发现爬下来的代理IP都不需要用户名和密码,那就只要ip和端口就行了,索性实现得简单点,只关心ip和端口好了。模块组织FileUtil:用于提供爬取到的IP和url的写入、读取接口。CheckUtil:用于校验爬取到的IP是否可用。SpiderUtil:爬虫动作的主

2020-06-26 11:23:50 2405

原创 spark对接hudi遇到的坑

1.spark-sql读写MOR 的hudi表spark版本:2.4.3hudi版本:0.9.0按照官网文档可正常独写cow表,但读写mor时报错:Caused by: org.apache.hudi.exception.HoodieException: Unable to load class at org.apache.hudi.common.util.ReflectionUtils.getClass(ReflectionUtils.java:57) at org.apache..

2021-12-06 14:34:35 5249

原创 python:pyinstaller打包后,运行exe提示缺失包文件

场景使用python写的一个ui工具,用于连接并操作impala。但在打包后,报错提示:FileNotFoundError: [Errno 2] No such file or directory: 'D:\\code\\python\\excel2csv\\dist\\UI\\impala\\thrift\\ExecStats.thrift'打包命令:pyinstaller -D -p D:\code\python\excel2csv\venv\Lib\site-packages U

2021-04-20 17:05:53 9420 6

原创 hive:引入hive-jdbc后导致jar包冲突

场景spark项目,需要在写入表后,连接impala执行invalidate。需要用到hive驱动org.apache.hive.jdbc.HiveDriver 。但是在引入hive-jdbc后,spark程序启动时报错,原因是spark-sql的包与hive-jdbc中的包冲突了。解决指定排除hive-jdbc中无用的jar包。如果你只需要hive-jdbc包,可直接排除hive-jdbc依赖的所有包: <dependency> <grou.

2021-03-03 19:25:09 2457 1

原创 Spark:distinct算子会把所有数据拉到Driver吗?

前言在大数据中写SQL时,通常不使用distinct关键字,因为这样效率较低。但是看到spark core中有一个distinct算子,官网上介绍用于返回一个不含重复元素的dataset。目前在打算用spark core解析json,并动态生成hive表,就需要对所有json数据的key做去重,reduce、reduceByKey、groupByKey都可以实现,但是实现起来都得转换成pairs rdd。打算用distinct算子,又怕数据倾斜,所以看看distinct的源码。distinct([n

2021-03-01 16:21:21 654

原创 Excel:替换换行符

需求excel中的数据有时包含换行符,在使用时想要把换行符替换成空格,以下是操作方法:方法1. 按住ctrl + h 唤出替换窗口:2.在“查找内容”中,按住ctrl + j,此时输入框中会出现闪烁的逗号。在"替换为"中,输入替换成的字符。3. 点击全部替换。...

2021-02-26 11:41:06 8019

原创 Pandas:合并excel的所有sheet

需求把excel的所有sheet合并到一个sheet中,方便后续转换csv和上传hdfs。代码from pandas import pandas as pdimport pyarrowimport openpyxlimport xlrdimport tkinter##把excel中相同格式的sheet合并起来def mergeSheets(fileName, savePath): #读取excel df : pd.DataFrame = pd.read_e.

2021-02-26 11:01:21 1366 3

原创 Spark:3.0版本报错“java.lang.NoSuchFieldError: JAVA_9“

spark 3.0 版本在创建SparkSession时报错:Exception in thread "main" java.lang.NoSuchFieldError: JAVA_9 at org.apache.spark.storage.StorageUtils$.<init>(StorageUtils.scala:207) at org.apache.spark.storage.StorageUtils$.<clinit>(StorageUtils.sca...

2021-02-18 15:30:49 6248

原创 SQL:开窗排序,在order by 后加判断条件的作用是什么?

select *,row_number() over(partition by a order by b is not null desc,c is not null desc, d is not null desc) from test.zixuan_test;select *,row_number() over(partition by a order by b desc,c desc, d desc) from test.zixuan_test;(1,null,3,4)...

2021-02-09 13:26:26 2182

原创 hadoop:MR报错Error in last collector was:java.lang.NullPointerException

报错26-01-2021 11:13:41 CST PbopPrimeNewCustomerDroolsData INFO - Error: java.io.IOException: Initialization of all the collectors failed. Error in last collector was:java.lang.NullPointerException26-01-2021 11:13:41 CST PbopPrimeNewCustomerDroolsData IN

2021-01-26 11:50:32 602

原创 hive:beeline提交SQL报错 return code 2

1.现象beeline连hive跑任务,报错:Error: Error while processing statement: FAILED: Execution Error, return code 2 from org.apache.hadoop.hive.ql.exec.mr.MapRedTask (state=08S01,code=2)除此以外没有其他详细的错误信息。如果直接到 org.apache.hadoop.hive.ql.exec.mr.MapRedTask 这个类里,是找.

2021-01-21 15:55:33 1560

原创 parquet:查看parquet文件的schema信息

1.下载社区工具parquet-tools-1.6.0rc3-SNAPSHOT.jar2.查看schema信息(我在windows下执行的,jar包和parquet文件都在D盘)java -jar D:\parquet-tools-1.6.0rc3-SNAPSHOT.jar schema -d D:\part-00001-de10a7bd-e360-4c02-b4f4-1c30c6b91be3-c000.snappy.parquet结果:D:\>java -jar D:\

2021-01-11 19:30:07 3068

原创 Spark-SQL:spark3.0后BROADCAST、SHUFFLE_MERGE、SHUFFLE_HASH三种调优参数

https://www.24tutorials.com/spark/joins-spark-sql-shuffle-hash-sort-merge-broadcast/https://github.com/apache/spark/pull/24164Spark 3.0 之后,对Spark-SQL增加了三种join选项:SHUFFLE_HASH, SHUFFLE_MERGE , SHUFFLE_REPLICATE_NL。(原先只有BROADCAST 一种)SHUFFLE_HASH(随机散列.

2021-01-05 13:34:40 1275

原创 hive:修改列名后查询结果全部变成null

用Hive创建的Parquet格式的表,在重命名表的列名后,查询重名的列数据时显示当前列所有值为NULL。ALTER TABLE edw.dim_own_info_snp CHANGE userid user_id bigint COMMENT '用户id'查询结果:0: jdbc:hive2://hadoopcbd008098.ppdgdsl.com:2> select user_id from edw.dim_own_info_snp where dt='2020-12-28'

2020-12-29 17:04:24 2809 1

原创 Hadoop:MR以parquet格式保存文件

需求此前公司用MR程序解析json,将结果以text file保存在hive表的目录下。随着数据量增大,text file的性能逐渐跟不上,需要修改代码将文件格式修改成parquet。实现以下是以parquet保存结果的demo。将文本中的每行以空格分隔,第一列作为id(int),第二列作为name(string),直接保存到指定目录。[root@kudu1 job]# cat test.txt 1 xiaoming2 hanmeimei3 kangkang4 maria5

2020-12-06 23:41:47 1097

原创 欧莱雅实时数仓设计与实现

系统分析需求分析公司现有多个的销售渠道,如京东、天猫等分销商,线下门店,以及品牌自身的官网和微信小程序等。其中官网和微信小程序因为是公司内部管理的,所以数据可以及时的取到;而其他部分的销售数据都是第二天由各渠道统一推送。随着业务的深入,对于数据的实时性要求越来越高,尤其是官网、微信小程序的数据,在实时数仓建设以前,已经由每天计算一次改为每小时计算一次。而数据原先是已csv文件形式给到IT部门,按照旧的架构,需要先把csv导入到内部SQL Server之后,再从SQL Server抽取数据到h

2020-11-09 23:04:45 913

原创 JAVA:将CSV文件转换成JSON

需求将从数据库导出的csv文件以指定对应的key转换成json。思路使用openCSV读取csv文件。 给定一个String数组,保存json的keys。 遍历csv的每行数组,遍历keys和一行的所有列,append到一个json中。将得到的json添加到一个数组中,最后返回这个json数组。实现1.pom依赖 <dependency> <groupId>net.sf.opencsv</groupId>

2020-11-03 11:40:24 5179 2

原创 Hive:从HDFS回收站恢复被删的表

[ldl-dwh@utility02 ~]$ hdfs dfs -ls ./.Trash/*/data/ldldws/i_pos_offtakeFound 9 itemsdrwx------ - ldl-dwh ldl-dwh 0 2020-10-22 04:38 .Trash/201022050000/data/ldldws/i_pos_offtake/offtake_year=1952drwx------ - ldl-dwh ldl-dwh 0 202.

2020-10-22 22:30:39 1363 1

原创 lftp:报错“get: Fatal error: Host key verification failed ”

场景公司每天都会从ftp服务器下载csv数据,上传到hdfs。最近任务都失败了,定位到报错代码是以下命令:lftp -c "get -e -O /home/ldl-dwh/project/ldl-dwh-file-import-1.0-workspace/sycm_store_flow sftp://CPDMedia:[email protected]:22/PRD/UPLOAD/店铺流量bysku_20200916.csv"报错:get: Fatal error

2020-10-15 10:59:04 3475

原创 spark:计算LTV3

需求以下是hive表,请通过Spark Code于每日凌晨计算注册用户的LTV3,需要考虑查看单区服维度的LTV3。最终报表将以BI展示。LTV3定义:注册角色在注册3天(含当天)的总充值金额除以当天注册用户数。注册用户表:dwd_regserver_id role_id log_time log_date int str timestamp date 区服ID 角色ID 注册时间 分区键、注册日期 用户充值表:dwd_tra

2020-10-14 11:44:21 1143 1

原创 python:pandas常用操作

一、安装pandas# 1、安装包$ pip install pandas# 2、进入python的交互式界面$ python -i# 3、使用Pandas>>> import pandas as pd>>> df = pd.DataFrame() >>> print(df)# 4、输出结果Empty DataFrameColumns: []Index: []...

2020-10-13 00:25:05 1689 1

原创 python:读取XML的类名并反射创建类对象(工厂模式)

需求根据传递的参数到xml中查找全类名,并反射获取类对象。XML的操作:https://www.runoob.com/python/python-xml.html实现

2020-10-11 18:56:57 1336

原创 SQL:分享两个SQL笔试题

前言公司招人招了四个多月了,前阵子让我出几个笔试题,这里把题目和答案分享下。题目一:连续登录问题user_id dt a 2020-09-01 a 2020-09-02 a 2020-09-05 a 2020-09-07 a 2020-09-08 a 2020-09-09 a 2020-09-10 b 2020-09-08 b 2020-09-09 c 2020-09-05 d 2

2020-10-09 16:56:33 428

原创 超基础算法:动态规划

算法介绍动态规划(Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程。20世纪50年代初,美国数学家贝尔曼(R.Bellman)等人在研究多阶段决策过程的优化问题时,提出了著名的最优化原理,从而创立了动态规划。动态规划的应用极其广泛,包括工程技术、经济、工业生产、军事以及自动化控制等领域,并在背包问题、生产经营问题、资金管理问题、资源分配问题、最短路径问题和复杂系统可靠性问题等中取得了显著的效果算法思想动态规划的本质其实是一种分治思想,即把原问题分解为若

2020-09-29 12:29:04 483

原创 HBase:Region的拆分

Region的拆分与合并当一个Region的数据量增长到一定大小只会,Hbase 会对Region进行拆分;当删除Region中大量数据后,region数据量变小,则可能会合并Region。Region的拆分为什么要拆分Region。首先,Region是一段Rowkey数据的集合,当查询一条数据时,会先从元数据中判断该条数据的Rowkye属于哪个Region,然后到指定的Region中查找。当一个Region过大时,在这个Region中查找Rowkey的时间也越长。举个栗子,假设某个r.

2020-09-25 15:48:16 1985

原创 Diango:Django基础

一、创建python虚拟机并安装Django解决同包不同版本的依赖问题,使用python虚拟机。在虚拟机中安装包,不影响实际环境中的包。虚拟机实际上是复制了物理机上的python环境。创建虚拟环境命令:mkvirtualenv <虚拟环境名>如创建python3虚拟环境:mkvirtualenv -p python3 bj11_py3进入虚拟环境工作:workon <虚拟环境名>查看机器上有多少个虚拟环境:workon 空格 + 两个t

2020-09-17 23:32:35 614

原创 HBase:Hbase架构以及WAL机制

前言Hbase是一个分布式、可扩展、支持海量数据存储的NoSQL数据库。当数据量较小时,Hbase的优势不仅体现不出来,反而相比其他传统数据库而言更加消耗性能,但在数据量巨大的情况下,Hbase能达到秒级查询。Hbase的数据存储于HDFS,HDFS不支持随机写,但一个数据库需要支持增删改查,所以Hbase在底层存储时,实际上是只增不删,每条数据都包含一个时间戳属性,和删除标记,用于判断数据是否有效。Hbase概念数据模型Hbase逻辑上和关系型数据库类似,数据都是存储在一张表中,有行有

2020-09-17 11:20:36 2002

原创 spark:计算订单中所有商品是否属于套包

需求计算订单中,所包含的商品(单品)是否可以组成套包,且得出这个套包在这个订单中的个数。即:假设某套包的商品为(a,b,c),某订单中购买了(a,b,c,d,a,b,c)五个商品,则这个订单中包含2个这个套包。之前是将套包维度表和订单事实表进行笛卡尔积,然后用hive的UDF函数计算,但是在实际使用过程中发现速度很慢。了解到spark 2.0之后的第二代钨丝计划对spark内部函数进行了大量优化,可能性能会比hive的UDF函数好点。考虑进行重构,使用spark的UDF函数进行计算。再此之前先使

2020-09-16 13:40:45 491

原创 Flink:配置基于yarn的JobManager 高可用

前言JobManager 用于协调每个Flink任务的调度和资源管理。默认情况下,每个Flink集群只有一个JobManager实例。那么就意味着存在单点故障。如果JobManager崩溃,就不能提交新的任务,且运行中的任务也会失败。JobManager 高可用可以在JobManager挂掉后,恢复JobManager,从而消除单点故障。Flink独立部署和部署在Yarn上都可以使用JobManager高可用,生产环境中大多是部署在yarn上的。基于Yarn的JobManager高可用任然只

2020-09-13 23:01:02 1516

原创 sqoop:导出MySQL数据至Hive时,数据中包含\001或\n等字符

场景使用sqoop从MySQL导出数据至Hive时,如果数据中包含hive指定的列分隔符,如\001 或\t,那么在Hive中就会导致数据错位;如果数据中包含换行符\n,那么就会导致原先的一行数据,在Hive中变成了两行。解决办法在sqoop执行时,使用如下参数:--hive-drop-import-delims 导入Hive时,从字符串字段中删除\n、\r和\01。 --hive-delims-replacement 导入Hive,将字符串字段中的\n、\r和\01替换为指

2020-09-10 17:40:03 2490

原创 Impala:架构及组件

概述Impala是一个实时查询工具,它提高了大数据在hadoop上的sql查询性能,Impala是对大数据查询工具的补充。Impala不取代基于MapReduce构建的批处理框架,如Hive。Impala直接读取存储在HDFS、HBase或亚马逊对象存储服务(S3)的数据。除了与Hive使用相同的存储平台以外,impala还与Hive使用相同的元数据、SQL语法(Hive SQL)、ODBC驱动程序和用户界面(HUE中的Impala查询UI)。基于MapReduce的Hive和其他框架(如spa

2020-09-08 23:58:01 868

原创 spark:报错com.esotericsoftware.kryo.KryoException: Buffer underflow.

场景spark-sql跑一个较大的任务(几亿条数据),数据落盘时报错:com.esotericsoftware.kryo.KryoException: Buffer underflow.探索查看抛出这个报错的源码:https://github.com/apache/spark/blob/ebdf41dd698ce138d07f63b1fa3ffbcc392e7fff/core/src/main/scala/org/apache/spark/serializer/KryoSerialize

2020-09-07 22:11:37 5527

原创 小记:记一次sort merge join导致的数据倾斜

场景同事的一张订单表,三年共2亿条左右数据,在join多张维度表后,写回hive中。发现每次任务都耗时三小时左右。而我的另一张表,数据量也在2亿左右,同样join了多张维度表,耗时仅6分钟。同事的任务:我的任务:数据量排查首先到spark的历史服务web页面,找到这条任务,查看时哪个job耗时比较长,发现有个job耗时2小时:查看这个job的执行计划,发现左边的大表有99G数据,而右边的小表仅16M数据,但是使用了sort merge join。在spark co

2020-08-31 11:11:29 1273

原创 flink:java.lang.NoClassDefFoundError: org/apache/kafka/common/errors/InvalidTxnStateException报错

报错flink消费kafka时正常,但是向kafka生产消息时产生报错:java.lang.NoClassDefFoundError: org/apache/kafka/common/errors/InvalidTxnStateException排错开始以为是jar包冲突导致,因为项目中即用到了kafka-clients,也用到flink-connector-kafka_2.11,分别排除两个依赖下的kafka-clients。 <dependency> &lt

2020-08-27 00:03:43 6097

原创 centos 7 安装mysql

前言centos 7默认数据库为mariaDB,安装mysql前需要先卸载mariaDB。安装mysql安装包下载:https://dev.mysql.com/downloads/mysql/5.7.html查看mariaDB是否安装[root@kudu1 mysql-libs]# rpm -qa|grep mariadbmariadb-libs-5.5.56-2.el7.x86_64卸载mariaDB[root@kudu1 mysql-libs]# rpm -e -

2020-08-15 22:51:13 533

kudu1.9的rpm安装包

kudu1.9的rpm安装包,包含以下六个资源: kudu-1.9.0+cdh6.2.1-1425774.el6.x86_64.rpm kudu-client0-1.9.0+cdh6.2.1-1425774.el6.x86_64.rpm kudu-client-devel-1.9.0+cdh6.2.1-1425774.el6.x86_64.rpm kudu-debuginfo-1.9.0+cdh6.2.1-1425774.el6.x86_64.rpm kudu-master-1.9.0+cdh6.2.1-1425774.el6.x86_64.rpm kudu-tserver-1.9.0+cdh6.2.1-1425774.el6.x86_64.rpm

2020-07-22

kudu1.4的rpm安装包

kudu的rpm安装包,包含以下六个资源: kudu-1.4.0+cdh5.12.2+0-1.cdh5.12.2.p0.8.el7.x86_64.rpm kudu-client0-1.4.0+cdh5.12.2+0-1.cdh5.12.2.p0.8.el7.x86_64.rpm kudu-client-devel-1.4.0+cdh5.12.2+0-1.cdh5.12.2.p0.8.el7.x86_64.rpm kudu-debuginfo-1.4.0+cdh5.12.2+0-1.cdh5.12.2.p0.8.el7.x86_64.rpm kudu-master-1.4.0+cdh5.12.2+0-1.cdh5.12.2.p0.8.el7.x86_64.rpm kudu-tserver-1.4.0+cdh5.12.2+0-1.cdh5.12.2.p0.8.el7.x86_64.rpm

2020-07-22

c# 屏幕广播

本程序根据本站power2008man发布的局域网屏幕广播C#上添加了开始和关闭广播,组播组自选等功能.源码地址https://download.csdn.net/download/power2008man/7857657

2018-05-10

FTP服务器 C#

用VS编写的FTP服务器软件,C#网络程序编程学习用。 代码: using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Net; using System.Net.Sockets; using System.Threading; using System.Windows.Forms; namespace FtpServer { public partial class FtpServerForm : Form { TcpListener myTcpListener = null; private Thread listenThread; // 保存用户名和密码 Dictionary users; public FtpServerForm() { InitializeComponent(); // 初始化用户名和密码 users = new Dictionary(); users.Add("admin", "admin"); // 设置默认的主目录 tbxFtpRoot.Text = "F:/MyFtpServerRoot/"; IPAddress[] ips = Dns.GetHostAddresses(""); tbxFtpServerIp.Text = ips[5].ToString(); tbxFtpServerPort.Text = "21"; lstboxStatus.Enabled = false; } // 启动服务器 private void btnFtpServerStartStop_Click(object sender, EventArgs e) { if (myTcpListener == null) { listenThread = new Thread(ListenClientConnect); listenThread.IsBackground = true; listenThread.Start(); lstboxStatus.Enabled = true; lstboxStatus.Items.Clear(); lstboxStatus.Items.Add("已经启动Ftp服务..."); btnFtpServerStartStop.Text = "停止"; } else { myTcpListener.Stop(); myTcpListener = null; listenThread.Abort(); lstboxStatus.Items.Add("Ftp服务已停止!"); lstboxStatus.TopIndex = lstboxStatus.Items.Count - 1; btnFtpServerStartStop.Text = "启动"; } } // 监听端口,处理客户端连接 private void ListenClientConnect() { myTcpListener = new TcpListener(IPAddress.Parse(tbxFtpServerIp.Text), int.Parse(tbxFtpServerPort.Text)); // 开始监听传入的请求 myTcpListener.Start(); AddInfo("启动FTP服务成功!"); AddInfo("Ftp服务器运行中...[点击”停止“按钮停止FTP服务]"); while (true) { try { // 接收连接请求 TcpClient tcpClient = myTcpListener.AcceptTcpClient(); AddInfo(string.Format("客户端({0})与本机({1})建立Ftp连接", tcpClient.Client.RemoteEndPoint, myTcpListener.LocalEndpoint)); User user = new User(); user.commandSession = new UserSeesion(tcpClient); user.workDir = tbxFtpRoot.Text; Thread t = new Thread(UserProcessing); t.IsBackground = true; t.Start(user); } catch { break; } } } // 处理客户端用户请求 private void UserProcessing(object obj) { User user = (User)obj; string sendString = "220 FTP Server v1.0"; RepleyCommandToUser(user, sendString); while (true) { string receiveString = null; try { // 读取客户端发来的请求信息 receiveString = user.commandSession.streamReader.ReadLine(); } catch(Exception ex) { if (user.commandSession.tcpClient.Connected == false) { AddInfo(string.Format("客户端({0}断开连接!)", user.commandSession.tcpClient.Client.RemoteEndPoint)); } else { AddInfo("接收命令失败!" + ex.Message); } break; } if (receiveString == null) { AddInfo("接收字符串为null,结束线程!"); break; } AddInfo(string.Format("来自{0}:[{1}]", user.commandSession.tcpClient.Client.RemoteEndPoint, receiveString)); // 分解客户端发来的控制信息中的命令和参数 string command = receiveString; string param = string.Empty; int index = receiveString.IndexOf(' '); if (index != -1) { command = receiveString.Substring(0, index).ToUpper(); param = receiveString.Substring(command.Length).Trim(); } // 处理不需登录即可响应的命令(这里只处理QUIT) if (command == "QUIT") { // 关闭TCP连接并释放与其关联的所有资源 user.commandSession.Close(); return; } else { switch (user.loginOK) { // 等待用户输入用户名: case 0: CommandUser(user, command, param); break; // 等待用户输入密码 case 1: CommandPassword(user, command, param); break; // 用户名和密码验证正确后登陆 case 2: switch (command) { case "CWD": CommandCWD(user, param); break; case "PWD": CommandPWD(user); break; case "PASV": CommandPASV(user); break; case "PORT": CommandPORT(user, param); break; case "LIST": CommandLIST(user, param); break; case "NLIST": CommandLIST(user, param); break; // 处理下载文件命令 case "RETR": CommandRETR(user, param); break; // 处理上传文件命令 case "STOR": CommandSTOR(user, param); break; // 处理删除命令 case "DELE": CommandDELE(user, param); break; // 使用Type命令在ASCII和二进制模式进行变换 case "TYPE": CommandTYPE(user, param); break; default: sendString = "502 command is not implemented."; RepleyCommandToUser(user, sendString); break; } break; } } } } // 想客户端返回响应码 private void RepleyCommandToUser(User user, string str) { try { user.commandSession.streamWriter.WriteLine(str); AddInfo(string.Format("向客户端({0})发送[{1}]", user.commandSession.tcpClient.Client.RemoteEndPoint, str)); } catch { AddInfo(string.Format("向客户端({0})发送信息失败", user.commandSession.tcpClient.Client.RemoteEndPoint)); } } // 向屏幕输出显示状态信息(这里使用了委托机制) private delegate void AddInfoDelegate(string str); private void AddInfo(string str) { // 如果调用AddInfo()方法的线程与创建ListView控件的线程不在一个线程时 // 此时利用委托在创建ListView的线程上调用 if (lstboxStatus.InvokeRequired == true) { AddInfoDelegate d = new AddInfoDelegate(AddInfo); this.Invoke(d, str); } else { lstboxStatus.Items.Add(str); lstboxStatus.TopIndex = lstboxStatus.Items.Count - 1; lstboxStatus.ClearSelected(); } } #region 处理各个命令 #region 登录过程,即用户身份验证过程 // 处理USER命令,接收用户名但不进行验证 private void CommandUser(User user, string command, string param) { string sendString = string.Empty; if (command == "USER") { sendString = "331 USER command OK, password required."; user.userName = param; // 设置loginOk=1为了确保后面紧接的要求输入密码 // 1表示已接收到用户名,等到接收密码 user.loginOK = 1; } else { sendString = "501 USER command syntax error."; } RepleyCommandToUser(user, sendString); } // 处理PASS命令,验证用户名和密码 private void CommandPassword(User user, string command, string param) { string sendString = string.Empty; if (command == "PASS") { string password = null; if (users.TryGetValue(user.userName, out password)) { if (password == param) { sendString = "230 User logged in success"; // 2表示登录成功 user.loginOK = 2; } else { sendString = "530 Password incorrect."; } } else { sendString = "530 User name or password incorrect."; } } else { sendString = "501 PASS command Syntax error."; } RepleyCommandToUser(user, sendString); // 用户当前工作目录 user.currentDir = user.workDir; } #endregion #region 文件管理命令 // 处理CWD命令,改变工作目录 private void CommandCWD(User user, string temp) { string sendString = string.Empty; try { string dir = user.workDir.TrimEnd('/') + temp; // 是否为当前目录的子目录,且不包含父目录名称 if (Directory.Exists(dir)) { user.currentDir = dir; sendString = "250 Directory changed to '" + dir + "' successfully"; } else { sendString = "550 Directory '" + dir + "' does not exist"; } } catch { sendString = "502 Directory changed unsuccessfully"; } RepleyCommandToUser(user,sendString); } // 处理PWD命令,显示工作目录 private void CommandPWD(User user) { string sendString = string.Empty; sendString = "257 '" + user.currentDir + "' is the current directory"; RepleyCommandToUser(user, sendString); } // 处理LIST/NLIST命令,想客户端发送当前或指定目录下的所有文件名和子目录名 private void CommandLIST(User user, string parameter) { string sendString = string.Empty; DateTimeFormatInfo dateTimeFormat = new CultureInfo("en-US", true).DateTimeFormat; // 得到目录列表 string[] dir = Directory.GetDirectories(user.currentDir); if (string.IsNullOrEmpty(parameter) == false) { if (Directory.Exists(user.currentDir + parameter)) { dir = Directory.GetDirectories(user.currentDir + parameter); } else { string s = user.currentDir.TrimEnd('/'); user.currentDir = s.Substring(0, s.LastIndexOf("/") + 1); } } for (int i = 0; i < dir.Length; i++) { string folderName = Path.GetFileName(dir[i]); DirectoryInfo d = new DirectoryInfo(dir[i]); // 按下面的格式输出目录列表 sendString += @"dwr-\t" + Dns.GetHostName() + "\t" + dateTimeFormat.GetAbbreviatedMonthName(d.CreationTime.Month) + d.CreationTime.ToString(" dd yyyy") + "\t" + folderName + Environment.NewLine; } // 得到文件列表 string[] files = Directory.GetFiles(user.currentDir); if (string.IsNullOrEmpty(parameter) == false) { if (Directory.Exists(user.currentDir + parameter + "/")) { files = Directory.GetFiles(user.currentDir + parameter + "/"); } } for (int i = 0; i 1024的随机端口 // 下面这个运算算法只是为了得到一个大于1024的端口值 port = random1 << 8 | random2; try { user.dataListener = new TcpListener(localip, port); AddInfo("TCP 数据连接已打开(被动模式)--" + localip.ToString() + ":" + port); } catch { continue; } user.isPassive = true; string temp = localip.ToString().Replace('.', ','); // 必须把端口号IP地址告诉客户端,客户端接收到响应命令后, // 再通过新的端口连接服务器的端口P,然后进行文件数据传输 sendString = "227 Entering Passive Mode(" + temp + "," + random1 + "," + random2 + ")"; RepleyCommandToUser(user, sendString); user.dataListener.Start(); break; } } // 处理PORT命令,使用主动模式进行传输 private void CommandPORT(User user, string portstring) { // 主动模式时,客户端必须告知服务器接收数据的端口号,PORT 命令格式为:PORT address // address参数的格式为i1、i2、i3、i4、p1、p2,其中i1、i2、i3、i4表示IP地址 // 下面通过.字符串来组合这四个参数得到IP地址 // p1、p2表示端口号,下面通过int.Parse(temp[4]) << 8) | int.Parse(temp[5] // 这个算法来获得一个大于1024的端口来发送给服务器 string sendString = string.Empty; string[] temp = portstring.Split(','); string ipString = "" + temp[0] + "." + temp[1] + "." + temp[2] + "." + temp[3]; // 客户端发出PORT命令把客户端的IP地址和随机的端口告诉服务器 int portNum = (int.Parse(temp[4]) < 0) { user.dataSession.binaryWriter.Write(bytes, 0, count); user.dataSession.binaryWriter.Flush(); count = binaryReader.Read(bytes, 0, bytes.Length); } } else { StreamReader streamReader = new StreamReader(fs); while (streamReader.Peek() > -1) { user.dataSession.streamWriter.WriteLine(streamReader.ReadLine()); } } AddInfo("...]发送完毕!"); } finally { user.dataSession.Close(); fs.Close(); } } // 使用数据连接接收文件流(客户端发送上传文件功能) private void ReadFileByUserSession(User user, FileStream fs) { AddInfo("接收用户上传数据(文件流):[..."); try { if (user.isBinary) { byte[] bytes = new byte[1024]; BinaryWriter binaryWriter = new BinaryWriter(fs); int count = user.dataSession.binaryReader.Read(bytes, 0, bytes.Length); while (count > 0) { binaryWriter.Write(bytes, 0, count); binaryWriter.Flush(); count = user.dataSession.binaryReader.Read(bytes, 0, bytes.Length); } } else { StreamWriter streamWriter = new StreamWriter(fs); while (user.dataSession.streamReader.Peek() > -1) { streamWriter.Write(user.dataSession.streamReader.ReadLine()); streamWriter.Flush(); } } AddInfo("...]接收完毕"); } finally { user.dataSession.Close(); fs.Close(); } } private void label3_Click(object sender, EventArgs e) { } } }

2018-03-10

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除