Linux网络编程 -- socketpair的使用

socketpair的使用

Linux实现了一个源自BSD的socketpair调用,可以实现在同一个文件描述符中进行读写的功能。
该系统调用能创建一对已连接的UNIX族socket。
在Linux中,完全可以把这一对socket当成pipe返回的文件描述符一样使用,唯一的区别就是这一对文件描述符中的任何一个都可读和可写,函数原型如下:

#include <sys/types.h>
#include <sys/socket.h>

int socketpair(int domain, int type, int protocol, int sv[2]);

socketpair()函数建立一对匿名的已经连接的套接字,其特性由协议族d、类型type、协议protocol决定,建立的两个套接字描述符会放在sv[0]和sv[1]中。

socketpair函数参数说明
第1个参数domain,表示协议族,只能为AF_LOCAL或者AF_UNIX
第2个参数type,表示协议,可以是SOCK_STREAM或者SOCK_DGRAM。用SOCK_STREAM建立的套接字对是管道流,与一般的管道相区别的是,套接字对建立的通道是双向的,即每一端都可以进行读写
第3个参数protocol,表示类型,只能为0。
第4个参数sv[2]是接收代表两个套接口的整数数组。每一个文件描述符代表一个套接口,并且与另一个并没有区别

函数返回值
如果函数成功,将会返回0值。否则将会返回-1表明创建失败,并且errno来表明特定的错误号。

以下给出个简单的例子,通过socketpair实现父子间进程通信:

/*socketpair1.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <stdio.h>

int main ()
{
    int fd[2];
    int r = socketpair(AF_UNIX, SOCK_STREAM, 0, fd);
    if (r < 0){
        perror( "socketpair()" );
        exit(1);
    }

    if (fork()){ /* 父进程 */
        int val = 0;
        close(fd[1]);
        while (1){
            sleep(1);
            ++val;
            printf("发送数据: %d\n", val);
            write(fd[0], &val, sizeof(val));
            read(fd[0], &val, sizeof(val));
            printf("接收数据: %d\n", val);
        }
    }else{  /*子进程*/
        int val;
        close(fd[0]);
        while(1){
            read(fd[1], &val, sizeof(val));
            ++val;
            write(fd[1], &val, sizeof(val));
        }
    }
}

执行结果:

yu@ubuntu:~/Linux/217/pro_pool/socketpair$ vi socketpair1.c
yu@ubuntu:~/Linux/217/pro_pool/socketpair$ gcc -o socketpair1 socketpair1.c
yu@ubuntu:~/Linux/217/pro_pool/socketpair$ ./socketpair1
发送数据: 1
接收数据: 2
发送数据: 3
接收数据: 4
发送数据: 5
接收数据: 6
...

分析程序:一开始由socketpair创建一个套接字对,父进程关闭fd[1],子进程关闭fd[0],父进程sleep(1)让子进程先执行,子进程read(fd[1], &val, sizeof(val))阻塞,然后父进程write(fd[0]..)发送数据,子进程接收数据处理后再发送给父进程数据write(fd[1]..),父进程读取数据,打印输出。(注意:socketpair产生的套接字对实现全双工通信

sendmsg, recvmsg , send函数的使用

sendmsg, recvmsg , send三个函数的头文件:

#include <sys/types.h> 
#include <sys/socket.h>

sendmsg函数
定义函数

int sendmsg(int s, const strcut msghdr *msg, unsigned int flags);

函数说明:sendmsg()用来将数据由指定的socket传给对方主机.
参数s:为已建立好连线的socket, 如果利用UDP协议则不需经过连线操作.
参数msg:指向欲连线的数据结构内容, 参数flags 一般默认为0, 详细描述请参考send().
返回值:成功返回发送的字节数,出错返回-1

recvmsg函数
定义函数

int recvmsg(int s, struct msghdr *msg, unsigned int flags);

函数说明:recvmsg()用来接收远程主机经指定的socket 传来的数据.
参数s 为已建立好连线的socket, 如果利用UDP 协议则不需经过连线操作.
参数msg 指向欲连线的数据结构内容,
参数flags 一般设0, 详细描述请参考send().
返回值:成功则返回接收到的字符数, 失败则返回-1, 错误原因存于errno 中.

send函数
定义函数:int send(int s, const void * msg, int len, unsigned int falgs);
函数说明:send()用来将数据由指定的socket 传给对方主机.
参数s 为已建立好连接的socket.
参数msg 指向欲连线的数据内容.
参数len 则为数据长度.
参数flags 一般设0, 其他数值定义如下:
MSG_OOB 传送的数据以out-of-band 送出.
MSG_DONTROUTE 取消路由表查询
MSG_DONTWAIT 设置为不可阻断运作
MSG_NOSIGNAL 此动作不愿被SIGPIPE 信号中断.
返回值:成功则返回实际传送出去的字符数, 失败返回-1. 错误原因存于errno.

结构msghdr定义如下:

struct msghdr
{
    void *msg_name; //发送或接收数据的地址
    socklen_t msg_namelen; //地址长度
    strcut iovec * msg_iov; //要发送或接受数据
    size_t msg_iovlen; //容器数据长度
    void * msg_control; //附属数据
    size_t msg_controllen; //附属数据长度
    int msg_flags; //接收消息的标志
};

返回值:成功则返回实际传送出去的字符数, 失败返回-1, 错误原因存于errno
错误代码:

1、EBADF 参数s 非合法的socket 处理代码.
2、EFAULT 参数中有一指针指向无法存取的内存空间
3、ENOTSOCK 参数s 为一文件描述词, 非socket.
4、EINTR 被信号所中断.
5、EAGAIN 此操作会令进程阻断, 但参数s 的socket 为不可阻断.
6、ENOBUFS 系统的缓冲内存不足
7、ENOMEM 核心内存不足 EINVAL 传给系统调用的参数不正确.

附属数据msg_control结构
控制信息头部本身由下面的C结构定义:

struct cmsghdr {
    socklen_t cmsg_len;
    int       cmsg_level;
    int       cmsg_type;
/* u_char cmsg_data[]; */
};

其成员描述如下:

成员             描述
cmsg_len        附属数据的字节计数,这包含结构头的尺寸。这个值是由CMSG_LEN()宏计算的。
cmsg_level      这个值表明了原始的协议级别(例如,SOL_SOCKET)。
cmsg_type       这个值表明了控制信息类型(例如,SCM_RIGHTS)。
cmsg_data       这个成员并不实际存在,用来指明实际的额外附属数据所在的位置。

用sendmsg来传递数据程序实例

/*sendmsg.c*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>

int main(int argc,char *argv[])
{
    int ret;     /* 返回值 */
    int sock[2];    /* 套接字对 */
    struct msghdr msg;
    struct iovec iov[1];
    char send_buf[100] = "it is a test";
    struct msghdr msgr;
    struct iovec iovr[1];
    char recv_buf[100];

    /* 创建套接字对 */
    ret = socketpair(AF_LOCAL,SOCK_STREAM,0,sock);
    if(ret == -1){
        printf("socketpair err\n");
        return 1;   
    }

    /* sock[1]发送数据到本地主机 */
    bzero(&msg, sizeof(msg));
    msg.msg_name = NULL;        /* void*类型 NULL本地地址*/
    msg.msg_namelen = 0;
    iov[0].iov_base = send_buf;
    iov[0].iov_len = sizeof(send_buf);
    msg.msg_iov = iov;//要发送或接受数据设为iov
    msg.msg_iovlen = 1;//1个元素

    printf("开始发送数据:\n");
    printf("发送的数据为: %s\n", send_buf);
    ret = sendmsg(sock[1], &msg, 0 );
    if(ret == -1 ){
        printf("sendmsg err\n");
        return -1;
    }
    printf("发送成功!\n");

    /* 通过sock[0]接收发送过来的数据 */
    bzero(&msg, sizeof(msg));
    msgr.msg_name = NULL;   
    msgr.msg_namelen = 0;
    iovr[0].iov_base = &recv_buf;
    iovr[0].iov_len = sizeof(recv_buf);
    msgr.msg_iov = iovr;
    msgr.msg_iovlen = 1;
    ret = recvmsg(sock[0], &msgr, 0);
    if(ret == -1 ){
        printf("recvmsg err\n");
        return -1;
    }
    printf("接收成功!\n");
    printf("收到数据为: %s\n", recv_buf);

    /* 关闭sockets */
    close(sock[0]);
    close(sock[1]);

    return 0;
}

执行程序结果:

yu@ubuntu:~/Linux/217/pro_pool/socketpair$ gcc -o sendmsg sendmsg.c
yu@ubuntu:~/Linux/217/pro_pool/socketpair$ ./sendmsg
开始发送数据:
发送的数据为: it is a test
发送成功!
接收成功!
收到数据为: it is a test

程序分析:由套接字sock[1]发数据到本地主机,由套接字sock[0]接收发送过来的数据。

你可能感兴趣的:(socket,网络编程)