输入系统:进程间双向通信(socketpair+binder)
一、双向通信(socketpair)
socketpair()函数用于创建一对无名的、相互连接的套接子,如果函数成功,则返回0,创建好的套接字分别是sv[0]和sv[1];否则返回-1,错误码保存于errno中。
- socketpair()函数的声明:
#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sv[]);
- 参数说明:
参数1(domain):表示协议族,在Linux下只能为AF_LOCAL或者AF_UNIX。(自从Linux 2.6.27后也支持SOCK_NONBLOCK和SOCK_CLOEXEC)
参数2(type):表示协议,可以是SOCK_STREAM或者SOCK_DGRAM。SOCK_STREAM是基于TCP的,而SOCK_DGRAM是基于UDP的
参数3(protocol):表示类型,只能为0
参数4(sv[2]):套节字柄对,该两个句柄作用相同,均能进行读写双向操作
返回errno含义:
EAFNOSUPPORT:本机上不支持指定的address。 EFAULT: 地址sv无法指向有效的进程地址空间内。 EMFILE: 已经达到了系统限制文件描述符,或者该进程使用过量的描述符。 EOPNOTSUPP:指定的协议不支持创建套接字对。 EPROTONOSUPPORT:本机不支持指定的协议。
- 注意:
1.该函数只能用于UNIX域(LINUX)下。
2.只能用于有亲缘关系的进程(或线程)间通信。
3.所创建的套节字对作用是一样的,均能够可读可写(而管道PIPE只能进行单向读或写)。
4.这对套接字可以用于全双工通信,每一个套接字既可以读也可以写。例如,可以往sv[0]中写,从sv[1]中读;或者从sv[1]中写,从sv[0]中读;
5.该函数是阻塞的,且如果往一个套接字(如sv[0])中写入后,再从该套接字读时会阻塞,只能在另一个套接字中(sv[1])上读成功;
6. 读、写操作可以位于同一个进程,也可以分别位于不同的进程,如父子进程。如果是父子进程时,一般会功能分离,一个进程用来读,一个用来写。因为文件描述副sv[0]和sv[1]是进程共享的,所以读的进程要关闭写描述符, 反之,写的进程关闭读描述符。
示例代码1(同一进程不同线程间通信):
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h> #define SOCKET_BUFFER_SIZE (32768U) /* 参考:
* frameworks\native\libs\input\InputTransport.cpp
*/ void *function_thread1 (void *arg)
{
int fd = (int)arg;
char buf[500];
int len;
int cnt = 0; while (1)
{
/* 向 main线程发出: Hello, main thread */
len = sprintf(buf, "Hello, main thread, cnt = %d", cnt++);
write(fd, buf, len); /* 读取数据(main线程发回的数据) */
len = read(fd, buf, 500);
buf[len] = '\0';
printf("%s\n", buf); sleep(5);
} return NULL;
} int main(int argc, char **argv)
{
int sockets[2]; socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets); int bufferSize = SOCKET_BUFFER_SIZE;
setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)); /* 创建线程1 */
pthread_t threadID;
pthread_create(&threadID, NULL, function_thread1, (void *)sockets[1]); char buf[500];
int len;
int cnt = 0;
int fd = sockets[0]; while(1)
{
/* 读数据: 线程1发出的数据 */
len = read(fd, buf, 500);
buf[len] = '\0';
printf("%s\n", buf); /* main thread向thread1 发出: Hello, thread1 */
len = sprintf(buf, "Hello, thread1, cnt = %d", cnt++);
write(fd, buf, len);
}
}
参考Android源码:
frameworks\native\libs\input\InputTransport.cpp (socketpair)
调用过程:
WindowManagerService.java
InputChannel.openInputChannelPair(name)
nativeOpenInputChannelPair(name);
android_view_InputChannel_nativeOpenInputChannelPair
InputChannel::openInputChannelPair (InputTransport.cpp)
示例代码2(父子进程间通信):
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <error.h>
#include <errno.h>
#include <sys/socket.h>
#include <stdlib.h> const char* str = "SOCKET PAIR TEST."; int main(int argc, char* argv[]){
char buf[] = {};
int socket_pair[];
pid_t pid; if(socketpair(AF_UNIX, SOCK_STREAM, , socket_pair) == - ) {
printf("Error, socketpair create failed, errno(%d): %s\n", errno, strerror(errno));
return EXIT_FAILURE;
} pid = fork(); //创建子进程
if(pid < ) {
printf("Error, fork failed, errno(%d): %s\n", errno, strerror(errno));
return EXIT_FAILURE;
} else if(pid > ) { //父进程
//关闭另外一个套接字
close(socket_pair[]);
int size = write(socket_pair[], str, strlen(str));
printf("Write success, pid: %d\n", getpid()); } else if(pid == ) { //子进程
//关闭另外一个套接字
close(socket_pair[]);
read(socket_pair[], buf, sizeof(buf));
printf("Read result: %s, pid: %d\n",buf, getpid());
} for(;;) {
sleep();
} return EXIT_SUCCESS;
}
二、任意进程间通信(socketpair_binder)
Android系统中进程间通信方式主要为Binder,而Binder通信方式中,Client端可以主动发起请求与Server端通信,但Server端无法向Client端主动发起请求,基于socketpair + binder可以实现任意进程间的双向通信(通过binder将fd句柄传到另一个非亲缘关系的进程),代码实现如下:
1.IHelloService.h (Hello服务接口定义)
/* 参考: frameworks\av\include\media\IMediaPlayerService.h */ #ifndef ANDROID_IHELLOERVICE_H
#define ANDROID_IHELLOERVICE_H #include <utils/Errors.h> // for status_t
#include <utils/KeyedVector.h>
#include <utils/RefBase.h>
#include <utils/String8.h>
#include <binder/IInterface.h>
#include <binder/Parcel.h> #define HELLO_SVR_CMD_SAYHELLO 1
#define HELLO_SVR_CMD_SAYHELLO_TO 2
#define HELLO_SVR_CMD_GET_FD 3 namespace android { class IHelloService: public IInterface
{
public:
DECLARE_META_INTERFACE(HelloService);
virtual void sayhello(void) = ;
virtual int sayhello_to(const char *name) = ;
virtual int get_fd(void) = ;
}; class BnHelloService: public BnInterface<IHelloService>
{
private:
int fd;
public:
virtual status_t onTransact( uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = ); virtual void sayhello(void);
virtual int sayhello_to(const char *name);
virtual int get_fd(void); BnHelloService();
BnHelloService(int fd); };
} #endif
2.IGoodbyeService.h (Goodbye服务接口定义)
/* 参考: frameworks\av\include\media\IMediaPlayerService.h */ #ifndef ANDROID_IGOODBYEERVICE_H
#define ANDROID_IGOODBYEERVICE_H #include <utils/Errors.h> // for status_t
#include <utils/KeyedVector.h>
#include <utils/RefBase.h>
#include <utils/String8.h>
#include <binder/IInterface.h>
#include <binder/Parcel.h> #define GOODBYE_SVR_CMD_SAYGOODBYE 1
#define GOODBYE_SVR_CMD_SAYGOODBYE_TO 2 namespace android { class IGoodbyeService: public IInterface
{
public:
DECLARE_META_INTERFACE(GoodbyeService);
virtual void saygoodbye(void) = ;
virtual int saygoodbye_to(const char *name) = ;
}; class BnGoodbyeService: public BnInterface<IGoodbyeService>
{
public:
virtual status_t onTransact( uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = ); virtual void saygoodbye(void);
virtual int saygoodbye_to(const char *name); };
} #endif
3.BnHelloService.cpp (Hello服务本地类)
/* 参考: frameworks\av\media\libmedia\IMediaPlayerService.cpp */ #define LOG_TAG "HelloService" #include "IHelloService.h" namespace android { BnHelloService::BnHelloService()
{
} BnHelloService::BnHelloService(int fd)
{
this->fd = fd;
} status_t BnHelloService::onTransact( uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags)
{
/* 解析数据,调用sayhello/sayhello_to */ switch (code) {
case HELLO_SVR_CMD_SAYHELLO: {
sayhello();
reply->writeInt32(); /* no exception */
return NO_ERROR;
} break; case HELLO_SVR_CMD_SAYHELLO_TO: { /* 从data中取出参数 */
int32_t policy = data.readInt32();
String16 name16_tmp = data.readString16(); /* IHelloService */ String16 name16 = data.readString16();
String8 name8(name16); int cnt = sayhello_to(name8.string()); /* 把返回值写入reply传回去 */
reply->writeInt32(); /* no exception */
reply->writeInt32(cnt); return NO_ERROR;
} break; case HELLO_SVR_CMD_GET_FD: {
int fd = this->get_fd();
reply->writeInt32(); /* no exception */ /* 参考:
* frameworks\base\core\jni\android_view_InputChannel.cpp
* android_view_InputChannel_nativeWriteToParcel
*/
reply->writeDupFileDescriptor(fd);
return NO_ERROR;
} break; default:
return BBinder::onTransact(code, data, reply, flags);
}
} void BnHelloService::sayhello(void)
{
static int cnt = ;
ALOGI("say hello : %d\n", ++cnt); } int BnHelloService::sayhello_to(const char *name)
{
static int cnt = ;
ALOGI("say hello to %s : %d\n", name, ++cnt);
return cnt;
} int BnHelloService::get_fd(void)
{
return fd;
} }
4.BnGoodbyeService.cpp (Goodbye服务本地类)
/* 参考: frameworks\av\media\libmedia\IMediaPlayerService.cpp */ #define LOG_TAG "GoodbyeService" #include "IGoodbyeService.h" namespace android { status_t BnGoodbyeService::onTransact( uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags)
{
/* 解析数据,调用saygoodbye/saygoodbye_to */ switch (code) {
case GOODBYE_SVR_CMD_SAYGOODBYE: {
saygoodbye();
reply->writeInt32(); /* no exception */
return NO_ERROR;
} break; case GOODBYE_SVR_CMD_SAYGOODBYE_TO: { /* 从data中取出参数 */
int32_t policy = data.readInt32();
String16 name16_tmp = data.readString16(); /* IGoodbyeService */ String16 name16 = data.readString16();
String8 name8(name16); int cnt = saygoodbye_to(name8.string()); /* 把返回值写入reply传回去 */
reply->writeInt32(); /* no exception */
reply->writeInt32(cnt); return NO_ERROR;
} break;
default:
return BBinder::onTransact(code, data, reply, flags);
}
} void BnGoodbyeService::saygoodbye(void)
{
static int cnt = ;
ALOGI("say goodbye : %d\n", ++cnt); } int BnGoodbyeService::saygoodbye_to(const char *name)
{
static int cnt = ;
ALOGI("say goodbye to %s : %d\n", name, ++cnt);
return cnt;
} }
5.BpHelloService.cpp (Hello服务代理类)
/* 参考: frameworks\av\media\libmedia\IMediaPlayerService.cpp */ #include "IHelloService.h" namespace android { class BpHelloService: public BpInterface<IHelloService>
{
public:
BpHelloService(const sp<IBinder>& impl)
: BpInterface<IHelloService>(impl)
{
} void sayhello(void)
{
/* 构造/发送数据 */ Parcel data, reply;
data.writeInt32();
data.writeString16(String16("IHelloService")); remote()->transact(HELLO_SVR_CMD_SAYHELLO, data, &reply);
} int sayhello_to(const char *name)
{
/* 构造/发送数据 */
Parcel data, reply;
int exception; data.writeInt32();
data.writeString16(String16("IHelloService")); data.writeString16(String16(name)); remote()->transact(HELLO_SVR_CMD_SAYHELLO_TO, data, &reply); exception = reply.readInt32();
if (exception)
return -;
else
return reply.readInt32();
} int get_fd(void)
{
/* 构造/发送数据 */
Parcel data, reply;
int exception; data.writeInt32();
data.writeString16(String16("IHelloService")); remote()->transact(HELLO_SVR_CMD_GET_FD, data, &reply); exception = reply.readInt32();
if (exception)
return -;
else
{ /* 参考:
* frameworks\base\core\jni\android_view_InputChannel.cpp
* android_view_InputChannel_nativeReadFromParcel
*/
int rawFd = reply.readFileDescriptor();
return dup(rawFd); //复制句柄,reply中析构会关闭原始句柄
}
} }; IMPLEMENT_META_INTERFACE(HelloService, "android.media.IHelloService"); }
6.BpGoodbyeService.cpp (Goodbye服务代理类)
/* 参考: frameworks\av\media\libmedia\IMediaPlayerService.cpp */ #include "IGoodbyeService.h" namespace android { class BpGoodbyeService: public BpInterface<IGoodbyeService>
{
public:
BpGoodbyeService(const sp<IBinder>& impl)
: BpInterface<IGoodbyeService>(impl)
{
} void saygoodbye(void)
{
/* 构造/发送数据 */ Parcel data, reply;
data.writeInt32();
data.writeString16(String16("IGoodbyeService")); remote()->transact(GOODBYE_SVR_CMD_SAYGOODBYE, data, &reply);
} int saygoodbye_to(const char *name)
{
/* 构造/发送数据 */
Parcel data, reply;
int exception; data.writeInt32();
data.writeString16(String16("IGoodbyeService")); data.writeString16(String16(name)); remote()->transact(GOODBYE_SVR_CMD_SAYGOODBYE_TO, data, &reply); exception = reply.readInt32();
if (exception)
return -;
else
return reply.readInt32();
} }; IMPLEMENT_META_INTERFACE(GoodbyeService, "android.media.IGoodbyeService"); }
7.test_server.cpp
/* 参考: frameworks\av\media\mediaserver\Main_mediaserver.cpp */ #define LOG_TAG "TestService"
//#define LOG_NDEBUG 0 #include <fcntl.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <cutils/properties.h>
#include <utils/Log.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h> #include "IHelloService.h"
#include "IGoodbyeService.h" #define SOCKET_BUFFER_SIZE (32768U) using namespace android; /* 参考:
* http://blog.csdn.net/linan_nwu/article/details/8222349
*/
class MyThread: public Thread {
private:
int fd;
public:
MyThread() {}
MyThread(int fd) { this->fd = fd; } //如果返回true,循环调用此函数,返回false下一次不会再调用此函数
bool threadLoop()
{
char buf[];
int len;
int cnt = ; while()
{
/* 读数据: test_client发出的数据 */
len = read(fd, buf, );
buf[len] = '\0';
ALOGI("%s\n", buf); /* 向 test_client 发出: Hello, test_client */
len = sprintf(buf, "Hello, test_client, cnt = %d", cnt++);
write(fd, buf, len);
} return true;
} }; /* usage : test_server */
int main(void)
{ int sockets[]; socketpair(AF_UNIX, SOCK_SEQPACKET, , sockets); int bufferSize = SOCKET_BUFFER_SIZE;
setsockopt(sockets[], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)); /* 创建一个线程, 用于跟test_client使用socketpiar通信 */
sp<MyThread> th = new MyThread(sockets[]);
th->run(); /* addService */ /* while(1){ read data, 解析数据, 调用服务函数 } */ /* 打开驱动, mmap */
sp<ProcessState> proc(ProcessState::self()); /* 获得BpServiceManager */
sp<IServiceManager> sm = defaultServiceManager(); sm->addService(String16("hello"), new BnHelloService(sockets[])); //传入句柄,Hello服务类的构造函数中会保存
sm->addService(String16("goodbye"), new BnGoodbyeService()); /* 循环体 */
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool(); return ;
}
8.test_client.cpp
#define LOG_TAG "TestService"
//#define LOG_NDEBUG 0 #include <fcntl.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <cutils/properties.h>
#include <utils/Log.h>
#include <unistd.h> #include "IHelloService.h"
#include "IGoodbyeService.h" using namespace android; /* ./test_client <hello|goodbye>
* ./test_client <readfile>
* ./test_client <hello|goodbye> <name>
*/
int main(int argc, char **argv)
{
int cnt; if (argc < ){
ALOGI("Usage:\n");
ALOGI("%s <readfile>\n", argv[]);
ALOGI("%s <hello|goodbye>\n", argv[]);
ALOGI("%s <hello|goodbye> <name>\n", argv[]);
return -;
} /* getService */
/* 打开驱动, mmap */
sp<ProcessState> proc(ProcessState::self()); /* 获得BpServiceManager */
sp<IServiceManager> sm = defaultServiceManager(); if (strcmp(argv[], "hello") == )
{ sp<IBinder> binder =
sm->getService(String16("hello")); if (binder == )
{
ALOGI("can't get hello service\n");
return -;
} /* service肯定是BpHelloServie指针 */
sp<IHelloService> service =
interface_cast<IHelloService>(binder); /* 调用Service的函数 */
if (argc < ) {
service->sayhello();
ALOGI("client call sayhello");
}
else {
cnt = service->sayhello_to(argv[]);
ALOGI("client call sayhello_to, cnt = %d", cnt);
}
}
else if (strcmp(argv[], "readfile") == )
{ sp<IBinder> binder =
sm->getService(String16("hello")); if (binder == )
{
ALOGI("can't get hello service\n");
return -;
} /* service肯定是BpHelloServie指针 */
sp<IHelloService> service =
interface_cast<IHelloService>(binder); /* 调用Service的函数 */
int fd = service->get_fd(); ALOGI("client call get_fd = %d", fd); char buf[];
int len;
int cnt = ; while ()
{
/* 向 test_server 进程发出: Hello, test_server */
len = sprintf(buf, "Hello, test_server, cnt = %d", cnt++);
write(fd, buf, len); /* 读取数据(test_server进程发回的数据) */
len = read(fd, buf, );
buf[len] = '\0';
ALOGI("%s\n", buf); sleep();
}
}
else
{ sp<IBinder> binder =
sm->getService(String16("goodbye")); if (binder == )
{
ALOGI("can't get goodbye service\n");
return -;
} /* service肯定是BpGoodbyeServie指针 */
sp<IGoodbyeService> service =
interface_cast<IGoodbyeService>(binder); /* 调用Service的函数 */
if (argc < ) {
service->saygoodbye();
ALOGI("client call saygoodbye");
}
else {
cnt = service->saygoodbye_to(argv[]);
ALOGI("client call saygoodbye_to, cnt = %d", cnt);
}
} return ;
}
9.Android.mk
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \
BnHelloService.cpp \
BpHelloService.cpp \
BnGoodbyeService.cpp \
BpGoodbyeService.cpp \
test_server.cpp LOCAL_SHARED_LIBRARIES := \
libcutils \
libutils \
liblog \
libbinder LOCAL_MODULE:= test_server
LOCAL_32_BIT_ONLY := true include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \
BpHelloService.cpp \
BpGoodbyeService.cpp \
test_client.cpp LOCAL_SHARED_LIBRARIES := \
libcutils \
libutils \
liblog \
libbinder LOCAL_MODULE:= test_client
LOCAL_32_BIT_ONLY := true include $(BUILD_EXECUTABLE)
参考代码:
frameworks\base\core\jni\android_view_InputChannel.cpp (用binder传文件句柄)
server端写fd: android_view_InputChannel_nativeWriteToParcel
parcel->writeDupFileDescriptor
client端读fd: android_view_InputChannel_nativeReadFromParcel
int rawFd = parcel->readFileDescriptor();
int dupFd = dup(rawFd); frameworks\native\libs\binder\Parcel.cpp
-end-
输入系统:进程间双向通信(socketpair+binder)的更多相关文章
- AIDL进程间调用与Binder的简单介绍
Binder是安卓中特有的一种进程间通信(IPC)方式,从Unix发展而来的手段,通信双方必须处理线程同步.内存管理等复杂问题,传统的Socket.匿名通道(Pipe).匿名管道(FIFO).信号量( ...
- QProcess进程间双向通信
记得以前写过Linux的C程序, 里面用popen打开一个子进程, 这样可以用read/write和子进程通讯, 而在子进程里则是通过从stdin读和向stdout写实现对父进程的通讯. QProce ...
- 10.2、android输入系统_必备Linux编程知识_双向通信(scoketpair)
2. 双向通信(socketpair) 输入系统肯定涉及进程通讯:进程A读取/分发输入事件,APP处理输入事件,进程A给APP发送输入事件,APP处理完事件回复信息给进程A,APP关闭的时候也要发信息 ...
- Android Binder 进程间通讯机制梳理
什么是 Binder ? Binder是Android系统中进程间通讯(IPC)的一种方式,也是Android系统中最重要的特性之一.Binder的设计采用了面向对象的思想,在Binder通信模型的四 ...
- Android系统--输入系统(三)必备Linux知识_双向通信(scoketpair)
Android系统--输入系统(三)必备Linux知识_双向通信(scoketpair) 引入 1. 进程和APP通信 创建进程 读取.分发 - 进程发送输入事件给APP 进程读取APP回应的事件 输 ...
- Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6666491 在前面一篇文章Android系统匿 ...
- 从AIDL开始谈Android进程间Binder通信机制
转自: http://tech.cnnetsec.com/585.html 本文首先概述了Android的进程间通信的Binder机制,然后结合一个AIDL的例子,对Binder机制进行了解析. 概述 ...
- 一篇文章了解相见恨晚的 Android Binder 进程间通讯机制【转】
本文转载自:https://blog.csdn.net/freekiteyu/article/details/70082302 Android-Binder进程间通讯机制 概述 最近在学习Binder ...
- android 进程间通信 messenger 是什么 binder 跟 aidl 区别 intent 进程间 通讯? android 消息机制 进程间 android 进程间 可以用 handler么 messenger 与 handler 机制 messenger 机制 是不是 就是 handler 机制 或 , 是不是就是 消息机制 android messenge
韩梦飞沙 韩亚飞 313134555@qq.com yue31313 han_meng_fei_sha messenger 是什么 binder 跟 aidl 区别 intent 进程间 通讯 ...
随机推荐
- 机器学习实战(笔记)------------KNN算法
1.KNN算法 KNN算法即K-临近算法,采用测量不同特征值之间的距离的方法进行分类. 以二维情况举例: 假设一条样本含有两个特征.将这两种特征进行数值化,我们就可以假设这两种特种分别 ...
- redux&&createStore
const createStore = (reducer,presetState, enhancer) => { if (typeof presetState === "functio ...
- centos7中bash: maven: 未找到命令... 解决办法
安装了maven,但在执行mvn -v或maven-versions时提示bash: maven: 未找到命令... 应该是环境变量出错,把MAVEN_HOME的路径换到PATH上就可以了,如下: e ...
- Django 建立用户的视图(搜索 )
在web应用上,有两个关于搜索获得巨大成功的故事:Google和Yahoo,通过搜索,他们建立了几十亿美元的业务.几乎每个网站都有很大的比例访问量来自这两个搜索引擎.甚至,一个网站是否成功取决于其站内 ...
- extract method
函数 简短,命名良好 函数名描述的是做什么 而不是怎么做 行数过高的代码中 将一大段做一个事的代码提取到独立的method 中 高层函数直接引用. 创建新函数 将提炼的代码平移到目标函数中 检查是否引 ...
- HTML CSS 特殊字符表
HTML有许多特殊的字符,您对此有多少了解?平时在WEB制作中,您又有用到多少?或者说你在平时使用之时,是否也会碰到,有许多特殊字符要如何打印出来?比如说“笑脸”,比如说“版权号”.要是你用时忘记了这 ...
- hdoj3138
题意:略 各点向原信念连INF+1的边,不同信念连INF的边,这样割原信念花费大一点.然后好友连1的边.最小割的结果-n*INF就是答案,因为割到哪边最少都要INF. #include <ios ...
- idea中xml打开方式变成file,代码变成灰色
耗费了很久啊.... 新建文件的时候没有输入后缀.xml.自此无论复制修改还是新建都是文件的格式,其他名字倒是可以,但是我不能修改名字. 解决办法: OK了:
- 使用Nome监控服务器各项指标
使用Nome监控服务器各项指标 关于Nome的使用: 1)如何将nome压缩文件上传到服务器是,首选需要将压缩包下载到本地 a.创建文件夹Nome:mk ...
- Centos7安装Nginx+PHP+MySQL
之前曾经在服务器上从头到尾搭过一次环境,但那时新手一枚,很多地方搞不定,是前辈帮忙解决的.这次独自一人在服务器上撘环境,感慨上次没有做好相关笔记,所以事后整理一下,下次再搭环境时可以轻车熟路. 一.准 ...