linux上的进程通信学习笔记

大城市里の小女人 提交于 2019-12-07 00:17:42

参考资料

<<精通Linux C编程>>

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

https://www.cnblogs.com/52php/p/5840229.html

Android中的Handler的Native层研究文章中研究一下一把Linux中的匿名管道的通信机制,今天这里Linux中的进程间通信补齐。

在Linux中,实现进程通信的方法包括管道(匿名管道和具名管道),消息队列,信号量,共享内存,套接口等。消息队列,信号量,共享内存统称为系统的(POSIX和System V)IPC,用于本地间的进程通信,套接口(socket)则运用于远程进程通信。

各个通信机制定义如下:

  • 匿名管道(Pipe)和具名管道(named pipe):匿名管道用于具有亲缘关系进程间的通信,具名管道克服了管道没有名字的限制,因此除了具有匿名管道的功能外,还允许在无亲缘关系的进程中进行通信。

  • 消息队列(Message):消息队列为消息的链接表,包括POSIX消息队列和System V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读取队列中的消息。

  • 共享内存:是的多个进程可以访问同一块内存空间,是最快的可以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命令是非常值得拥有的。

https://segmentfault.com/a/1190000010838127

https://www.cnblogs.com/jiangzhaowei/p/8261174.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信号,关闭服务端

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!