2015-12-05 11:14:31 Jeffrey0000 阅读数 1281

        我们熟知的sockek通信一般都是基于IP的,最近见到了本机客户端与服务端通信的方法(虽然lo地址也可以本机通信),写了一些程序做实验,主要实现客户端和服务端套接字的连接、各自创建收发线程进行通信。由于功能没有优化,纠结了很久才贴上来,重点是AF_UNIX的功能已经实现了。其实AF_UNIX和AF_INET交互的流程是一样的,只不过创建套接字的宏不一样,以及数据结构的填充不一样。这种机制对Linux C编程模块化非常有用,尤其是两个进程间通信。

1、AF_UNIX对应的结构体。

struct socketaddr_un
{
 _SOCKADDR_COMMON(sun_);  // __SOCKADDR_COMMON(sun_) 宏定义对应的定义为 sa_family_t sun_family
 char sun_path[108];
};

        struct sockaddr_un是UNIX环境下套接字的地址形式,一般情况下,需要把sockaddr_un结构强制转换成sockaddr结构再传入系统调用函数中。

 

2、客户端代码。

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>

#define SERVER_SOCK_PATH "server_socket"

void *send_data(void *param);
void *recv_data(void *param);
int main(int argc, char *argv[])
{
    struct sockaddr_un address;
    int sockfd;
    int len;
    int i, bytes;
    int result;
	pthread_t send_id, recv_id;

    sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (-1 == sockfd)
    {
        perror("socket");
        exit(-1);
    }

    address.sun_family = AF_UNIX;
    strcpy(address.sun_path, SERVER_SOCK_PATH);
    len = sizeof(address);

    result = connect(sockfd, (struct sockaddr *)&address, len);
    if (-1 == result)
    {
        perror("connect");
        close(sockfd);
        exit(-1);
    }

	pthread_create(&send_id, NULL, send_data, &sockfd);
	pthread_create(&recv_id, NULL, recv_data, &sockfd);
	
    pthread_join(send_id,NULL);
    pthread_join(recv_id,NULL);

    close(sockfd);
    return 0;
}
void *send_data(void *param)
{
	int sock = 0;
	int ret = 0;
	int send_len = 0;
	fd_set sendfd;
	struct timeval timeout={1,0};
    char string_send[128] = {0};
	
	sock = *(int *)param;

    while(1)
    {
        printf("Please enter string to send:\r\n");
        memset(string_send, 0, sizeof(string_send));
        scanf("%s", string_send);
        
        FD_ZERO(&sendfd);
        FD_SET(sock, &sendfd);
        ret = select(sock + 1, NULL, &sendfd, NULL, &timeout);
        if (ret <= 0)
        {
            continue;
        }


        if (FD_ISSET(sock, &sendfd))
        {
            send_len = send(sock, string_send, strlen(string_send), 0);
            if (strlen(string_send) != send_len)
            {
                printf("send string fail!\r\n");
                continue;
            }
        }
    }

	return;
}

void *recv_data(void *param)
{
	int sock = 0;
	int ret = 0;
	int recv_len = 0;
	fd_set recvfd;
	struct timeval timeout={1,0};
    char string_recv[128] = {0};

	sock = *(int *)param;

    while(1)
    {
        FD_ZERO(&recvfd);
        FD_SET(sock, &recvfd);
        ret = select(sock + 1, &recvfd, NULL, NULL, &timeout);
        if (ret <= 0)
        {
            continue;
        }

        if (FD_ISSET(sock, &recvfd))
        {
            recv_len = recv(sock, string_recv, sizeof(string_recv), 0);
            if (recv_len <= 0)
            {
                printf("recv string fail!\r\n");
                continue;
            }

            printf("recv string:%s\r\n", string_recv);
        }
    }

	return;
}

 

3、服务端代码。

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

#define SERVER_SOCK_PATH "server_socket"
void *send_data(void *param);
void *recv_data(void *param);
int main(int argc, char *argv[])
{
	int server_sockfd, client_sockfd;
	int server_len, client_len;
	struct sockaddr_un server_address;
	struct sockaddr_un client_address;
	int i, bytes;
	char ch_send, ch_recv;
	pthread_t send_id, recv_id;
	
	unlink(SERVER_SOCK_PATH);
	
	server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (-1 == server_sockfd)
    {
        perror("socket");
        exit(-1);
    }
	
	server_address.sun_family = AF_UNIX;
	strcpy(server_address.sun_path, SERVER_SOCK_PATH);
	server_len = sizeof(server_address);
	
	bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
	listen(server_sockfd, 5);
	
	client_len = sizeof(client_address);
	
	client_sockfd = accept(server_sockfd, (struct sockaddr *)&server_address, (socklen_t *)&client_len);
	
	if (client_sockfd == -1)
	{
		perror("accept");
		close (server_sockfd);
		exit(-1);
	}
	
	pthread_create(&send_id, NULL, send_data, &client_sockfd);
	pthread_create(&recv_id, NULL, recv_data, &client_sockfd);
	
    pthread_join(send_id,NULL);
    pthread_join(recv_id,NULL);
	
	close(client_sockfd);
	return 0;
}

void *send_data(void *param)
{
	int sock = 0;
	int ret = 0;
	int send_len = 0;
	fd_set sendfd;
	struct timeval timeout={1,0};
    char string_send[128] = {0};
	
	sock = *(int *)param;

    while(1)
    {
        printf("Please enter string to send:\r\n");
        memset(string_send, 0, sizeof(string_send));
        scanf("%s", string_send);
        
        FD_ZERO(&sendfd);
        FD_SET(sock, &sendfd);
        ret = select(sock + 1, NULL, &sendfd, NULL, &timeout);
        if (ret <= 0)
        {
            continue;
        }


        if (FD_ISSET(sock, &sendfd))
        {
            send_len = send(sock, string_send, strlen(string_send), 0);
            if (strlen(string_send) != send_len)
            {
                printf("send string fail!\r\n");
                continue;
            }
        }
    }

	return;
}

void *recv_data(void *param)
{
	int sock = 0;
	int ret = 0;
	int recv_len = 0;
	fd_set recvfd;
	struct timeval timeout={1,0};
    char string_recv[128] = {0};

	sock = *(int *)param;

    while(1)
    {
        FD_ZERO(&recvfd);
        FD_SET(sock, &recvfd);
        ret = select(sock + 1, &recvfd, NULL, NULL, &timeout);
        if (ret <= 0)
        {
            continue;
        }

        if (FD_ISSET(sock, &recvfd))
        {
            recv_len = recv(sock, string_recv, sizeof(string_recv), 0);
            if (recv_len <= 0)
            {
                printf("recv string fail!\r\n");
                continue;
            }

            printf("recv string:%s\r\n", string_recv);
        }

    }

	return;
}

 

4、测试结果。

略(功能可以保证已经实现,公司网络不能上传图片,&…#*%&*…(@¥…&)。

 

 

 

 

2014-11-21 21:22:35 chenhanzhun 阅读数 1578

UNIX 域套接字

        UNIX 套接字可在用一台机器上实现进程间通信,因为 UNIX 域套接字仅仅复制数据,不执行协议处理,不需要添加或删除网络报头,无需验证和,不产生顺序号,无需发送确认报文,比因特网域套接字的效率更高。UNIX域套接字提供流和数据报两种接口,UNIX域数据报服务是可靠的,既不会丢失消息也不会传递出错。UNIX域套接字是套接字和管道之间的混合物。为了创建一对非命名的,相互连接的 UNXI 域套接字,用户可以使用socketopair函数。其实现如下:

#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sockfd[2]);
/* 返回值:若成功则返回0,出错则返回-1 */

#include "apue.h"
#include <sys/socket.h>

/*
* Return a full-duplex "stream" pipe (a UNIX domain socket)
* with the two file descriptors returned in fd[0] and fd[1].
*/
int
s_pipe(int fd[2])
{
    return(socketpair(AF_UNIX, SOCK_STREAM, 0, fd));
}


命名 UNIX 域套接字

        命名 UNIX 域套接字也是针对没有亲缘关系进程之间的通信,它的地址结构和因特网的地址结构不同,其地址结构如下:

struct sockaddr_un{
sa_family_t sun_family; /* AF_UNIX */
char sun_path[108]; /* pathname */
};

       sockaddr_un 结构的 sun_path 成员包含一路径名。当我们将以地址绑定至UNIX域套接字时,系统用该路径名创建一类型为S_IFSOCK的文件。该文件仅用于向客户进程告知套接字名字。该文件不能打开,也不能由应用程序用于通信。如果当我们试图绑定地址时,该文件已经存在,那么bind请求失败。当关闭套接字时,并不自动删除该文件,所以我们必须确保在应用程序终止前,对该文件执行解除链接操作。下面例子是实现地址绑定到 UNIX 域套接字:

#include "apue.h"
#include <sys/socket.h>
#include <sys/un.h>

int
main(void)
{
    int            fd, size;
    struct sockaddr_un    un;

    un.sun_family = AF_UNIX;
    strcpy(un.sun_path, "foo.socket");
    
    if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
        err_sys("socket failed");
    size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
    if(bind(fd, (struct sockaddr *)&un, size) < 0)
        err_sys("bind failed");
    printf("UNIX domain socket bound\n");
    exit(0);
}

唯一连接

        服务器进程可以使用标准 bind、listen 和 accept 函数,为客户进程安排一个唯一的 UNIX 域连接。客户进程使用 connect 与服务器进程关联;服务器进程接受了 connect 请求后,在服务器进程和客户进程之间就存在了唯一连接。以下是 UNIX 域套接字唯一连接的实现:这些函数可以套用在前面介绍《基于 socket 的编程》的框架中。

#include "apue.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>

#define    QLEN    10

/*
* Create a server endpoint of a connection.
* Return fd if all ok, <0 on error. 
*/
int
serv_listen(const char *name)
{
    int            fd, len, err, rval;
    struct sockaddr_un    un;
    
    /* create a UNIX domain stream socket */
    if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
        return(-1);
    unlink(name);    /* in case it already exists */

    /* fill in socket address structure */
    memset(&un, 0, sizeof(un));
    un.sun_family = AF_UNIX;
    strcpy(un.sun_path, name);
    len = offsetof(struct sockaddr_un, sun_path) + strlen(name);

    /* bind the name to the descriptor */
    if(bind(fd, (struct sockaddr *)&un, len) < 0)
    {
        rval = -2;
        goto errout;
    }
    if(listen(fd, QLEN) < 0)    /* tell kernel we're a server */
    {
        rval = -3;
        goto errout;
    }
    return(fd);

errout:
    err = errno;
    close(fd);
    errno = err;
    return(rval);
}

#include "apue.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <time.h>
#include <errno.h>

#define STALE    30    /* client's name can't be older than this (sec) */

/*
* Wait for a client connection  to arrive, and accept it.
* We also obtain the client's usr ID from the pathname
* that it must bind before calling us.
* Returns new fd if all ok, <0 on error
*/
int serv_accept(int listenfd, uid_t *uidptr)
{
    int                   clifd, len, err, rval;
    time_t                staletime;
    struct sockaddr_un    un;
    struct stat           statbuf;
    
    len = sizeof(un);
    if((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0)
        return(-1);    /* often errno=EINTR, if signal caught */

    /* obtain the client's uid from its calling address */
    len -= offsetof(struct sockaddr_un, sun_path);    /* len of pathname */
    un.sun_path[len] = 0;    /* null terminate */

    if(stat(un.sun_path, &statbuf) < 0)
    {
        rval = -2;
        goto errout;
    }
#ifdef    S_ISSOCK    /* not defined fro SVR4 */
    if(S_ISSOCK(statbuf.st_mode) == 0)
    {
        rval = -3;    /* not a socket */
        goto errout;
    }
#endif
    if((statbuf.st_mode & (S_IRWXG | S_IRWXO)) ||
           (statbuf.st_mode & S_IRWXU) != S_IRWXU)
    {
        rval = -4;    /* is not rwx------ */
        goto errout;
    }
    
    staletime = time(NULL) - STALE;
    if(statbuf.st_atime < staletime ||
       statbuf.st_ctime < staletime ||
       statbuf.st_mtime < staletime)
    {
        rval = -5;    /* i-node is too old */    
        goto errout;
    }
    
    if(uidptr != NULL)
        *uidptr = statbuf.st_uid;    /* return uid of caller */
    unlink(un.sun_path);    /* we're done with pathname now */
    return(clifd);

errout:
    err = errno;
    close(clifd);
    errno = err;
    return(rval);    
}

#include "apue.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>

#define CLI_PATH    "/var/tmp/"    /* +5 fro pid = 14 chars */
#define CLI_PERM    S_IRWXU        /* rwx for user only */

/*
* Create a client endpoint and connect to a server.
* Returns fd if all ok, <0 on error. 
*/
int 
cli_conn(const char *name)
{
    int            fd, len, err, rval;
    struct sockaddr_un    un;
    
    /* create a UNIX domain stream socket */
    if((fd = socket(AF_UNIX, SOCK_STREM, 0)) < 0)
        return(-1);

    /* fill socket address structure with our address */
    memset(&un, 0, sizeof(un));
    un.sun_family = AF_UNIX;
    sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid());
    len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);

    unlink(un.sun_path);    /* in case it already exits */
    if(bind(fd, (struct sockaddr *)&un, len) < 0)
    {
        rval = -2;
        goto errout;
    }
    if(chmod(un.sun_path, CLI_PERM) < 0)
    {
        rval = -3;
        goto errout;
    }

    /* fill socket address structure with server's address */
    memset(&un, 0, sizeof(un));
    un.sun_family = AF_UNIX;
    strcpy(un.sun_path, name);
    len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
    
    if(connect(fd, (struct sockaddr *)&un, len) < 0)
    {
        rval = -4;
        goto errout;
    }
    return(fd);

errout:
    err = errno;
    close(fd);
    errno = err;
    return(rval);
    
}



参考资料:

《UNIX高级环境编程》

2016-08-20 12:46:50 ZhichengYee 阅读数 561

1、UNIX域套接字

UNIX域套接字用于在同一台计算机上运行的进程之间的通信。
UNIX域套接字提供流和数据报两种接口。UNIX域数据报服务是可靠的,既不会丢失报文也不会传递出错。

使用socketpair函数来创建一对无命名的、相互连接的UNIX域套接字。

#include <sys/socket.h>
int socketpair(int domain,int type,int protocol,int sockfd[2]);
//若成功,返回0;若出错,返回-1

一对相互连接的UNIX域套接字可以起到全双工管道的作用:两端对读和写开放。将其称为fd管道。
这里写图片描述

命名UNIX域套接字

虽然socketpair函数能创建一对相互连接的套接字,但是每一个套接字都没有名字,无关进程不能使用它们。
UNIX域套接字的地址由sockaddr_un结构表示。在linux 3.2.0中,sockaddr_un结构在头文件

struct sockaddr_un{
sa_family_t sun_family;    /*AF_UNIX*/
char        sun_path[108]; /*pathname*/
};

sockaddr_un结构的sun_path成员包含一个路径名。当我们将一个地址绑定到一个UNIX域套接字时,系统会用该路径名创建一个S_ISSOCK类型的文件。该文件仅用于向客户进程告示套接字名字。该文件无法打开,也不能由应用程序用于通信。

如果我们试图绑定同一地址时,该文件已经存在,那么bing请求会失败。当关闭套接字时,并不自动删除该文件,所以必须确保在应用程序推出前,对该文件执行解除链接操作。

确定socklen_t的方法:
size=offsetof(struct sockaddr_un,sun_path)+strlen(un.sun_path

3、唯一连接

服务器进程可以使用标准bind、listen和accept函数,为客户进程安排一个唯一UNIX域连接。客户进程使用connect与服务器进程联系。在服务器进程接受了connect请求后,在服务器进程和客户进程之间就存在了唯一连接。
这里写图片描述

我们将开发3个函数,使用这些函数可以在运行于同一台计算机上点的两个无关进程之间穿点唯一连接:

#include "apue.h"
int serv_listen(const char *name);
//若成功,返回要监听的文件描述符;若出错,返回负值
int serv_accept(int listenfd,uid_t *uidptr);
//若成功,返回新文件描述符;若出错,返回负值
int cli_conn(const char *name);
//若成功,返回文件描述符;若出错,返回负值

服务器进程可以调用serv_listen函数声明它要在一个众所周知的名字(文件系统中的某个路径名)上监听客户进程的连接请求。serv_listen函数的返回值是用于接受客户进程连接请求的服务器UNIX域套接字。
客户进程调用cli_conn函数连接至服务器进程。客户进程指定的name参数必须与服务器进程调用serv_listen函数时所用的名字相同。

4、传送文件描述符

在两个进程之间传送打开文件描述符的技术是非常有用的。因此可以对客户进程-服务器进程应用进行不同的设计。它使一个进程(通常是服务器进程)能够处理打开一个文件所要做的一切操作以及向调用进程送回一个描述符,该描述符可被用于以后的所有I/O函数。涉及打开文件或设备的所有细节对客户进程而言都是透明的。
当一个进程向另一个进程传送一个打开文件描述符时,我们想让发送进程和接收进程共享同一文件表项:
这里写图片描述

在技术上,我们是将指向一个打开文件表项的指针从一个进程发送到另外一个进程。该指针被分配存放在接收进程的第一个可用描述符中。两个进程共享同一个打开文件表,这与fork之后的父进程和子进程共享打开文件表的情况完全相同。

#include "apue.h
int send_fd(int fd,int fd_to_send);
int send_err(int fd,int status,const char *errmsg);
//若成功,返回0;若出错,返回-1
int recv_fd(int fd,ssize_t (*userfunc)(int,const void*,size_t));
//若成功,返回文件描述符;若出错,返回负值

send_fd使用fd代表的UNIX域套接字发送描述符fd_to_send。send_err使用fd发送errmsg以及后随的status字节。status的值应在-1~-255.

为了用UNIX域套接字交换文件描述符,调用sendmsg和recvmsg函数。这两个函数的参数中都有一个指向maghdr结构的指针,该结构包含了所有关于要发送或要接收的消息的信息:

struct msghdr{
void         *msg_name;
socklen_t     msg_namelen;
struct iovec *msg_iov;
int           msg_iovlen;
void         *msg_control;
socklen_t     msg_controllen;
int           msg_flags;
};

前两个元素通常用于在网络连接上发送数据报。msg_flags字段包含了描述接收到的消息的标志。
两个元素处理控制信息的传送和接收。msg_control字段指向cmsghdr(控制信息头)结构,msg_controllen字段包含控制信息的字节数。

struct cmsghdr {
socklen_t  cmsg_len;
int        cmsg_level;
int        cmsg_type;
/*followed by the actual control message data*/
};

为了发送文件描述符,将cmsg_len设置为cmsghdr结构的长度加一个整型的长度(描述符的长度),cmg_level字段设置为SOL_SOCKET,cmsg_type字段设置为SCM_RIGHTS,用以表明在传送访问权。访问权仅能通过UNIX域套接字传送。描述符紧随cmsg_type字段之后存储,用CMSG_DATA宏获得该整型量的指针。

#include <sys/socket.h>
unsigned char *CMSG_DATA(struct cmsghdr *cp);
//返回一个指针,指向与cmsghdr结构相关联的数据
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *mp);
//返回一个指针,指向与msghdr结构相关联的第一个cmsghdr结构;若无这样的结构,返回NULL
struct cmsghdr *CMSG_NXTHDR(sturct msghdr *mp,
                            struct cmsghdr *cp);
//返回一个指针,指向与msghdr结构相关联的下一个cmsghdr结构,该msghdr结构给出了当前的cmsghdr结构;若当前cmsghdr结构已是最后一个,返回NULL
unsigned int CMSG_LEN(unsigned int nbytes);
//返回为nbytes长的数据对象分配的长度。

回忆serv_accept函数确定调用者身份的步骤。如果内核能够把调用者的证书在调用accept之后返回给调用处会更好。
在linux中,将证书作为ucred结构传送:

struct ucred {
pid_t pid;
uid_t uid;
gid_t gid;
};

注:需要在传输前初始化这个结构。

5、处理命令行选项:

#include <unistd.h>
int getopt(int argc,char *const argv[],const char *options);
extern int optind,opterr,optopt;
extern char *optarg;
若所有选项被处理完,返回-1;否则,返回下一个选项字符。

参数argc和argv与传入main函数的一样。options参数是一个包含该命令支持的选项字符的字符串。如果一个选项字符后面接了一个冒号,则表示该选项需要参数;否则,该选项不需要额外参数。
函数getopt一般用在循环体内,循环直到getopt返回-1时退出。每次迭代中,getopt会返回下一个选项。
当遇到无效的选项时,getopt返回一个问题标记而不是这个字符。如果选项缺少参数,getopt也会返回一个问题标记,但如果选项字符串的第一个字符是冒号,getopt会直接返回冒号。而特殊的“–”格式则会导致getopt**停止处理选项**并返回-1。这允许用户传递以“-”开头但不是选项的参数。

getopt参数支持以下4个外部变量:
这里写图片描述

2017-09-24 12:07:21 u013457167 阅读数 255

本次博客主要总结参考《Unix网络编程》卷一前四章的知识,对TCP一对一通信进行重新改造和分析,经典就是经典,无可替代!


一、为什么使用包裹函数

任何现实世界的程序都必须检查每个函数调用是否返回错误。当某个函数发生错误时,就调用我们自己的err_quit或err_sys函数输出一个错误消息并终止程序的运行(当然有时候并非简单的终止程序运行,还需要检查问题所在:系统调用是否被中断了)。
既然发生错误时终止程序的运行时普遍的情况,我们可以通过定义包裹函数(wrapper function)来缩短程序。每个包裹函数完成实际函数的函数调用,检查返回值,并在发生错误时终止进程。我们约定包裹函数名是实际函数名的首字母大写形式。例如

listenfd = Socket(AF_INET, SOCK_STREAM, 0);

包裹函数Socket定义为:

int Socket(int family, int type, int protocol)
{
    int n;
    if ((n = socket(family, type, protocol)) < 0)
        err_sys("socket error");

    return n;
}

因此,在UNP课程后面的所有例子中,我们建议使用包裹函数的形式,不仅缩短了代码,有利于开发者看清楚具体的功能实现;而且考虑了对于每一步的出错信息及处理,使代码更具有健壮性。


二、Unix errno值

只要一个Unix函数(例如某个套接字函数)中有错误发生,全局变量errno就被置为一个指明该错误类型的正值,函数本身则通常返回-1。
errno的值是只在函数发生错误时设置。如果函数不返回错误,errno的值就没有定义。errno的所有正数错误值都是常值,具有以“E”开头的全大写字母名字,并通常在头文件sys/errno.h定义。值0不表示任何错误。
在全局变量中存放errno值对于共享所有全局变量的多个线程并不适合。

三、TCP通信流程

基于TCP(面向连接)的socket编程,分为服务器端和客户端

服务器端的流程如下:
(1)创建套接字(socket)
(2)将套接字绑定到一个本地地址和端口上(bind)
(3)将套接字设为监听模式,准备接收客户端请求(listen)
(4)等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)
(5)用返回的套接字和客户端进行通信(send/recv 、write/read)
(6)返回,等待另一个客户请求。
(7)关闭套接字。

客户端的流程如下:
(1)创建套接字(socket)
(2)向服务器发出连接请求(connect)
(3)和服务器端进行通信(send/recv 、write/read)
(4)关闭套接字

这里写图片描述

2015-04-02 21:02:18 Cashey1991 阅读数 2366

Unix域套接字简介

《Unix环境高级编程》中对Unix域套接字有如下介绍:

虽然socketpair函数创建相互连接的一对套接字,但是每一个套接字都没有名字。这意味着无关进程不能使用它们。

我们可以命名unix域套接字,并可将其用于告示服务。但是要注意的是,UNXI与套接字使用的地址不同与因特网域套接字。

UNIX域套接字的地址由sockaddr_un结构表示。

在linux2.4.22中,sockaddr_un结构按下列形式定义在有文件

    struct sockaddr_un{
    sa_family_t sun_family; //AF_UNIX
    char sun_path[108]; //pathname
    };

sun_path成员包含一路经名,当我们将一个地址绑定至UNIX域套接字时,系统用该路经名创建一类型为S_IFSOCK文件。该文件仅用于向客户进程告知套接字名字。该文件不能打开,也不能由应用程序用于通信。

如果当我们试图绑定地址时,该文件已经存在,那么bind请求失败。当关闭套接字时,并不自动删除该文件,所以我们必须确保在应用程序终止前,对该文件执行解除链接操作。

服务器进程可以使用标准bind、listen和accept函数,为客户进程安排一个唯一UNIX域连接。客户进程使用connect与服务器进程连接;

服务器进程接受了connect请求后,在服务器进程和客户进程之间就存在了唯一连接。这种风格和因特网套接字的操作很像。

使用命名的Unix域套接字进程编程

示例为使用Unix域套接字实现一个Client-Server交互的功能

Server端代码如下:创建Unix套接字并绑定到 /tmp/test.sock 下进行监听,当有客户端连接时fork出子进程进行处理,子进程负责接收数据并打印到屏幕上:

/******************************************************************************
 * 文件名称:TestUnixSocket.cpp
 * 文件描述:Unix域套接字测试
 * 创建日期:2015-04-02
 * 作    者:casheywen
 ******************************************************************************/

#include <iostream>
using namespace std;
#include <errno.h>

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

#define LOG_ERR(fmt, args...) fprintf(stderr, "PID:%d|"fmt"\n", getpid(), ##args)
#define LOG_INFO(fmt, args...) fprintf(stdout, "PID:%d|"fmt"\n", getpid(), ##args)

int CreateUnixSocket(const char *pszPath)
{
    int iFd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (iFd < 0)
    {
        LOG_ERR("Create Socket Fail: %s", strerror(errno));
        return -1;
    }

    struct sockaddr_un stAddr;
    memset(&stAddr, 0, sizeof(stAddr));

    stAddr.sun_family = AF_UNIX;
    strncpy(stAddr.sun_path, pszPath, sizeof(stAddr.sun_path));


    int iRet = bind(iFd, (struct sockaddr *)&stAddr, sizeof(stAddr));
    if (iRet < 0)
    {
        LOG_ERR("Bind Fail: %s", strerror(errno));
        return -1;
    }

    return iFd;
}

int main(int argc, char *argv[])
{
    const char szSocketPath[] = "/tmp/test.sock";

    if (0 == access(szSocketPath, F_OK))
    {
        unlink(szSocketPath);
    }

    int iFd = CreateUnixSocket(szSocketPath);

    if (iFd < 0)
    {
        LOG_ERR("CreateUnixSocket %s Fail", szSocketPath);
        return 1;
    }

    int iRet = 0;

    while (true)
    {
        iRet = listen(iFd, 5);
        if (iRet < 0)
        {
            LOG_ERR("listen Fail: %s", strerror(errno));
            return 1;
        }

        LOG_INFO("Listening on %s", szSocketPath);

        struct sockaddr_un stClientAddr;
        socklen_t nClientAddrLen = sizeof(stClientAddr);
        memset(&stClientAddr, 0, sizeof(stClientAddr));

        int iClientFd = accept(iFd, (struct sockaddr *)&stClientAddr, &nClientAddrLen);
        if (iClientFd < 0)
        {
            LOG_ERR("accept Fail: %s", strerror(errno)); 
            return 1;
        }

        LOG_INFO("Connected: Client Addr %s", stClientAddr.sun_path);

        pid_t pid = fork();

        if (pid == 0)   // 父进程
        {
            close(iClientFd);
            continue;
        }
        else if (pid < 0)
        {
            LOG_ERR("fork Fail: %s", strerror(errno));
        }

        char acBuf[4096] = {0};

        int iCnt = 0;

        while ((iCnt = read(iClientFd, acBuf, sizeof(acBuf))))
        {
            LOG_INFO("Read %d Bytes:[%s]", iCnt, acBuf); 
        }

        LOG_INFO("Disconnected: Client Addr %s", stClientAddr.sun_path);

        return 0;
    }
}

客户端代码如下:创建Unix套接字并连接/tmp/test.sock 下监听的套接字,从标准输入读取数据并通过套接字发送到Server端

/******************************************************************************
 * 文件名称:Client.cpp
 * 文件描述:Unix域套接字测试
 * 创建日期:2015-04-02
 * 作    者:casheywen
 ******************************************************************************/

#include <iostream>
using namespace std;
#include <errno.h>

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

#define LOG_ERR(fmt, args...) fprintf(stderr, "PID:%d|"fmt"\n", getpid(), ##args)
#define LOG_INFO(fmt, args...) fprintf(stdout, "PID:%d|"fmt"\n", getpid(), ##args)

void SigPipeHandler(int iSigno)
{
    LOG_ERR("SigPipe received");
    exit(1);
}

bool ConnectUnixSocket(const char *pszPath, int iFd)
{
    struct sockaddr_un stAddr;
    memset(&stAddr, 0, sizeof(stAddr));

    stAddr.sun_family = AF_UNIX;
    strncpy(stAddr.sun_path, pszPath, sizeof(stAddr.sun_path));

    int iRet = connect(iFd, (struct sockaddr *)&stAddr, sizeof(stAddr));
    if (iRet < 0)
    {
        LOG_ERR("Connect Fail: %s", strerror(errno));
        return false;
    }

    return true;
}


int main()
{
    const char szSocketPath[] = "/tmp/test.sock";

    int iFd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (iFd < 0)
    {
        LOG_ERR("Create Socket Fail: %s", strerror(errno));
        return 1;
    }

    if (!ConnectUnixSocket(szSocketPath, iFd))
    {
        LOG_ERR("ConnectUnixSocket Fail");
        return 1;
    }

    LOG_INFO("Connect Success");

    if (SIG_ERR == signal(SIGPIPE, SigPipeHandler))    // 当连接中断时调用write函数会收到SIGPIPE信号
    {
        LOG_ERR("Signal Fail: %s", strerror(errno));
        return 1;
    }

    char szContent[2048];
    ssize_t nWrite = 0;

    while (cin >> szContent)
    {
        nWrite = write(iFd, szContent, strlen(szContent));

        if (nWrite < 0)
        {
            LOG_ERR("write Fail: %s", strerror(errno));
            return 1;
        }
    }

    return 0;
}

程序测试结果:

Server端

$ ./TestUnixSocket 
PID:10013|Listening on /tmp/test.sock
PID:10013|Connected: Client Addr 
PID:10037|Listening on /tmp/test.sock
PID:10013|Read 13 Bytes:[alsdkfjlasjdf]
PID:10013|Read 18 Bytes:[asdfljasldfalskdjf]
PID:10013|Read 20 Bytes:[alsdjkfasldfjalsdkfj]
PID:10013|Read 29 Bytes:[asdasdfasdfasdfasdasdflkjsadf]
^C

Client端

$ ./Client 
PID:10036|Connect Success
alsdkfjlasjdf
asdfljasldfalskdjf
alsdjkfasldfjalsdkfj
asdasdfasdfasdfasdasdflkjsadf
asdfasdffsd
PID:10036|SigPipe received          --- Server端退出后对Fd写入,收到SIGPIPE

Android通信之 BitTube

阅读数 671

没有更多推荐了,返回首页