linux上的進程通訊學習筆記

參考資料php

<<精通Linux C編程>>html

http://man7.org/linux/man-pages/man2/open.2.htmljava

http://www.javashuo.com/article/p-zxzvasel-dv.htmllinux

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():打開命名管道
  • read():讀取命名管道數據
  • write(): 向命名管道寫入數據
  • close():關閉命名管道
  • unlink():刪除命名管道

在操做命令管道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

POSIX消息隊列

消息隊列爲以一種鏈表式結構組織的一組數據,存放於內核之中,由個進程經過消息隊列標識符引用傳遞數據的一種方式,由內核維護。消息隊列爲最具備數據操做性的數據牀送方式,在消息隊列中能夠隨意的根據特定的數據類型來檢索消息。

消息隊列跟匿名管道以及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的時候會按照優先級進行讀取,而不是客戶端最早發送的。


POSIX信號量

信號量(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命令是很是值得擁有的。

http://www.javashuo.com/article/p-phvftvep-kh.html

http://www.javashuo.com/article/p-vhovybls-m.html

針對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信號,關閉服務端
相關文章
相關標籤/搜索