使用udev规则固定设备名

OS:ubuntu 12.04LTE

查询设备信息

udevadm info --attribute-walk --name=/dev/video0

其中/dev/video0是一个usb的摄像头。当有其他视频设备插入机器的时候,就不能够保证这个摄像头的设备文件还是/dev/video0了,可能是/dev/video1等等,这样就无法唯一标识这个设备,将不利于自动化脚本处理。 下面通过编写自己的udev 规则来固定这个设备的设备名称。

输出(节选):

 looking at device '/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/video4linux/video0':
    KERNEL=="video0"
    SUBSYSTEM=="video4linux"
    DRIVER==""
    ATTR{name}=="A4 TECH HD PC Camera"
    ATTR{index}=="0"

上面的输出可以确定该设备的系统设备名为 A4 TECH HD PC Camera

编写udev规则

ubuntu下udev rules文件在/etc/udev/rules.d/下,在该目录下建立文件: 60-usb_camera.rules,文件内容如下:

SUBSYSTEM=="video4linux",ATTRS{name}=="A4 TECH HD PC Camera",SYMLINK+="usb_camera"

这条语句告知udev,当发现插入设备的SUBSYSTEM为video4linux,并且设备的name属性是A4 TECH HD PC Camera,那么建立一个软链接到这个设备的设备文件。 usb_camera就是我们需要的固定设备名称。我在这里只是简单匹配了name这个属性,可能不适合其他复杂的环境。 键值对的名称一定要严格匹配,”[“,”]”需要转义。

重启udev

重启udev服务使新的规则生效:

/etc/init.d/udev restart

测试新规则

重新插入usb摄像头,在/dev/目录下可以发现设备usb_camera。在自动化脚本中,我们可以直接使用/dev/usb_camera

更多参考

关于udev规则编写的更多细节,可以查看 这里

CPP 单例模式和缺陷

实现一个单例模式

class Singleton {
    private:
        Singleton() { cout << "Singleton::constructor" << endl; }
        ~Singlton() { cout << "Singleton::destructor" << endl; }
        Singleton(const Singleton&) {};
        Singleton &operator=(const Singleton&) {};
    public:
        static Singleton* getInstance() {
            if(m_aInstance == NULL) {
                m_aInstance = new Singleton();
            }
            return m_aInstance;
        }
        void show() {
            cout << "Singleton::show" << endl;
        }
    private:
        static Singleton* m_aInstance;
};

Singleton* Singleton::m_aInstance = NULL;

int main(int argc, char **argv) {
    Singleton* aSingleton = Singleton::getInstance();
    aSingleton->show(); 
    return 0;
}
Singleton::constructor
Singleton::show

系统会自动调用在栈和静态数据区上分配的对象的析构函数来释放资源。

修改程序如下:

class Singleton {
    private:
        Singleton() { cout << "Singleton::constructor" << endl; }
        ~Singleton() { cout << "Singleton::destructor" << endl; }
        Singleton(const Singleton&) {};
        Singleton &operator=(const Singleton&) {};
    public:
        static Singleton* getInstance() {
            if(m_aInstance == NULL) {
                m_aInstance = new Singleton();
            }
            return m_aInstance;
        }
        void show() {
            cout << "Singleton::show" << endl;
        }

    private:
        class Garbage{
            public:
                ~Garbage() {
                    if(m_aInstance != NULL) {
                        delete m_aInstance;
                    }
                }
        };
    
    private:
        static Singleton* m_aInstance;
        static Garbage m_garbage;
};

Singleton* Singleton::m_aInstance = NULL;
Singleton::Garbage Singleton::m_garbage;

int main(int argc, char **argv) {
    Singleton* aSingleton = Singleton::getInstance();
    aSingleton->show(); 
    return 0;
}
Singleton::constructor
Singleton::show
Singleton::destructor

我们看到Singleton::destructor被明确的执行了。

Android WebView滑动翻页

函数原型:
android.view.GestureDetector.OnGestureListener.onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)

Notified of a fling event when it occurs with the initial on down MotionEvent and the matching up MotionEvent. The calculated velocity is supplied along the x and y axis in pixels per second.

Parameters
e1	The first down motion event that started the fling.
e2	The move motion event that triggered the current onFling.
velocityX	The velocity of this fling measured in pixels per second along the x axis.
velocityY	The velocity of this fling measured in pixels per second along the y axis.
Returns
true if the event is consumed, else false

	private GestureDetector gestureDetector;
	private WebView webView01;
	gestureDetector = new GestureDetector(this, this);

	@Override
	public boolean onTouch(View v, MotionEvent event) {
                //转发手势事件
		gestureDetector.onTouchEvent(event);
		return false;
	}

	@Override
	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
			float velocityY) {

		float vx = Math.abs(velocityX);
		float vy = Math.abs(velocityY);
		
		//处理左右滑屏,实现翻页效果
		if(vx > vy) {
			if(velocityX > 0) {
				if(webView01.canGoBack()) {
					webView01.goBack();
					int width = webView01.getWidth();
					Animation translateAnimation = new TranslateAnimation(0, width, 0, 0);
					translateAnimation.setDuration(500);
					webView01.setAnimation(translateAnimation);
				}
			} else if(velocityX < 0) {
				if(webView01.canGoForward()) {
					webView01.goForward();
					int width = webView01.getWidth();
					Animation translateAnimation = new TranslateAnimation(0, -width, 0, 0);
					translateAnimation.setDuration(500);
					webView01.setAnimation(translateAnimation);
				}
			}
		}
		return false;
	}

Android 中 WebChromeClient 获取网站标题图标等信息

            private WebView webView01;
	        private ProgressBar progressBar;
	        private ImageView iconIV;
	        private WebIconDatabase webIcondb;

                iconIV = (ImageView) findViewById(R.id.icon);
		webIcondb = WebIconDatabase.getInstance();
		webIcondb.open(getDir("icons", MODE_PRIVATE).getPath());
		progressBar = (ProgressBar)findViewById(R.id.progress01);

 		webView01 = (WebView) findViewById(R.id.WebView01);
		webView01.setOnTouchListener(this);
		webView01.setWebViewClient(new WebViewClient() {

		});

		webView01.setWebChromeClient(new WebChromeClient() {
                        //获取网页标题
			@Override
			public void onReceivedTitle(WebView view, String title) {
				super.onReceivedTitle(view, title);
				titleTV.setText(title);
			}
                        //获取网页图标
			@Override
			public void onReceivedIcon(WebView view, Bitmap icon) {
				super.onReceivedIcon(view, icon);
				iconIV.setImageBitmap(icon);
			}
			//获取网页打开进度
			@Override
			public void onProgressChanged(WebView view, int newProgress) {
				progressBar.setProgress(newProgress);
				if(newProgress == 100) {
					progressBar.setProgress(0);
                                        //获取当前网页地址
                                        url01.setText("url: " + webView01.getUrl());
				}
			}
		});

使用广播查询服务器地址

流程

  1. 服务器端监听端口9999
  2. 客户端通过发送广播信息向网络查询需要的服务器地址
  3. 服务器端收到客户端的查询后把自己的地址信息发送给客户端

代码

公共头文件common.h

#ifndef __COMMON_H
#define __COMMON_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <net/if.h>

/*客户端查询消息*/
#define QUERY_SERV "QUERY_SERV"
/*服务器应答消息*/
#define IAMSERV "IAMSERV"
#define PORT 9999

/*返回值*/
#define ERR 1
#define OK 0

#endif

服务器端serv.c

#include "common.h"

int main(int argc, char *argv[]) {
	int ret = -1;
	int sock = -1;
	struct sockaddr_in serv_addr;
	struct sockaddr_in cli_addr;
	socklen_t cli_len = sizeof(struct sockaddr_in);
	fd_set readfd;
	char buffer[1024] = {0};

	sock = socket(AF_INET, SOCK_DGRAM, 0);
	if(sock == -1) {
		perror("sock error");
		return ERR;
	}

	memset(&serv_addr, 0x0, sizeof(struct sockaddr_in));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port = htons(PORT);

	ret = bind(sock, (struct sockaddr*)&serv_addr, sizeof(struct sockaddr));
	if(ret == -1) {
		perror("bind error");
		return ERR;
	}

	while(1) {
		FD_ZERO(&readfd);
		FD_SET(sock, &readfd);

		ret = select(sock + 1, &readfd, NULL, NULL, NULL);
		switch(ret) {
		case -1:
			perror("select error");
			break;
		case 0:
			fprintf(stderr, "select timeout\n");
			break;
		default:
			if(FD_ISSET(sock, &readfd)) {
				recvfrom(sock, buffer, 1024, 0, (struct sockaddr*)&cli_addr, &cli_len);
				if(strstr(buffer, QUERY_SERV)) {
					printf("Client IP: %s  PORT: %d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
					sendto(sock, IAMSERV, strlen(IAMSERV), 0, (struct sockaddr*)&cli_addr, cli_len);
				}
			}
			break;
		}
	}
	return OK;
}

客户端cli.c

#include "common.h"

int main(int argc, char *argv[]) {
    int ret = -1;
    struct sockaddr_in serv_addr;
    socklen_t serv_len = sizeof(struct sockaddr_in);
    fd_set readfd;
    char buffer[1024] = {0};
	char *netDev = NULL;

    struct timeval timeout;
    timeout.tv_sec = 2;
    timeout.tv_usec = 0;

	/*设置客户端的网口*/
	if(argc == 2) {
		netDev = argv[1];	
	} else {
		/*默认使用eth0*/
		netDev = "eth0";	
	}

    int sock = -1;
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock ==  -1) {
		perror("socket error");
        return ERR; 
    }
    
    struct ifreq ifr;
	memset(&ifr, 0x0, sizeof(struct ifreq));
    strncpy(ifr.ifr_name, netDev, strlen(netDev));
	printf("use device: %s\n", ifr.ifr_name);
	/*查询选定网口的广播地址*/
    if(ioctl(sock, SIOCGIFBRDADDR, &ifr) == -1) {
        perror("ioctl error");
        return ERR;
    }

    int so_broadcast = 1;
    struct sockaddr_in broadcast_addr; 
    memcpy(&broadcast_addr, &ifr.ifr_broadaddr, sizeof(struct sockaddr_in));
    printf("broadcast IP is: %s\n", inet_ntoa(broadcast_addr.sin_addr));
    
    broadcast_addr.sin_family = AF_INET;
    broadcast_addr.sin_port = htons(PORT);
    ret = setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &so_broadcast, sizeof(so_broadcast));
	if(ret == -1) {
		perror("setsockopt error");
		return ERR;
	}

	/*广播查询服务器信息,如果没有回应,则重新发送,共重复10次*/
    int times = 10;
	int trys = 0;

    for(trys = 0; trys < times; trys++) {
        timeout.tv_sec = 2;
        timeout.tv_usec = 0;

        ret = sendto(sock, QUERY_SERV, strlen(QUERY_SERV), 0, (struct sockaddr*)&broadcast_addr, sizeof(struct sockaddr));
        if(ret == -1) {
            continue;
        }
        
        FD_ZERO(&readfd);
        FD_SET(sock, &readfd);

        ret = select(sock + 1, &readfd, NULL, NULL, &timeout);
        switch(ret) {
        case -1:
			perror("select error");
            break;
        case 0:
            fprintf(stderr, "select timeout\n");
            break;
        default:
            if(FD_ISSET(sock, &readfd)) {
                recvfrom(sock, buffer, 1024, 0, (struct sockaddr*)&serv_addr, &serv_len);
                printf("recvmsg is: %s\n", buffer);

                if(strstr(buffer, IAMSERV)) {
                    printf("Server found, IP: %s PORT: %d \n", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port));
                }
                return OK;
            }
            break;
        }
    }

	if(trys == 10) {
		printf("Sorry, cannot find the server\n");
	}
	
    return OK;
}

测试运行

  1. 打开服务器端: ./serv

  2. 打开客户端: ./cli 客户端参数: ./cli [ethx] 参数说明:ethx是你要使用的网络端口,如eth0, eth1等,默认为eth0

运行输出

  1. 客户端输出: [客户端输出

  2. 服务器端输出: [服务器端输出

github: https://github.com/lnmcc/BroadcastQuery.git