Sphinx是由俄罗斯人Andrew Aksyonoff开发的一个可以结合MySQL,PostgreSQL全文检索引擎。意图为其他应用提供高速、低空间占用、高结果 相关度的全文搜索功能。是做站内全文搜索的一把利器。
sphinx已经出现很多年,并不是一个新鲜技术,但如今仍被广泛使用者。但由于IT技术的不断创新,在圈子中又出现了几款用于全文检索的新技术,如lucene就是一款与之媲美的工具,但相对而言,它的建立索引的速度却远远不如sphinx。次文不介绍sphinx的如何优越,主要介绍一下我在使用sphinx是的一些心得笔记,勿拍砖,希望给大家一个参考。
sphinx拥有丰富的学习材料,http://www.sphinxsearch.org/archives/80 这文档拥有详细的sphinx安装步骤。
本次使用操作系统为centos6.5,sphinx2.2版本。
Sphinx在mysql上的应用有两种方式:(下面这一段是摘抄的,勿拍砖)
①、采用API调用,如使用PHP、java等的API函数或方法查询。优点是可不必对mysql重新编译,服务端进程“低耦合”,且程序可灵活、方便的调用;
缺点是如已有搜索程序的条件下,需修改部分程序。推荐程序员使用。
②、使用插件方式(sphinxSE)把sphinx编译成一个mysql插件并使用特定的sql语句进行检索。其特点是,在sql端方便组合,且能直接返回数据给客户端。不必二次查询(注),在程序上仅需要修改对应的sql,但这对使用框架开发的程序很不方便,比如使用了ORM。另外还需要对mysql进行重新编译,且需要mysql-5.1以上版本支持插件存储。
系统管理员可使用这种方式sphinx在检索到结果后只能返回记录的ID,而非要查的sql数据,故需要重新根据这些ID再次从数据库中查询。
红色这段话是学习sphinx最为重要信息。通常为了快速开发,或者为了对已有项目进行优化,只能使用第一种方案。第二种插件方式,对于如今orm框架盛行的今天,可能选择的人会很少,对于我们.net程序员,更不会考虑。
那我们先看看Sphinx第一种方式的实现,第一种方式配置比较简单,根据中文手册可顺利配置完成,重点是配置完成的sphinx,在.net下我们该如何使用:
(1)sphinx connector.net
sphinx官网本身不支持c#.net的API这让我们改怎么活。不过后来伟大的人出现了,相继出现了sphinx connector.net为我们提供了连接sphinx服务器的接口,而且跟linq语法紧密结合,真的很不错,但是。。。它是付费的,不然搜索出的记录最多只能有5条。我们.net程序员本来工资就不高,放弃,继续寻找免费的大萝卜。。。。
(2)sphinx.client
网上还是有大牛仿照php的api源码,写出了sphinxClient的.net类库,调调就能用。站在巨人的肩膀上吹风,凉快。。。。
Sphinx在mysql中的使用,无非是为数据库各个字段提供更为快速的索引库,当mysql出现巨量数据的时候能够提供更为快速的秒搜能力,如果你还用传统sql like。。。。结果可想而知。
sphinx本身不支持中文字体的搜索,如果想实现中文的搜索必须使用字符表,或者实现中文分词。
(1)字符表,sphinx会对中文进行单字切分,进行字索引,速度慢点
字符表的实现很简单http://www.sphinxsearch.org/archives/80 已经实现。
(2)中文分词,使用分词插件如 coreseek,sfc,它里面有很好的算法,速度现对好点
coreseek至今还在不断更新,已经到了4版本,推荐使用,可参考资料:http://www.coreseek.cn/docs/coreseek_3.2-sphinx_0.9.9.html#installing 很详细,真好
当服务器配置完成后我们创建.net解决方案,实现sphinx服务器的通信;
using System; namespace phinxDemo { class Program { static void Main(string[] args) { SphinxClient sphinxClient = new SphinxClient("192.168.233.129", 9312); Console.WriteLine("请输入操作模式:[a]查询,[b]插入新数据,[c]删除数据"); string inputstr = Console.ReadLine(); while (true) { switch (inputstr) { case "a": //查询数据 Console.WriteLine("---------------查询数据---------------------"); Console.WriteLine("请输入匹配的字符串"); QueryData(Console.ReadLine(), sphinxClient); break; case "b": //插入数据 Console.WriteLine("---------------插入数据---------------------"); Console.WriteLine("请输入要插入的字符串"); break; case "c": //删除数据 Console.WriteLine("---------------删除数据---------------------"); Console.WriteLine("请输入要删除的字符串"); break; } } } private static void QueryData(string p, SphinxClient sphinxClient) { var sphinxSearchResult = sphinxClient.Query(p); Console.WriteLine("此查询在服务器检索所得的匹配文档总数:{0}", sphinxSearchResult.total); Console.WriteLine("索引中匹配文档的总数:{0}", sphinxSearchResult.totalFound); Console.WriteLine("将查询关键字映射到一个包含关于关键字的统计数据的小hash表上:{0}", sphinxSearchResult.totalFound); Console.WriteLine("searchd报告的错误信息:{0}", sphinxSearchResult.error); Console.WriteLine("searchd报告的警告信息:{0}", sphinxSearchResult.warning); foreach (SphinxMatch match in sphinxSearchResult.matches) { Console.WriteLine("DocumentId {0} Weight {1}", match.docId, match.weight); } Console.ReadLine(); } } }
提供大牛的sphinxClient
using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Sockets; using System.Text; namespace phinxDemo { public class SphinxClient : IDisposable { #region Static Values /* matching modes */ public static int SPH_MATCH_ALL = 0; public static int SPH_MATCH_ANY = 1; public static int SPH_MATCH_PHRASE = 2; public static int SPH_MATCH_BOOLEAN = 3; public static int SPH_MATCH_EXTENDED = 4; public static int SPH_MATCH_FULLSCAN = 5; public static int SPH_MATCH_EXTENDED2 = 6; /* sorting modes */ public static int SPH_SORT_RELEVANCE = 0; public static int SPH_SORT_ATTR_DESC = 1; public static int SPH_SORT_ATTR_ASC = 2; public static int SPH_SORT_TIME_SEGMENTS = 3; public static int SPH_SORT_EXTENDED = 4; public static int SPH_SORT_EXPR = 5; /* grouping functions */ public static int SPH_GROUPBY_DAY = 0; public static int SPH_GROUPBY_WEEK = 1; public static int SPH_GROUPBY_MONTH = 2; public static int SPH_GROUPBY_YEAR = 3; public static int SPH_GROUPBY_ATTR = 4; public static int SPH_GROUPBY_ATTRPAIR = 5; /* searchd reply status codes */ public static int SEARCHD_OK = 0; public static int SEARCHD_ERROR = 1; public static int SEARCHD_RETRY = 2; public static int SEARCHD_WARNING = 3; /* attribute types */ public static int SPH_ATTR_INTEGER = 1; public static int SPH_ATTR_TIMESTAMP = 2; public static int SPH_ATTR_ORDINAL = 3; public static int SPH_ATTR_BOOL = 4; public static int SPH_ATTR_FLOAT = 5; public static int SPH_ATTR_BIGINT = 6; public static int SPH_ATTR_MULTI = 0x40000000; /* searchd commands */ private static int SEARCHD_COMMAND_SEARCH = 0; private static int SEARCHD_COMMAND_EXCERPT = 1; private static int SEARCHD_COMMAND_UPDATE = 2; private static int SEARCHD_COMMAND_KEYWORDS = 3; private static int SEARCHD_COMMAND_PERSIST = 4; private static int SEARCHD_COMMAND_STATUS = 5; private static int SEARCHD_COMMAND_QUERY = 6; /* searchd command versions */ private static int VER_COMMAND_SEARCH = 0x116; private static int VER_COMMAND_EXCERPT = 0x100; private static int VER_COMMAND_UPDATE = 0x102; private static int VER_COMMAND_KEYWORDS = 0x100; private static int VER_COMMAND_STATUS = 0x100; private static int VER_COMMAND_QUERY = 0x100; /* filter types */ private static int SPH_FILTER_VALUES = 0; private static int SPH_FILTER_RANGE = 1; private static int SPH_FILTER_FLOATRANGE = 2; private static int SPH_CLIENT_TIMEOUT_MILLISEC = 0; #endregion #region Variable Declaration private string _host; private int _port; private int _offset; private int _limit; private int _mode; private int[] _weights; private int _sort; private string _sortby; private long _minId; private long _maxId; private int _filterCount; private string _groupBy; private int _groupFunc; private string _groupSort; private string _groupDistinct; private int _maxMatches; private int _cutoff; private int _retrycount; private int _retrydelay; private string _latitudeAttr; private string _longitudeAttr; private float _latitude; private float _longitude; private string _error; private string _warning; private Dictionary<string, int> _fieldWeights; private TcpClient _conn; // request queries already created List<byte[]> _requestQueries = new List<byte[]>(); private Dictionary<string, int> _indexWeights; // use a memorystream instead of a byte array because it's easier to augment MemoryStream _filterStreamData = new MemoryStream(); #endregion #region Constructors /** * Creates new SphinxClient instance. * * Default host and port that the instance will connect to are * localhost:3312. That can be overriden using {@link #SetServer SetServer()}. */ public SphinxClient() : this(AppConfig.GetSetting("SphinxServer"), AppConfig.GetRequiredSettingAsInt("SphinxServerPort")) { } /** * Creates new SphinxClient instance, with host:port specification. * * Host and port can be later overriden using {@link #SetServer SetServer()}. * * @param host searchd host name (default: localhost) * @param port searchd port number (default: 3312) */ public SphinxClient(string host, int port) { _host = host; _port = port; _offset = 0; _limit = 20; _mode = SPH_MATCH_ALL; _sort = SPH_SORT_RELEVANCE; _sortby = ""; _minId = 0; _maxId = 0; _filterCount = 0; _groupBy = ""; _groupFunc = SPH_GROUPBY_DAY; // _groupSort = "@group desc"; _groupSort = ""; _groupDistinct = ""; _maxMatches = 1000; _cutoff = 0; _retrycount = 0; _retrydelay = 0; _latitudeAttr = null; _longitudeAttr = null; _latitude = 0; _longitude = 0; _error = ""; _warning = ""; //_reqs = new ArrayList(); _weights = null; _indexWeights = new Dictionary<string, int>(); _fieldWeights = new Dictionary<string, int>(); } #endregion #region Main Functions /** Connect to searchd server and run current search query against all indexes (syntax sugar). */ public SphinxResult Query(string query) { return Query(query, "*"); } /** * Connect to searchd server and run current search query. * * @param query query string * @param index index name(s) to query. May contain anything-separated * list of index names, or "*" which means to query all indexes. * @return {@link SphinxResult} object * * @throws SphinxException on invalid parameters */ public SphinxResult Query(string query, string index) { //MyAssert(_requestQueries == null || _requestQueries.Count == 0, "AddQuery() and Query() can not be combined; use RunQueries() instead"); AddQuery(query, index); SphinxResult[] results = RunQueries(); if (results == null || results.Length < 1) { return null; /* probably network error; error message should be already filled */ } SphinxResult res = results[0]; _warning = res.warning; _error = res.error; return res; } public int AddQuery(string query, string index) { byte[] outputdata = new byte[2048]; /* build request */ try { MemoryStream ms = new MemoryStream(); BinaryWriter sw = new BinaryWriter(ms); WriteToStream(sw, _offset); WriteToStream(sw, _limit); WriteToStream(sw, _mode); WriteToStream(sw, 0); //SPH_RANK_PROXIMITY_BM25 WriteToStream(sw, _sort); WriteToStream(sw, _sortby); WriteToStream(sw, query); //_weights = new int[] { 100, 1 }; _weights = null; int weightLen = _weights != null ? _weights.Length : 0; WriteToStream(sw, weightLen); if (_weights != null) { for (int i = 0; i < _weights.Length; i++) WriteToStream(sw, _weights[i]); } WriteToStream(sw, index); WriteToStream(sw, 1); // id64 WriteToStream(sw, _minId); WriteToStream(sw, _maxId); /* filters */ WriteToStream(sw, _filterCount); if (_filterCount > 0 && _filterStreamData.Length > 0) { byte[] filterdata = new byte[_filterStreamData.Length]; _filterStreamData.Seek(0, SeekOrigin.Begin); _filterStreamData.Read(filterdata, 0, (int)_filterStreamData.Length); WriteToStream(sw, filterdata); } /* group-by, max matches, sort-by-group flag */ WriteToStream(sw, _groupFunc); WriteToStream(sw, _groupBy); WriteToStream(sw, _maxMatches); WriteToStream(sw, _groupSort); WriteToStream(sw, _cutoff); WriteToStream(sw, _retrycount); WriteToStream(sw, _retrydelay); WriteToStream(sw, _groupDistinct); /* anchor point */ if (_latitudeAttr == null || _latitudeAttr.Length == 0 || _longitudeAttr == null || _longitudeAttr.Length == 0) { WriteToStream(sw, 0); } else { WriteToStream(sw, 1); WriteToStream(sw, _latitudeAttr); WriteToStream(sw, _longitudeAttr); WriteToStream(sw, _latitude); WriteToStream(sw, _longitude); } /* per-index weights */ //sw.Write(_indexWeights.size()); WriteToStream(sw, this._indexWeights.Count); foreach (KeyValuePair<string, int> item in this._indexWeights) { WriteToStream(sw, item.Key); WriteToStream(sw, item.Value); } // max query time WriteToStream(sw, 0); // per-field weights WriteToStream(sw, this._fieldWeights.Count); foreach (KeyValuePair<string, int> item in this._fieldWeights) { WriteToStream(sw, item.Key); WriteToStream(sw, item.Value); } // comment WriteToStream(sw, ""); // attribute overrides WriteToStream(sw, 0); // select-list WriteToStream(sw, "*"); sw.Flush(); ms.Seek(0, SeekOrigin.Begin); byte[] data = new byte[ms.Length]; ms.Read(data, 0, (int)ms.Length); int qIndex = _requestQueries.Count; _requestQueries.Add(data); return qIndex; } catch (Exception ex) { //MyAssert(false, "error on AddQuery: " + ex.Message); } return -1; } /** Run all previously added search queries. */ public SphinxResult[] RunQueries() { if (_requestQueries == null || _requestQueries.Count < 1) { _error = "no queries defined, issue AddQuery() first"; return null; } if (Conn == null) return null; MemoryStream ms = new MemoryStream(); BinaryWriter bw = new BinaryWriter(ms); /* send query, get response */ int nreqs = _requestQueries.Count; try { WriteToStream(bw, (short)SEARCHD_COMMAND_SEARCH); WriteToStream(bw, (short)VER_COMMAND_SEARCH); //return null; int rqLen = 4; for (int i = 0; i < nreqs; i++) { byte[] subRq = (byte[])_requestQueries[i]; rqLen += subRq.Length; } WriteToStream(bw, rqLen); WriteToStream(bw, nreqs); for (int i = 0; i < nreqs; i++) { byte[] subRq = (byte[])_requestQueries[i]; WriteToStream(bw, subRq); } ms.Flush(); byte[] buffer = new byte[ms.Length]; ms.Seek(0, SeekOrigin.Begin); ms.Read(buffer, 0, buffer.Length); bw = new BinaryWriter(Conn.GetStream()); bw.Write(buffer, 0, buffer.Length); bw.Flush(); bw.BaseStream.Flush(); ms.Close(); } catch (Exception e) { //MyAssert(false, "Query: Unable to create read/write streams: " + e.Message); return null; } /* get response */ byte[] response = GetResponse(Conn, VER_COMMAND_SEARCH); /* parse response */ SphinxResult[] results = ParseResponse(response); /* reset requests */ _requestQueries = new List<byte[]>(); return results; } private SphinxResult[] ParseResponse(byte[] response) { if (response == null) return null; /* parse response */ SphinxResult[] results = new SphinxResult[_requestQueries.Count]; BinaryReader br = new BinaryReader(new MemoryStream(response)); /* read schema */ int ires; try { for (ires = 0; ires < _requestQueries.Count; ires++) { SphinxResult res = new SphinxResult(); results[ires] = res; int status = ReadInt32(br); res.setStatus(status); if (status != SEARCHD_OK) { string message = ReadUtf8(br); if (status == SEARCHD_WARNING) { res.warning = message; } else { res.error = message; continue; } } /* read fields */ int nfields = ReadInt32(br); res.fields = new string[nfields]; //int pos = 0; for (int i = 0; i < nfields; i++) res.fields[i] = ReadUtf8(br); /* read arrts */ int nattrs = ReadInt32(br); res.attrTypes = new int[nattrs]; res.attrNames = new string[nattrs]; for (int i = 0; i < nattrs; i++) { string AttrName = ReadUtf8(br); int AttrType = ReadInt32(br); res.attrNames[i] = AttrName; res.attrTypes[i] = AttrType; } /* read match count */ int count = ReadInt32(br); int id64 = ReadInt32(br); res.matches = new SphinxMatch[count]; for (int matchesNo = 0; matchesNo < count; matchesNo++) { SphinxMatch docInfo; docInfo = new SphinxMatch( (id64 == 0) ? ReadUInt32(br) : ReadInt64(br), ReadInt32(br)); /* read matches */ for (int attrNumber = 0; attrNumber < res.attrTypes.Length; attrNumber++) { string attrName = res.attrNames[attrNumber]; int type = res.attrTypes[attrNumber]; /* handle bigint */ if (type == SPH_ATTR_BIGINT) { docInfo.attrValues.Add(ReadInt64(br)); continue; } /* handle floats */ if (type == SPH_ATTR_FLOAT) { docInfo.attrValues.Add(ReadFloat(br)); //docInfo.attrValues.add ( attrNumber, bw.ReadDouble ) ); //throw new NotImplementedException("we don't read floats yet"); continue; } /* handle everything else as unsigned ints */ long val = ReadUInt32(br); if ((type & SPH_ATTR_MULTI) != 0) { long[] vals = new long[(int)val]; for (int k = 0; k < val; k++) vals[k] = ReadUInt32(br); docInfo.attrValues.Add(vals); } else { docInfo.attrValues.Add(val); } } res.matches[matchesNo] = docInfo; } res.total = ReadInt32(br); res.totalFound = ReadInt32(br); res.time = ReadInt32(br) / 1000.0f; /* format is %.3f */ int wordsCount = ReadInt32(br); //res.words = new SphinxWordInfo[ReadInt32(bw)]; //for (int i = 0; i < res.words.Length; i++) // res.words[i] = new SphinxWordInfo(ReadUtf8(bw), ReadUInt32(bw), ReadUInt32(bw)); } br.Close(); return results; } catch (IOException e) { //MyAssert(false, "unable to parse response: " + e.Message); return null; } } private TcpClient Conn { get { try { if (_conn == null || !_conn.Connected) { _conn = new TcpClient(_host, _port); NetworkStream ns = _conn.GetStream(); BinaryReader sr = new BinaryReader(ns); BinaryWriter sw = new BinaryWriter(ns); // check the version. WriteToStream(sw, 1); sw.Flush(); int version = 0; version = ReadInt32(sr); if (version < 1) { _conn.Close(); // "expected searchd protocol version 1+, got version " + version; _conn = null; return null; } // set persist connect WriteToStream(sw, (short)4); // COMMAND_Persist WriteToStream(sw, (short)0); //PERSIST_COMMAND_VERSION WriteToStream(sw, 4); // COMMAND_LENGTH WriteToStream(sw, 1); // PERSIST_COMMAND_BODY sw.Flush(); } } catch (IOException e) { try { _conn.Close(); } catch { _conn = null; } return null; } return _conn; } } #endregion #region Getters and Setters /** * Get last error message, if any. * * @return string with last error message (empty string if no errors occured) */ public string GetLastError() { return _error; } /** * Get last warning message, if any. * * @return string with last warning message (empty string if no errors occured) */ public string GetLastWarning() { return _warning; } /** * Set searchd host and port to connect to. * * @param host searchd host name (default: localhost) * @param port searchd port number (default: 3312) * * @throws SphinxException on invalid parameters */ public void SetServer(string host, int port) { //MyAssert(host != null && host.Length > 0, "host name must not be empty"); //MyAssert(port > 0 && port < 65536, "port must be in 1..65535 range"); _host = host; _port = port; } /** Set matches offset and limit to return to client, max matches to retrieve on server, and cutoff. */ public void SetLimits(int offset, int limit, int max, int cutoff) { //MyAssert(offset >= 0, "offset must be greater than or equal to 0"); //MyAssert(limit > 0, "limit must be greater than 0"); //MyAssert(max > 0, "max must be greater than 0"); //MyAssert(cutoff >= 0, "max must be greater than or equal to 0"); _offset = offset; _limit = limit; _maxMatches = max; _cutoff = cutoff; } /** Set matches offset and limit to return to client, and max matches to retrieve on server. */ public void SetLimits(int offset, int limit, int max) { SetLimits(offset, limit, max, _cutoff); } /** Set matches offset and limit to return to client. */ public void SetLimits(int offset, int limit) { SetLimits(offset, limit, _maxMatches, _cutoff); } /** Set matching mode. */ public void SetMatchMode(int mode) { //MyAssert( // mode == SPH_MATCH_ALL || // mode == SPH_MATCH_ANY || // mode == SPH_MATCH_PHRASE || // mode == SPH_MATCH_BOOLEAN || // mode == SPH_MATCH_EXTENDED, "unknown mode value; use one of the available SPH_MATCH_xxx constants"); _mode = mode; } /** Set sorting mode. */ public void SetSortMode(int mode, string sortby) { //MyAssert( // mode == SPH_SORT_RELEVANCE || // mode == SPH_SORT_ATTR_DESC || // mode == SPH_SORT_ATTR_ASC || // mode == SPH_SORT_TIME_SEGMENTS || // mode == SPH_SORT_EXTENDED, "unknown mode value; use one of the available SPH_SORT_xxx constants"); //MyAssert(mode == SPH_SORT_RELEVANCE || (sortby != null && sortby.Length > 0), "sortby string must not be empty in selected mode"); _sort = mode; _sortby = (sortby == null) ? "" : sortby; } /** Set per-field weights (all values must be positive). */ public void SetWeights(int[] weights) { //MyAssert(weights != null, "weights must not be null"); for (int i = 0; i < weights.Length; i++) { int weight = weights[i]; //MyAssert(weight > 0, "all weights must be greater than 0"); } _weights = weights; } public void SetFieldWeights(string field, int weight) { if (this._fieldWeights.ContainsKey(field)) this._fieldWeights[field] = weight; else this._fieldWeights.Add(field, weight); } /** * Set per-index weights * * @param indexWeights hash which maps string index names to Integer weights */ public void SetIndexWeights(string index, int weight) { if (this._indexWeights.ContainsKey(index)) this._indexWeights[index] = weight; else this._indexWeights.Add(index, weight); } /** * Set document IDs range to match. * * Only match those documents where document ID is beetwen given * min and max values (including themselves). * * @param min minimum document ID to match * @param max maximum document ID to match * * @throws SphinxException on invalid parameters */ public void SetIDRange(int min, int max) { //MyAssert(min <= max, "min must be less or equal to max"); _minId = min; _maxId = max; } /** * Set values filter. * * Only match those documents where <code>attribute</code> column value * is in given values set. * * @param attribute attribute name to filter by * @param values values set to match the attribute value by * @param exclude whether to exclude matching documents instead * * @throws SphinxException on invalid parameters */ public void SetFilter(string attribute, long[] values, bool exclude) { //MyAssert(values != null && values.Length > 0, "values array must not be null or empty"); //MyAssert(attribute != null && attribute.Length > 0, "attribute name must not be null or empty"); if (values == null || values.Length == 0) return; try { BinaryWriter bw = new BinaryWriter(_filterStreamData); WriteToStream(bw, attribute); WriteToStream(bw, SPH_FILTER_VALUES); WriteToStream(bw, values.Length); for (int i = 0; i < values.Length; i++) WriteToStream(bw, values[i]); WriteToStream(bw, exclude ? 1 : 0); } catch (Exception e) { //MyAssert(false, "IOException: " + e.Message); } _filterCount++; } public void SetFilter(string attribute, int[] values, bool exclude) { //MyAssert(values != null && values.Length > 0, "values array must not be null or empty"); //MyAssert(attribute != null && attribute.Length > 0, "attribute name must not be null or empty"); if (values == null || values.Length == 0) return; long[] v = new long[values.Length]; for (int i = 0; i < values.Length; i++) v[i] = (long)values[i]; SetFilter(attribute, v, exclude); } /** Set values filter with a single value (syntax sugar; see {@link #SetFilter(string,int[],bool)}). */ public void SetFilter(string attribute, long value, bool exclude) { long[] values = new long[] { value }; SetFilter(attribute, values, exclude); } /** Set values filter with a single value (syntax sugar; see {@link #SetFilter(string,int[],bool)}). */ public void SetFilter(string attribute, int value, bool exclude) { long[] values = new long[] { value }; SetFilter(attribute, values, exclude); } public void SetFilter(string attribute, bool value, bool exclude) { SetFilter(attribute, value ? 1 : 0, exclude); } public void SetFilter(string attribute, DateTime value, bool exclude) { SetFilter(attribute, ConvertToUnixTimestamp(value), exclude); } public void SetFilter(string attribute, DateTime[] values, bool exclude) { if (values == null || values.Length == 0) return; int[] items = new int[values.Length]; for (int i = 0; i < items.Length; i++) items[i] = ConvertToUnixTimestamp(values[i]); SetFilter(attribute, items, exclude); } /** * Set integer range filter. * * Only match those documents where <code>attribute</code> column value * is beetwen given min and max values (including themselves). * * @param attribute attribute name to filter by * @param min min attribute value * @param max max attribute value * @param exclude whether to exclude matching documents instead * * @throws SphinxException on invalid parameters */ public void SetFilterRange(string attribute, int min, int max, bool exclude) { SetFilterRange(attribute, (long)min, (long)max, exclude); } public void SetFilterRange(string attribute, DateTime min, DateTime max, bool exclude) { SetFilterRange(attribute, ConvertToUnixTimestamp(min), ConvertToUnixTimestamp(max), exclude); } public void SetFilterRange(string attribute, long min, long max, bool exclude) { //MyAssert(min <= max, "min must be less or equal to max"); try { BinaryWriter bw = new BinaryWriter(_filterStreamData); WriteToStream(bw, attribute); WriteToStream(bw, SPH_FILTER_RANGE); WriteToStream(bw, min); WriteToStream(bw, max); WriteToStream(bw, exclude ? 1 : 0); } catch (Exception e) { //MyAssert(false, "IOException: " + e.Message); } _filterCount++; } /** * Set float range filter. * * Only match those documents where <code>attribute</code> column value * is beetwen given min and max values (including themselves). * * @param attribute attribute name to filter by * @param min min attribute value * @param max max attribute value * @param exclude whether to exclude matching documents instead * * @throws SphinxException on invalid parameters * Set float range filter. */ public void SetFilterFloatRange(string attribute, float min, float max, bool exclude) { //MyAssert(min <= max, "min must be less or equal to max"); try { BinaryWriter bw = new BinaryWriter(_filterStreamData); WriteToStream(bw, attribute); WriteToStream(bw, SPH_FILTER_FLOATRANGE); WriteToStream(bw, min); WriteToStream(bw, max); WriteToStream(bw, exclude ? 1 : 0); } catch (Exception e) { //MyAssert(false, "IOException: " + e.Message); } _filterCount++; } /** Reset all currently set filters (for multi-queries). */ public void ResetFilters() { /* should we close them first? */ _filterStreamData = new MemoryStream(); _filterCount = 0; /* reset GEO anchor */ _latitudeAttr = null; _longitudeAttr = null; _latitude = 0; _longitude = 0; } /** * Setup geographical anchor point. * * Required to use @geodist in filters and sorting. * Distance will be computed to this point. * * @param latitudeAttr the name of latitude attribute * @param longitudeAttr the name of longitude attribute * @param latitude anchor point latitude, in radians * @param longitude anchor point longitude, in radians * * @throws SphinxException on invalid parameters */ public void SetGeoAnchor(string latitudeAttr, string longitudeAttr, float latitude, float longitude) { //MyAssert(latitudeAttr != null && latitudeAttr.Length > 0, "longitudeAttr string must not be null or empty"); //MyAssert(longitudeAttr != null && longitudeAttr.Length > 0, "longitudeAttr string must not be null or empty"); _latitudeAttr = latitudeAttr; _longitudeAttr = longitudeAttr; _latitude = latitude; _longitude = longitude; } /** Set grouping attribute and function. */ public void SetGroupBy(string attribute, int func, string groupsort) { //MyAssert( // func == SPH_GROUPBY_DAY || // func == SPH_GROUPBY_WEEK || // func == SPH_GROUPBY_MONTH || // func == SPH_GROUPBY_YEAR || // func == SPH_GROUPBY_ATTR || // func == SPH_GROUPBY_ATTRPAIR, "unknown func value; use one of the available SPH_GROUPBY_xxx constants"); _groupBy = attribute; _groupFunc = func; _groupSort = groupsort; } /** Set grouping attribute and function with default ("@group desc") groupsort (syntax sugar). */ public void SetGroupBy(string attribute, int func) { SetGroupBy(attribute, func, "@group desc"); } /** Set count-distinct attribute for group-by queries. */ public void SetGroupDistinct(string attribute) { _groupDistinct = attribute; } /** Set distributed retries count and delay. */ public void SetRetries(int count, int delay) { //MyAssert(count >= 0, "count must not be negative"); //MyAssert(delay >= 0, "delay must not be negative"); _retrycount = count; _retrydelay = delay; } /** Set distributed retries count with default (zero) delay (syntax sugar). */ public void SetRetries(int count) { SetRetries(count, 0); } #endregion #region Private Methods /** Get and check response packet from searchd (internal method). */ private byte[] GetResponse(TcpClient sock, int client_ver) { /* connect */ BinaryReader br = null; NetworkStream SockInput = null; try { SockInput = sock.GetStream(); br = new BinaryReader(SockInput); } catch (IOException e) { //MyAssert(false, "getInputStream() failed: " + e.Message); return null; } /* read response */ byte[] response = null; int status = 0, ver = 0; int len = 0; try { /* read status fields */ status = ReadInt16(br); ver = ReadInt16(br); len = ReadInt32(br); /* read response if non-empty */ //MyAssert(len > 0, "zero-sized searchd response body"); if (len > 0) { response = br.ReadBytes(len); } else { /* FIXME! no response, return null? */ } /* check status */ if (status == SEARCHD_WARNING) { //DataInputStream in = new DataInputStream ( new ByteArrayInputStream ( response ) ); //int iWarnLen = in.ReadInt32 (); //_warning = new string ( response, 4, iWarnLen ); //System.arraycopy ( response, 4+iWarnLen, response, 0, response.Length-4-iWarnLen ); _error = "searchd warning"; return null; } else if (status == SEARCHD_ERROR) { _error = "searchd error: " + Encoding.UTF8.GetString(response, 4, response.Length - 4); return null; } else if (status == SEARCHD_RETRY) { _error = "temporary searchd error: " + Encoding.UTF8.GetString(response, 4, response.Length - 4); return null; } else if (status != SEARCHD_OK) { _error = "searched returned unknown status, code=" + status; return null; } } catch (IOException e) { if (len != 0) { /* get trace, to provide even more failure details */ //PrintWriter ew = new PrintWriter ( new StringWriter() ); //e.printStackTrace ( ew ); //ew.flush (); //ew.close (); //string sTrace = ew.toString (); /* build error message */ _error = "failed to read searchd response (status=" + status + ", ver=" + ver + ", len=" + len + ", trace=" + e.StackTrace + ")"; } else { _error = "received zero-sized searchd response (searchd crashed?): " + e.Message; } return null; } finally { try { if (br != null) br.Close(); if (sock != null && !sock.Connected) sock.Close(); } catch (IOException e) { /* silently ignore close failures; nothing could be done anyway */ } } return response; } /** Connect to searchd and exchange versions (internal method). */ //private TcpClient Connect() //{ // TcpClient sock; // try // { // //sock = new Socket(_host, _port); // //sock = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IPv4); // sock = new TcpClient(_host, _port); // //sock.ReceiveTimeout = SPH_CLIENT_TIMEOUT_MILLISEC; // } // catch (Exception e) // { // _error = "connection to " + _host + ":" + _port + " failed: " + e.Message; // return null; // } // NetworkStream ns = null; // try // { // ns = sock.GetStream(); // BinaryReader sr = new BinaryReader(ns); // BinaryWriter sw = new BinaryWriter(ns); // WriteToStream(sw, 1); // sw.Flush(); // int version = 0; // version = ReadInt32(sr); // if (version < 1) // { // sock.Close(); // _error = "expected searchd protocol version 1+, got version " + version; // return null; // } // //WriteToStream(sw, VER_MAJOR_PROTO); // //sw.Flush(); // WriteToStream(sw, (short)4); // COMMAND_Persist // WriteToStream(sw, (short)0); //PERSIST_COMMAND_VERSION // WriteToStream(sw, 4); // COMMAND_LENGTH // WriteToStream(sw, 1); // PERSIST_COMMAND_BODY // sw.Flush(); // } // catch (IOException e) // { // _error = "Connect: Read from socket failed: " + e.Message; // try // { // sock.Close(); // } // catch (IOException e1) // { // _error = _error + " Cannot close socket: " + e1.Message; // } // return null; // } // return sock; //} #endregion #region Network IO Helpers private string ReadUtf8(BinaryReader br) { int length = ReadInt32(br); if (length > 0) { byte[] data = br.ReadBytes(length); return Encoding.UTF8.GetString(data); } return ""; } private short ReadInt16(BinaryReader br) { byte[] idata = br.ReadBytes(2); if (BitConverter.IsLittleEndian) Array.Reverse(idata); return BitConverter.ToInt16(idata, 0); //return BitConverter.ToInt16(_Reverse(idata), 0); } private int ReadInt32(BinaryReader br) { byte[] idata = br.ReadBytes(4); if (BitConverter.IsLittleEndian) Array.Reverse(idata); return BitConverter.ToInt32(idata, 0); //return BitConverter.ToInt32(_Reverse(idata), 0); } private float ReadFloat(BinaryReader br) { byte[] idata = br.ReadBytes(4); if (BitConverter.IsLittleEndian) Array.Reverse(idata); return BitConverter.ToSingle(idata, 0); //return BitConverter.ToSingle(_Reverse(idata), 0); } private uint ReadUInt32(BinaryReader br) { byte[] idata = br.ReadBytes(4); if (BitConverter.IsLittleEndian) Array.Reverse(idata); return BitConverter.ToUInt32(idata, 0); //return BitConverter.ToUInt32(_Reverse(idata), 0); } private Int64 ReadInt64(BinaryReader br) { byte[] idata = br.ReadBytes(8); if (BitConverter.IsLittleEndian) Array.Reverse(idata); return BitConverter.ToInt64(idata, 0); //return BitConverter.ToInt64(_Reverse(idata), 0); } private void WriteToStream(BinaryWriter bw, short data) { byte[] d = BitConverter.GetBytes(data); if (BitConverter.IsLittleEndian) Array.Reverse(d); bw.Write(d); //sw.Write(_Reverse(d)); } private void WriteToStream(BinaryWriter bw, int data) { byte[] d = BitConverter.GetBytes(data); if (BitConverter.IsLittleEndian) Array.Reverse(d); bw.Write(d); //sw.Write(_Reverse(d)); } private void WriteToStream(BinaryWriter bw, float data) { byte[] d = BitConverter.GetBytes(data); if (BitConverter.IsLittleEndian) Array.Reverse(d); bw.Write(d); //sw.Write(_Reverse(d)); } private void WriteToStream(BinaryWriter bw, long data) { byte[] d = BitConverter.GetBytes(data); if (BitConverter.IsLittleEndian) Array.Reverse(d); bw.Write(d); //sw.Write(_Reverse(d)); } private void WriteToStream(BinaryWriter bw, byte[] data) { bw.Write(data); } private void WriteToStream(BinaryWriter bw, string data) { byte[] d = Encoding.UTF8.GetBytes(data); WriteToStream(bw, d.Length); bw.Write(d); } #endregion #region Other Helpers static readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); public static int ConvertToUnixTimestamp(DateTime dateTime) { TimeSpan diff = dateTime.ToUniversalTime() - _epoch; return Convert.ToInt32(Math.Floor(diff.TotalSeconds)); } #endregion #region IDisposable Members public void Dispose() { if (this._conn != null) { try { if (this._conn.Connected) this._conn.Close(); } finally { this._conn = null; } } } #endregion } public class SphinxResult { /** Full-text field namess. */ public String[] fields; /** Attribute names. */ public String[] attrNames; /** Attribute types (refer to SPH_ATTR_xxx constants in SphinxClient). */ public int[] attrTypes; /** Retrieved matches. */ public SphinxMatch[] matches; /** Total matches in this result set. */ public int total; /** Total matches found in the index(es). */ public int totalFound; /** Elapsed time (as reported by searchd), in seconds. */ public float time; /** Per-word statistics. */ public SphinxWordInfo[] words; /** Warning message, if any. */ public String warning = null; /** Error message, if any. */ public String error = null; /** Query status (refer to SEARCHD_xxx constants in SphinxClient). */ private int status = -1; /** Trivial constructor, initializes an empty result set. */ public SphinxResult() { this.attrNames = new String[0]; this.matches = new SphinxMatch[0]; ; this.words = new SphinxWordInfo[0]; this.fields = new String[0]; this.attrTypes = new int[0]; } public bool Success { get { return this.status == SphinxClient.SEARCHD_OK; } } /** Get query status. */ public int getStatus() { return status; } /** Set query status (accessible from API package only). */ internal void setStatus(int status) { this.status = status; } } public class SphinxMatch { /** Matched document ID. */ public long docId; /** Matched document weight. */ public int weight; /** Matched document attribute values. */ public ArrayList attrValues; /** Trivial constructor. */ public SphinxMatch(long docId, int weight) { this.docId = docId; this.weight = weight; this.attrValues = new ArrayList(); } } public class SphinxWordInfo { /** Word form as returned from search daemon, stemmed or otherwise postprocessed. */ public String word; /** Total amount of matching documents in collection. */ public long docs; /** Total amount of hits (occurences) in collection. */ public long hits; /** Trivial constructor. */ public SphinxWordInfo(String word, long docs, long hits) { this.word = word; this.docs = docs; this.hits = hits; } } }
看看调试成功的样子
ok,收工