參考資料php
<<精通Linux C編程>>html
在Android中的Handler的Native層研究文章中研究一下一把Linux中的匿名管道的通訊機制,今天這裏Linux中的進程間通訊補齊。ios
在Linux中,實現進程通訊的方法包括管道(匿名管道和具名管道),消息隊列,信號量,共享內存,套接口等。消息隊列,信號量,共享內存統稱爲系統的(POSIX和System V)IPC,用於本地間的進程通訊,套接口(socket)則運用於遠程進程通訊。c++
各個通訊機制定義以下:編程
匿名管道(Pipe)和具名管道(named pipe):匿名管道用於具備親緣關係進程間的通訊,具名管道克服了管道沒有名字的限制,所以除了具備匿名管道的功能外,還容許在無親緣關係的進程中進行通訊。ubuntu
消息隊列(Message):消息隊列爲消息的連接表,包括POSIX消息隊列和System V消息隊列。有足夠權限的進程能夠向隊列中添加消息,被賦予讀權限的進程則能夠讀取隊列中的消息。segmentfault
共享內存:是的多個進程能夠訪問同一塊內存空間,是最快的能夠IPC形式。是針對其餘的通訊機制運行效率較低而設計出來的。每每與其餘通訊機制,如信號量結合使用,來達到進程間的同步與互斥。服務器
信號量(semaphore):主要做爲進程間以及同一進程不一樣線程的同步手段。
套接口(socket):最通常的進程通訊機制,可用於遠程通訊。
關於匿名管道的理解以及Demo,在Android中的Handler的Native層研究文章中已經講述過了,這裏就不作介紹了。直接看具名管道(FIFO)。具名管道的提出是爲了解決匿名管道只能用於具備親緣關係(父子,兄弟)的進程間通訊,具名管道提供了一個路徑名與之關聯,以FIFO的文件形式存在於文件系統中,即便沒有親緣關係的進程也可經過該路徑名達到互相通訊的目的。
匿名管道與FIFO的區別主要在以下倆個點:
FIFO能夠用於任何兩個進程的通訊,而匿名管道只能用於有親緣關係的進程中
FIFO做爲一種特殊的文件存放於系統中,不像匿名管道存放於內存當中(使用後消失)。當進程對FIFO使用完畢後,FIFO依然存活於文件系統當中,除非主動刪除,不然不會消失。
因爲上面的第二個特性,能夠解決系統在應用中產生的大量的中間臨時文件的問題,達到重用的目的。
建立一個命令管道可使用以下兩個命令建立:
#include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *filename, mode_t mode); int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t)0); //不建議使用了
關於mode_t,看過其實就是文件訪問權限表示,在鳥哥linux私房菜的權限一章中有介紹,這裏就不講了,詳細的本身查看連接吧,下面簡單實現一個Demo:
#include<iostream> #include<signal.h> #include<sys/types.h> #include<sys/stat.h> #include<errno.h> void testNamePipe(){ mode_t mode=0666; const char* name="namePipeTest"; int ret=mkfifo(name,mode); if(errno==EEXIST){ printf("對象存在"); }else if(ret<0){ printf("建立命名管道失敗,自動退出"); exit(1); }else{ printf("建立命名管道成功"); } }
編譯須要加入-lrt 如g++ main.cpp -lrt -o main
對於管道的操做以下:
在操做命令管道open()函數的傳參主要實現有以下幾種:
open(const char *path, O_RDONLY); // 1 open(const char *path, O_RDONLY | O_NONBLOCK); // 2 open(const char *path, O_WRONLY); // 3 open(const char *path, O_WRONLY | O_NONBLOCK); // 4
引用自這篇文章
在open函數的調用的第二個參數中,你看到一個陌生的選項 O_NONBLOCK,選項 O_NONBLOCK 表示非阻塞,加上這個選項後,表示open調用是非阻塞的,若是沒有這個選項,則表示open調用是阻塞的。
open調用的阻塞是什麼一回事呢?很簡單,對於以只讀方式(O_RDONLY)打開的FIFO文件,若是open調用是阻塞的(即第二個參數爲O_RDONLY),除非有一個進程以寫方式打開同一個FIFO,不然它不會返回;若是open調用是非阻塞的的(即第二個參數爲O_RDONLY | O_NONBLOCK),則即便沒有其餘進程以寫方式打開同一個FIFO文件,open調用將成功並當即返回。
對於以只寫方式(O_WRONLY)打開的FIFO文件,若是open調用是阻塞的(即第二個參數爲O_WRONLY),open調用將被阻塞,直到有一個進程以只讀方式打開同一個FIFO文件爲止;若是open調用是非阻塞的(即第二個參數爲O_WRONLY | O_NONBLOCK),open總會當即返回,但若是沒有其餘進程以只讀方式打開同一個FIFO文件,open調用將返回-1,而且FIFO也不會被打開。
簡單實現阻塞的Demo以下,這裏直接使用父子進程進行測試:
#include <errno.h> #include <fcntl.h> //O_WRONLY等頭文件 #include <iostream> #include <signal.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> using namespace std; const char* name = "namePipeTest"; void rwNamePipe() { int pid = -1; if ((pid = fork()) < 0) { printf("%s", "fork error"); } else if (pid == 0) { //子進程 printf("%s\n", "子進程建立成功"); int writeId = open(name, O_WRONLY); //以只寫的形式 if (writeId < 0) { printf("寫端打開失敗"); exit(1); } else { printf("寫端打開成功"); char buf[] = "hello named pipe"; for (int i = 0; i < 10; i++) { cout << "寫入數據中" << endl; write(writeId, buf, sizeof(buf)); sleep(2); //睡眠兩秒 } close(writeId); exit(0); } } else { //父進程,即當前進程 printf("%s\n", "父進程開始做業"); int readId = open(name, O_RDONLY); if (readId < 0) { printf("讀端打開失敗"); exit(1); } else { printf("開始進入讀取數據階段"); char buffer[1024]; while (read(readId, buffer, sizeof(buffer)) > 0) { printf("父進程讀到數據=%s\n", buffer); } close(readId); exit(0); } } } void testNamePipe() { mode_t mode = 0666;//owner,group,others都有讀寫權限 int ret = mkfifo(name, mode); if (errno == EEXIST) { printf("對象存在"); rwNamePipe(); } else if (ret < 0) { printf("建立命名管道失敗,自動退出"); exit(1); } else { printf("建立命名管道成功"); rwNamePipe(); } } int main() { testNamePipe(); return 0; }
獲得的結果:
$ ./main 對象存在父進程開始做業 對象存在子進程建立成功 寫端打開成功寫入數據中 開始進入讀取數據階段父進程讀到數據=hello named pipe 寫入數據中 父進程讀到數據=hello named pipe 寫入數據中 父進程讀到數據=hello named pipe 寫入數據中 父進程讀到數據=hello named pipe 寫入數據中 父進程讀到數據=hello named pipe 寫入數據中 父進程讀到數據=hello named pipe 寫入數據中 父進程讀到數據=hello named pipe 寫入數據中 父進程讀到數據=hello named pipe 寫入數據中 父進程讀到數據=hello named pipe 寫入數據中 父進程讀到數據=hello named pipe
消息隊列爲以一種鏈表式結構組織的一組數據,存放於內核之中,由個進程經過消息隊列標識符引用傳遞數據的一種方式,由內核維護。消息隊列爲最具備數據操做性的數據牀送方式,在消息隊列中能夠隨意的根據特定的數據類型來檢索消息。
消息隊列跟匿名管道以及FIFO的區別(來自該篇文章):
一個進程向消息隊列寫入消息以前,並不須要某個進程在該隊列上等待該消息的到達,而管道和FIFO是相反的,進程向其中寫消息時,管道和FIFO必需已經打開來讀,那麼內核會產生SIGPIPE信號。
IPC的持續性不一樣。管道和FIFO是隨進程的持續性,當管道和FIFO最後一次關閉發生時,仍在管道和FIFO中的數據會被丟棄。消息隊列是隨內核的持續性,即一個進程向消息隊列寫入消息後,而後終止,另一個進程能夠在之後某個時刻打開該隊列讀取消息。只要內核沒有從新自舉,消息隊列沒有被刪除。
POSIX消息隊列的相關操做(更詳細的能夠man各個函數查看):
//打開一個消息隊列 mqd_t mq_open(const char *name, int oflag); mqd_t mq_open(const char *name, int oflag, mode_t mode,struct mq_attr *attr); //關閉消息隊列 int mq_close(mqd_t mqdes); //從系統中刪除消息隊列 int mq_unlink(const char *name); //獲取以及設置消息隊列屬性 int mq_getattr(mqd_t mqdes, struct mq_attr *attr); int mq_setattr(mqd_t mqdes, struct mq_attr *newattr, struct mq_attr *oldattr); //man查閱可知 struct mq_attr { long mq_flags; /* Flags: 0 or O_NONBLOCK */ long mq_maxmsg; /* Max. # of messages on queue */ long mq_msgsize; /* Max. message size (bytes) */ long mq_curmsgs; /* # of messages currently in queue */ }; //發送以及接收消息 int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio); ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio);
測試代碼以下:
測試中遇到了兩個問題記錄以下:
在調用mq_receive()時候遇到了Message too long的問題,主要是由於主要緣由在於傳遞的msg_len小於mq_msgsize致使,詳細可查看文章1以及文章2。
在mq_open()時候爆出invalid argument錯誤,緣由是不一樣ubuntu系統中對於mq_attr支持的設置不同,可經過文章3查看系統支持的參數大小
測試Demo:
#ifndef MES_QUEUE_H_ #define MES_QUEUE_H_ #include <fcntl.h> #include <iostream> #include <mqueue.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> using namespace std; void receiveQueue(string name) { cout << "客戶端讀取消息-----------------------" << endl; mode_t mode = 0666; struct mq_attr att; att.mq_msgsize = 30; att.mq_maxmsg = 10; att.mq_curmsgs = 0; att.mq_flags = 0; mqd_t openId = mq_open(name.c_str(),O_RDWR | O_CREAT|O_EXCL, mode,&att ); if (openId < 0 && errno != EEXIST) { cout << "error open mq:" << strerror(errno) << endl; return; } if(openId<0&&errno==EEXIST){ cout<<"文件存在打開"<<endl; openId=mq_open(name.c_str(),O_RDONLY); if(openId<0){ cout<<"打開失敗:"<<strerror(errno)<<endl; return; } } struct mq_attr attr; if (mq_getattr(openId, &attr) < 0) { cout << "error get attr" << endl; return; } else { printf("flags: %ld, maxmsg: %ld, msgsize: %ld, curmsgs: %ld\n", attr.mq_flags, attr.mq_maxmsg, attr.mq_msgsize, attr.mq_curmsgs); } char buffer[50]; cout << "開始讀取消息" << endl; while (true) { if(mq_receive(openId, buffer,50, NULL)>=0){ printf("讀取的消息是:%s\n", buffer); }else{ //cout<<strerror(errno)<<endl; } } mq_close(openId); } void sendQueue(string name) { cout << "服務端發送消息----------------------" << endl; mqd_t openId = mq_open(name.c_str(),O_RDWR); if (openId < 0) { cout << "error open mq" << errno << endl; return; } struct mq_attr attr; if (mq_getattr(openId, &attr) < 0) { cout << "error get attr" << endl; fprintf(stderr, "發送失敗: %s\n", strerror(errno)); return; } else { printf("flags: %ld, maxmsg: %ld, msgsize: %ld, curmsgs: %ld\n", attr.mq_flags, attr.mq_maxmsg, attr.mq_msgsize, attr.mq_curmsgs); } //int size=static_cast<int>(attr.mq_msgsize); cout << "開始發送消息" << endl; for (int i = 0; i < 10; i++) { string result = "msq no: " + to_string(i); const char* msg_ptr = result.c_str(); cout<<"發送消息中"<<endl; int rec = mq_send(openId, msg_ptr, strlen(msg_ptr)+1, 1); if (rec < 0) { cout << "發送信息失敗" << rec << endl; fprintf(stderr, "發送失敗: %s\n", strerror(errno)); break; } else { cout << "寫入消息爲" << result << endl; } sleep(3); } cout<<"發送完畢"<<endl; mq_close(openId); } void runMsgQueue() { string name = "/msq_test"; int pid = -1; if ((pid = fork()) < 0) { printf("%s", "fork error"); exit(1); } else if (pid == 0) { //子進程 sendQueue(name); } else { //本進程 receiveQueue(name); } } #endif
輸出結果以下:
$ ./main 客戶端讀取消息----------------------- flags: 0, maxmsg: 10, msgsize: 30, curmsgs: 0 開始讀取消息 服務端發送消息---------------------- flags: 0, maxmsg: 10, msgsize: 30, curmsgs: 0 開始發送消息 發送消息中 寫入消息爲msq no: 0 讀取的消息是:msq no: 0 發送消息中 寫入消息爲msq no: 1 讀取的消息是:msq no: 1 發送消息中 寫入消息爲msq no: 2 讀取的消息是:msq no: 2 發送消息中 寫入消息爲msq no: 3 讀取的消息是:msq no: 3 發送消息中 寫入消息爲msq no: 4 讀取的消息是:msq no: 4 發送消息中 寫入消息爲msq no: 5 讀取的消息是:msq no: 5 發送消息中 寫入消息爲msq no: 6 讀取的消息是:msq no: 6 發送消息中 寫入消息爲msq no: 7 讀取的消息是:msq no: 7 發送消息中 寫入消息爲msq no: 8 讀取的消息是:msq no: 8 發送消息中 寫入消息爲msq no: 9 讀取的消息是:msq no: 9 發送完畢
額外提個tip,這裏隊列能夠理解成優先級隊列的概念,在咱們mq_send()最後一個參數爲優先級,在服務端receive的時候會按照優先級進行讀取,而不是客戶端最早發送的。
信號量(semaphore)是一種提供不一樣進程間或者一個給定進程不一樣線程之間的同步,這裏依然分爲POSIX信號量和SystemV信號量,文章中只對POSIX信號量進行學習概括。
在POSIX信號量中,分爲有名信號量和無名信號量:
有名信號量:使用Posix IPC名字標識,既可用於線程間的同步,又能夠用於進程間的同步。
無名信號量:無名信號量只存在於內存中,而且規定可以訪問該內存的進程纔可以使用該內存中的信號量。這就意味着,無名信號量只能被這樣兩種線程使用:(1)來自同一進程的各個線程(2)來自不一樣進程的各個線程,可是這些進程映射了相同的內存範圍到本身的地址空間。
總而言之,無名信號量通常用於線程間同步或互斥,而有名信號量通常用於進程間同步或互斥。
有名信號量和無名信號量的使用區別以下:
有名信號量的頭文件在semaphore.h
中,具體涉及的函數以下所示:
注: Link with -pthread
sem_open() //初始化並打開有名信號量 sem_wait()/sem_trywait()/sem_timedwait()/sem_post()/sem_getvalue() //操做信號量 sem_close() //退出有名信號量 sem_unlink() //銷燬有名信號量
打開一個有名信號量:
//傳入參數參考消息隊列的mq_open()中相對應參數,value參數用來指定信號量的初始值,取值範圍[0,SEM_VALUE_MAX] sem_t *sem_open(const char *name, int oflag); sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
操做有名信號量:
//成功返回下降後的信號量的值,失敗返回-1以及errno //試圖佔用信號量,若是信號量值>0,就-1,若是已經=0,就block,直到>0 int sem_wait(sem_t *sem); //試圖佔用信號量,若是信號量已經=0,當即報錯 int sem_trywait(sem_t *sem); //試圖佔用信號量 //若是信號量=0,就block abs_timeout那麼久,超時則報錯 int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); //歸還信號量,成功返回0,失敗返回-1,以及errno int sem_post(sem_t *sem); //得到信號量sem的當前的值,放到sval中。若是有線程正在block這個信號量,sval可能返回兩個值,0或「-正在block的線程的數目」,Linux返回0 //成功返回0,失敗返回-1以及errno int sem_getvalue(sem_t *sem, int *sval);
關閉有名信號量:
//關閉有名信號量,成功返回0,失敗返回-1以及errno int sem_close(sem_t *sem);
刪除有名信號量:
//試圖銷燬信號量,一旦全部佔用該信號量的進程都關閉了該信號量,那麼就會銷燬這個信號量,成功返回0,失敗返回-1以及errno int sem_unlink(const char *name);
這裏選擇使用線程進行測試,測試Demo以下:
#ifndef SEMAPHORE_TEST_H_ #define SEMAPHORE_TEST_H_ #include<iostream> #include<fcntl.h> #include<unistd.h> #include <semaphore.h> #include <pthread.h> #include <sys/stat.h> #include<string.h> using namespace std; static sem_t* sem; void *runChildThread(void* arg) { int * id=static_cast<int*>(arg); int pid=*id; cout<<"pid="<<pid<<"的線程等待信號量"<<endl; sem_wait(sem); //申請信號量 cout<<"pid="<<pid<<"得到信號量"<<endl; sleep(2); sem_post(sem); /*釋放信號量*/ cout<<"pid="<<pid<<"釋放信號量"<<endl; } void runSemaphoreTest() { string name="/sem_test"; mode_t mode=0666; uint value=1; sem= sem_open(name.c_str(),O_CREAT,mode,value); if(sem==SEM_FAILED){ cout<<"create name sem error:"<<strerror(errno)<<endl; return; } cout<<"成功建立信號量"<<endl; pthread_t tid=12; for(int i=0;i<10;i++){ int result= pthread_create(&tid,NULL,runChildThread,&i); if(result!=0){ cout<<"建立線程失敗,程序退出"<<endl; exit(1); } } sleep(30);//測試線程執行 sem_close(sem); } #endif
結果以下:
$ ./main 成功建立信號量 pid=1的線程等待信號量 pid=1得到信號量 pid=2的線程等待信號量 pid=3的線程等待信號量 pid=4的線程等待信號量 pid=5的線程等待信號量 pid=6的線程等待信號量 pid=7的線程等待信號量 pid=8的線程等待信號量 pid=9的線程等待信號量 pid=10的線程等待信號量 pid=1釋放信號量 pid=2得到信號量 pid=2釋放信號量 pid=3得到信號量 pid=3釋放信號量 pid=4得到信號量 pid=4釋放信號量 pid=5得到信號量 pid=5釋放信號量 pid=6得到信號量 pid=6釋放信號量 pid=7得到信號量 pid=7釋放信號量 pid=8得到信號量 pid=8釋放信號量 pid=9得到信號量 pid=9釋放信號量 pid=10得到信號量 pid=10釋放信號量
上述Demo的信號量的數量設置爲1,只有一個線程能獲取到信號量進入代碼執行,其餘線程須要等待當前線程釋放信號量後,而後進行搶奪信號量,或獲得的線程進行代碼執行,依次進行下去,若是把sem_open()的value參數改爲三則說明最多三個線程能夠同時進行,這裏就不寫Demo了。
須要注意的一點是有名信號量的值是隨內核持續的。也就是說,一個進程建立了一個信號量,這個進程結束後,這個信號量還存在,而且信號量的值也不會改變。
無名信號量因爲沒有名字,因此使用方法與有名信號量略有不一樣,卻別主要在建立以及銷燬的操做上,區別的函數以下:
sem_init() //建立/得到無名信號量 sem_destroy() //銷燬無名信號量
測試的Demo能夠把對應有名信號量的方法換成上述兩個方法便可,就不詳細介紹了。
內核管理一片物理內存,容許不一樣的進程同時映射,多個進程能夠映射同一塊內存,被多個進程同時映射的物理內存,即共享內存。因爲自己實現並不能保證同步,因此須要咱們本身進行同步,最多見的是使用信號量的方式進行同步。
使用說明:
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg); void *shmat(int shm_id, const void *shm_addr, int shmflg); int shmdt(const void *shm_addr); int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
因爲共享內存自己不涉及進程通訊,就不給出Demo了,想要了解使用方式的能夠百度一下。
套接字,就是咱們的Socket,經過套接字,咱們能夠實現本地或者遠程兩個進程之間 的通訊,在網絡編程中常常能碰見Socket編程。上面介紹的進程通訊侷限於本機的進程之間通訊,而Socket則主要實現遠端與本機的進程通訊。
這裏簡單介紹一下Socket與Http的區別把。在大學裏都學過網絡由下往上分爲,物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層和應用層,通常來講咱們把會話層,表示層和應用層統稱爲應用層。IP協議對應於網絡層,TCP協議對應於傳輸層,而HTTP協議在應用層。如圖下所示([圖片來自網絡):
而Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口,封裝了TCP/IP的調用實現,固然也支持UDP協議。http的本質實現上也須要依賴Socket進行通訊。
關於linux下的Socket通訊用法,網上寫的文章我以爲比我整理學習的好的多,再次放上幾個連接把(吐個槽,網上基本一篇文章複製來複制去的),就不整理了,要整理的話不是一篇文章能夠寫的。其實最好就是看文檔了,用man命令是很是值得擁有的。
針對TCP放上Demo,linux下使用c++開發服務端,使用JAVA充當客戶端《服務端以下:
#ifndef SOCKET_TEST_H_ #define SOCKET_TEST_H_ #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include<iostream> #include<string.h> #include<errno.h> #include<sys/types.h> #include<netinet/in.h> #include<unistd.h> #include <arpa/inet.h> #include <net/if.h> #include <sys/ioctl.h> #define PORT 9876 #define MAXLINE 4096 using namespace std; void getSockName(int& sock){ struct sockaddr_in addr; socklen_t addr_len = sizeof(struct sockaddr_in); /* 獲取本端的socket地址 */ int nRet = getsockname(sock,(struct sockaddr*)&addr,&addr_len); if(nRet == -1) { perror("getsockname error: "); }else{ printf("this socket addr %s %d successful\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port)); } } void startServer(){ //ipv4 int socketFd=socket(AF_INET,SOCK_STREAM,0); if(socketFd==-1){ cout<<"open socket error: "<<strerror(errno)<<endl; exit(1); } cout<<"建立socket成功"<<endl; struct sockaddr_in addr; addr.sin_family = AF_INET; //若是使用INADDR_ANY方式,須要加以判斷 //是否符合網絡字節序,即大端的傳輸方式,若是機器爲小端, //須要經過htonl轉換成網絡字節序相配的,若是使用inet_addr()方法 //,無需考慮大小端的問題 addr.sin_addr.s_addr =(inet_addr("192.168.199.244")); addr.sin_port = htons(PORT); //綁定 int bindStatus=bind(socketFd,(struct sockaddr*)&addr,sizeof(addr)); getSockName(socketFd); if(bindStatus==-1){ cout<<"bind error "<<strerror(errno)<<endl; exit(1); } cout<<"綁定接口ok"<<endl; if(listen(socketFd,10)==-1){ cout<<"開啓監聽失敗"<<endl; exit(1); } char buffer[50]; while(1){ memset(buffer,0,50); cout<<"開始接收"<<endl; int isAccept=accept(socketFd, (struct sockaddr*)NULL, NULL); if( isAccept== -1){ cout<<"接收失敗"<<strerror(errno)<<endl; exit(1); } int result=recv(isAccept,buffer,MAXLINE,0); if(result==-1){ cout<<"接收消息失敗"<<strerror(errno)<<endl; exit(1); } string bufferStr=buffer; close(isAccept); if(bufferStr=="over"){ cout<<"收到over信號,關閉服務端"<<endl; break; }else{ printf("接收到的消息爲:%s\n",buffer); } } close(socketFd); } #endif
客戶端代碼:
private static void startClient(){ new Thread(() -> { try { Socket socket = new Socket("192.168.199.244",9876); //2.拿到客戶端的socket對象的輸出流發送給服務器數據 OutputStream os = socket.getOutputStream(); //寫入要發送給服務器的數據 os.write(("over" ).getBytes(StandardCharsets.UTF_8)); os.flush(); os.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); System.out.print("socket connect erro"); } }).start(); }
最終輸出結果:
建立socket成功 this socket addr 192.168.199.244 9876 successful 綁定接口ok 開始接收 接收到的消息爲:01234 開始接收 收到over信號,關閉服務端