一、双向通信(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 进程间 通讯 ...
随机推荐
-
mysql登录基本语句
默认密码:root mysql 显示所有的数据库,代码如下: mysql> show databases; mysql> show tables; MySQL显示命令二.显示命令 1.显示 ...
-
VS2010 自动关闭的问题解决方法
分为如下几个解决方法: 没有安装VS2010的SP1,安装后,问题解决了 自定义设置,出现了不正确的情况,执行 devenv.exe /resetsettings 可以排除故障 使用 devenv.e ...
-
C#中的可空类型
public class Person { public DateTime birth; public DateTime? death; string name; public TimeSpan Ag ...
-
js 浏览器兼容css中webkit、Moz、O、ms...写法封装(es6语法)
/** *浏览器兼容写法封装 */ let elementStyle = document.createElement('div').style let vendor = (() => { le ...
-
BZOJ2946 [Poi2000]公共串(后缀自动机)
Description 给出几个由小写字母构成的单词,求它们最长的公共子串的长度. 任务: l 读入单词 l 计算最长公共子串的长度 l 输 ...
-
_编程语言_C++_简介
扩展名: .cpp..cp或.c C++编译器: GNU的gcc 编译器
-
Spark Shuffle原理解析
Spark Shuffle原理解析 一:到底什么是Shuffle? Shuffle中文翻译为“洗牌”,需要Shuffle的关键性原因是某种具有共同特征的数据需要最终汇聚到一个计算节点上进行计算. 二: ...
-
Android 布局学习之——Layout(布局)详解一
layout(布局)定义了用户界面的可视化结构(visual structure),如Activity的UI,应用窗口的UI. 有两种方式声明layout: 1.在xml文件中声明UI组件. 2.在运 ...
-
jquery动态创建表格
1.代码实例 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UT ...
-
jave web 监听器。
https://www.imooc.com/video/5664 Web监听器由Servlet规范提供的,可以监听客户端的请求以及服务端的操作,即监听ServletContext.HttpSessio ...