在HTML中上传文件时会为文件内容加入一头一尾,如下所示:
-----------------------8cc0b8cfcfd5ed2 Content-Disposition: form-data; name="file"; filename="item3.xml" Content-Type: application/octet-stream 这里是真正的文件内容 -----------------------8cc0b8cfcfd5ed2--
因此服务端接收后要手动对其作解析。
代码 网页端
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf8" /> <meta http-equiv="pragma" content="no-cache" /> <title>测试页</title> </head> <body> 文件上传页 <form action="http://localhost:4513/IVisitorRestService/Upload/utf-8" method="POST" enctype="multipart/form-data"> <input type="file" /> <input type="submit" /> </form> </body> </html>
服务端 契约
/// <summary> /// 使用HTML上传文件 /// </summary> /// <param name="file">文件流</param> /// <param name="encodingName">HTML的文字编码名</param> [Custom(AllowAnonymousForOperation = true)] [WebInvoke(Method = "POST", UriTemplate = "Upload/{encodingName}")] void Upload(Stream file, string encodingName);
实现
public void Upload(Stream file, string encodingName) { using (var ms = new MemoryStream()) { file.CopyTo(ms); ms.Position = 0; var encoding = Encoding.GetEncoding(encodingName); var reader = new StreamReader(ms, encoding); var headerLength = 0L; //读取第一行 var firstLine = reader.ReadLine(); //计算偏移(字符串长度+回车换行2个字符) headerLength += encoding.GetBytes(firstLine).LongLength + 2; //读取第二行 var secondLine = reader.ReadLine(); //计算偏移(字符串长度+回车换行2个字符) headerLength += encoding.GetBytes(secondLine).LongLength + 2; //解析文件名 var fileName = new System.Text.RegularExpressions.Regex("filename=\"(?<fn>.*)\"").Match(secondLine).Groups["fn"].Value; //一直读到空行为止 while (true) { //读取一行 var line = reader.ReadLine(); //若到头,则直接返回 if (line == null) break; //若未到头,则计算偏移(字符串长度+回车换行2个字符) headerLength += encoding.GetBytes(line).LongLength + 2; if (line == "") break; } //设置偏移,以开始读取文件内容 ms.Position = headerLength; ////减去末尾的字符串:“\r\n--\r\n” ms.SetLength(ms.Length - encoding.GetBytes(firstLine).LongLength - 3 * 2); using (var fileToupload = new FileStream("D:\\FileUpload\\" + fileName, FileMode.Create)) { ms.CopyTo(fileToupload); fileToupload.Close(); fileToupload.Dispose(); } } }
转载请注明出处:http://blog.csdn.net/qq405180763/article/details/8797236
实际上skb_buf结构只是一块已经申请好的套接字缓冲区的指针和属性数据的描述集合,netdev_alloc_skb函数申请到一块套接字缓冲区后,返回记录这块缓冲区信息的skb_buf结构,在各个网络层传输的只是skb_buf结构,换句话说,仅仅是该套接字缓冲区的指针而已,各个网络层根据传来的指针,对指针进行操作,往已经申请好的固定套接字缓冲区里读写数据。
sk_buff结构的成员skb->head指向一个已分配的空间的头部,即申请到的整个缓冲区的头,skb->end指向该空间的尾部,这两个成员指针从空间创建之后,就不能被修改。skb->data指向分配空间中数据的头部,skb->tail指向数据的尾部,这两个值随着网络数据在各层之间的传递、修改,会被不断改动,而skb->head和skb->date之间,则是相应层对应的协议头。四个指针间存在如下关系:
sk_buff结构被不同的网络层(MAC或者其他二层链路协议,三层的IP,四层的TCP或UDP等)使用,并且其中的成员变量(一般是数据指针)在结构从一层向另一层传递时改变。L4向L3传递前会添加一个L4的头部,同样,L3向L2传递前,会添加一个L3的头部。添加头部比在不同层之间拷贝数据的效率更高。由于在缓冲区的头部(skb->head和skb->date之间)添加数据意味着要修改指向缓冲区的指针,这是个复杂的操作,所以内核提供了一个函数skb_reserve将数据(date)部分往后移。协议栈中的每一层在往下一层传递缓冲区前,第一件事就是调用skb_reserve在缓冲区的头部给协议头预留一定的空间,再用skb_push函数将协议头查到skb->data指针之前。
skb_reserve同样被设备驱动使用来对齐接收到包的包头。如果缓冲区向上层协议传递,旧的协议层的头部信息就没什么用了。例如,L2的头部只有在网络驱动处理L2的协议时有用,L3是不会关心它的信息的。但是,内核并没有把L2的头部从缓冲区中删除,而是用sk_buff结构中对应的头指针指向这个头,再把有效荷载的指针指向L3的头部,这样做,可以节省CPU时间。当接收一个包时,处理n层协议头的函数从n-1层收到一个缓冲区,因为在n-1层处理的最后,skb->data是指向n-1层的数据开始指针,所以到了n层后skb->data就是指向n层协议的头。处理n层协议的函数把本层的指针(例如,L3对应的是skb- >nh指针)初始化为skb->data,取出本层的协议头指针,在处理n层协议的函数结束时,在把包传递给n+1层的处理函数前,它会把skb->data指针指向n层协议头的末尾,这正好是n+1层协议的协议头(参见下图)。
skb_buf在各个网络层传输过程及skb_buf操作函数。
在网络数据发送过程中,由netdev_alloc_skb申请得到套接字缓冲区后,skb_reserve将整个数据块往后移动MAX_TCP_HEADER个字节,预留出skb中协议头的最大长度,确保在套接字从上层不断往下层传递的过程中,有足够的协议头空间。在数据每传到一个网络层的时候,通过skb_push函数将该层的协议头插到skb->data前面,一直到链路层将所有层的协议头加入到套接字协议头部分(从skb->head到skb->data之间),skb_push函数几乎就是用来在skb->data前面插入协议头的。最后由驱动程序把数据发给网卡,释放掉套接字缓存,网卡负责把数据发到网络上。
在网络数据接收过程中,网卡收到数据触发中断,驱动程序响应中断接收网卡缓存中数据,因为网卡中以太网帧头的长度为14个字节,而紧接着的IP协议头需要在16字节对齐的边界上,所以在申请到套接字缓存后,用skb_reserve函数先预留出2字节的空间,确保IP协议头的16字节对齐。刚申请到的套接字缓存,其sbk_buf的skb->data和skb->tail是同一个值,是没有数据的,用skb_put函数将skb->tail往后拉数据包长度个字节空间,然后把网络数据从网卡中拷贝到套接字缓存里,skb_put函数就是在skb->tail和skb->end之间加数据。接着将数据往上层发送,在每一层中用skb_pull函数或者移动skb->data指针的方式,将各个层对应的协议头从数据中剥离开来,一直到最上面的应用层,剩下真正的从其他机器发过来的有用数据。
新浪、腾讯、搜狐等微博网站都加入了短超链接的功能。之所以要是使用短链接,主要是因为微博只允许发140 字,如果链接地址太长的话,那么发送的字数将大大减少。短链接的主要职责就是把原始链接很长的地址压缩成只有6 个字母的短链接地址,当我们点击这6 个字母的链接后,我们又可以跳转到原始链接地址。
开始以为短链接是按照某种算法把原始链接压缩为短链接,再根据算法从短链接反算成原始链接的。后来尝试了下压缩算法(gzip 压缩算法),发现对于url 这种字符串越是压缩,长度就越长。通过对压缩算法的一些了解,发现靠压缩算法来实现这个功能不太靠谱。
后来在网上找到一个生成算法,该算法主要使用MD5 算法对原始链接进行加密(这里使用的MD5 加密后的字符串长度为32 位),然后对加密后的字符串进行处理以得到短链接的地址。原始的算法是C# 版本的,这里我把该算法修改成Java 版本的. 算法的具体代码如下,代码中有注释:
一、 代码
package com.csdn.shorturl;
public class ShortUrlGenerator {
/**
* @param args
*/
public static void main(String[] args) {
// 长连接: http://tech.sina.com.cn/i/2011-03-23/11285321288.shtml
// 新浪解析后的短链接为: http://t.cn/h1jGSC
String sLongUrl = "http://tech.sina.com.cn/i/2011-03-23/11285321288.shtml" ; // 3BD768E58042156E54626860E241E999
String[] aResult = shortUrl (sLongUrl);
// 打印出结果
for ( int i = 0; i < aResult. length ; i++) {
System. out .println( "[" + i + "]:::" + aResult[i]);
}
}
public static String[] shortUrl(/blog_article/String url/index.html) {
// 可以自定义生成 MD5 加密字符传前的混合 KEY
String key = "wuguowei" ;
// 要使用生成 URL 的字符
String[] chars = new String[] { "a" , "b" , "c" , "d" , "e" , "f" , "g" , "h" ,
"i" , "j" , "k" , "l" , "m" , "n" , "o" , "p" , "q" , "r" , "s" , "t" ,
"u" , "v" , "w" , "x" , "y" , "z" , "0" , "1" , "2" , "3" , "4" , "5" ,
"6" , "7" , "8" , "9" , "A" , "B" , "C" , "D" , "E" , "F" , "G" , "H" ,
"I" , "J" , "K" , "L" , "M" , "N" , "O" , "P" , "Q" , "R" , "S" , "T" ,
"U" , "V" , "W" , "X" , "Y" , "Z"
};
// 对传入网址进行 MD5 加密
String sMD5EncryptResult = ( new CMyEncrypt()).getMD5OfStr(key + url);
String hex = sMD5EncryptResult;
String[] resUrl = new String[4];
for ( int i = 0; i < 4; i++) {
// 把加密字符按照 8 位一组 16 进制与 0x3FFFFFFF 进行位与运算
String sTempSubString = hex.substring(i * 8, i * 8 + 8);
// 这里需要使用 long 型来转换,因为 Inteper .parseInt() 只能处理 31 位 , 首位为符号位 , 如果不用 long ,则会越界
long lHexLong = 0x3FFFFFFF & Long.parseLong (sTempSubString, 16);
String outChars = "" ;
for ( int j = 0; j < 6; j++) {
// 把得到的值与 0x0000003D 进行位与运算,取得字符数组 chars 索引
long index = 0x0000003D & lHexLong;
// 把取得的字符相加
outChars += chars[( int ) index];
// 每次循环按位右移 5 位
lHexLong = lHexLong >> 5;
}
// 把字符串存入对应索引的输出数组
resUrl[i] = outChars;
}