一.多進程
1.創(chuàng)建多進程:
- 在android 中通過
android:process
為四大組件創(chuàng)建新的進程 - 在特殊情況下也可以通過JNI去native層fork一個新的進程独悴,但是一般不會這么做
-
android:process = ":remote"
是私有進程(其他應(yīng)用的組件不會和他跑在同一個進程中) -
android:process = "com.test.remote"
是全局進程(其他應(yīng)用通過ShareUID和它跑在同一個進程中)
2.Share UID:
- android 系統(tǒng)會為每一個應(yīng)用都分配一個UID(鑰匙)
- 只有UID相同的才能共享數(shù)據(jù)(房間)
- 注意:要想在同一個進程中運行焦影,除了UID要一樣,簽名也得一樣留特;這種情況下的兩個應(yīng)用也切,不光能訪問私有數(shù)據(jù)(比如data目錄)檬姥,還能共享內(nèi)存數(shù)據(jù)
3.多進程的運行機制
- 系統(tǒng)會為每個應(yīng)用分配一個虛擬機涧卵,或者說蟹瘾,會為每一個進程分配一個虛擬機
- 同一個應(yīng)用內(nèi)不同進程直接就像是復(fù)制品一樣圾浅,每個進程內(nèi)的東西是完全一樣的,一個進程內(nèi)變量或其他東西的改變憾朴,不影響其他進程狸捕,他們都有自己的內(nèi)存空間
4.多進程的影響:
- 靜態(tài)成員變量和單例模式無效(原因就是3中所說的,每一個進程都有自己的內(nèi)存空間了)
- SharePreference無效(SharePreference不允許多個進程同時進行讀寫操作)
- 線程同步機制無效(因為不同進程鎖的都不是同一個對象)
- 會創(chuàng)建多個Application(創(chuàng)建一個新的進程众雷,就要創(chuàng)建一個新的虛擬機灸拍,就相當(dāng)于打開了一個新的應(yīng)用,會重新創(chuàng)建一個application砾省〖Ω冢可以這么理解,在同一個進程中的應(yīng)用编兄,用同一個虛擬機和同一個Application)
二.IPC機制
1.Serizable接口
- 使用:
- 寫一個類實現(xiàn)Serializable接口
- 自己寫一個serialVersionUID或者是系統(tǒng)自動生成轩性,自己寫的話可以保證序列化和反序列化正確
- 不參與序列化的有:
- 對于靜態(tài)的成員變量
- 用transient關(guān)鍵字標記的不參與
String fname = Environment.getExternalStorageDirectory() + "/test.txt";
//序列化
User user = new User(1,"lili",true);
try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(fname));
outputStream.writeObject(user);
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
Log.e("user","存入失敗:"+e.getMessage());
}
//反序列化
try {
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(fname));
User user = (User) inputStream.readObject();
Log.e("user","讀取結(jié)果:"+user.toString());
inputStream.close();
textEdit.setText(user.toString());
} catch (IOException e) {
e.printStackTrace();
Log.e("user","讀取失敽菰А:"+e.getMessage());
} catch (ClassNotFoundException e) {
e.printStackTrace();
Log.e("user","讀取失敗222:"+e.getMessage());
}
2.Parceable接口
- 寫一個類實現(xiàn)Parcelable接口
- parceable方法說明.png
- 在序列化對象中有一個是實現(xiàn)了Parcelable的類揣苏,序列化時需要:
//序列化
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(book,0);
}
//反序列化
protected User(Parcel in) {
//需要上傳當(dāng)前上下文的線程
book = in.readParcelable(Thread.currentThread().getContextClassLoader());
}
3.這兩個接口的區(qū)別
區(qū)別 | Serializable | Parcelable |
---|---|---|
平臺 | java的序列化接口 | android的序列化接口 |
原理 | 將一個對象轉(zhuǎn)成可存儲或可傳輸?shù)臓顟B(tài) | 將一個對象分解成若干個支持傳輸?shù)念愋?/td> |
使用場景 | 將序列化對象存儲到設(shè)備上或者是用于網(wǎng)絡(luò)傳輸(推薦) | 內(nèi)存序列化 |
優(yōu)缺點 | 用到了大量的I/O操作(ObjectOutputStream/ObjectInoutStream)悯嗓,使用簡單,但是消耗過大 | 高效卸察,但是使用復(fù)雜 |
4.Binder
4.1 理解
- Binder是android的一個類脯厨,實現(xiàn)了IBinder接口
- 從IPC角度:是Android中一種跨進程通信方式,可以理解為一種虛擬的物理設(shè)備坑质,它的驅(qū)動是/dev/binder
- 從Framework層合武,是ServiceManager連接各種Manager和相應(yīng)ManagerService的橋梁
- 從應(yīng)用層,是連接客戶端和服務(wù)端的橋梁洪乍,bindService的時候眯杏,服務(wù)端會返回一個服務(wù)端的Binder對象給客戶端,客戶端可以通過這個Binder可以獲取服務(wù)端提供的服務(wù)和數(shù)據(jù)
4.2 創(chuàng)建aidl
①創(chuàng)建Book.java實現(xiàn)Parceable接口
②創(chuàng)建Book.aidl(先取別的名字壳澳,之后再重命名)
// Book.aidl
package ly.com.artres.aidl;
parcelable Book;
③創(chuàng)建IBookManager.aidl
// IBookManager.aidl
package ly.com.artres.aidl;
//手動導(dǎo)包
import ly.com.artres.aidl.Book;
// Declare any non-default types here with import statements
interface IBookManager {
//獲取所有圖書列表
List<Book> getBookList();
//添加圖書:AIDL中除了基本數(shù)據(jù)類型岂贩,其他類型的參數(shù)必須標上方向:in、out巷波、inout萎津,in表示輸入型參數(shù),
//out輸出型參數(shù)抹镊,inout表示輸入輸出型參數(shù)锉屈,AIDL中只支持方法,不支持聲明靜態(tài)常量垮耳,是不同于一般接口的
void addBook(in Book book);
}
④Build-->Make project之后在
build/generate/source/aidl中可以看到編譯后的IBookManager .java
4.3 手寫aidl
所有通過Binder傳輸?shù)慕涌诙家^承IInterface接口
/**
* 手寫aidl實現(xiàn)
* 所有通過Binder傳輸?shù)慕涌诙家^承IInterface接口
*/
public interface BookManager extends IInterface{
//1.1創(chuàng)建文件描述類
static final String DESCRIPTION = "ly.com.artres.aidl.BookManager";
//1.2創(chuàng)建兩個方法的標識
static final int TRANSACT_GETBOOKLIST = IBinder.FIRST_CALL_TRANSACTION + 0;
static final int TRANSACT_ADDBOOK = IBinder.FIRST_CALL_TRANSACTION + 1;
//1.3創(chuàng)建客戶端要調(diào)用的幾個方法
List<Book> getBookList() throws RemoteException;
void addBook(Book book) throws RemoteException;
//1.4創(chuàng)建內(nèi)部類Stub颈渊,實現(xiàn)外部接口,實際上是一個Binder
public class BookManageImpl extends Binder implements BookManager{
//2.1要有一個構(gòu)造函數(shù)和外部接口進行綁定
public BookManageImpl(){
this.attachInterface(this,DESCRIPTION);
}
//2.2 將服務(wù)端的Binder轉(zhuǎn)化為客戶端需要的接口(BookManager)
public static BookManager asInterface(Binder binder){
if (binder == null){
//客戶端與服務(wù)端連接失敗
return null;
}
//根據(jù)描述终佛,取出當(dāng)前進程的接口
IInterface iin = binder.queryLocalInterface(DESCRIPTION);
if (iin != null && iin instanceof BookManager){
//相同進程俊嗽,取出的接口和客戶端的接口相同,則返回服務(wù)端binder
return (BookManager) binder;
}
//不同進程
return new BookManageImpl.Proxy(binder);
}
//2.3 返回當(dāng)前的binder對象
@Override
public IBinder asBinder() {
return this;
}
/**
* 2.4 客戶端發(fā)起RPC(遠程過程調(diào)用)請求--->運行在服務(wù)端的Binder線程池中铃彰,所以所有的Binder請求都要是同步的
* @param code : 請求的方法標識
* @param data : 請求參數(shù)
* @param reply : 請求的返回結(jié)果
* @param flags : 參數(shù)flags只有0和FLAG_ONEWAY兩種绍豁,默認的跨進程操作是同步的,所以transact()方法的執(zhí)行會阻塞牙捉,
* 調(diào)用以同步的形式傳遞到遠程的transact()竹揍,等待遠端的transact()返回后繼續(xù)執(zhí)行——最好理解的方式就是把
* 兩端的transact()看作一個方法,Binder機制的目標也就是這樣邪铲。指定FLAG_ONEWAY時芬位,表示Client的transact()
* 是單向調(diào)用,執(zhí)行后立即返回带到,無需等待Server端transact()返回
* @return 返回false晶衷,則客戶端請求失敗
* @throws RemoteException
*/
@Override
public boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
//判斷請求的哪個方法
switch (code){
case TRANSACT_GETBOOKLIST:
//獲取圖書列表
//有請求參數(shù),讀取請求參數(shù)(沒有)
data.enforceInterface(DESCRIPTION);
//調(diào)用請求方法
List<Book> list = this.getBookList();
//請求完成后阴孟,需要返回值晌纫,則寫到reply中
reply.writeNoException();
reply.writeTypedList(list);
return true;
case TRANSACT_ADDBOOK:
//添加圖書
//有請求參數(shù),讀取請求參數(shù)(有)
data.enforceInterface(DESCRIPTION);
Book _arg0;
if (0 != data.readInt()){
//將之前我們所存入的數(shù)據(jù)按照順序進行獲取,反序列化
_arg0 = Book.CREATOR.createFromParcel(data);
}else {
_arg0 = null;
}
//調(diào)用方法
this.addBook(_arg0);
//請求完成后永丝,需要返回值锹漱,則寫到reply中(沒有返回值)
reply.writeNoException();
return true;
case INTERFACE_TRANSACTION:
reply.writeString(DESCRIPTION);
return true;
default:
break;
}
return super.onTransact(code, data, reply, flags);
}
//2.4創(chuàng)建代理類
private static class Proxy implements BookManager{
IBinder mRemote;
/**
* 服務(wù)端的binder
* @param binder
*/
Proxy(IBinder binder){
this.mRemote = binder;
}
/**
* 運行在客戶端
* @return
* @throws RemoteException
*/
@Override
public List<Book> getBookList() throws RemoteException {
Parcel _data = Parcel.obtain();//創(chuàng)建輸入型對象
Parcel _reply = Parcel.obtain();//創(chuàng)建輸出型對象
List<Book> _result = null;//創(chuàng)建結(jié)果對象
try {
//寫入請求參數(shù)(為空)
_data.writeInterfaceToken(DESCRIPTION);
//發(fā)起RPC請求
mRemote.transact(TRANSACT_GETBOOKLIST,_data,_reply,0);
//返回結(jié)果(list)
_reply.readException();
_result = _reply.createTypedArrayList(Book.CREATOR);
}catch (Exception e){
e.getMessage();
}finally {
//回收
_reply.recycle();
_data.recycle();
}
return _result;
}
/**
* 運行在客戶端
* @param book
* @throws RemoteException
*/
@Override
public void addBook(Book book) throws RemoteException {
Parcel _data = Parcel.obtain();//創(chuàng)建輸入型對象
Parcel _reply = Parcel.obtain();//創(chuàng)建輸出型對象
try {
//寫入請求參數(shù)
_data.writeInterfaceToken(DESCRIPTION);
if (book != null){
//序列化
_data.writeInt(1);
book.writeToParcel(_data,0);
}else {
_data.writeInt(0);
}
//發(fā)起RPC請求
mRemote.transact(TRANSACT_ADDBOOK,_data,_reply,0);
//返回結(jié)果(為空)
_reply.readException();
}catch (Exception e){
e.getMessage();
}finally {
//回收
_reply.recycle();
_data.recycle();
}
}
@Override
public IBinder asBinder() {
return mRemote;
}
public String getInterfaceDescription(){
return DESCRIPTION;
}
}
@Override
public List<Book> getBookList() throws RemoteException {
return null;
}
@Override
public void addBook(Book book) throws RemoteException {
}
}
}
4.4 兩個重要方法
- unlinkToDeath()
private IBinder.DeathRecipient mRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (mManage == null){
//斷開之前的連接
mManage.asBinder().unlinkToDeath(mRecipient,0);
mManage = null;
}
}
};
- linkToDeath():
binder.linkToDeath(mRecipient,0);
三、Android中的IPC方式:
- Bundle
- ContentProvider
- AIDL
- Messenger
- 文件共享
- Socket
3.1 Bundle
- Bundle實現(xiàn)了Parcelable接口慕嚷,可以通過Intent傳遞
- Bundle可以放基本數(shù)據(jù)類型哥牍,實現(xiàn)了Parcelale或Serializable接口的類,還有特殊數(shù)據(jù)類型
Intent intent = new Intent(this,BActivity.class);
Bundle bundle = new Bundle();
bundle.putString("key1","你在干啥");
intent.putExtra("intentkey",bundle);
startActivity(intent);
- 特殊使用場景:
進程A中的計算結(jié)果要在進程B中展示喝检,且這個計算結(jié)果不支持Bundle傳輸嗅辣,可以通過Intent啟動B的一個Service組件,然后B在后臺進行計算挠说,計算完成后在B中顯示
3.2 Messenger(信使)
- 是一種輕量級的ipc方案
- 底層是AIDL
- 串行的方式處理消息澡谭,客戶端發(fā)一個消息,服務(wù)端處理一個损俭,當(dāng)客戶端發(fā)了大量的消息的時候蛙奖,服務(wù)端也只能一個一個處理,效率低杆兵,不適合用Messenger處理雁仲。
- Messenger主要是用來傳遞消息,當(dāng)要調(diào)用服務(wù)端的方法時就不適用了琐脏。
messenger流程圖.jpg
原理分析:
①client和server綁定后
②client在onServiceConnected中通過IBinder生成一個Messenger發(fā)送Message消息
③server接收到client發(fā)來的消息后攒砖,會在Handler中進行處理
④server回復(fù)消息給client,也是通過Messenger日裙,他發(fā)送的消息需要客戶端處理吹艇,所以這個Messenger得是client的,怎么創(chuàng)建客戶端的Messenger呢阅签?只需要在②中掐暮,發(fā)送消息的時候,通過msg.replyTo = xx政钟,就可以把client的messenger傳過來了
- 注意:
在跨進程中不能傳遞自定義的Parceable對象
3.3 AIDL
-
(1)支持的數(shù)據(jù)類型:
aidl支持的數(shù)據(jù)類型.png
自定義的Parcelable對象和AIDL都要手動import (2)解注冊跨進程的接口時:
@Override
protected void onDestroy() {
if (mRemoteManager != null && mRemoteManager.asBinder().isBinderAlive()){
//注銷
Log.e("testaidl","onDestroy unregister listener:"+mNewBookListener);
try {
mRemoteManager.unregisterListener(mNewBookListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(mConnection);
super.onDestroy();
}
這樣寫的時候會解注冊失敗路克,因為這時候的manager和service端的都不是同一個了,aidl不能傳輸對象养交,這里的manager相當(dāng)于兩個新對象精算。
要刪除這個跨進程的接口,要用到RemoteCallBackListener;
使用方法:
//用于刪除跨進程的接口
private RemoteCallbackList<IOnNewBookArrivedLisneer> mListennerist = new RemoteCallbackList<>();
@Override
public void registerListener(IOnNewBookArrivedLisneer listener) throws RemoteException {
mListennerist.register(listener);
}
@Override
public void unregisterListener(IOnNewBookArrivedLisneer listener) throws RemoteException {
mListennerist.unregister(listener);
}
注意:
在使用RemoteCallbackList時要注意:
//獲取元素個數(shù)
int N = mListennerist.beginBroadcast();
for (int i=0;i<N;i++){
IOnNewBookArrivedLisneer lisneer = mListennerist.getBroadcastItem(i);
if (lisneer != null){
try {
lisneer.onNewBookArrived(book);
}catch (Exception e){
e.printStackTrace();
}
}
}
//要和beginBroadcast配套使用
mListennerist.finishBroadcast();
(3)在UI線程中調(diào)用遠程端的耗時方法(互調(diào)):
- 服務(wù)端中的方法都運行在Binder線程池中碎连,客戶端調(diào)用服務(wù)端中的耗時方法時灰羽,會造成ANR,所以要在客戶端開啟一個子線程才能調(diào)用服務(wù)端的方法;
- 服務(wù)端的調(diào)用客戶端Binder線程池中的某一個耗時方法時廉嚼,也要開始一個線程才行玫镐;
(4)Binder意外死亡的時候:
- 可以在onServiceDisconnected中重新連接
- 可以在DeathRecipient 中斷開之前的連接,再重新綁定
IBinder.DeathRecipient recipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
}
};
- 他們二者的區(qū)別:
onServiceDisconnected是運行在UI線程中怠噪,而DeathRecipient 是運行在客戶端的Binder線程池中的
(5)權(quán)限驗證:
聲明權(quán)限
<!--權(quán)限驗證恐似,驗證通過才能連接服務(wù)器-->
<permission android:name="ly.com.artres.aidl.ACCESS_BOOK_SERVICE" android:protectionLevel="normal"/>
使用權(quán)限:
<uses-permission android:name="ly.com.artres.aidl.ACCESS_BOOK_SERVICE"/>
- 可以在onBinder()中驗證權(quán)限
@Override
public IBinder onBind(Intent intent) {
//權(quán)限驗證
int permission = checkCallingOrSelfPermission("ly.com.artres.aidl.ACCESS_BOOK_SERVICE");
if (permission == PackageManager.PERMISSION_DENIED){
return null;
}
return mBinder;
}
可以在onTransact()中通過權(quán)限驗證或者Uid,Pid進行驗證
總結(jié):創(chuàng)建一個service和AIDL接口,創(chuàng)建一個類實現(xiàn)AIDL接口中的Stub類并實現(xiàn)里面的抽象方法傍念,在service的onBind()中返回這個類的對象矫夷,然后在客戶端就可以綁定服務(wù)端Service,繼而訪問遠程接口
3.4 ContentProvider
- 底層是Binder
- 寫一個類繼承ContentProvider
要實現(xiàn)6個方法:
①boolen onCreate():創(chuàng)建憋槐,做初始化工作双藕,運行在UI線程中;
②String getType(Uri uri):根據(jù)uri獲取對應(yīng)的MIME(媒體)類型:文字/圖片/視頻,在主線程阳仔;
③Uri insert(@NonNull Uri uri, @Nullable ContentValues values):插入數(shù)據(jù)忧陪,在binder線程池中運行;
④int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs):刪除數(shù)據(jù)驳概,在binder線程池中運行赤嚼;
⑤int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs):更新數(shù)據(jù),在binder線程池中運行顺又;
⑥Cursor query(...):查詢數(shù)據(jù)更卒,在binder線程池中運行;
實現(xiàn)步驟:
- 在manifest中定義這個provider
<provider
android:name=".contentproviderdemo.BookProvider"
android:authorities="ly.com.artres.provider"http://唯一標識
android:permission="ly.com.PROVIDER"http://權(quán)限
android:process=":process" />//這個可以不寫
根據(jù)Uri-->得到UriCode--->再判斷是哪一張表
- 將uri和uricode進行綁定
//創(chuàng)建Uri和UriCode
private static String AUTHORITY = "ly.com.artres.provider";
private static Uri BOOK_URI = Uri.parse("content://"+AUTHORITY+"/book");
private static Uri USER_URI = Uri.parse("content://"+AUTHORITY+"/user");
public static final int BOOK_CODE = 0;
private static final int USER_CODE = 1;
//創(chuàng)建urimatch
private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
uriMatcher.addURI(AUTHORITY,"book",BOOK_CODE);
uriMatcher.addURI(AUTHORITY,"user",USER_CODE);
}
/**
* 根據(jù)Uri得到Uricode,再判斷是哪一張表
* @param uri
* @return
*/
private String getTableName(Uri uri){
String tableName = null;
switch (uriMatcher.match(uri)){
case BOOK_CODE:
tableName = DBOpenHelper.BOOK_TABLE_NAME;
break;
case USER_CODE:
tableName = DBOpenHelper.USER_TABLE_NAME;
break;
default:
break;
}
return tableName;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
Log.e("testcp","insert():"+Thread.currentThread().getName());
String tableName = getTableName(uri);
if (tableName == null){
throw new IllegalArgumentException("insert unsupport uri:"+uri);
}
mDb.insert(tableName,null,values);
mContext.getContentResolver().notifyChange(uri,null);
return null;
}
SQLite數(shù)據(jù)庫:
- 底部實現(xiàn)了線程同步,如果provider不使用sqlite數(shù)據(jù)庫芳悲,要注意在CRUD中使用線程同步牛柒;
- 自定義一個類繼承SQLiteOpenHelper颠蕴,必須要有一個構(gòu)造方法;
- SQLiteOpenHelper用于創(chuàng)建升級數(shù)據(jù)庫;
public class DBOpenHelper extends SQLiteOpenHelper {
public static final String DB_NAME = "book.db";
public static final String BOOK_TABLE_NAME = "booktb";
public static final String USER_TABLE_NAME = "usertb";
public static final int DB_VERSION = 1;
private String CREATE_BOOK = "create table if not exists "+BOOK_TABLE_NAME+"(_id integer primary key,name text)";
private String CREATE_USER = "create table if not exists "+USER_TABLE_NAME+"(_id integer primary key,name text)";
/**
* 必須要有一個構(gòu)造函數(shù)
* @param context
*/
public DBOpenHelper(Context context) {
super(context,DB_NAME,null,DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
//創(chuàng)建表
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_USER);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
根據(jù)sqliteopenhelprt可以創(chuàng)建sqlitedatabase,然后在contentprovider的CRUD方法中調(diào)用sqlitedatabase的增刪改查的方法;
SQLiteDatabase mDb = new DBOpenHelper(mContext).getWritableDatabase();
在Activity中往provider中插入數(shù)據(jù)時:
Uri uri = Uri.parse("content://ly.com.artres.provider/book");
ContentValues values = new ContentValues();
values.put("_id",4);
values.put("name","語文");
getContentResolver().insert(uri,values);
在Activity中從provider中查詢數(shù)據(jù)時:
Uri uri = Uri.parse("content://ly.com.artres.provider/book");
Cursor bookCursor = getContentResolver().query(uri,new String[]{"_id","name"},null,null,null);
while (bookCursor.moveToNext()){
int id = bookCursor.getInt(0);
String name = bookCursor.getString(1);
Log.e("testcp","query book結(jié)果:"+id+",name:"+name+"\n");
}
bookCursor.close();
3.5 文件共享
- 實現(xiàn)Serializable接口
- 通過ObjectOutputStream()和ObjectInputStream()實現(xiàn)
3.6 Socket(套接字)
1.分為兩種:
- 流式套接字-->對應(yīng)TCP(面向連接辨萍,提供穩(wěn)定的雙方通信功能,要建立三次握手返弹,本身具有超時重連機制)
- 用戶數(shù)據(jù)報套接字-->對應(yīng)UDP(也能實現(xiàn)雙方通信功能锈玉,性能會更高,但是數(shù)據(jù)不一定能傳輸成功义起,)
2.可以傳輸任意字節(jié)流
3.使用:
- 要有權(quán)限(不能在主線程訪問網(wǎng)絡(luò))
<!-- socket要的權(quán)限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
- 步驟:
服務(wù)端:
①服務(wù)端創(chuàng)建Socket
ServerSocket serverSocket = new ServerSocket(8688);
②接收客戶端請求:
final Socket client = serverSocket.accept();
③接收客戶端請求
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
String str = in.readLine();
④向客戶端發(fā)送消息
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())),true);
out.println("歡迎來到聊天室");
客戶端:
①連接服務(wù)端
Socket socket = new Socket("localhost",8688);
②向服務(wù)端發(fā)送請求:
PrintWriter mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
mPrintWriter.println(“hello”);
③接收服務(wù)端的消息:
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg = in.readLine();