UNIX(编程-进程间通信):15---自定义函数(serv_listen、serv_accept、cli_conn)

域套接字的监听、接受、连接(listen、accept、connect)

  • 域套接字可以调用网络套接字函数接口来实现不同进程之间的通信。类似于与TCP、UDP之间的通信
  • 下面是我们自定义的3个函数:

UNIX(编程-进程间通信):15---自定义函数(serv_listen、serv_accept、cli_conn)_第1张图片

一、serv_listen函数

  • 参数name:服务端调用serv_listen函数声明它要在一个众所周知的名字上监听客户进程的连接请求。客户端通过这个名字来连接到服务器
  • 返回值:成功服务端要监听的域套接字描述符,出错返回负值
  • 此函数中包含了服务端的socket、bind、listen三个步骤的函数
  • 调用unlink()函数的原因:调用bind绑定域套接字地址时,如果文件名已存在,则bind会失败,因此要调用先调用unlink删除这个路径名,以防止该文件已经存在
#define QLEN	10

int serv_listen(const char *name)
{
	int					fd, len, err, rval;
	struct sockaddr_un	un;

	if (strlen(name) >= sizeof(un.sun_path)) {
		errno = ENAMETOOLONG;
		return(-1);
	}

    //创建域套接字
	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
		return(-2);

	unlink(name);

    //初始化域套接字地址信息
	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 (bind(fd, (struct sockaddr *)&un, len) < 0) {
		rval = -3;
		goto errout;
	}

    //监听客户进程连接请求
	if (listen(fd, QLEN) < 0) {
		rval = -4;
		goto errout;
	}
	return(fd);

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

二、serv_accept函数

  • 此函数用来等待接受客户端的请求,模仿accept函数,因为此函数也是阻塞函数。直到一个客户端调用connect函数连接才返回
  • listenfd参数:服务端的域套接字
  • uidptr参数:当有一个客户进程连接之后,此参数存放客户进程的有效用户ID
  • 返回值:成功返回连接到服务端的客户端的域套接字描述符。出错返回-1
  • 下面的代码有很多的验证。虽然下面这些检验并不完善,但是对当前系统所能做到的最佳方案
#define	STALE	30	/* client's name can't be older than this (sec) */

int serv_accept(int listenfd, uid_t *uidptr)
{
	int					clifd, err, rval;
	socklen_t			len;
	time_t				staletime;
	struct sockaddr_un	un;
	struct stat			statbuf;
	char				*name;

	if ((name = malloc(sizeof(un.sun_path + 1))) == NULL)
		return(-1);
	len = sizeof(un);
	if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) {
		free(name);
		return(-2);		/* often errno=EINTR, if signal caught */
	}

	len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */
	
    //复制客户进程赋给其套接字的路径名
    memcpy(name, un.sun_path, len);
	name[len] = 0; //赋值0,确保路径名为null终止
    //调用stat函数验证:该路径名确实是一个套接字
	if (stat(name, &statbuf) < 0) {
		rval = -3;
		goto errout;
	}

#ifdef	S_ISSOCK  /* not defined for SVR4 */
	if (S_ISSOCK(statbuf.st_mode) == 0) {
		rval = -4;		/* not a socket */
		goto errout;
	}
#endif
    //验证其权限仅允许用户读、写以及用户执行
	if ((statbuf.st_mode & (S_IRWXG | S_IRWXO)) ||
		(statbuf.st_mode & S_IRWXU) != S_IRWXU) {
		  rval = -5;	/* is not rwx------ */
		  goto errout;
	}

    //验证与套接字相关的3个时间参数不必当前时间早30秒
	staletime = time(NULL) - STALE;
	if (statbuf.st_atime < staletime ||
		statbuf.st_ctime < staletime ||
		statbuf.st_mtime < staletime) {
		  rval = -6;	/* i-node is too old */
		  goto errout;
	}

	if (uidptr != NULL)
		*uidptr = statbuf.st_uid;	/* return uid of caller */
	unlink(name);
	free(name);
	return(clifd);

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

三、cli_conn函数

  • 客户端调用函数创建客户端域套接字并绑定地址,最后再connect连接服务端
  • name参数:与要连接的客户端的域套接字绑定的sockaddr_un中的sun_path名字相同
  • 返回值:成功返回服务端的域套接字描述符。出错返回负数
  • 客户端绑定地址时,要显式地为客户端绑定自己的地址。如果不绑定,那么系统不会自动给这个域套接字绑定一个路径名,所以服务端接收到这个没有绑定地址的消息时,无法发回应数据报。这一点与UDP/TCP不同,如果UDP/TCP客户端套接字没有绑定地址,系统会自动分配
  • 如果connect连接服务端时如果发现监听域套接字队列已满,则返回一个ECONNREFUSED错误。这一点不同于TCP,TCP如果监听套接字的队列已满,TCP监听就忽略新到达的SYN,而TCP连接发起端将数次发送SYN重试
#define	CLI_PATH	"/var/tmp/"
#define	CLI_PERM	S_IRWXU			/* rwx for user only */

int cli_conn(const char *name)
{
	int					fd, len, err, rval;
	struct sockaddr_un	un, sun;
	int					do_unlink = 0;

	if (strlen(name) >= sizeof(un.sun_path)) {
		errno = ENAMETOOLONG;
		return(-1);
	}

	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
		return(-1);

	memset(&un, 0, sizeof(un));
	un.sun_family = AF_UNIX;
	sprintf(un.sun_path, "%s%05ld", CLI_PATH, (long)getpid());
        printf("file is %s\n", un.sun_path);
	len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);

	unlink(un.sun_path);
	if (bind(fd, (struct sockaddr *)&un, len) < 0) {
		rval = -2;
		goto errout;
	}

    //调用chmod关闭除用户读、写以及用户执行以外的其它权限
	if (chmod(un.sun_path, CLI_PERM) < 0) {
		rval = -3;
		do_unlink = 1;
		goto errout;
	}

	memset(&sun, 0, sizeof(sun));
	sun.sun_family = AF_UNIX;
	strcpy(sun.sun_path, name);
	len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
	if (connect(fd, (struct sockaddr *)&sun, len) < 0) {
		rval = -4;
		do_unlink = 1;
		goto errout;
	}
	return(fd);

errout:
	err = errno;
	close(fd);
	if (do_unlink)
		unlink(un.sun_path);
	errno = err;
	return(rval);
}

你可能感兴趣的:(UNIX(编程-进程间通信),serv_accept)