进程间通信IPC------Inter-Process Communication
由于内存管理的一些机制,导致两个进程间并不能直接的进行通信(在独立的用户空间),因此我们需要利用一些介质来完成两个进程之间的通信。以下是常用的进程间通信方式。
# 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
# 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
# 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
# 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
# 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
# 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
# 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
-
Android之IPC(三常用的进程间通信方式的介绍)
2018-06-15 13:15:10Android之IPC(三常用的进程间通信方式的介绍) 使用Bundle 之前序列化介绍过Parcelable接口,是支持不同进程传输的,一般我们都时把它放在Intent中传递,当然还有其他的用法,下面的Messenger就会用到。在...Android之IPC(三常用的进程间通信方式的介绍)
使用Bundle
之前序列化介绍过Parcelable接口,是支持不同进程传输的,一般我们都时把它放在Intent中传递,当然还有其他的用法,下面的Messenger就会用到。在Bundle中我们放入我们需要的信息即可。(后面就会用的到,这里不做过多介绍,常用的Intent,相信大家很熟悉了)
使用文件共享(FileShare)
顾名思义,Android基于Linux,并发读和写是没有限制的,多个线程来写也是可以的(可能会出问题)
要点: 我们可以使用之前说的序列化和反序列化,通过指定文件保存的地方,来进行数据的传输(有点类似我们操作系统的管道)
注意:SharedPreference是一个轻量级的方案,我们平时也就是用来存储一些配置文件,这里不建议使用在进程间通信。 原因:SharedPreference有缓存方案,内存会存一份,多进程下,就会在读/写中变得不可靠。
使用Messenger
在进程之间传递Message对象,通过handler来接收来完成,当然,这个底层也是用的AIDL
步骤解读
从Client到Service,首先在服务端创建一个Service来处理连接的请求,客户端传过来的消息要通过服务端的handler来处理,于是就创建一个Handler,通过这个handler进而创建一个Messenger对象,用来返回底层的binder对象(是不是和AIDL很相像,如果要想给客户端发送信息,使用msg.replyTo调用send方法,向Client发送消息)
public class MessengerService extends Service { private static final int MSG_FROM_CLIENT = 1; // 用来接受信息 private final Messenger mMessenger = new Messenger(new MessengerHandler()); @SuppressLint("HandlerLeak") private class MessengerHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_FROM_CLIENT: // 接受local发送来的消息 Toast.makeText(MessengerService.this, msg.getData().getString("msg"), Toast.LENGTH_SHORT).show(); // 向Client发送消息 Messenger reply = msg.replyTo; Message replyMessage = Message.obtain(null, 2); Bundle data = new Bundle(); data.putString("reply", "good"); replyMessage.setData(data); try { reply.send(replyMessage); } catch (RemoteException e) { e.printStackTrace(); } break; default: super.handleMessage(msg); break; } } } @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); } }
当然,客户端,首先要绑定服务端的Service,通过返回的IBinder创建Messenger,通过这Messenger来向服务端发送消息(注意这里同样用的时Binder,当然,如果你想处理来自客户端的消息,同样在Client创建一个handler,进而创建一个Messenger,message.replyTo = Messenger,让handler来处理消息
private Messenger mService; private static final int MSG_FROM_SERVER = 2; // 接受回复信息并处理的handler private Messenger mGetMessage = new Messenger(new MessengerHandler()); // 用于接受回复的消息 private class MessengerHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_FROM_SERVER: Toast.makeText(MainActivity.this, msg.getData().getString("reply"), Toast.LENGTH_LONG).show(); break; default: super.handleMessage(msg); break; } } } // 用来发送信息 private ServiceConnection messengerConn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { // 发送数据 mService = new Messenger(service); // 携带发送数据 Message message = Message.obtain(null, 1); Bundle data = new Bundle(); data.putString("msg", "messenger"); message.setData(data); // 接受信息媒介 message.replyTo = mGetMessage; // 发送数据 try { mService.send(message); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } };
小结:通过以上我们发现,Messenger发送消息是通过把消息填充到Bundle中,让Message携带Bundle来处理的,Bundle时实现Parcelable接口的。
AIDL
客户端代码
private IBookManager mRemoteBookManager; private static final int MSG_NEW_BOOK_ARRIVED = 1; // 这里设置死亡代理,当Binder死亡的时候,会回调其中的方法 private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { // 运行在binder线程池中 @Override public void binderDied() { if (mRemoteBookManager == null) return; mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0); mRemoteBookManager = null; // 重新绑定服务 Intent intent = new Intent("com.ali.aidl"); bindService(intent, aidlConn, BIND_AUTO_CREATE); } }; @SuppressLint("HandlerLeak") private Handler messengerHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case MSG_NEW_BOOK_ARRIVED: System.out.println("receive new book : " + msg.obj); break; default: break; } } }; // 从这里可以看出一个binder对应一个listener,跨进程传输的时候,binder是同一个 private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() { @Override public void onNewBookArrived(Book book) { // 此时mOnNewBookArrivedListener是观察者的具体实现(换言之治理就是具体的实现逻辑, //相当于服务端,remote必须想要通知观察者,就必须调用这里的方法,remote相当于客户端 messengerHandler.obtainMessage(MSG_NEW_BOOK_ARRIVED, book).sendToTarget(); } }; // ServiceConnection方法运行在主线程,所以调用服务端的方法最好在非UI线程(远程调用本地也是一样) private ServiceConnection aidlConn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { // 获取接口对象 IBookManager bookManager = IBookManager.Stub.asInterface(service); try { mRemoteBookManager = bookManager; // 设置死亡代理 mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0); List<Book> bookList = bookManager.getBookList(); System.out.println("bookList size : " + bookList.size()); // 向服务端提交信息 bookList.add(new Book(3, "JAVA")); List<Book> newList = bookManager.getBookList(); bookManager.registerListener(mOnNewBookArrivedListener); System.out.println("bookList size : " + newList.size()); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { // 档然在这里也是可以重新绑定绑定服务的(相当于死亡代理的,具体区别后面有介绍) System.out.println("binder died"); } };
服务端代码
public class BookManagerService extends Service { private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>(); private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>(); //private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<>(); private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false); private Binder mBinder = new IBookManager.Stub() { @Override public List<Book> getBookList() { return mBookList; } @Override public void addBook(Book book) { mBookList.add(book); } // 如果没有特殊的处理,跨进程之后传过来就会变你成一个不确定对象(对象的地址控件会发生变化,导致对象不一样 // 所以要使用RemoteCallbackList(实现了自动同步,如果客户端停止,自动移除相关的listener)下面的unRegister一样如此 @Override public void registerListener(IOnNewBookArrivedListener listener) { //if (!mListenerList.contains(listener)) mListenerList.add(listener); //else System.out.println("not found"); mListenerList.register(listener); } @Override public void unregisterListener(IOnNewBookArrivedListener listener) { // if (mListenerList.contains(listener)) mListenerList.remove(listener); //else System.out.println("not found"); mListenerList.unregister(listener); } }; @Override public void onCreate() { super.onCreate(); mBookList.add(new Book(1, "Android")); mBookList.add(new Book(2, "IOS")); new Thread(new ServiceWorker()).start(); } @Override public IBinder onBind(Intent intent) { // 在这里可以进行权限的验证,不是所有的都可以调用来自服务端的方法 return mBinder; } private void onNewBookArrived(Book book) throws RemoteException { mBookList.add(book); //for (IOnNewBookArrivedListener listener : mListenerList) { // listener.onNewBookArrived(book); //} IOnNewBookArrivedListener listener; int count = mListenerList.beginBroadcast(); for (int i = 0; i < count; i++) { listener = mListenerList.getBroadcastItem(i); // 这里是为了演示,其实这个应该也放在非UI线程 if (listener != null) listener.onNewBookArrived(book); } mListenerList.finishBroadcast(); } private class ServiceWorker implements Runnable { @Override public void run() { Book newBook; // 这里处理一些后太进程 while (!mIsServiceDestroyed.get()) { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } int bookId = mBookList.size() + 1; newBook = new Book(bookId, "newBook" + bookId); try { onNewBookArrived(newBook); } catch (RemoteException e) { e.printStackTrace(); } } } } }
AIDL文件
1.
// Book.aidl
package com.example.remote;parcelable Book;
2.
// IBokManager.aidl
package com.example.remote;import com.example.remote.Book; import com.example.remote.IOnNewBookArrivedListener; interface IBookManager { List<Book> getBookList(); void addBook(in Book book); void registerListener(IOnNewBookArrivedListener listener); void unregisterListener(IOnNewBookArrivedListener listener); }
3.
// IOnNewBookArrivedListener.aidl package com.example.remote; import com.example.remote.Book; interface IOnNewBookArrivedListener { void onNewBookArrived(in Book book); }
小结:
1.这里从Client通过Binder传递到Server的时候,会产生两个对象,但是Binder确是一样的,因此,这里我们用的观察者模式,在unRegistere的时候就会出意外,完全是两个对象,因此,要利用Binder一样,使用RemoteCallbackList(专门用来提供删除跨进程的接口,是一个泛型,支持管理任意的AIDL接口。而且内部自动实现了线程同步的功能,我们就不需要额外的线程同步的功能是不是很NB)
2.同时RemoteCallbackList并不是一个List,在遍历的时候beginBroadCase和finishBroadcase要配对使用,哪怕是获取其中元素的个数。
3.之前说过,客户端请求当前线程会挂起,不同进程的方法会运行在服务端的Binder线程池中,当前线程阻塞,如果在UI线程可能导致ANR。也要注意,onServiceConnected和onServiceDisconnected也是运行在UI线程,不能有耗时方法。
4.我们为了方法对远程连接不意外中断,都有重连机制。其一,我们可以使用设置死亡代理,通过监听来判断。其二,在onServiceDisconnect中重新连接。死亡代理,我们用的到服务端传过来的Ibinder来设置监听,运行在客户端的Binder线程池中,不能访问UI。但是后者是运行在UI线程当中的。
5.aidl文件中用到了自定义序列化对象不要忘记导入(哪怕是在同一个包)
使用ContentProvider
这是Android的四大组件之一,通过表格的形式组织数据,和数据库很类似,一般也是和数据库联系起来用的。
客户端
private void contentProvider() { Uri bookUri = Uri.parse("content://com.ali.contentProvider/book"); ContentValues bookValues = new ContentValues(); bookValues.put("_id", 6); bookValues.put("name", "Python"); getContentResolver().insert(bookUri, bookValues); Cursor bookCursor = getContentResolver().query(bookUri, new String[]{"_id", "name"}, null, null, null); Book book; while (bookCursor.moveToNext()) { book = new Book(bookCursor.getInt(0), bookCursor.getString(1)); System.out.println(book.toString()); } bookCursor.close(); Uri userUri = Uri.parse("content://com.ali.contentProvider/user"); Cursor userCursor = getContentResolver().query(userUri, new String[]{"_id", "name", "sex"}, null, null, null); User user; while (userCursor.moveToNext()) { user = new User(userCursor.getInt(0), userCursor.getString(1), userCursor.getInt(2) == 1); System.out.println(user.toString()); } userCursor.close(); }
服务端
ContentProvider
public class BookProvider extends ContentProvider { // 可以理解为URI匹配的必要前缀(自己定义的) private static final String AUTHORITY = "com.ali.contentProvider"; // content:// 默认前缀 private static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book"); private static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user"); // 表的选择索引 private static final int BOOK_URI_CODE = 0; private static final int USER_URI_CODE = 1; // 将匹配夫和选择具体的表关联 private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); static { sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE); sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE); } private Context mContext; private SQLiteDatabase mDataBase; @Override public boolean onCreate() { mContext = getContext(); // 初始化数据库(这是为了演示,实际开发中不应该在主线程中进行) initProviderData(); return true; } // 初始化创建数据库并插入部分数据 private void initProviderData() { mDataBase = new DBOpenHelper(mContext).getReadableDatabase(); mDataBase.execSQL("delete from " + DBOpenHelper.BOOK_TABLE_NAME); mDataBase.execSQL("delete from " + DBOpenHelper.USER_TABLE_NAME); mDataBase.execSQL("insert into book values(3, 'Android');"); mDataBase.execSQL("insert into book values(4, 'IOS');"); mDataBase.execSQL("insert into book values(5, 'HTML5');"); mDataBase.execSQL("insert into user values(1, 'jake', 1);"); mDataBase.execSQL("insert into user values(2, 'jasmine', 0);"); } // 以下是具体的增删改查等基本操作 @Override public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { String tableName = getTableName(uri); if (tableName == null) throw new IllegalArgumentException("Unsupported URI :" + uri); return mDataBase.query(tableName, projection, selection, selectionArgs, null, null, sortOrder, null); } @Override public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) { String tableName = getTableName(uri); if (tableName == null) throw new IllegalArgumentException("Unsupported URI :" + uri); int count = mDataBase.delete(tableName, selection, selectionArgs); if (count > 0) mContext.getContentResolver().notifyChange(uri, null); return count; } @Override public String getType(@NonNull Uri uri) { return null; } @Override public Uri insert(@NonNull Uri uri, ContentValues values) { String tableName = getTableName(uri); if (tableName == null) throw new IllegalArgumentException("Unsupported URI :" + uri); mDataBase.insert(tableName, null, values); mContext.getContentResolver().notifyChange(uri, null); return uri; } @Override public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) { String tableName = getTableName(uri); if (tableName == null) throw new IllegalArgumentException("Unsupported URI :" + uri); int count = mDataBase.update(tableName, values, selection, selectionArgs); if (count > 0) mContext.getContentResolver().notifyChange(uri, null); return count; } // 选择表 private String getTableName(Uri uri) { String tableName = null; switch (sUriMatcher.match(uri)) { case BOOK_URI_CODE: tableName = DBOpenHelper.BOOK_TABLE_NAME; break; case USER_URI_CODE: tableName = DBOpenHelper.USER_TABLE_NAME; break; default: break; } return tableName; } }
SQLiteOpenHelper
public class DBOpenHelper extends SQLiteOpenHelper { private static final String DB_NAME = "book_provider.db"; public static final String BOOK_TABLE_NAME = "book"; public static final String USER_TABLE_NAME = "user"; private static final int DB_VERSION = 1; // 图书和用户信息表 private String CREATE_BOOK_TABLE = "create table if not exists " + BOOK_TABLE_NAME + " (_id integer primary key, " + " name text)"; private String CREATE_USER_TABLE = "create table if not exists " + USER_TABLE_NAME + " (_id integer primary key, " + " name text, " + " sex int)"; public DBOpenHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase db) { // 创建表 db.execSQL(CREATE_BOOK_TABLE); db.execSQL(CREATE_USER_TABLE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
小结:
1.这个一般就是一些固定的格式,书写没有什么过多讲解的。ContentProvider中处理onCreate方法运行在UI线程,其他的五个方法都是运行在Binder线程池中的,query,update,insert,delete,getType。在我们对数据有更改的时候,一般都是调用mContext.getContentResolver().notifyChange(uri, null)来发出通知。我们通过ContentResolyer来注册观察者检测即可。
2.同时query,inset,update,delete,存在多线程并发访问,内部要做好同步。当时SQLiteDatabase内部对数据库是由同步处理的,当时多个SQLiteDatabase就无法保证了,因为多个SQLiteDatabase对象之间没有办法做到同步,需要使用者自己处理。
Socket
也是可以使用进程间通信的,我们注意不要在主线程中访问网络。
客户端(UI的更新通过Handler在UI更新)下面是主要方法
private void connectTCPServer() { Socket socket = null; try { // 建立连接 socket = new Socket("localhost", 8688); mClientSocket = socket; // 向服务端提供客户端给的消息 writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true); socketHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED); System.out.println("connected success"); } catch (IOException e) { e.printStackTrace(); System.out.println("connected failed"); } try { // 接受服务端的信息 BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); while (!isFinishing()) { String content = reader.readLine(); if (content != null) { String time = "server " + formatDateTime(System.currentTimeMillis()) + ":" + content + "\n"; socketHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, time).sendToTarget(); System.out.println("对话"); } } System.out.println("quit..."); reader.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } @SuppressLint("SimpleDateFormat") private String formatDateTime(long time) { return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time)); } // 发送信息 @SuppressLint("SetTextI18n") private void sendMessage() { String content = etContent.getText().toString().trim(); if (!TextUtils.isEmpty(content) && writer != null) { writer.println(content); etContent.setText(""); String showMsg = "self" + formatDateTime(System.currentTimeMillis()) + ":" + content + "\n"; tvShow.setText(tvShow.getText() + showMsg); } }
服务端
public class TCPService extends Service { private boolean mIsServiceDestroyed = false; private String[] mDefinedMessages = new String[]{ "你好啊,很高兴认识你!", "欢迎来到我见做客。", "今天天气不错啊,很适合出去走走,约不?", "你知道吗?据说爱笑的女孩子,运气不会太差。", "做我女朋友吧。" }; @Override public void onCreate() { super.onCreate(); new Thread(new TCPServer()).start(); } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { super.onDestroy(); mIsServiceDestroyed = true; } private class TCPServer implements Runnable { @Override public void run() { ServerSocket serverSocket; try { // 监听8688端口 serverSocket = new ServerSocket(8688); } catch (IOException e) { e.printStackTrace(); System.out.println("establish tcp server failed, port:8688"); return; } while (!mIsServiceDestroyed) { try { // 接受客户端的请求 Socket client = serverSocket.accept(); System.out.println("accept"); responseClient(client); } catch (IOException e) { e.printStackTrace(); } } } } private void responseClient(Socket client) throws IOException { // 用于接受客户端消息 BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream())); // 用于向客户端发送消息 PrintWriter writer = new PrintWriter(new OutputStreamWriter(client.getOutputStream()),true); writer.println("欢迎来到聊天室"); String clientContent, serverContent; while (!mIsServiceDestroyed) { clientContent = reader.readLine(); if (clientContent == null) break; // 断开连接 serverContent = mDefinedMessages[(int) (Math.random() * (mDefinedMessages.length))]; writer.println(serverContent); } System.out.println("client quit"); // 关闭输入输出流 reader.close(); writer.close(); client.close(); } }
小结: 形成一个循环,客户端和服务端两者之间相互传递消息即可,就是输入输出流的使用。
总结
以上的通信方式,都不要放及解绑和停止服务即可。
-
进程间通信方式
2019-10-08 10:11:47进程间通信IPC------Inter-...以下是常用的进程间通信方式。# 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。# 有...转载于:https://www.cnblogs.com/davidshi/p/3366253.html
-
几种常用的进程间通信的方式,通信特点和通信方式的优缺点
2013-12-22 10:55:13程序员必须让拥有依赖关系的进程集协调,这样才能达到进程的共同目标。可以使用两种技术来达到协调。...这种技术称做进程间通信(interprocess communication)。第二种技术是同步,当进程间相互具有合作依赖时使用http://blog.csdn.net/liuzhanchen1987/article/details/7452910
程序员必须让拥有依赖关系的进程集协调,这样才能达到进程的共同目标。可以使用两种技术来达到协调。第一种技术在具有通信依赖关系的两个进程间传递信息。这种技术称做进程间通信(interprocess communication)。第二种技术是同步,当进程间相互具有合作依赖时使用。这两种类型的依赖关系可以同时存在。
一般而言,进程有单独的地址空间。我们可以了解下可执行程序被装载到内存后建立的一系列映射等理解这一点。如此以来意味着如果我们有两个进程(进程A和进程B),那么,在进程A中声明的数据对于进程B是不可用的。而且,进程B看不到进程A中发生的事件,反之亦然。如果进程A和B一起工作来完成某个任务,必须有一个在两个进程间通信信息和时间的方法。我们这里可以去看看基本的进程组件。注意进程有一个文本、数据以及堆栈片断。进程可能也有从自由存储空间中分配的其它内存。进程所占有的数据一般位于数据片断、堆栈片断或进程的动态分配内存中。数据对于其它进程来说是受保护的。为了让一个进程访问另外一个进程的数据,必须最终使用操作系统调用。与之类似,为了让一个进程知道另一个进程中文本片断中发生的事件,必须在进程间建立一种通信方式。这也需要来自操作系统API的帮助。当进程将数据发送到另一进程时,称做IPC(interprocess communication,进程间通信)。下面先列举几种不同类型的进程间通信方式:
进程间通信 描述
环境变量/文件描述符 子进程接受父进程环境数据的拷贝以及所有文件描述符。父进程可以在它的数据片断或环境中设置一定的变量,同时子进程接收这些值。父进程可以打开文件,同时推进读/写指针的位置,而且子进程使用相同的偏移访问该文件。
命令行参数 在调用exec或派生函数期间,命令行参数可以传递给子进程。
管道 用于相关和无关进程间的通信,而且形成两个进程间的一个通信通道,通常使用文件读写程序访问。
共享内存 两个进程之外的内存块,两个进程均可以访问它。
DDE(动态数据交换,Dynamic data exchange) 使用客户机/服务器模型(C/S),服务器对客户的数据 或动作请求作出反应。
一、环境变量、文件描述符:
当创建一个子进程时,它接受了父进程许多资源的拷贝。子进程接受了父进程的文本、堆栈
以及数据片断的拷贝。子进程也接受了父进程的环境数据以及所有文件描述符的拷贝。子进
程从父进程继承资源的过程创造了进程间通信的一个机会。父进程可以在它的数据片断或环
境中设置一定的变量,子进程于是接受这些值。同样,父进程也可以打开一个文件,推进到
文件内的期望位置,子进程接着就可以在父进程离开读/写指针的准确位置访问该文件。
这类通信的缺陷在于它是单向的、一次性的通信。也就是说,除了文件描述外,如果子进程
继承了任何其它数据,也仅仅是父进程拷贝的所有数据。 一旦创建了子进程,由子进程对
这些变量的任何改变都不会反映到父进程的数据中。同样,创建子进程后,对父进程数据的
任何改变也不会反映到子进程中。所以,这种类型的进程间通信更像指挥棒传递。一旦父进
程传递了某些资源的拷贝,子进程对它的使用就是独立的,必须使用原始传递资源。
二、命令行参数:
通过命令行参数(command-line argument)可以完成另一种单向、一次性的进程间通信
我前面的文章已经提到过使用命令行参数。命令行参数在调用一个exec或派生调用操作系
统时传递给子进程。命令行参数通常在其中一个参数中作为NULL终止字符串传递给exec
或派生函数调用。这些函数可以按单向、一次性方式给子进程传递值。WINDOWS有调用执行
exe程序的API。大家可以去参考一下ShellExecuteA函数相关。
三、管道通信:
继承资源以及命令行参数是最简单形式的进程间通信。它们同时有两个主要限制。除了文件
描述符外,继承资源是IPC的单向、一次性形式。传递命令参数也是单向、一次性的IPC
方法。这些方法也只有限制于关联进程,如果不关联,命令行参数和继承资源不能使用。还
有另一种结构,称做管道(Pipe),它可以用于在关联进程间以及无关联进程间进行通信。
管道是一种数据结构,像一个序列化文件一样访问。它形成了两个进程间的一种通信渠道。
管道结构通过使用文本和写方式来访问。如果进程A希望通过管道发送数据给进程B,那么
进程A向管道写入数据。为了让进程B接收此数据,进程B必须读取管道,与命令行参数的
IPC形式不一样。管道可以双向通信。两进程间的数据流是双向通信的。管道可以在程序的
整个执行期间使用,在进程间发送和接收数据。所以,管道充当可访问管道的进程间的一种
可活链接,有两种基本管道类型:
1. 匿名管道
2. 命名管道
上面的图可以看出在没有管道时,两进程是不能互写的。
建立管道后就可以相互通信了。
只有关联进程可以使用匿名管道来通信。无关联进程必须使用命名管道。
匿名管道:通过文件描述符或文件句柄提供对匿名管道的访问。对系统API的调用创建一个管道,并返回一个文件描述符。这个文件描述符是用作read()或write()函数的一个参数。当通过文件描述符调用read()或write()时,数据的源和目标就是管道。例如,在OS/2环境中使用操作系统函数DosCreatePipe()创建匿名管道:
int mian( void )
{
PFHILE readHandle;
PFHILE writeHandle;
DosCreatePipe( readHandle, writeHandle, size );
…
…
…
}
在WINDOWS下例如我写的ASM集成环境通过管道与DOS命令行通信的MFC下代码块:
void CIDEManager::Commond( CString cmd, char* buf, unsigned int bufsize )
{
SECURITY_ATTRIBUTES sa;
HANDLE hRead, hWrite;
sa.nLength = sizeof( SECURITY_ATTRIBUTES );
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
if ( !CreatePipe( &hRead, &hWrite, &sa, 0 ) ) // 创建管道
{
return;
}
STARTUPINFO si;
PROCESS_INFORMATION pi;
si.cb = sizeof( STARTUPINFO );
GetStartupInfo( &si );
si.hStdError = hWrite;
si.hStdOutput = hWrite;
si.wShowWindow = SW_HIDE;
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
if ( !CreateProcess( NULL, ( LPTSTR )( LPCTSTR )cmd, NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi ) )
{
return;
}
CloseHandle( hWrite );
DWORD bytesRead;
while ( TRUE )
{
memset( buf, 0, bufsize );
if ( ReadFile( hRead, buf, bufsize, &bytesRead, NULL ) != NULL )
{
break;
}
Sleep( 200 );
}
CloseHandle( hRead );
return;
}
命名管道:将管道用作两个无关联进程间的通信渠道,程序员必须使用命名管道,它可以看作一种具有某名字的特殊类型文件。进程可以根据它的名字访问这个管道。通过匿名管道,父和子进程可以单独使用文件描述符来访问他们所共享的管道,因为子进程继承了父进程的文件描述符,同时文件描述符用read()或write()函数的参数。因为无关进程不能访问彼此的文件描述符,所以不能使用匿名管道。由于命名管道提供该管道的一个等价文件名,任何知道此管道名字的进程都可以访问它。下面是命名管道相对于匿名管道的优点:
命名管道可以被无关联进程使用。
命名管道可以持久。创建它的程序退出后,它们仍然可以存在。
命名管道可以在网络或分布环境中使用。
命名管道容易用于多对一关系中。
与访问匿名管道一样,命名管道也是通过read()或write()函数来访问。两者之间的主要区别在于命名管道的创建方式以及谁可以反问它们。命名管道可以建立一个进程间通信的C/S模型。访问命名管道的进程可能都位于同一台机器上,或位于通过网络通信的不同机器上。由于管道的名字可以通过管道所在服务器的逻辑名,所以能够跨网络访问管道。例如,ServerName//Pipe//MyPipe(不区分大小写)可以作为一个管道名字。假如Server1是网络服务器的名字。当打开或访问这个管道的调用解析文件名时,首先应该定位Server1,然后访问MyPipe。例子如下:
服务器端:
int main( void )
{
HANDLE pipehandle;
char buf[ 256 ];
DWORD bytesRead;
if( ( pipehandle = CreateNamedPipe( ".//Pipe//cao", PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, 1, 0, 0, 5000, NULL ) ) == INVALID_HANDLE_VALUE )
{
printf( "CreateNamedPipe failed with error %d/n", GetLastError() );
system( "pause" );
return 0;
}
printf( "server is running/n" );
if( ConnectNamedPipe( pipehandle, NULL ) == 0 )
{
printf( "connectNamedPipe failed with error %d/n", GetLastError() );
CloseHandle( pipehandle );
system( "pause" );
return 0;
}
if( ReadFile( pipehandle, buf, sizeof( buf ), &bytesRead, NULL ) == 0 )
{
printf( "ReadFile failed with error %d/n", GetLastError() );
CloseHandle( pipehandle );
system( "pause" );
return 0;
}
printf( "%s/n", buf );
if ( DisconnectNamedPipe( pipehandle ) == 0 )
{
printf( "DisconnectNamedPipe failed with error %d/n", GetLastError() );
CloseHandle( pipehandle );
system( "pause" );
return 0;
}
system( "pause" );
return 0;
}
客户端:
int main( void )
{
HANDLE pipehandle;
DWORD writesbytes;
char buff[ 256 ];
if( WaitNamedPipe( ".//Pipe//cao", NMPWAIT_WAIT_FOREVER ) == 0 )
{
printf( "WaitNamedPipe failed with error %d/n", GetLastError() );
system( "pause" );
return 0;
}
if( ( pipehandle = CreateFile( ".//Pipe//cao", GENERIC_READ | GENERIC_WRITE, 0, ( LPSECURITY_ATTRIBUTES )NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, ( HANDLE )NULL ) ) == INVALID_HANDLE_VALUE )
{
printf( "CreateFile failed with error %d/n", GetLastError() );
system( "pause" );
return 0;
}
ZeroMemory( &buff, sizeof( buff ) );
gets( buff );
if( WriteFile( pipehandle, buff, sizeof( buff ), &writesbytes, NULL ) == 0 )
{
printf( "WriteFile failed with error %d/n", GetLastError() );
CloseHandle( pipehandle );
system( "pause" );
return 0;
}
printf( "write %d bytes", writesbytes );
CloseHandle( pipehandle );
system( "pause" );
return 0;
}
命名管道不仅可用于无关联进程间、位于不同机器上的两进程间的通信,而且可用于多对一通信,可以建立服务器进程,允许同时通过多个客户访问命名管道。命名管道常常用于多线程服务器。
四、 共享内存
共享内存也可以实现进程间的通信。进程需要可以被其他进程浏览的内存块。希望访问这个内存块的其他进程请求对它的访问,或由创建它的进程授予访问内存块的权限。可以访问特定内存块的所有进程对它具有即时可见性。共享内存被映射到使用它的每个进程的地址空间。所以,它看起来像是另一个在进程内声明的变量。当一个进程写共享内存,所有的进程都立即知道写入的内容,而且可以访问。
进程间共享内存的关系与函数间全局变量的关系相似。程序中的所有函数都可以使用全局变量的值。同样,共享内存块可以被正在执行的所有进程访问。内存块可能共享一个逻辑地址,进程也可以共享某些物理地址。
共享内存块的创建必须由一个系统API调用来完成。在WIN32环境中,使用CreateFileMapping()、MapViewOfFile()以及MapViewOfFileEx() API能很好地完成。
共享内存分配位于WIN32系统中2~3GB地址范围内。一旦调用MapViewOfFile()和MapViewOfFileEx(),共享文件映射对象的所有进程都可以立即访问此内存块,而且在需要时,可以读写此内存块。
五、动态数据交换
动态数据交换( dynamic data exchange ) 是当今可用的进程间通信最强大和完善的形式之一。动态数据交换使用消息传递、共享内存、事务协议、客户/服务器范畴、同步规则以及会话协议来让数据和控制信息在进程间流动。动态数据交换对话( dynamic data exchange session, DDE )的基本模型是客户、服务器。服务器对来自客户的数据或动作作出反应。客户和服务器可以以多种关系来通信。
一个服务器可以与任意数量的客户通信。一个客户也可以与任意数量的服务器通信。单个DDE代理既可以是客户,也可以是服务器。也就是说,进程可以从一个正为另一个进程执行服务的DDE代理请求服务。
-
进程间通信方式:
2019-11-15 10:04:34常用的进程间通信方式有: 管道通信; 消息队列; 信号量; 共享内存; 信号; 套接字; 内容 管道通信 消息队列 信号量 共享内存 原理 在管道中开启内存空间生成管道的操作对象多个进程使用同一个管道对象进行...常用的进程间通信方式有:
- 管道通信;
- 消息队列;
- 信号量;
- 共享内存;
- 信号;
- 套接字;
内容 管道通信 消息队列 信号量 共享内存 原理 在管道中开启内存空间生成管道的操作对象多个进程使用同一个管道对象进行读写,这样实现了进程间通信 在内存中建立队列模型,进程通过队列将信息存入,或者从队列取出完成进程间通信 给定一个数量对多个进程可见,多个进程都可以操作该数量增减,根据数量值决定自己的行为 在内存中开辟一块空间,进程可以写入内容和读取内容完成通信,但每次写入内容会覆盖之前的内容 实现方法 使用: from mutiprocessing import Pipe
使用: from mutiprocessing import Queue
使用: from mutiprocessing improt Semaphore
使用: from mutiprocessing import Value,Array
创建 fd1,fd2 = Pipe(duplex = True)
q = Queue(maxsize=0)
sem = Semaphore(num)
obj = Value(ctype,data)或者obj = Array(ctype,data)
参数 参数:默认表示双向管道 最多存放消息个数 信号量的初始值 ctype 表示共享内存空间类型 ‘i’‘f’‘c’;data共享内存空间初始数据(ctype 表示共享内存数据类型data整数则表示开辟空间的大小,其他数据类型表示开辟空间存放的初始化数据) 方法或属性 fd.recv()
从管道获取内容;fd.send()
向管道写入数据q.put(data,[False为非阻塞,超时检测])
向队列中存入数据;q.get([False为非阻塞,超时检测])
从队列中取出数据;q.full()
判读是否为满;q.empty()
是否为空;q.qsize()
队列中消息个数;q.close()
关闭队列sem.acquire()
信号量减1,为零时阻塞,sem.release()
将信号量加1;sem.get_value()
获取信号量obj.value
对该属性的修改查看即对共享读写;(Array可以使用obj.value
直接打印共享内存中的字节串)代码演示:
管道通信:""" pipe.py 管道通信 注意: 管道对象需在父进程中创建,子进程从父进程中获取 """ from multiprocessing import Process,Pipe # 创建管道 # False单向管道 fd1->recv fd2->send # 不要在一个进程中同时使用fd1 fd2 fd1,fd2 = Pipe(False) def app1(): print("启动app1,请登录,(可以使用app2)") print("向app2发请求") fd1.send("app1需要:用户名,头像") # 写管道 data = fd1.recv() print("Oh yeah",data) def app2(): data = fd2.recv() # 读管道 print("app1请求:",data) fd2.send({'name':'Han','image':'有'}) p1 = Process(target=app1) p2 = Process(target=app2) p1.start() p2.start() p1.join() p2.join()
消息队列:
""" queue_0.py 消息队列演示 注意 : 通过一个对象操作队列,满足先进先出原则 """ from multiprocessing import Queue,Process from time import sleep from random import randint # 创建消息队列 q = Queue(5) # 请求进程 def request(): for i in range(10): sleep(0.5) t = (randint(1,100),randint(1,100)) q.put(t) print("=====================") # 数据处理进程 def handle(): while True: sleep(2) x,y = q.get() print("数据处理结果 x + y=",x + y) p1 = Process(target=request) p2 = Process(target=handle) p1.start() p2.start() p1.join() p2.join()
内存共享:
""" value.py 开辟共享内存 注意: 共享内存只能有一个值 """ from multiprocessing import Process,Value import time from random import randint # 创建共享内存 money = Value('i',5000) # 操作内存 def man(): for i in range(30): time.sleep(0.2) money.value += randint(1,1000) def girl(): for i in range(30): time.sleep(0.15) money.value -= randint(100,800) p1 = Process(target=man) p2 = Process(target=girl) p1.start() p2.start() p1.join() p2.join() print("一个月余额:",money.value)
信号量:
""" array.py 存放一组数据 """ from multiprocessing import Process,Array # 共享内存,初始[1,2,3,4,5] # shm = Array('i',[1,2,3,4,5]) # shm = Array('i',4) #共享内存,初始[0,0,0,0] shm = Array('c',b'Hello') def fun(): # 迭代获取共享内存值 for i in shm: print(i) shm[0] = b'h' p = Process(target = fun) p.start() p.join() # for i in shm: # print(i) print(shm.value) # 用于打印共享内存字节串
信号量:
""" sem.py 信号量演示 注意: 信号量相当于资源,多个进程对数量进行控制 """ from multiprocessing import Process,Semaphore from time import sleep import os # 创建信号量 sem = Semaphore(3) # 任务函数 def handle(): sem.acquire() # 执行任务必须消耗一个信号量 print("开始执行任务:",os.getpid()) sleep(2) print("执行任务结束:", os.getpid()) sem.release() # 增加一个信号量 for i in range(5): p = Process(target = handle) p.start()
-
Linux进程间通信的常用方式
2019-06-21 14:37:57文章目录1.pipe匿名管道实现进程间通信2.fifo有名管道进行进程间通信3.共享内存实现进程间的通信4.有关于ipcs -q/m/s和ipcrm -q/m/s的用法 1.pipe匿名管道实现进程间通信 关于进程间通信的相关概念,请点击这里 pipe... -
几种进程间通信方式还记得吗?
2020-05-14 18:27:22目录简介常用的进程间通信方式 简介 linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的。而对Unix发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD,在进程间通信方面的侧重点有所不同。前者... -
进程间通信的几种方式
2014-10-09 21:38:54进程间通信IPC------Inter-Process ...以下是常用的进程间通信方式。 # 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。 -
muduo库学习之常用编程模型04——常用编程模型与进程间通信方式的选择
2021-02-01 14:04:51进程间通信与线程间通信进程间通信 原文链接:https://blog.csdn.net/yuyin86/article/details/7086424 1. 单线程服务器常用地编程模型 常用的单线程编程模型见... -
进程间的通信方式之无名管道
2016-08-24 18:04:05前面我们学习了一下进程,我们知道多,进程间的地址空间相对独立。进程与进程间不能像线程间通过全局变量通信。... 常用的进程间通信方式有这几种 A.传统的进程间通信方式 无名管道(pipe)、有名管道(f -
几种进程间的通信方式
2015-04-14 21:50:12 进程间通信IPC------Inter-...以下是常用的进程间通信方式。 # 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。 -
Windows平台下常用进程间通信的实现方式
2009-06-09 09:44:00Windows平台下常用进程间通信的实现方式收藏Windows平台为我们提供了多种进程间通信的机制,主要包括:注册表方式、共享文件方式、共享内存方式、共享数据段、映射文件方式、管道方式、剪贴板方式、消息方式。... -
ipcs命令 多进程间通信常用的工具
2021-01-09 19:00:47ipcs命令是用于报告Linux中进程间通信设施的状态,显示的信息包括消息列表、共享内存和信号量的信息 。 语法格式:ipcs [参数] 常用参数: -a 默认的输出信息 -m 打印出使用共享内存进行进程间通信的信息 -q ... -
进程间的通信方式及线程间的通信方式
2020-02-14 21:29:06进程间的通信方式及线程间的通信方式 0X00 进程间的通信方式 0X01 总述 每个进程都有自己的地址空间,因此从资源占用上来看,不同进程是相互独立的,也就是存在着明显边界的。因此相比于线程,通信比较困难一点。... -
Linux系统编程10.进程间通信的常见方式
2020-03-09 18:32:28如今常用的进程间通信方式有: (1)管道(使用最简单) (2)信号(开销最小) (3)共享映射区(无血缘关系) (4)本地套结字(最稳定) -
进程间的几种通信方式概述
2016-12-27 13:21:51进程间通信IPC------Inter-Process ...以下是常用的进程间通信方式。 # 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。 -
Android进程间通信(IPC)常用方式
2018-05-28 10:09:04进程间通信方式在Android开发中我们可以通过Intent、ContentProviders来实现进程间通信,如果不限于Android特有的话,我们还可以使用File、Socket等方式,反正只要进程间能交换信息就行了。像Intent,我们平时使用的... -
常用的进程间通信的方法
2008-05-27 23:05:001:最简单的方法可能是直接给目标进程的窗口发生消息了。但是发生消息是有些限制的,对于数据量比较大的时候,需要发生WM_COPYDATA这个消息,也就是用剪贴板来实现消息传递。这种方式应该是比较灵活的,实现起来也最... -
linux常用的进程间的通讯方式
2020-05-15 14:37:54linux常用的进程间的通讯方式 (1)、管道(pipe):管道可用于具有亲缘关系的进程间的通信,是一种半双工的方式,数据只能单向流动,允许一个进程和另一个与它有共同祖先的进程之间进行通信。 (2)、命名管道(named ... -
进程间的通信方式
2019-08-01 13:56:02常用的进程间通信(IPC,InterProcess Communication)的方法有: 1. 信号 ( Sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生; 像在我之间写的mqtt博客中就使用到了信号函数。 2. 管道...