c++小牛钱小白

c中linux中进程间通信IPC在实战中的应用??

进程间通信的8种方式:

同一主机上的进程通信方式

   * UNIX进程间通信方式:包括管道(PIPE),有名管道(FIFO)和

                                         信号(Signal)

   * System V进程通信方式(IPC对象):包括消息队列(Message

                                                Queue),共享内存(Shared Memory)和

                                                信号量(Semaphore)

网络主机间的进程通信方式(BSD):

   * RPC: Remote Procedure Call 远程过程调用

   * Socket: 当前最流行的网络通信方式,基于TCP/IP协议的通信方式

管道(PIPE):

函数:int pipe(int fd[2]);    //返回两个文件描述符,fd[0]读操作,fd[1]写操作,默认阻塞方式。

(1)有读进程,无写进程:

  1. 管道内无数据时,立即返回

  2. 管道内数据不足,读出所有数据

  3. 管道内数据充足,读出期望数据

(2)有读进程,有写进程:

  1. 管道内无数据时,读进程阻塞

  2. 管道内数据不足,读出所有数据

  3. 管道内数据充足,读出期望数据

PIPE案例

//管道,匿名管道应用实例

#include <stdio.h>

#include <unistd.h>

#include <sys/types.h>

#include <errno.h>

#include <stdlib.h>

#include <string.h>

#define MAX_DATA_LEN 256

#define DELAY_TIME 1


int main()

{

pid_t pid1, pid2, pid3;

int pipe_fd[2];

int len, realen, pipeid;

char buf[MAX_DATA_LEN], outpipe[MAX_DATA_LEN]; 

pipeid = pipe(pipe_fd);

if(pipeid < 0){

printf("pipe error!\n");

exit(1);

}

if((pid1=fork()) == -1){

printf("pid1 error!\n");

exit(1);

}

if(pid1 == 0){

sleep(DELAY_TIME);

printf("pid1 is:%d\n", getpid());

close(pipe_fd[0]);// 关闭读操作

sprintf(outpipe, "I'm child1!\n");

len = strlen(outpipe);

realen = write(pipe_fd[1], outpipe, len);

printf("child1 writes %d bytes\n", realen);

exit(0);

}

if((pid2=fork()) == -1){

printf("pid2 error!\n");

exit(1);

}

if(pid2 == 0){

sleep(DELAY_TIME);

printf("pid2 id is:%d\n", getpid());

close(pipe_fd[0]);

sprintf(outpipe, "I'm child2!\n");

len = strlen(outpipe);

realen = write(pipe_fd[1], outpipe, len);

printf("child2 writes %d bytes\n", realen);

exit(0);

}

if((pid3=fork()) == -1){

printf("pid3 error!\n");

exit(1);

}

if(pid3 == 0){

sleep(DELAY_TIME);

printf("pid3 is:%d\n", getpid());

close(pipe_fd[0]);

sprintf(outpipe, "I'm child3!\n");

len = strlen(outpipe);

realen = write(pipe_fd[1], outpipe, len);

printf("child3 writes %d bytes\n", realen);

exit(0);

}

else{

sleep(DELAY_TIME*3);

pid1 = waitpid(pid1, NULL, WUNTRACED);

pid2 = waitpid(pid2, NULL, WUNTRACED);

pid3 = waitpid(pid3, NULL, WUNTRACED);

printf("father pid is:%d\n", getpid());

close(pipe_fd[1]);

read(pipe_fd[0], buf, sizeof(buf));

printf("children data is:\n%s\n", buf);

}

char *p = "your name is lily!";

printf("what's your name? %s\n", p);

printf("pipeid is:%d\n", pipeid);

printf("pid1 is:%d\n", pid1);

    return 0;

}

更多匿名管道PIPE参见文章:c中linux中进程间通信IPC之匿名管道pipe在实战中的应用

有名管道(FIFO):

函数:int mkfifo(char* path, mode_t mode);    //path为命名管道名字

mkfifo(path,*)--->open(path,*)--->write/read

阻塞:(1)当进程以写或读的方式打开管道文件,必须有另一个进程以相对应的读或写方式也打开该文件,否则该进程将阻塞在open()位置。
(2)若两个进程都已打开,但中途某进程退出,则:①读进程退出,返回SIGPIPE信号 ②写进程退出,读进程将不再阻塞,直接返回 0

FIFO案例】

//有名管道案例:向有名管道中写数据

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <errno.h>

#include <sys/stat.h>

#include <sys/types.h>

#include <fcntl.h>


#define MAX 100


//注:只有读端存在,写端才有意义。如果读端不在,写端向FIFO写数据,内核将向对应的进程发送SIGPIPE信号(默认终止进程); 

int main(int argc, char *argv[])

{

int fd, rfd, nw, nr;

char buf[MAX], rbuf[MAX];

argc = 2;

if(argc < 2){

fprintf(stderr, "usage : %s argv[1]\n", argv[0]);

exit(EXIT_FAILURE);

}

//定义文件

argv[1] = "fileforw.txt";

//权限0666位八进制表示形式,表示什么意思??

//创建有名管道,其中mode与打开普通文件open的mode相同

if(mkfifo(argv[1], 0666) < 0 && errno != EEXIST) 

{

fprintf(stderr, "Fail to mkfifo %s : %s, %d\n", argv[1], strerror(errno), errno);

exit(EXIT_FAILURE);

}

system("pwd fileforw.txt");

//为写而打开FIFO O_WRONLY,为读而打开FIFO O_RDONLY

//注:如果open时没有使用O_NONBLOCK参数,我们发现不论读端还是写端先打开,先打开者都会阻塞,一直阻塞到另一端打开

//如果open时使用了O_NONBLOCK参数,此时打开FIFO又会是什么情况??

if((fd = open("./fileforw.txt", O_WRONLY|O_RDONLY)) < 0)

{

fprintf(stderr, "Fail to open %s : %s\n", argv[1], strerror(errno));

exit(EXIT_FAILURE);

}

printf("return open for write success!\n");

//向管道写入数据

printf(">");

sprintf(buf, "this is a write operator!");

nw = write(fd, buf, sizeof(buf));

printf("write %d bytes:%s \n", nw, buf);

system("ls -l");

/**

if((rfd = open(argv[1], O_RDONLY)) < 0)

    {

        fprintf(stderr,"[read]Fail to open %s : %s.\n",argv[1],strerror(errno));

        exit(EXIT_FAILURE);

    }

nr = read(rfd, rbuf, sizeof(rbuf));

printf("Read %d bytes \n", nr);

*/

return 0;

}

//从有名管道中读数据

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <errno.h>

#include <sys/stat.h>

#include <sys/types.h>

#include <fcntl.h>


#define MAX 100


//注:只有读端存在,写端才有意义。如果读端不在,写端向FIFO写数据,内核将向对应的进程发送SIGPIPE信号(默认终止进程); 

int main(int argc, char *argv[])

{

int fd, rfd, nw, nr;

char buf[MAX], rbuf[MAX];

argc = 2;

if(argc < 2){

fprintf(stderr, "usage : %s argv[1]\n", argv[0]);

exit(EXIT_FAILURE);

}

//定义文件

argv[1] = "fileforw.txt";

//权限0666位八进制表示形式,表示什么意思??

if(mkfifo(argv[1], 0666) < 0 && errno != EEXIST) 

{

fprintf(stderr, "Fail to mkfifo %s : %s, %d\n", argv[1], strerror(errno), errno);

exit(EXIT_FAILURE);

}

system("ls");

//为写而打开FIFO O_WRONLY,为读而打开FIFO O_RDONLY

//注:如果open时没有使用O_NONBLOCK参数,我们发现不论读端还是写端先打开,先打开者都会阻塞,一直阻塞到另一端打开

//如果open时使用了O_NONBLOCK参数,此时打开FIFO又会是什么情况??

if((rfd = open(argv[1], O_WRONLY|O_RDONLY)) < 0)

    {

        fprintf(stderr,"[read]Fail to open %s : %s.\n",argv[1],strerror(errno));

        exit(EXIT_FAILURE);

    }

printf("return open for read success!\n");

nr = read(rfd, rbuf, sizeof(rbuf));

printf("Read %d bytes \n", nr);

return 0;

}

更多有名管道FIFO案例参见文章:c中linux中进程间通信IPC之有名管道fifo在实战中的应用

信号(Signal):

概述

  • 信号本质:类似于中断机制,进程收到一个信号与处理器收到一个中断请求是一样的

  • 信号种类:可靠信号&不可靠信号;实时信号&非实时信号

  • 进程对信号的响应:①忽略信号,除了SIGKILL和SIGSTOP②捕捉信号,定义某种信号处理函数。③执行缺省操作,Linux对每种信号都规定了默认操作

信号的发送

       发送信号的主要函数有kill(),raise(),sigqueue(),alarm(),setitimer()以及abort()

  • kill:int kill(pid_t pid, int signo)

       pid是进程ID

  参数pid的值               信号的接收进程

    pid>0                    进程ID为pid的进程

    pid=0                    同一个进程组的进程

    pid<0 & pid!=-1   进程组ID为-pid的所有进程

    pid=-1                   除发送进程自身外,所有进程ID大于1的进程

       signo是信号值,当为0时,不发送任何信号,主要用于检查目标进程是否存在,以及当前进程是否具有向目标发送信号的权限(root权限的进程可以向任何进程发送信号,非root权限的进程只能向属于同一个session或者同一个用户的进程发送信号)0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号

  • raise:int raise(int signo) 向进程本身发送信号,参数即为发送的信号值

  • sigqueue:int sigqueue(pid_t pid, int sig, const union sigval val)

       该函数针对实时信号提出的,支持信号带有参数,与函数sigaction()配合使用

       pid:接收信号的进程ID;sig:即将发送的信号;union sigval:信号传递的参数,4字节值

注:比kill传递多了更多的附加信息,但只能向一个进程发送信号,而不能发送信号给一个进程组。

  • alarm:unsigned int alarm(unsigned int seconds);

       专门为SIGALRM信号而设,在指定的时间seconds秒后,将向进程本身发送SIGALRM信号,又称为闹钟时间。进程调用alarm后,任何以前的alarm()调用都将无效。如果参数seconds为零,那么进程内将不再包含任何闹钟时间。返回值,如果调用alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。

  • setitimer:int setitimer(int which, const struct itimerval *val, struct itimeval *oval));

       比alarm功能强大,支持三种类型的定时器(which参数):①ITIMER_REAL:设定绝对时间;经过指定的时间后,内核将发送SIGALRM信号给本进程;②ITIMER_VIRTUAL设定程序执行时间,经过指定的时间后,内核将发送SIGVTALRM信号给本进程;③ITIMER_PROF设定进程执行以及内核因本进程而消耗的时间和,经过指定时间后,内核将发送ITIMER_VIRTUAL信号给本进程。

  • abort:void abort(void);

       向进程发送SIGABORT信号,默认情况下进程会异常退出,当然可定义自己的信号处理函数。即使SIGABORT被进程设置为阻塞信号,调用abort()后,SIGABORT仍然能被进程接收。该函数无返回值。

安装信号

       安装信号主要用来确定信号值与进程针对该信号值的响应动作之间的映射关系。

  • signal函数:在可靠信号系统调用的基础上实现,是库函数。它只有两个参数,不支持信号传递信息,主要是用于前32种非实时信号的安装。

void (*signal(int signum, void (*handler))(int)))(int);可以看成

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler));

其中,signum:指定信号的值;handler:信号的处理,可以忽略该信号(设为SIG_IGN),可以采用默认方式处理(SIG_DFL),也可以自己实现处理函数(参数函数指针);返回值:若成功,返回最后一层为安装信号signum而调用signal时的handler值,若失败,返回SIG_ERR。

  • sigaction函数:(由两个系统调用实现:sys_signal以及sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与sigqueue()系统调用配合使用,当然,sigaction()同样支持非实时信号的安装。

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact));

signum:信号值(除了SIGKILL和SIGSTOP);act:指向结构sigaction的一个实例的指针,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;oldact:原来对相应信号的处理。若act和oldact都为NULL,则函数用于检查信号的有效性。

阻塞信号

       每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。

#include<signal.h>

int sigprocmask(inthow, constsigset_t *set, sigset_t *oldset));

int sigpending(sigset_t *set)); 

int sigsuspend(constsigset_t *mask));

  • sigprocmask()函数能够根据参数how来实现对信号集的操作,操作主要有三种:

        参数how                进程当前信号集

    SIG_BLOCK            在进程当前阻塞信号集中添加set指向信号集的信号

    SIG_UNBLOCK       如果进程阻塞信号集中包含set指向信号集中的信                                       号,则解除对该信号的阻塞

    SIG_SETMASK        更新进程阻塞信号集中为set指向的信号集

  • sigpending(sigset_t *set)函数获得当前已递送到进程,却被阻塞的所有信号,在set指向的信号集中返回结果。

  • sigsuspend(const sigset_t *mask)函数用于在接收到某个信号之前,临时用mask替换进程的信号掩码,并暂停进程执行,直到收到信号为止。sigsuspend返回后将恢复调用之前的信号掩码。

Signal案例

//发送信号

#include <stdio.h>

#include <unistd.h>

#include <signal.h>


//sig_send.c发送信号

int main(int argc, char** argv)

{

union sigval sigv;

sigv.sival_int = 9;

//初始化argv参数,运行时出错??

argv[1] = getpid();

argv[2] = "SIGINT";

pid_t pid = atoi(argv[1]);

int sig = atoi(argv[2]);

sigqueue(pid, sig, sigv);

printf("Message has been send!\n");

sleep(7);

printf("Send dead!\n");

return 0;

}

//接收信号

#include <signal.h>

#include <sys/types.h>

#include <unistd.h>


//sig_recv.c接收信号

void op(int sig,siginfo_t *info,void* sigact){

    printf("inside op\n");

    int count=0;

    count++;

    printf("op value:%d\n",info->si_int);

}


int main(int argc,char** argv){

//初始化argv参数,设置为SIGINT,运行时出错??

argv[1] = "SIGINT";

    int sig = atoi(argv[1]);

    pid_t pid = getpid();

    printf("Rev pid:%d\n",pid);

    struct sigaction act;

    sigemptyset(&act.sa_mask);

    act.sa_flags=SA_SIGINFO;

    act.sa_sigaction = op;

    if(sigaction(sig,&act,NULL)<0){

printf("install sig error\n");

    }

 

    int count=0;

    while(1){

        sleep(1);

        count++;

        printf("wait for sig %d\n",count);

        if(count>30) break;

    }

    return 0;

}

消息队列(Message Queue):

函数:

  • int msgget(key_t key, int msgflg);

该函数用来创建和访问一个消息队列。其参数如下:

key:与其他的IPC机制一样,程序必须提供一个键来命名某个特定的消息队列。

msgflg:是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。

它返回一个以key命名的消息队列的标识符(非零整数),失败时返回-1。

  • int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);

该函数用来把消息添加到消息队列中。其参数如下:

msgid:是由msgget函数返回的消息队列标识符。

msg_ptr:一个指向准备发送消息的指针,但是消息的数据结构却有一定的要求,指针msg_ptr所指向的消息结构一定要是以一个长整型成员变量开始的结构体,接收函数将用这个成员来确定消息的类型。形如:

struct my_message{

    long int message_type;    

    /* The data you wish to transfer */

};

msg_sz:msg_ptr指向的消息的长度,不是结构体的大小(即除了message_type的大小)

msgflg:控制当前消息队列满或队列消息到达系统范围的限制时将要发生的事情。① 0表示当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列 ② IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回 ③ IPC_NOERROR:若发送的消息大于size字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程。

  • int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);

该函数用来从一个消息队列获取消息。其参数如下:

msg_ptr:存放消息的结构体;msg_st:要接收消息的大小,不含消息类型占用的4个字节

msgtype:可以实现一种简单的接收优先级。如果msgtype为0,就获取队列中的第一个消息。如果它的值大于零,将获取具有相同消息类型的第一个信息。如果它小于零,就获取类型等于或小于msgtype的绝对值的第一个消息

msgflg:0:阻塞式接收消息,没有该类型的消息msgrcv函数一直阻塞等待;IPC_NOWAIT:如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG;IPC_EXCEPT:与msgtype配合使用返回队列中第一个类型不为msgtype的消息;IPC_NOERROR:如果队列中满足条件的消息内容大于所请求的size字节,则把该消息截断,截断部分将被丢弃。

  • int msgctl(int msgid, int command, struct msgid_ds *buf);

该函数用来控制消息队列,它与共享内存的shmctl函数相似。其参数如下:

command:是将要采取的动作,它可以取3个值,① IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。② IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值③ IPC_RMID:删除消息队列。

buf:指向msgid_ds结构的指针,它指向消息队列模式和访问权限的结构。msgid_ds结构至少包括以下成员:

struct msgid_ds

{

    uid_t shm_perm.uid;

    uid_t shm_perm.gid;

    mode_t shm_perm.mode;

};

MessageQueue案例

cat>>recv.c<<EOF

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/msg.h>

#include <errno.h>


//recv.c 接收消息

struct msg_st

{

long int msg_type;

char text[BUFSIZ];

};


int main(int argc, char **argv)

{

int msgid = -1;

struct msg_st data;

long int msgtype = 0;// 注意1

//建立消息队列

msgid = msgget((key_t)1234, 0666|IPC_CREAT);

if(msgid == -1)

{

fprintf(stderr, "msgget failed with error: %d\n", errno);

exit(EXIT_FAILURE);

}

//从消息队列中获取消息,直到遇到end消息为止

while(1)

{

if(msgrcv(msgid, (void *)&data, BUFSIZ, msgtype, 0) == -1)

{

fprintf(stderr, "msgrcv failed with error: %d\n", errno);

}

printf("You wrote: %s\n", data.text);

//遇到end结束

if(strncmp(data.text, "end", 3) == 0)

{

break;

}

}

//删除消息队列

if(msgctl(msgid, IPC_RMID, 0) == -1)

{

fprintf(stderr, "msgctl(IPC_RMID) failed with error: %d", errno);

}

   /* recv接收消息 C 程序 */

   printf("recv.c Hello, World! \n");

   exit(EXIT_SUCCESS);

}

EOF


cat>>send.c<<EOF

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

#include <unistd.h>

#include <sys/msg.h>

#include <errno.h>


#define MAX_TEXT 512


//send.c 发送消息

struct msg_st

{

    long int msg_type;

    char text[MAX_TEXT];

};


int main(int argc, char **argv)

{

struct msg_st data;

    char buffer[BUFSIZ];

    int msgid = -1;

 

    // 建立消息队列

    msgid = msgget((key_t)1234, 0666 | IPC_CREAT);

    if (msgid == -1)

    {

        fprintf(stderr, "msgget failed error: %d\n", errno);

        exit(EXIT_FAILURE);

    }

while(1)

{

printf("Enter some text: \n");

//为什么给buff直接赋值不行??没有返回结果??需要使用sprintf赋值

//buffer[0] = "end.\nthis is a first message.";

sprintf(buffer, "end.\nI'm sending message.");

        //fgets(buffer, BUFSIZ, stdin);

        data.msg_type = 1; // 注意2

        strcpy(data.text, buffer);

printf("data.text is: %s\n", data.text);

 

        // 向队列里发送数据

        if (msgsnd(msgid, (void *)&data, MAX_TEXT, 0) == -1)

        {

            fprintf(stderr, "msgsnd failed\n");

            exit(EXIT_FAILURE);

        }

 

        // 输入end结束输入

        if (strncmp(buffer, "end", 3) == 0)

        {

break;

        }

 

        sleep(1);

}

   /* send发送消息 C 程序 */

   printf("Hi, send.c, Hello, World! \n");

   exit(EXIT_SUCCESS);

}

EOF

ls -lh

#编译运行,通过-lm链接libm.so库

gcc -o recv.exe recv.c -lm

gcc -o send.exe send.c -lm

ls -lh

./recv.exe &    # 后台运行

./send.exe

注:更多消息队列实例参见文章:c中linux中进程间通信IPC之消息队列messagequeue在实战中的应用

共享内存(Shared Memory):

       不同进程之间共享的内存通常安排为同一段物理内存,进程可以将同一段共享内存连接到它们自己的地址空间中,实现读写。

函数介绍:

  • shmget函数:int shmget(key_t key,size_t size,int shmflg); //创建共享内存

key:提供一个key,有效的为共享内存段命名;返回值:若成功,返回一个与key相关的共享内存标识符。若失败,返回-1(注:其他进程可以通过该返回值访问同一共享内存);

size:以字节为单位指定需要共享的内存容量;

shmflg:权限标志,比如IPC_CREAT表示若共享内存不存在则创建,0644表示当前进程可读写,其他进程只能读。

注:通常情况下,key通过ftok函数得到,由内核变成标识符,要想让两个进程看到同一个信号集,只需设置key值不变就可以。

  • shmat函数:void *shmat(int shm_id, const void *shm_addr, int shmflg); //创建完共享内存,还不能被任何进程访问,shmat函数的作用就是启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间

shm_id:由shmget返回的共享内存标识符;

shm_addr:共享内存连接到当前进程中的地址位置,通常为NULL,表示让系统来选择;

shmflg:标志位,通常为0。

  • shmdt函数:int shmdt(const void* shmadrr); //将共享内存从当前进程中分离,不是删除,只是不可用

shmadrr:shmat函数返回的地址指针;成功时返回0,失败返回-1。

  • shmctl函数:int shmctl(int shm_id, int cmd, struct shmid_ds *buf); //删除共享内存

shm_id:shmget的返回值,即共享内存标识符;

cmd:采取的操作,IPC_STAT表示“共享内存的当前关联值覆盖shmid_ds的值”,IPC_SET表示“如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值”,IPC_RMID表示“删除共享内存段”;

buf:一个结构指针,指向共享内存模式和访问权限的结构体。结构体如下所示:

struct shmid_ds {

    uid_t shm_perm.uid;

    uid_t shm_perm.gid;

    mode_t shm_perm.mode;

};

SharedMemory案例

       其中一个文件shmread.c创建共享内存,并读取其中的信息,另一个文件shmwrite.c向共享内存中写入数据。为了方便操作和数据结构的统一,为这两个文件定义了相同的数据结构,定义在文件shmdata.c中。

cat>>shmdata.c<<EOF

#ifndef _SHMDATA_H_HEADER

#define _SHMDATA_H_HEADER


#define TEXT_SZ 2048


struct shared_use_st

{

int written;//作为一个标志;非0表示可读,0表示可写 

char text[TEXT_SZ];//记录写入和读取的文本

};

#endif

EOF


cat>>shmread.c<<EOF

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#include <sys/shm.h>

#include "shmdata.c"


//shmread.c 读取共享内存

int main()

{

int running = 1;//程序是否继续运行的标志 

void *shm = NULL;//分配的共享内存的原始首地址 

struct shared_use_st *shared;//指向shm

int shmid;//共享内存标识符


//创建共享内存

shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);

if( shmid == -1){

fprintf(stderr, "shmget failed!\n");

exit(EXIT_FAILURE);

}


//将共享内存连接到当前进程的地址空间

shm = shmat(shmid, 0, 0);


if(shm == (void*)-1){

fprintf(stderr, "shmat failed!\n");

exit(EXIT_FAILURE);

}

printf("shmread Memory attached at %X\n", (int)shm);


//设置共享内存

shared = (struct shared_use_st*)shm;

shared->written = 0;


while(running){//读取共享内存中的数据

//没有进程向共享内存中写数据,有数据可读取

if(shared->written != 0)

{

printf("You Wrote: %s\n", shared->text);

sleep(rand() % 3);

//读取完数据,设置written,使共享内存段可写

shared->written = 0;

//输入end,退出循环(程序)

if(strncmp(shared->text, "end", 3) == 0)

running = 0;

}

else//有其他进程写数据,不能读取数据

sleep(1);

}


//把共享内存从当前进程中分离

if(shmdt(shm) == -1){

fprintf(stderr, "shmdt failed!\n");

exit(EXIT_FAILURE);

}


//删除共享内存

if(shmctl(shmid, IPC_RMID, 0) == -1){

fprintf(stderr, "shmctl(IPC_RMID) failed!\n");

exit(EXIT_FAILURE);

}

   /* 我的第一个 shmread C 程序 */

   printf("shmread.c Hello, World! \n");

   exit(EXIT_SUCCESS);

}

EOF


cat>>shmwrite.c<<EOF

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

#include <sys/shm.h>

#include "shmdata.c"


//向共享内存写入数据shmwrite.c

int main()

{

int running = 1;

void *shm = NULL;


struct shared_use_st *shared = NULL;

char buffer[BUFSIZ + 1];//用于保存输入的文本

int shmid;


//创建共享内存

shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);

if(shmid == -1)

{

fprintf(stderr, "shmget failed\n");

exit(EXIT_FAILURE);

}


//将共享内存连接到当前进程的地址空间

shm = shmat(shmid, (void*)0, 0);

if(shm == (void*)-1)

{

fprintf(stderr, "shmat failed\n");

exit(EXIT_FAILURE);

}

printf("Memory attached at %X\n", (int)shm);


//设置共享内存

shared = (struct shared_use_st*)shm;


while(running)//向共享内存中写数据

{

//数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本

while(shared->written == 1){

sleep(1);

printf("waiting...\n");

}


//向共享内存中写入数据

printf("Enter some text:");

sprintf(buffer, "end.\n this is a written data.\n");

//fgets(buffer, BUFSIZ, stdin);// 从标准输入stdin中获取数据

strncpy(shared->text, buffer, TEXT_SZ);

printf("shared->text is: %s\n", shared->text);


//写完数据,设置written使共享内存段可读

shared->written = 1;


//输入了end,退出循环(程序)

if(strncmp(buffer, "end", 3) == 0)

running = 0;

}


//把共享内存从当前进程中分离

if(shmdt(shm) == -1)

{

fprintf(stderr, "shmdt failed\n");

exit(EXIT_FAILURE);

}


sleep(2);

/* 我的第一个 shmwrite C 程序 */

   printf("shmwrite Hello, World! \n");

   exit(EXIT_SUCCESS);

}

EOF

gcc -o shmread.exe shmread.c -lm

gcc -o shmwrite.exe shmwrite.c -lm

ls -lh

./shmread.exe &        # &表示后台运行

ipcs                            # 查看共享内存状态

./shmwrite.exe

jobs                            # 查看进程状态

//注:对written的操作(+1和-1)是非原子的,所以是不安全的。

更多共享内存案例参见文章:c中linux中进程间通信IPC之共享内存SharedMemory在实战中的应用

优缺点:

  • 优点:直接访问内存,加快了程序的效率。不要求是父子进程

  • 缺点:没有提供同步机制,需要借助信号量等其他手段同步

信号量(Semaphore):

函数介绍

  • int semget(key_t key, int num_sems, int sem_flags);

key:整数值(唯一非零),不相关的进程可以通过它访问一个信号量。(程序对所有信号量的访问都是间接的,程序先通过调用semget()函数并提供一个键,再由系统生成一个相应的信号标识符(semget()函数的返回值),只有semget()函数才直接使用信号量键,所有其他的信号量函数使用由semget()函数返回的信号量标识符)。

num_sems:指定需要的信号量数目,它的值几乎总是1。

sem_flags:一组标志。当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。

  • int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);

sem_id:是由semget()返回的信号量标识符。

sem_opa:未解释??其中sembuf定义如下:

struct sembuf {

    shortsem_num;    // 除非使用一组信号量,否则它为0

    shortsem_op;    // 信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作;// 一个是+1,即V(发送信号)操作。

    shortsem_flg;    // 通常为SEM_UNDO;使操作系统跟踪信号,// 并在进程没有释放该信号量而终止时,操作系统释放信号量。

};

num_sem_ops:未解释??

  • int semctl(int sem_id, int sem_num, int command, ...);

sem_id:是由semget()返回的信号量标识符。

sem_num:未解释??

command:通常是下面两个值中的一个:SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置;IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。

Semaphore案例

代码介绍

#ipc_semaphore

cat>>ipc_semaphore.cpp<<EOF

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/sem.h>//包含信号量定义的头文件


//联合类型semun定义

union semun {

int val;

struct semid_ds *buf;

unsigned short *array;

};

//函数声明

//设置信号量的值

static int set_semvalue(void);

//函数:删除信号量

static void del_semvalue(void);

//函数:信号量P操作。等待一个信号量:该操作会测试这个信号量的值,如果小于0,就阻塞。也称为P操作 

static int semaphore_p(void);

//函数:信号量V操作。挂出(发出)一个信号量:该操作将信号量的值加1,也称为V操作 

static int semaphore_v(void);


static int sem_id;//信号量ID


int main(int argc, char *argv[])

{

   int i;

int pause_time;

char op_char = 'O';

argc = 2;//设置条件判断参数值

//创建一个新的信号量或者取得一个已有信号量的键

sem_id = semget((key_t)1234, 1, 0666|IPC_CREAT);

//如果参数数量大于1,则这个程序负责创建信号量和删除信号量

if(argc > 1){

if(!set_semvalue()){

fprintf(stderr, "failed to initialize semaphore!\n");

exit(EXIT_FAILURE);

}

op_char = 'X';//对进程进行标记

sleep(5);

}

//循环访问临界区

for(i=0; i<10; i++){

//P操作,尝试进入缓冲区

if(!semaphore_p())

exit(EXIT_FAILURE);

printf("%c", op_char);

fflush(stdout);//刷新标准输出缓冲区,把标准输出缓冲区里的东西打印到标准输出设备上 

pause_time = rand() % 3;

sleep(pause_time);

printf("%c", op_char);

fflush(stdout);

//V操作,尝试离开缓冲区

if(!semaphore_v())

exit(EXIT_FAILURE);

pause_time = rand() % 2;

sleep(pause_time);

}

printf("\n %d - finished!\n", getpid());

if(argc > 1){

sleep(10);

del_semvalue();// 删除信号量

}

/* 我的第一个 C 程序 */

  printf("semaphore Hello, World! \n");

printf("__LINE__:%d\n", __LINE__);

}


//函数:设置信号量的值

static int set_semvalue(void){

union semun sem_union;

sem_union.val = 1;

if(semctl(sem_id, 0, SETVAL, sem_union))

return 0;

return 1;

}

//函数:删除信号量

static void del_semvalue(void){

union semun sem_union;

if(semctl(sem_id, 0, IPC_RMID, sem_union))

fprintf(stderr, "Failed to delete semaphore!\n");

}

//函数:信号量P操作,对信号量减一操作

static int semaphore_p(void){

struct sembuf sem_b;

sem_b.sem_num = 0;//信号量编号

sem_b.sem_op = -1;//P操作,该信号量对应的进程或线程将阻塞

sem_b.sem_flg = SEM_UNDO;//使操作系统跟踪信号

if(semop(sem_id, &sem_b, 1) == -1){

fprintf(stderr, "semaphore_p failed!\n");

return 0;

}

return 1;

}

//函数:信号量V操作,对信号量加一操作

static int semaphore_v(void){

struct sembuf sem_b;

sem_b.sem_num = 0;//信号量编号

sem_b.sem_op = 1;//V操作,发送信号

sem_b.sem_flg = SEM_UNDO;//使操作系统跟踪信号

if(semop(sem_id, &sem_b, 1) == -1){

fprintf(stderr, "semaphore_v failed!\n");

return 0;

}

return 1;

}

EOF

g++ -g -o ipc_semaphore ipc_semaphore.cpp

#./ipc_semaphore;    # 运行出错??没有返回结果??

ls -lh

ls -lh /dev/shm/|grep mem;    # 查找SytemV共享内存文件


ipcs -m;    # 查看共享内存资源

#ipcrm -m 131076;    # 删除共享内存,131079表示shmid

#ulimit -a;    # 查看core文件大小 

#file core;    # 显示core内容

查看进程间通信资源的命令:

ipcs 查看进程间通信资源

-m 查看共享内存

-q 查看消息队列

-s 查看信号量

ipcrm -m/q/s shmid;    # 删除指定的通信资源

远程过程调用(RPC):

定义:??

函数:??

       目前有很多Java的RPC框架,有基于Json的,有基于XML,也有基于二进制对象的。

RPC案例

代码说明

mkdir testcrpc;

cd testcrpc;

cat>>test.x<<EOF

program TESTPROG {

   version VERSION {

     string TEST(string) = 1;

   } = 1;

} = 87654321;

EOF


#Connection refused;解决方式安装portmap

sudo apt-get install portmap;

sudo service portmap start;


#Client credential too weak unable to register(TESTPROG, VERSION, udp);解决方式如下

sudo -i service portmap stop;

sudo -i rpcbind -i -w;# 启动rpcbind服务,sudo没有权限??

sudo -i service portmap start;


rpcgen test.x;

#more test_clnt.c test_svc.c test.h;

rpcgen -Sc -o test_clnt_func.c test.x;

rpcgen -Ss -o test_svc_func.c test.x;

more test_clnt_func.c test_svc_func.c


#覆盖服务器端程序

cat>test_svc_func.c<<EOF

#include "test.h"

#include <time.h>


char **

test_1_svc(char **argp, struct svc_req *rqstp)

{

    static char * result;


    static char tmp_char[128];

    time_t rawtime;

    /*

     * insert server code here

     */

    if( time(&rawtime) == ((time_t)-1) ) {

strcpy(tmp_char, "Error");

result = tmp_char;

return &result;

    }

    sprintf(tmp_char, "服务器当前时间是 :%s", ctime(&rawtime));

    result = tmp_char;

    return &result;

}

EOF

#覆盖客户端程序

cat>test_clnt_func.c<<EOF

#include "test.h"


void

testprog_1(char *host) 

    CLIENT *clnt; 

    char * *result_1; 

    char * test_1_arg;


#ifndef    DEBUG

    clnt = clnt_create(host, TESTPROG, VERSION, "udp"); 

    if (clnt == NULL) { 

        clnt_pcreateerror(host); 

        exit (1); 

    } 

#endif    /* DEBUG */

    result_1 = test_1(&test_1_arg, clnt); 

    if (result_1 == (char **) NULL) { 

        clnt_perror(clnt, "call failed"); 

    }

    if (strcmp(*result_1, "Error") == 0) { 

fprintf(stderr, "%s: could not get the time\n", host); 

exit(1);

    }

    printf("收到消息 ... %s\n", *result_1); 

    

#ifndef    DEBUG

    clnt_destroy (clnt); 

#endif     /* DEBUG */

}

/* 没有带返回值的main函数;报错:undefined reference to main|collect2: error: ld returned 1 exit status?? */

int

main (int argc, char *argv[])

{

char *host;


if (argc < 2) {

printf ("usage: %s server_host\n", argv[0]);

exit (1);

}

printf("argv[0] = %s, argv[1] = %s\n", argv[0], argv[1]);

host = argv[1];

testprog_1 (host);

exit (0);

}

EOF

#more test_svc_func.c test_clnt_func.c test.h


#编译服务端程序 (test_clnt.c)

gcc -Wall -o test_server test_clnt.c test_svc_func.c test_svc.c

#编译客户端程序

gcc -Wall -o test_client test_clnt_func.c test_clnt.c

./test_server;# 启动服务端

#./test_client 127.0.0.1;# 启动客户端

#运行服务端;报错:unable to register (TESTPROG, VERSION, udp).??

ls -lh

#解方参考:https://blog.csdn.net/DaSunWarman/article/details/80055163

注:更多RPC案例参见文章:c中linux中进程间通信IPC之远程过程调用RPC在实战中的应用

套接字(Socket):

       TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。

       UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。

       TCP/IP协议族包括运输层、网络层、链路层,而socket所在位置是应用层与TCP/IP协议族通信的中间软件抽象层。

定义:

       Socket是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。

       Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

函数:

socket()函数:

int  socket(int protofamily, int type, int protocol);    //返回sockfd

       sockfd是描述符。

       socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。

       正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:

  • protofamily:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。

  • type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(Socket的类型有哪些??)。

  • protocol:顾名思义,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议(这个协议我将会单独开篇讨论!)。

注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。

       当我们调用socket函数创建一个socket时,返回的socket描述符它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。

bind()函数:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

       bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

函数的三个参数分别为:

  • sockfd:即socket描述字,它是通过socket()函数创建的,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。

  • addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4对应的是:略

  • addrlen:对应的是地址的长度。

       通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

注意:在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,再赋给socket,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。(字节序分为大端和小端模式,即Big-Endian和Little-Endian)

listen()、connect()函数:

int listen(int sockfd, int backlog);

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

       如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。

listen函数

  • sockfd:要监听的socket描述字。

  • backlog:相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。

connect函数

  • sockfd:客户端的socket描述字。

  • addr:服务器的socket地址。

  • addrlen:socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。

accept()函数:

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);    //返回连接connect_fd

       TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就向TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数去接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。

  • sockfd:参数sockfd就是上面解释的监听套接字,这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用这个端口号,而此时这个端口号正与这个套接字关联。当然客户不知道套接字这些细节,它只知道一个地址和一个端口号。

  • addr:这是一个结果参数,它用来接受一个返回值,通过这个返回值指定客户端的地址,当然这个地址是通过某个地址结构来描述的,用户应该知道这一个什么样的地址结构。如果对客户的地址不感兴趣,那么可以把这个值设置为NULL。

  • addrlen:它也是结果的参数,用来接受上述addr的结构的大小的,它指明addr结构所占有的字节个数。同样的,它也可以被设置为NULL。

注意:

       如果accept成功返回,则服务器与客户已经正确建立连接了,此时服务器通过accept返回的套接字来完成与客户的通信。

      accept默认会阻塞进程,直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字是连接套接字

区分两种套接字:

       监听套接字:监听套接字正如accept的参数sockfd一样,它是监听套接字,在调用listen函数之后,是服务器开始调用socket()函数生成的,称为监听socket描述字(监听套接字)。

       连接套接字:一个套接字会从主动连接的套接字变身为一个监听套接字;而accept函数返回的是已连接的socket描述字(一个连接套接字),它代表着一个网络已经存在的点点连接。

       一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。

       连接套接字socketfd_new 并没有占用新的端口与客户端通信,依然使用的是与监听套接字socketfd一样的端口号。

read()、write()等函数:

网络I/O操作有下面几组:

  • read()/write()

  • recv()/send()

  • readv()/writev()

  • recvmsg()/sendmsg()

  • recvfrom()/sendto()

       推荐使用recvmsg()/sendmsg()函数,这两个函数是最通用的I/O函数,实际上可以把上面的其它函数都替换成这两个函数。它们的声明如下:

       #include <unistd.h>

       ssize_t read(int fd, void *buf, size_t count);

       ssize_t write(int fd, const void *buf, size_t count);


       #include <sys/types.h>

       #include <sys/socket.h>

       ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

       ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

       read函数是负责从fd中读取内容。当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。

       write函数将buf中的nbytes字节内容写入文件描述符fd。成功时返回写的字节数。失败时返回-1,并设置errno变量。 在网络程序中,当我们向套接字文件描述符写时有俩种可能。1)write的返回值大于0,表示写了部分或者是全部的数据。2)返回的值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接)。

close()函数:

#include <unistd.h>

int close(int fd);

       在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。

       close一个TCP socket的缺省行为是把该socket标记为已关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。

注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

Socket案例

代码说明

#socket通信案例

cat>>server.c<<EOF

/* File Name: server.c*/

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <errno.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>


#define DEFAULT_PORT 8000

#define MAXLINE 4096


//socket案例 server.c

//服务器端:一直监听本机的8000号端口,如果收到连接请求,将接收请求并接收客户端发来的消息,并向客户端返回消息

int main(int argc, char** argv)

{

int socket_fd, connect_fd;

struct sockaddr_in servaddr;

char buff[4096];

int n;

//初始化socket

if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){

printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);

exit(0);

}

//初始化

memset(&servaddr, 0, sizeof(servaddr));

servaddr.sin_family = AF_INET;

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY表示让系统自动获取本机IP地址

servaddr.sin_port = htons(DEFAULT_PORT);//设置端口号

//将本地地址绑定到所创建的套接字上

if( bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){

printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);

exit(0);

}

//开始监听是否有客户端连接

if( listen(socket_fd, 10) == -1){

printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);

exit(0);

}

printf("==============waiting for client's request!==================\n");

while(1){

//阻塞直到有客户端连接,不然浪费CPU资源

if( (connect_fd == accept(socket_fd, (struct sockaddr*)NULL, NULL)) == -1){

printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);

continue;

}

//接收客户端传过来的数据

n = recv(connect_fd, buff, MAXLINE, 0);

//向客户端发送回应数据

if(!fork()){//子进程

if(send(connect_fd, "Hello, you are connected!\n", 26, 0) == -1)

perror("send error");

close(connect_fd);

exit(0);

}

buff[n] = '\0';

printf("recv msg from client:%s\n", buff);

close(connect_fd);

}

close(socket_fd);

   /* 我的第一个 server C 程序 */

   printf("server Hello, World! \n");

   exit(0);

}

EOF

cat>>client.c<<EOF

/* File name: client.c*/

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <errno.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>


#define MAXLINE 4096


int main(int argc, char** argv)

{

int sockfd, n, rec_len;

char recvline[4096], sendline[4096];

char buf[MAXLINE];

struct sockaddr_in servaddr;

if(argc != 2){

printf("usage: ./client <ipaddress>\n"); 

exit(0);

}

if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){

printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);

exit(0);

memset(&servaddr, 0, sizeof(servaddr));

servaddr.sin_family = AF_INET;

servaddr.sin_port = htons(8000);

//转换ip地址

if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){

printf("inet_pton error for: %s\n", argv[1]);

exit(0);

}

if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){

printf("connect error: %s(errno: %d)\n", strerror(errno), errno);

exit(0);

}

//send message

printf("send msg to server:\n");

sprintf(sendline, "this is client, ok!\n");

//fgets(sendline, 4096, stdin);// 从标准输入获取数据

if( send(sockfd, sendline, strlen(sendline), 0) < 0){

printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);

exit(0);

}

//recv message

if( (rec_len = recv(sockfd, buf, MAXLINE, 0)) == -1){

perror("recv error");

exit(1);

}

buf[rec_len] = '\0';

printf("Client Received: %s", buf);

close(sockfd);

   /* 我的第一个 client C 程序 */

   printf("client Hello, World! \n");

   exit(0);

}

EOF

       inet_pton 是Linux下IP地址转换函数,可以在将IP地址在“点分十进制”和“整数”之间转换,是inet_addr的扩展。

int inet_pton(int af, const char *src, void *dst);        //转换字符串到网络地址:

  • af:是地址族,转换后存在dst中

       af = AF_INET: src为指向字符型的地址,即ASCII的地址的首地址(ddd.ddd.ddd.ddd格式的),函数将该地址转换为in_addr的结构体,并复制在*dst中;

       af =AF_INET6: src为指向IPV6的地址,函数将该地址转换为in6_addr的结构体,并复制在*dst中。

       如果函数出错将返回一个负值,并将errno设置为EAFNOSUPPORT,如果参数af指定的地址族和src格式不对,函数将返回0。

gcc -o server server.c;

gcc -o client client.c;

./server &               # &表示后台运行

./client 127.0.0.1;    # client运行报错??:connect error: Network is unreachable(errno: 101)

ls -lh;

注:更多Socket案例参见文章:c中linux中进程间通信IPC之套接字Socket在实战中的应用

注:socket参考:https://blog.csdn.net/hguisu/article/details/7445768

IPC通信实例参考:https://zhuanlan.zhihu.com/p/197726941

延伸阅读参考:https://www.jianshu.com/p/c1015f5ffa74


评论
© c++小牛钱小白 | Powered by LOFTER