UNIX环境高级编程-网络socket编程

本文详细介绍了Socket通信的基本流程,包括socket()、bind()、listen()、accept()、connect()和close()等关键API函数的使用,并提供了客户端和服务器的代码示例,展示了如何建立和管理TCP连接。同时,文章提到了套接字的类型如流格式套接字和数据报格式套接字,以及它们在TCP和UDP协议中的应用。
摘要由CSDN通过智能技术生成


大家可以提前看一下OSI七层模型的文章:OSI(open system internet)七层模型介绍以及NAT(Network Address Translation)技术详解

socket 的原意是”插座“,在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。

1- socket通信流程图

socket通信基本原理:“open—write/read—close“模式。

下面是网络socket通信的基本流程:

在这里插入图片描述


2- socket操作API函数介绍

(1)socket()函数

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

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
  • domain:
    即协议域,又称为协议族(family)。常用的协议族有,AF_INET、AF_INET6、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类型:

  • 流格式套接字(SOCK_STREAM)

流格式套接字(Stream Sockets)也叫“面向连接的套接字”,在代码中使用 SOCK_STREAM 表示。SOCK_STREAM 是一种可靠的、双向的通信数据流,数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送。使用了TCP协议,所以流格式套接字可以达到高质量的数据传输。

  • 数据报格式套接字(SOCK_DGRAM)

数据报格式套接字(Datagram Sockets)也叫“无连接的套接字”,在代码中使用 SOCK_DGRAM 表示。数据报套接字是一种不可靠的、不按顺序传递的、以追求速度为目的的套接字。使用了UDP协议,所以传输速率很快。

(2)bind函数

当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数。通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,由系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。当然客户端也可以在调用connect()之前bind一个地址和端口,这样就能使用特定的IP和端口来连服务器了。

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd:
    即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
  • addrlen:
    对应的是地址的长度。
  • addr:
    一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,但最终都会强制转换后赋值给sockaddr这种类型的指针传给内核。

下面简单来看一下通用套接字socketaddr类型定义:

typedef unsigned short int sa_family_t;

struct sockaddr { 
	sa_family_t sa_family; /* 2 bytes address family, AF_xxx */
 	char sa_data[14]; /* 14 bytes of protocol address */
}

ipv4对应的是sockaddr_in类型定义:

typedef unsigned short sa_family_t;
typedef uint16_t in_port_t;

struct in_addr {
 	uint32_t s_addr; 
};

struct sockaddr_in {
 	sa_family_t sin_family; /* 2 bytes address family, AF_xxx such as AF_INET */
 	in_port_t sin_port; /* 2 bytes port*/
	 struct in_addr sin_addr; /* 4 bytes IPv4 address*/
	 /* Pad to size of `struct sockaddr'. */
	 unsigned char sin_zero[8]; /* 8 bytes unused padding data, always set be zero */
};

ipv6对应的sockaddr_in6类型定义:

typedef unsigned short sa_family_t;
typedef uint16_t in_port_t;

struct in6_addr{
 union{
 	 uint8_t __u6_addr8[16];
	 uint16_t __u6_addr16[8];
	 uint32_t __u6_addr32[4];
 } __in6_u;
}
 
struct sockaddr_in6 {
	 sa_family_t sin6_family; /*2B*/
	 in_port_t sin6_port; /*2B*/
	 uint32_t sin6_flowinfo; /*4B*/
	 struct in6_addr sin6_addr; /*16B*/
	 uint32_t sin6_scope_id; /*4B*/
};

(3)listen()函数

socket()函数创建的socket默认是一个主动类型的,如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,该函数将socket变为被动类型的,等待客户的连接请求。

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);
  • sockefd:
    socket()系统调用创建的要监听的socket描述字。
  • backlog:
    相应socket可以在内核里排队的最大连接个数。

(4)accept()函数

accept函数的返回值是由内核自动生成的一个全新的描述字(fd),代表与返回客户的TCP连接。如果想发送数据给该客户端,则我们可以调用write()等函数往该fd里写内容即可;而如果想从该客户端读内容则调用read()等函数从该fd里读数据即可。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个新的socket描述字,当服务器完成了对某个客户的服务,就应当把该客户端相应的的socket描述字关闭。

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • sockfd:
    服务器开始调用socket()函数生成的,称为监听socket描述字;
  • *addr:
    用于返回客户端的协议地址,这个地址里包含有客户端的IP和端口信息等;
  • addrlen:
    返回客户端协议地址的长度

(5)connect()函数

TCP客户端程序调用socket()创建socket fd之后,就可以调用connect()函数来连接服务器。如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求并使accept()返回,accept()返回的新的文件描述符就是对应到该客户的TCP连接,通过这两个文件描述符(客户端connect的fd和服务器端accept返回的fd)就可以实现客户端和服务器端的相互通信。

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd: 客户端的socket()创建的描述字
  • addr: 要连接的服务器的socket地址信息,这里面包含有服务器的IP地址和端口等信息
  • addrlen: socket地址的长度

(6)close()函数

close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。

#include <unistd.h>

int close(int fd);

3- 客户端-服务器代码编写

客户端和服务器端代码都是在虚拟机测试,所以测试的时候没有加IP地址,默认是主机ip地址,大家想要分开测试的话可以使用tcp_test_tools工具单独测试,那个时候就需要指定IP地址了。

代码中命令行参数解析函数不了解的可以参考一下这篇文章:浅谈linux的命令行解析参数之getopt_long函数

如果是不同的电脑一定需要在一个网段内才可以连接的。

服务器:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>

#define     MSG_STR    "My name is server! Nice to meet you!\n"
#define     BACKLOG    13
int main(int argc, char *argv[])
{
	int                   sockfd = -1;
	int                   client_fd = -1;
	int                   rv = -1;
	int                   port;
	char                  *IP;
	int                   new_fd = -1;
	char                  buf[1024];
	struct sockaddr_in    server_addr;
	struct sockaddr_in    client_addr;
	int                   ch = -1;
	socklen_t             addr_len;
	pid_t                   pid = -1;
	static struct option long_options[] = {
		{"ipaddr", required_argument, 0, 'i'},
		{"port", required_argument, 0, 'p'},
		{"help", no_argument, 0, 'h'},
		{0, 0, 0, 0}
	};
	void usage( char *program_name)
	{
		printf("%s usage: \n", program_name);

		printf("-i(--ipaddr): server IP address \n");
		printf("-p(--port):server Port \n");
		printf("-h(--help): For help \n");
		return ;
	}
	while((ch = getopt_long(argc, argv, "i:p:h", long_options, NULL)) != -1)
	{
		switch(ch)
		{
			case 'i':
				{
					IP = optarg;
					break;
				}
			case 'p':
				{
					port = atoi(optarg);	
					break;
				}
			case 'h':
				{
					usage(argv[0]);
					return 0;
				}
		}
	}

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd < 0)
	{
		printf("Creat socket failure: %s\n", strerror(errno));
		return -1;
	}
	printf("Creat sockfd[%d] successfully!\n", sockfd);

	memset(&server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(port);
	//	inet_aton(IP, &server_addr.sin_addr);
	/*
	   这里通过调用两个函数 htons() 和 htolnl() 分别用来将 端口和IP地址转换成网络字节序,这两个函数名中的 h表示host, n表
	   示network, s表示short(2字节/16位), l表示long(4字节/32位)。因为端口号是16位的,所以我们用htons()把端口号从主机字节
	   序转换成网络字节序, 而IP地址是32位的,所以我们用htonl()函数把IP地址从主机字节序转换成网络字节序。INADDR_ANY
	   就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。 一般来说,在各个系统中均
	   定义成为0值。这里也就意味着监听所有的IP地址。
	   */
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int opt = 1;
	setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

	rv = bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
	if(rv < 0)
	{
		printf("Bind socket failure: %s\n", strerror(errno));
		return -2;
	}
	printf("Bind socket[%d] successfully!\n", port);

	listen(sockfd, BACKLOG);
	printf("Start to listen client[%d] \n", port);
	printf("Waiting to client come to connect...\n");
	new_fd = accept(sockfd, (struct sockaddr*)&client_addr, &addr_len);
	if(new_fd < 0)
	{
		printf("Accept client failure: %s\n", strerror(errno));
		return -3;
	}
	printf("Accept client[%s:%d] successfully!\n", inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));

	while(1)
	{
		memset(buf, 0, sizeof(buf));
		rv = read(new_fd, buf, sizeof(buf));
		if (rv < 0)
		{
			printf("read client[%s:%d] failure: %s\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), strerror(errno));
			close(new_fd);
			continue;
		}
		if(rv == 0)
		{
			printf("Read client disconnected!: %s\n", strerror(errno));
			close(new_fd);
			continue;
		}
		printf("Read %d bytes from client[%s:%d]: %s\n", rv, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port),  buf);

		rv = write(new_fd, MSG_STR, strlen(MSG_STR));
		if(rv < 0)
		{
			printf("Write MSG_STR failure to client: %s\n", strerror(errno));
			continue;
		}
		printf("Write MSG_STR to client[%s:%d] successfully!\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
		sleep(3);
	}
	close(new_fd);
	return 0;
}

客户端:

#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<string.h>
#include<errno.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>

#define     MSG_STR    "My name is client! Nice to meet you!\n"

int main(int argc, char *argv[])
{
	int                   sockfd = -1;
	int                   client_fd = -1;
	int                   rv = -1;
	int                   port;
	char                  *IP;
	char                  buf[1024];
	struct sockaddr_in    server_addr;
	int                   ch = -1;
	static struct option long_options[] = {
		{"ipaddr", required_argument, 0, 'i'},
		{"port", required_argument, 0, 'p'},
		{"help", no_argument, 0, 'h'},
		{0, 0, 0, 0}
	};
	void usage( char *program_name)
	{
		printf("%s usage: \n", program_name);
		printf("-i(--ipaddr): server IP address \n");
		printf("-p(--port):server Port \n");
		printf("-h(--help): For help \n");
		return ;
	}
	while((ch = getopt_long(argc, argv, "i:p:h", long_options, NULL)) != -1)
	{
		switch(ch)
		{
			case 'i':
			{
				IP = optarg;
				break;
			}
			case 'p':
			{
			        port = atoi(optarg);	
				break;
			}
			case 'h':
			{
				usage(argv[0]);
				return 0;
			}
		}
	}

	sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建一个socket描述符
	if(sockfd < 0)
	{
		printf("Creat socket failure: %s\n", strerror(errno));
		return -1;
	}
	printf("Creat sockfd[%d] successfully!\n", sockfd);
	
	memset(&server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = AF_INET;//AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合
	server_addr.sin_port = htons(port);//将主机字节序转换为网络子节序,h(host),s(short),n(network)
	inet_aton(IP, &server_addr.sin_addr);//调用 inet_aton() 函数将点分十进制字符串转换成 32位整形类型

	client_fd = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
	if(client_fd < 0)
	{
		printf("Connect socket failure: %s\n", strerror(errno));
		return -2;
	}
	printf("Connect server[%s:%d] successfully!\n", IP, sockfd);
	
	while(1)
	{
		rv = write(sockfd, MSG_STR, strlen(MSG_STR));
		if(rv < 0)
		{
			printf("Write MSG_STR failure to server[%s:%d]\n", IP, sockfd);
			break;
		}
		printf("Write MSG_STR to server[%s:%d] successfully!\n",IP, sockfd);
		
		memset(buf, 0, sizeof(buf));
		rv = read(sockfd, buf, sizeof(buf));
		if (rv < 0)
		{
			printf("read sever[%s:%d] failure: %s\n", IP, sockfd, strerror(errno));
			return -4; 
		}
		if(rv == 0)
		{
			printf("Read server disconnected!: %s\n", strerror(errno));
			return -5;
		}
		printf("Read %d bytes from sever[%s:%d]: %s\n", rv, IP, sockfd, buf);
		sleep(3);
	}
	close(sockfd);
}

结果:
在这里插入图片描述
在这里插入图片描述


目 录 译者序 译者简介 前言 第1章 UNIX基础知识 1 1.1 引言 1 1.2 登录 1 1.2.1 登录名 1 1.2.2 shell 1 1.3 文件和目录 2 1.3.1 文件系统 2 1.3.2 文件名 2 1.3.3 路径名 2 1.3.4 工作目录 4 1.3.5 起始目录 4 1.4 输入和输出 5 1.4.1 文件描述符 5 1.4.2 标准输入、标准输出和标准 出错 5 1.4.3 不用缓存的I/O 5 1.4.4 标准I/O 6 1.5 程序和进程 7 1.5.1 程序 7 1.5.2 进程和进程ID 7 1.5.3 进程控制 7 1.6 ANSI C 9 1.6.1 函数原型 9 1.6.2 类属指针 9 1.6.3 原始系统数据类型 10 1.7 出错处理 10 1.8 用户标识 11 1.8.1 用户ID 11 1.8.2 组ID 12 1.8.3 添加组ID 12 1.9 信号 12 1.10 UNIX时间值 14 1.11 系统调用和库函数 14 1.12 小结 16 习题 16 第2章 UNIX标准化及实现 17 2.1 引言 17 2.2 UNIX标准化 17 2.2.1 ANSI C 17 2.2.2 IEEE POSIX 18 2.2.3 X/Open XPG3 19 2.2.4 FIPS 19 2.3 UNIX实现 19 2.3.1 SVR4 20 2.3.2 4.3+BSD 20 2.4 标准和实现的关系 21 2.5 限制 21 2.5.1 ANSI C限制 22 2.5.2 POSIX限制 22 2.5.3 XPG3限制 24 2.5.4 sysconf、pathconf 和fpathconf 函数 24 2.5.5 FIPS 151-1要求 28 2.5.6 限制总结 28 2.5.7 未确定的运行时间限制 29 2.6 功能测试宏 32 2.7 基本系统数据类型 32 2.8 标准之间的冲突 33 2.9 小结 34 习题 34 第3章 文件I/O 35 3.1 引言 35 3.2 文件描述符 35 3.3 open函数 35 3.4 creat函数 37 3.5 close函数 37 3.6 lseek函数 38 3.7 read函数 40 3.8 write函数 41 3.9 I/O的效率 41 3.10 文件共享 42 3.11 原子操作 45 3.11.1 添加至一个文件 45 3.11.2 创建一个文件 45 3.12 dup和dup2函数 46 3.13 fcntl函数 47 3.14 ioctl函数 50 3.15 /dev/fd 51 3.16 小结 52 习题 52 第4章 文件和目录 54 4.1 引言 54 4.2 stat, fstat和lstat函数 54 4.3 文件类型 55 4.4 设置-用户-ID和设置-组-ID 57 4.5 文件存取许可权 58 4.6 新文件和目录的所有权 60 4.7 access函数 60 4.8 umask函数 62 4.9 chmod和fchmod函数 63 4.10 粘住位 65 4.11 chown, fchown和 lchown函数 66 4.12 文件长度 67 4.13 文件截短 68 4.14 文件系统 69 4.15 link, unlink, remove和rename 函数 71 4.16 符号连接 73 4.17 symlink 和readlink函数 76 4.18 文件的时间 76 4.19 utime函数 78 4.20 mkdir和rmdir函数 79 4.21 读目录 80 4.22 chdir, fchdir和getcwd函数 84 4.23 特殊设备文件 86 4.24 sync和fsync函数 87 4.25 文件存取许可权位小结 88 4.26 小结 89 习题 89 第5章 标准I/O库 91 5.1 引言 91 5.2 流和FILE对象 91 5.3 标准输入、标准输出和标准出错 91 5.4 缓存 91 5.5 打开流 94 5.6 读和写流 96 5.6.1 输入函数 96 5.6.2 输出函数 97 5.7 每次一行I/O 98 5.8 标准I/O的效率 99 5.9 二进制I/O 100 5.10 定位流 102 5.11 格式化I/O 103 5.11.1 格式化输出 103 5.11.2 格式化输入 103 5.12 实现细节 104 5.13 临时文件 105 5.14 标准I/O的替代软件 108 5.15 小结 108 习题 108 第6章 系统数据文件和信息 110 6.1 引言 110 6.2 口令文件 110 6.3 阴影口令 112 6.4 组文件 113 6.5 添加组ID 114 6.6 其他数据文件 115 6.7 登录会计 116 6.8 系统标识 116 6.9 时间和日期例程 117 6.10 小结 121 习题 121 第7章 UNIX进程的环境 122 7.1 引言 122 7.2 main 函数 122 7.3 进程终止 122 7.3.1 exit和_exit函数 122 7.3.2 atexit函数 124 7.4 命令行参数 125 7.5 环境表 126 7.6 C程序的存储空间布局 126 7.7 共享库 127 7.8 存储器分配 128 7.9 环境变量 130 7.10 setjmp 和longjmp函数 132 7.10.1 自动、寄存器和易失变量 134 7.10.2 自动变量的潜在问题 136 7.11 getrlimit 和setrlimit函数 136 7.12 小结 139 习题 140 第8章 进程控制 141 8.1 引言 141 8.2 进程标识 141 8.3 fork函数 142 8.4 vfork 函数 145 8.5 exit函数 147 8.6 wait和waitpid函数 148 8.7 wait3和wait4函数 152 8.8 竞态条件 153 8.9 exec函数 156 8.10 更改用户ID和组ID 160 8.10.1 setreuid 和setregid函数 162 8.10.2 seteuid和 setegid函数 163 8.10.3 组ID 163 8.11 解释器文件 164 8.12 system函数 167 8.13 进程会计 171 8.14 用户标识 175 8.15 进程时间 176 8.16 小结 178 习题 178 第9章 进程关系 180 9.1 引言 180 9.2 终端登录 180 9.2.1 4.3+BSD终端登录 180 9.2.2 SVR4终端登录 182 9.3 网络登录 182 9.3.1 4.3+BSD网络登录 182 9.3.2 SVR4网络登录 183 9.4 进程组 183 9.5 对话期 184 9.6 控制终端 185 9.7 tcgetpgrp 和tcsetpgrp函数 187 9.8 作业控制 187 9.9 shell执行程序 189 9.10 孤儿进程组 193 9.11 4.3+BSD实现 195 9.12 小结 197 习题 197 第10章 信号 198 10.1 引言 198 10.2 信号的概念 198 10.3 signal函数 203 10.3.1 程序起动 205 10.3.2 进程创建 206 10.4 不可靠的信号 206 10.5 中断的系统调用 207 10.6 可再入函数 209 10.7 SIGCLD语义 211 10.8 可靠信号术语和语义 213 10.9 kill和raise函数 213 10.10 alarm和pause函数 214 10.11 信号集 219 10.12 sigprocmask 函数 220 10.13 sigpending函数 222 10.14 sigaction函数 223 10.15 sigsetjmp 和siglongjmp函数 226 10.16 sigsuspend函数 229 10.17 abort函数 234 10.18 system函数 235 10.19 sleep函数 240 10.20 作业控制信号 241 10.21 其他特征 243 10.21.1 信号名字 243 10.21.2 SVR4信号处理程序的附 加参数 244 10.21.3 4.3+BSD信号处理程序的附 加参数 244 10.22 小结 244 习题 244 第11章 终端I/O 246 11.1 引言 246 11.2 综述 246 11.3 特殊输入字符 250 11.4 获得和设置终端属性 254 11.5 终端选择标志 254 11.6 stty命令 258 11.7 波特率函数 259 11.8 行控制函数 260 11.9 终端标识 260 11.10 规范方式 263 11.11 非规范方式 266 11.12 终端的窗口大小 270 11.13 termcap, terminfo和 curses 271 11.14 小结 272 习题 272 第12章 高级I/O 273 12.1 引言 273 12.2 非阻塞I/O 273 12.3 记录锁 275 12.3.1 历史 276 12.3.2 fcntl记录锁 276 12.3.3 锁的隐含继承和释放 280 12.3.4 4.3+BSD的实现 281 12.3.5 建议性锁和强制性锁 284 12.4 流 288 12.4.1 流消息 289 12.4.2 putmsg和putpmsg函数 290 12.4.3 流ioctl操作 291 12.4.4 write至流设备 294 12.4.5 写方式 294 12.4.6 getmsg和getpmsg函数 294 12.4.7 读方式 295 12.5 I/O多路转接 296 12.5.1 select函数 298 12.5.2 poll函数 301 12.6 异步I/O 303 12.6.1 SVR4 303 12.6.2 4.3+BSD 303 12.7 readv和writev函数 304 12.8 readn和writen函数 306 12.9 存储映射I/O 307 12.10 小结 311 习题 311 第13章 精灵进程 312 13.1 引言 312 13.2 精灵进程的特征 312 13.3 编程规则 313 13.4 出错记录 314 13.4.1 SVR4流log驱动程序 315 13.4.2 4.3+BSD syslog设施 316 13.5 客户机-服务器模型 319 13.6 小结 319 习题 319 第14章 进程间通信 320 14.1 引言 320 14.2 管道 320 14.3 popen和pclose函数 325 14.4 协同进程 330 14.5 FIFO 333 14.6 系统V IPC 335 14.6.1 标识符和关键字 336 14.6.2 许可权结构 337 14.6.3 结构限制 337 14.6.4 优点和缺点 337 14.7 消息队列 338 14.8 信号量 342 14.9 共享存储 346 14.10 客户机-服务器属性 351 14.11 小结 353 习题 353 第15章 高级进程间通信 355 15.1 引言 355 15.2 流管道 355 15.3 传送文件描述符 358 15.3.1 SVR4 360 15.3.2 4.3BSD 361 15.3.3 4.3+BSD 364 15.4 open服务器第1版 366 15.5 客户机-服务器连接函数 371 15.5.1 SVR4 372 15.5.2 4.3+BSD 375 15.6 open服务器第2版 378 15.7 小结 385 习题 385 第16章 数据库函数库 386 16.1 引言 386 16.2 历史 386 16.3 函数库 386 16.4 实现概述 388 16.5 集中式或非集中式 390 16.6 并发 391 16.6.1 粗锁 391 16.6.2 细锁 391 16.7 源码 392 16.8 性能 409 16.8.1 单进程的结果 410 16.8.2 多进程的结果 410 16.9 小结 412 习题 412 第17章 与PostScript打印机通信 413 17.1 引言 413 17.2 PostScript通信机制 413 17.3 假脱机打印 415 17.4 源码 417 17.5 小结 434 习题 434 第18章 调制解调器拨号器 435 18.1 引言 435 18.2 历史 435 18.3 程序设计 436 18.4 数据文件 437 18.5 服务器设计 439 18.6 服务器源码 439 18.7 客户机设计 463 18.7.1 终端行规程 463 18.7.2 一个进程还是两个进程 464 18.8 客户机源码 465 18.9 小结 474 习题 474 第19章 伪终端 476 19.1 引言 476 19.2 概述 476 19.2.1 网络登录服务器 477 19.2.2 script程序 478 19.2.3 expect程序 479 19.2.4 运行协同进程 479 19.2.5 观看长时间运行程序的输出 479 19.3 打开伪终端设备 480 19.3.1 SVR4 481 19.3.2 4.3+BSD 482 19.4 pty_fork函数 484 19.5 pty程序 486 19.6 使用pty程序 489 19.6.1 utmp文件 489 19.6.2 作业控制交互 489 19.6.3 检查长时间运行程序的输出 491 19.6.4 script程序 491 19.6.5 运行协同进程 492 19.6.6 用非交互模式驱动交互式 程序 492 19.7 其他特性 494 19.7.1 打包模式 494 19.7.2 远程模式 494 19.7.3 窗口大小变化 495 19.7.4 信号发生 495 19.8 小结 495 习题 495 附录A 函数原型 497 附录B 其他源代码 512 附录C 习题答案 518 参考书目 536
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值