网络编程套接字编程-代码实现-复习总结

一、客户库例程设计

源文件 connectsock.c 中定义的函数 connectsock 分配套接字和连接该套接字,该函数通常作为库例程被其他程序调用(如 UDPecho.c 和 TCPecho.c 等)。

背景知识

库例程:例程的作用类似于函数,但含义更为丰富一些。例程是某个系统对外提供的功能接口或服务的集合。故一个库例程可以理解为一个库函数,可以方便地被别的程序调用的库函数。

IPv4 的 sockaddr_in 结构体:指明了端点地址,其包括用来识别地址类型2 字节字段(必须为AF_INET),还有一个 2 字节端口号字段,一个 4 字节的 具体 IP 地址的字段,还有一个未使用的 8 字节字段。

struct in_addr
{
    in_addr_t          s_addr;  //表示32位的IP地址,32位无符号整型
}

struct sockaddr_in
{
    uint8_t            sin_len;       //表示该结构体的长度,8位无符号整型
    sa_family_t        sin_family;    //表示套接口使用的协议族,8位无符号整型
    in_port_t          sin_port;      //表示套接口使用的端口号,16位无符号整型
    struct in_addr     sin_addr;      //表示IP地址,32位无符号整型
    char               sin_zero[8];   //该成员基本不使用,总是置为0
}

IPv6 的 sockaddr_in6 结构体

struct in6_addr
{
    unit8_t              s6_addr[16]; //表示128位的IP地址,这里采用数组的形式,点分十进制
}

struct sockaddr_in6
{
    uint8_t              sin6_len;         //表示该结构体的长度,8位无符号整型
    sa_family_t          sin6_family;      //表示套接口使用的协议族,8位无符号整型
    in_port_t            sin_port;         //表示套接口使用的端口号,16位无符号整型
    uint32_t             sin_flowinfo;     //低序20位是流标签,高序12位保留
    struct in6_addr      sin6_addr         //表示128位的IP地址,16进制
    uint32_t             sin6_scope_id;    //标识对于具备范围的地址而言有意义的范围
}

family 参数根据协议族的不同,选择 AF_INET 或 AF_INET6。

通用套接口地址结构

struct sockaddr
{
    uint8_t        sa_len;
    sa_family_t    sa_family;
    char           sa_data[14]; //表示14字节的协议地址
}

**新的通用套接口地址结构:**兼容 IPv6 地址

struct sockaddr_storage
{
    uint8_t       ss_len; //表示该结构的长度
    sa_family_t   ss_family; //表示协议族
    char          __ss_padding[_SS_PADSIZE];          
}

前言知识

考试中的库例程程序相当于把复习资料里的示例程序更加抽象化了。

addrinfo 结构体

包含在 netdb.h 中,主要在网络编程解析时使用。

struct addrinfo {
    int ai_flags;   /* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */
    int ai_family;  /* PF_xxx */
    int ai_socktype;    /* SOCK_xxx */
    int ai_protocol;    /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
    socklen_t ai_addrlen;   /* ai_addr的长度 */
    char    *ai_canonname;  /* 主机名的规范名称 */
    struct  sockaddr *ai_addr;  /* 二进制地址 */
    struct  addrinfo *ai_next;  /* 链接列表中的下一个结构 */
};

getaddrinfo()

getaddrinfo(const char *restrict nodename, const char *restrict servname, const struct addrinfo *restrict hints, struct addrinfo **restrict res)

返回值0表示成功,非0表示错误。

该方法是在 IPv6 中引入,协议无关,即可用于 IPv4 也可用于 IPv6。getaddrinfo() 函数可以处理名称到地址以及服务到端口的这两种转换,返回的是一个 struct addrinfo 的结构体指针而不是一个地址清单。

  • nodename 参数:主机名,或者是点分十进制地址串(IPv4),或16进制串(IPv6)

  • servname 参数:服务名,可以是端口号,也可以是已定义的服务名称如“https”等

  • hints 参数:指向用户指定的 struct addrinfo 结构体,只能设定其中 ai_family, ai_socktype, ai_protocol 和 ai_flags 四个域,其他域必须为 0 或者是 NULL。其中:

    • ai_family:指定返回地址的协议簇,AF_INET(IPv4), AF_INET6(IPv6), AF_UNSPEC(IPv4 and IPv6)

    • ai_socktype:用于设定返回地址的 socket 类型,常用有 SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, 设置为0表示所有类型等

    • ai_protocol:指定协议,常用有 IPPROTO_TCP、IPPROTO_UDP等,设置为 0 表示所有协议

    • ai_flags:附加选项,AI_PASSIVE, AI_CANONNAME等(不考故不赘述

  • res 参数:获取一个指向存储结果的 struct addrinfo 结构体,使用完成后调用 freeaddrinfo() 释放存储结果空间

freeaddrinfo()

释放 addrinfo 结构体动态内存;

freeaddrinfo(struct addrinfo *ai)

  • ai 参数: 即要释放的 addrinfo 结构体指针

socket()

socket 编程需要基于一个文件描述符,即 socket 文件描述符。socket 系统调用就是用来创建 socket 文件描述符。成功返回0,失败返回-1并设置 errno 值。

int socket(int domain, int type, int protocol);

  • domain参数:协议族/协议域
  • type 参数:套接字类型
  • protocol 参数:一般设置为 0,满足所有协议,表示系统会根据 domain 和 type 的值自动选择一个合适的值。

connect()

创建主动套接字的一方(客户端)调用 connect() 系统调用,可建立与被动套接字的一方(服务端)的连接。成功返回0,失败返回-1并设置 errno 值。

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

  • **sockfd 参数:**连接文件描述符

  • addr 参数:指定协议的地址结构体指针

  • addrlen 参数:协议地址结构体长度

close()

close() 一个TCP套接字的默认行为是把该套接字标记为关闭,此后不能再对该文件描述符进行读写操作。TCP协议将尝试发送已排队等待发送到对端的任何数据,发送完毕后发生的是正常的TCP连接终止序列。成功返回0,失败返回-1并设置 errno 值。

int close(int fd)

  • fd 参数:待关闭的 socket 文件描述符

gai_strerror()

char *gai_strerror(int error)

getaddrinfo出错时返回非零值,gai_strerror根据返回的非零值返回指向对应的出错信息字符串的指针。

strerror()

char * strerror(int errnum)

从内部数组中搜索错误号 errnum,并返回一个指向错误消息字符串的指针。

程序代码

注:本篇博客的所有程序都省略了头文件引用

/* connectsock.c - allocate & connect a socket using TCP or UDP */
/* 此处省略预编译指令的#include 部分 */

int connectsock(const char *node, const char *service, const char *transport)
{
	struct addrinfo *result, *resptr; // result保存获取结果,resptr为结果链表指针
	struct addrinfo hints = { 0 };
	int ret, connectsocket, type; // ret记录了getaddrinfo的返回值,connectsocket记录socket文件描述符
	if (strcmp(transport, "udp") == 0)
		type = SOCK_DGRAM;
	else if (strcmp(transport, "tcp") == 0)
		type = SOCK_STREAM;
	else
		errx(1, "connectsock arguments error: unknown transport\n");

	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = type;
	hints.ai_protocol = 0;

	if ((ret = getaddrinfo(node, service, &hints, &result)) != 0) // ret 不等于 0,表示失败
		errx(1, "getaddrinfo error: %s\n", gai_strerror(ret));
	for (resptr = result; resptr != NULL; resptr = resptr->ai_next) {
		if ((connectsocket = socket(resptr->ai_family, resptr->ai_socktype, resptr->ai_protocol)) < 0) {
			ret = errno; // socket 系统调用用来创建 socket 文件描述符,成功返回0,否则-1
		}
		else if ((connect(connectsocket, resptr->ai_addr, resptr->ai_addrlen)) < 0) {
			ret = errno; //connect可以建立与被动套接字的一方的连接,成功返回0,否则-1
			close(connectsocket); //出错则关闭该连接
		}
		else {
			freeaddrinfo(result); // 释放动态内存
			return connectsocket; // 返回结果
		}
	}
	
	errno = ret;
	errx(1, "can't connect to %s.%s: %s\n", node, service, strerror(errno));
	exit(1);
}

注意:return 函数有没有括号 “()” 都是正确的,为了简明,一般省略不写

二、针对 DAYTIME 服务的 TCP 客户实现

源文件 TCPdaytime.c 包含针对 DAYTIME 服务的简单 TCP 客户程序。本题程序调用了自定义例程库函数 connectsock 分配套接口和连接该套接口。

前言知识

read()

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

read() 会把参数 fd 所指的文件传送 count 个字节到 buf 指针所指的内存中。若参数count 为0,则 read() 不会有作用并返回 0。返回值为实际读取到的字节数,如果返回0,表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动。

程序代码

/* TCPdaytime.c - main, TCPdaytime */
/* 此处省略预编译指令的#include 部分 */

int TCPdaytime(int);
int connectsock(const char *, const char *, const char *);
#define LINELEN 4096  /* 文本行的最大长度 */
int main(int argc, char *argv[])
{
	char *host = "localhost"; /* 如果没有提供,主机使用 */
	char *service = "daytime"; /* 默认服务端口 */
	int sock; /* 套接字文件描述符 */
	switch (argc) {
	case 1:
		host = "localhost";
		break;
	case 3:
		service = argv[2];
		/* 失败 */
	case 2:
		host = argv[1];
		break;
	default:
		errx(1, "usage: %s [host [port]]\n", argv[0]);
	}
	sock = connectsock(host, service, "tcp");
	exit(TCPdaytime(sock));
}
int TCPdaytime(int sd)
{
	char buf[LINELEN+1]; /* 一行文本的缓冲区 */
	int nchars; /* 读取的字符数 */
	while ((nchars = read(sd, buf, LINELEN))) {
		if (nchars < 0)
			err(1, "TCPdaytime read error");
		buf[nchars] = '\0'; /* 确保空值终止 */
		fputs(buf, stdout);
	}
	return 0;
}

三、针对 TIME 服务的 UDP 客户实现

源文件 UDPtime.c 包含针对 TIME 服务的简单 UDP 客户程序。 本题程序调用了自定义例程库函数 connectsock 分配套接口和连接该套接口。

前言知识

write()

size_t write(int flides, const void *buf, size_t nbytes)

write系统调用,将缓存区buf中的前nbytes字节写入到与文件描述符flides有关的文件中,write系统调用返回的是实际写入到文件中的字节数

ntohl()

uint32_t ntohl(uint32_t netlong);

ntohl() 将一个无符号长整形数从网络字节顺序转换为主机字节顺序,返回一个以主机字节顺序表达的数。

localtime()

struct tm *localtime(const time_t *timer)

把从1970-1-1零点零分到当前时间系统所偏移的秒数时间转换为本地时间。

timer 是指向表示日历时间的 time_t 值的指针,timer的值被分解为 tm 结构,并用本地时区表示。

asctime()

char *asctime(const struct tm *timeptr)

把 timeptr 指向的 tm 结构体中储存的时间转换为字符串。

程序代码

/* UDPtime.c - main, UDPtime */
/* 此处省略预编译指令的#include 部分 */

#define BUFFSIZE 8192 /* 缓冲区大小为读取和写入 */
#define UNIXEPOCH 2208988800UL /* UNIX epoch, in UCT secs */
#define MSG "what time is it?\n"
int UDPtime(int);
int connectsock(const char *, const char *, const char *);

int main(int argc, char *argv[])
{
	char *host = "localhost"; /* 如果没有设置主机地址则默认本地 */
	char *service = "time"; /* 默认服务名称即默认端口 */
	int sock; /* 套接字文件描述符 */
	switch (argc) {
	case 1:
		host = "localhost";
		break;
	case 3:
		service = argv[2];
		/* 失败 */
	case 2:
		host = argv[1];
		break;
	default:
		errx(1, "usage: %s [host [port]]\n", argv[0]);
	}
	sock = connectsock(host, service, "udp");
	exit(UDPtime(sock));
}
int UDPtime(int sd)
{
	time_t now; /* 使用 32 位整数来保存时间 */
	int nchars; /* 记录读取的字符数 */
	(void) write(sd, MSG, strlen(MSG));
	nchars = read(sd, (char *)&now, sizeof(now));
	if (nchar < 0)
		err(1, "UDPtime read error");
	now = ntohl((unsigned long)now); /* 转换为主机字节序 */
	now -= UNIXEPOCH; /* UCT转换为UNIX计时 */
	printf("%s", asctime(localtime(&now)));
	return 0;
}

四、针对 ECHO 服务的客户实现

源文件 TCPecho.c 包含针对 ECHO 服务的简单 TCP 客户程序。 源文件 UDPecho.c 包含针对 ECHO 服务的简单 UDP 客户程序。 本题程序调用了自定义例程库函数 connectsock 分配套接口和连接该套接口。

前言知识

fgets()

char *fgets(char *str, int n, FILE *stream)

从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

参数:

  • str:这是指向一个字符数组的指针,该数组存储了要读取的字符串。
  • n:这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。
  • stream:指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。

fputs()

int fputs(const char *str, FILE *stream)

把字符串写入到指定的流 stream 中,但不包括空字符。

参数:

  • str – 这是一个数组,包含了要写入的以空字符终止的字符序列。
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符串的流。

程序代码

TCP

/* TCPecho.c - main, TCPecho */
/* 此处省略预编译指令的#include 部分 */

int TCPecho(int);
int connectsock(const char *, const char *, const char *);
#define LINELEN 128
/* main - ECHO 服务的 TCP 客户端 */
int main(int argc, char *argv[])
{
	char *host = "localhost";
	char *service = "echo";
	int socketd;
	switch (argc) {
	case 1:
		host = "localhost";
		break;
	case 3:
		service = argv[2];
	case 2:
		host = argv[1];
		break;
	default:
		errx(1, "usage: %s [host [port]]\n", argv[0]);
	}
	socketd = connectsock(host, service, "tcp");
	exit(TCPecho(socketd));
}
/* TCPecho - 向指定的主机发送ECHO服务的输入并打印答复 */
int TCPecho(int sock)
{
	char buf[LINELEN+1];
	int cc, inchars, outchars;
	while (fgets(buf, sizeof(buf), stdin)) {
		buf[LINELEN] = '\0';
		outchars = strlen(buf);
		(void) write(sock, buf, outchars);
		for (inchars = 0; inchars < outchars; inchars += cc) {
			cc = read(sock, &buf[inchars], outchars - inchars);
			if (cc < 0)
				err(1, "TCPecho read");
		}
		fputs(buf, stdout);
	}
	return 0;
}

UDP

/* UDPecho.c - main, UDPecho */
/* 此处省略预编译指令的#include 部分 */

int UDPecho(int);
int connectsock(const char *, const char *, const char *);
#define LINELEN 128
/* main - ECHO 服务的 UDP 客户端 */
int main(int argc, char *argv[])
{
	char *host = "localhost";
	char *service = "echo";
	int socketd;
	switch (argc) {
	case 1:
		host = "localhost";
		break;
	case 3:
		service = argv[2];
	case 2:
		host = argv[1];
		break;
	default:
		errx(1, "usage: %s [host [port]]\n", argv[0]);
	}
	socketd = connectsock(host, service, "udp");
	exit(UDPecho(socketd));
}
/* UDPecho - 向指定的主机发送ECHO服务的输入并打印答复 */
int UDPecho(int sock)
{
	char buf[LINELEN+1];
	int cc;
	while (fgets(buf, sizeof(buf), stdin)) {
		buf[LINELEN] = '\0';
		cc = strlen(buf);
		(void) write(sock, buf, cc);
		if (read(sock, buf, cc) < 0)
			err(1, "UDPecho read");
		fputs(buf, stdout);
	}
	return 0;
}

五、服务器库例程实现

源文件 passivesock.c 中定义的函数 passivesock 包含分配和绑定服务器套接口的细节。该函数通常作为库例程被其它程序调用(如 UDPechod.c 和 TCPechod.c 等)。

前言知识

在本篇博客前面部分有讲解到 addrinfo 结构体,其中 hints 参数的 ai_flags 成员没有细说,在服务器库例程中又有涉及,故在此叙述下:

ai_flags参数:

  • AI_PASSIVE:设置了 AI_PASSIVE 标志,则

    • 如果 nodename 是 NULL,那么返回的socket地址可以用于 bind() 函数,返回的地址是通配符地址,这样应用程序(典型是server)就可以使用这个通配符地址用来接收任何请求主机地址的连接;
    • 如果 nodename 不是NULL,那么 AI_PASSIVE 标志被忽略;
    • 如果未设置AI_PASSIVE标志,返回的socket地址可以用于connect(),sendto(),或者 sendmsg() 函数。
    • 如果 nodename 是NULL,那么网络地址会被设置为 lookback 接口地址 (IPv4 时是 INADDR_LOOPBACK,IPv6 时是 IN6ADDR_LOOPBACK_INIT),这种情况下,应用是想与运行在同一个主机上另一个应用通信。
  • AI_CANONNAME:请求canonical(主机的official name)名字。如果设置了该标志,那么 res 返回的第一个 struct addrinfo 中的 ai_canonname 域会存储official name的指针。

  • AI_NUMERICHOST:阻止域名解析。

  • AI_NUMERICSERV:阻止服务名解析。

  • AI_V4MAPPED:当 ai_family 指定为AF_INT6(IPv6)时,如果没有找到IPv6地址,那么会返回IPv4-mapped IPv6 地址。

setsockopt()

int setsockopt(int s, int level, int optname, const void * optval, socklen_toptlen)

函数说明:setsockopt() 用来设置参数 s 所指定的 socket 状态

  • 参数 level 代表欲设置的网络层,一般设成 SOL_SOCKET 以存取 socket 层

  • 参数 optname 代表欲设置的选项,有下列几种数值:

    • SO_DEBUG 打开或关闭排错模式

    • SO_REUSEADDR 允许在 bind () 过程中本地地址可重复使用

    • SO_TYPE 返回 socket 形态

    • SO_ERROR 返回 socket 已发生的错误原因

    • SO_DONTROUTE 送出的数据包不要利用路由设备来传输

    • SO_BROADCAST 使用广播方式传送

    • SO_SNDBUF 设置送出的暂存区大小

    • SO_RCVBUF 设置接收的暂存区大小

    • SO_KEEPALIVE 定期确定连线是否已终止.

    • SO_OOBINLINE 当接收到 OOB 数据时会马上送至标准输入设备

    • SO_LINGER 确保数据安全且可靠的传送出去.

  • 参数 optval 代表欲设置的值

  • 参数 optlen 则为optval 的长度

bind()

int bind(int socket, const struct sockaddr *address, socklen_t address_len)

通过 socket 系统调用创建的文件描述符并不能直接使用,TCP/UDP协议中所涉及的协议、IP、端口等基本要素并未体现,而 bind() 系统调用就是将这些要素与文件描述符关联起来。

  • socket:socket 文件描述符。
  • address:特定协议的地址结构体指针。
    通常,在实际的编程中并不会直接使用结构体 struct sockaddr,而是使用对编程更加友好的的 struct sockaddr_in 或者 struct sockaddr_in6,它为协议IP端口等要素分别定义了字段。
  • address_len:协议地址结构体长度。

listen()

int listen(int socket, int backlog)

使用 socket 系统调用创建一个套接字时,它被假设是一个主动套接字(客户端套接字),而调用 listen() 系统调用就是将这个主动套接字转换成被动套接字,指示内核应接受指向该套接字的连接请求。返回值为 0 则成功,-1 表示失败并设置 errno 值。

  • socket:socket 监听文件描述符。

  • backlog:设置未完成连接队列和已完成连接队列各自的队列长度(注意:不同的系统对该值的解释会存在差异)。
    Linux 系统下,SYN QUEUE 队列长度阈值存放在 /proc/sys/net/ipv4/tcp_max_syn_backlog 文件中,ACCEPT QUEUE 队列长度阈值存放在 /proc/sys/net/core/somaxconn 文件中。两个队列长度的计算公式如下:

    • SYN QUEUE 队列的长度:min(backlog, somaxconn, tcp_max_syn_backlog) + 1 再上取整到 2 的幂次但(最小不能小于16)
      • ACCEPT QUEUE 队列长度:min(backlog, somaxconn)

程序代码

/* passivesock.c - allocate & bind a server socket using TCP or UDP */
/* 此处省略预编译指令的#include 部分 */

int passivesock(const char *service, const char *transport, const int qlen)
{
	struct addrinfo *result, *resptr;
	struct addrinfo hints = { 0 };
	int sockopt = 0;
	int ret, passivesocket, type;
	if (strcmp(transport, "udp") == 0)
		type = SOCK_DGRAM;
	else if (strcmp(transport, "tcp") == 0)
		type = SOCK_STREAM;
	else
		errx(1, "passivesock arguments error: unknown transport\n");

	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = type;
	hints.ai_flags = AI_PASSIVE;
	hints.ai_protocol = 0;

	if ((ret = getaddrinfo(NULL, service, &hints, &result)) != 0)
		errx(1, "getaddrinfo error: %s\n", gai_strerror(ret));
	for (resptr = result; resptr != NULL; resptr = resptr->ai_next) {
		if ((passivesocket = socket(resptr->ai_family, resptr->ai_socktype, resptr->ai_protocol)) < 0)
			ret = errno;
		else if (resptr->ai_family == AF_INET6 && setsockopt(passivesocket, IPPROTO_IPV6, IPV6_V6ONLY, &sockopt, sizeof(sockopt)) < 0) {
			ret = errno;
			close(passivesocket);
		}
		else if (bind(passivesocket, resptr->ai_addr, resptr->ai_addrlen) < 0){
			ret = errno;
			close(passivesocket);
		}
		else if (resptr->ai_socktype == SOCK_STREAM && listen(passivesocket, qlen) < 0){
			ret = errno;
			close(passivesocket);
		}
		else {
			freeaddrinfo(result);
			return passivesocket;
		}
	}
	errno = ret;
	errx(1, "can't allocate & bind %s socket: %s\n", service, strerror(errno));
	exit(1);
}

六、TIME 服务器实现

源文件 UDPtimed.c 包含迭代的、无连接 TIME 服务器所用的代码。 本题程序调用了自定义例程库函数 passivesock 分配和绑定服务器套接口。

前言知识

sockaddr_storage 结构体

struct sockaddr_storage
{
    uint8_t       ss_len; //表示该结构的长度
    sa_family_t   ss_family; //表示协议族
    char          __ss_padding[_SS_PADSIZE];          
}

该结构体即本篇博客开头的背景知识部分所提到的新的通用套接字地址结构体,兼容 IPv6。

recvfrom()

int recvfrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from,int *fromlen)

用于从(已连接)套接口上接收数据,并捕获数据发送源的地址。

  • 参数 s:标识一个已连接套接口的描述字。
  • 参数 buf:接收数据缓冲区。
  • 参数 len:缓冲区长度。
  • 参数 flags:调用操作方式。
  • 参数 from:(可选)指针,指向装有源地址的缓冲区。
  • 参数 fromlen:(可选)指针,指向from缓冲区长度值。

htonl()

uint32 htonl(uint32 hl)

htonl()将主机数转换成无符号长整型的网络字节顺序。本函数将一个32位数从主机字节顺序转换成网络字节顺序。可类比于 ntohl()

sendto()

int sendto ( socket s , const void * msg, int len, unsigned int flags, const struct sockaddr * to , int tolen )

sendto() 指向一指定目的地发送数据,sendto() 适用于发送未建立连接的 UDP 数据包 (参数为 SOCK_DGRAM )。sendto() 用来将数据由指定的 socket 传给对方主机。

  • 参数 s 为已建好连线的 socket,如果利用 UDP 协议则不需经过连线操作

  • 参数 msg 指向欲连线的数据内容

  • 参数 len 数据内容长度

  • 参数 flags 一般设0

  • 参数 to 用来指定欲传送的网络地址

  • 参数 tolen 为 sockaddr 的结构长度

程序代码

/* UDPtimed.c - main, UDPtimed */
/* 此处省略预编译指令的#include 部分 */

int passivesock(const char *, const char *, const int);
void UDPtimed(int);
#define UNIXEPOCH 2208988800UL /* UNIX epoch, in UCT secs */
int main(int argc, char *argv[])
{
	char *service = "time"; /* 服务名称或端口号 */
	int sock; /* 服务器套接字 */
	switch ( argc ) {
	case 1:
		break;
	case 2:
		service = argv[ 1 ];
		break;
	default:
		errx(1, "usage: %s [port]\n", argv[0]);
	}
	sock = passivesock(service, "udp", 0);
	while (1) {
		UDPtimed(sock);
	}
}
void UDPtimed(int sd)
{
	struct sockaddr_storage fsin; /* 一个客户端的请求地址 */
	char buf[1]; /* "input" 缓冲区; 大小大于 0 即可 */
	time_t now; /* 当前时间 */
	socklen_t alen; /* 请求地址的长度 */
	alen = sizeof(fsin);
	if (recvfrom(sd, buf, sizeof(buf), 0, (struct sockaddr *)&fsin, &alen) < 0)
		err(1, "UDPtimed recvfrom error");
	(void) time(&now);
	now = htonl((unsigned long)(now + UNIXEPOCH));
	if (sendto(sd, (char *)&now, sizeof(now), 0, (struct sockaddr *)&fsin, alen) < 0)
		err(1, "UDPtimed sendto");
}

七、DAYTIME 服务器实现

源文件 TCPdaytimed.c 包含迭代的、面向连接的 DAYTIME 服务器所用的代码。 本题程序调用了自定义例程库函数 passivesock 分配和绑定服务器套接口。

前言知识

strlen() 与 sizeof() 区别

strlen 用来求字符串的长度;而 size of 是用来求指定变量或者变量类型等所占内存大小。

accept()

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)

accept() 系统调用将尝试从已完成连接队列的队头中取出一个连接进行服务,因此产生的队列空缺将从未完成连接队列中取出一个进行补充。若此时已完成连接队列为空,且 socket 文件描述符为默认的阻塞模式,那么进程将被挂起。成功返回已连接套接字文件描述符,否则返回 -1。

  • socket:socket 监听文件描述符。
  • addr:已连接的对端进程的协议地址。
    若不关注对端信息,可设置为NULL。与 bind 系统调用的参数类似,在实际编程中会使用对编程更加友好的的 struct sockaddr_in 或者 struct sockaddr_in6 的指针作为入参。
  • addrlen:地址结构体长度的指针。addr 参数设置为 NULL 时,可设置为 NULL 。

程序代码

/* TCPdaytimed.c - main, TCPdaytimed */
/* 此处省略预编译指令的#include 部分 */

void TCPdaytimed(int);
int passivesock(const char *, const char *, const int);
#define QLEN 32 /* 连接队列的最大长度 */
int main(int argc, char *argv[])
{
	struct sockaddr_storage fsin; /* 客户端的请求地址 */
	char *service = "daytime"; /* 服务名称或端口号 */
	int msock, ssock; /* 主从服务器之间套接字 */
	socklen_t alen; /* 请求地址长度 */
	switch (argc) {
	case 1:
		break;
	case 2:
		service = argv[1];
		break;
	default:
		errx(1, "usage: %s [port]\n", argv[0]);
	}
	msock = passivesock();
	while (1) {
		alen = sizeof(fsin);
		ssock = accept(msock, (struct sockaddr *)&fsin, &alen);
		if (ssock < 0)
			err(1, "accept error");
		TCPdaytimed(ssock);
		(void) close(ssock);
	}
}
void TCPdaytimed(int sd)
{
	char *pts; /* 时间字符串的指针 */
	time_t now; /* 当前时间 */
	(void) time(&now);
	pts = asctime(localtime(&now)); // 这些函数上文都有解释
	(void) write(sd, pts, strlen(pts));
}

八、ECHO 服务器实现

源文件 TCPechod.c 包含 ECHO 服务器的代码,它使用并发进程为多个客户提供并发服务。 源文件 UDPechod.c 包含 ECHO 服务器的代码,它使用迭代的、无连接算法为多个客户提 供服务。 本题程序调用了自定义例程库函数 passivesock 分配和绑定服务器套接口。

前沿知识

fork()

pid_t fork( void)

返回值: 若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1。

一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程。fork 函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回 0 值而父进程中返回子进程 ID。
子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本,即存储空间的“副本”,意味着父子进程间不共享这些存储空间。子进程有了独立的地址空间,故无法确定fork之后是子进程先运行还是父进程先运行,这依赖于系统的实现。

程序代码

TCP

/* TCPechod.c - main, TCPechod */
/* 此处省略预编译指令的#include 部分 */

#define QLEN 32
#define BUFSIZE 4096
void reaper(int);
int TCPechod(int);
int passivesock(const char *, const char *, const int);
/* main - 并发TCP服务器的ECHO服务 */
int main(int argc, char *argv[])
{
	char *service = "echo";
	struct sockaddr_storage fromsin;
	socklen_t alen;
	int msocket, ssocket;
	switch (argc) {
	case 1:
		break;
	case 2:
		service = argv[1];
		break;
	default:
		errx(1, "usage: %s [port]\n", argv[0]);
	}
	msocket = passivesock(service, "tcp", QLEN);
	(void) signal(SIGCHLD, reaper);
	while (1) {
		alen = sizeof(fromsin);
		ssocket = accept(msock, (struct sockaddr *)&fromsin, &alen);
		if (ssocket < 0) {
			if (errno == EINTR)
				continue;
			err(1, "accept");
		}
		switch (fork()) {
		case 0:
			(void) close(msock);
			exit(TCPechod(ssock));
		default:
			(void) close(ssock);
			break;
		case -1:
			err(1, "fork");
		}
	}
}
/* TCPechod - 在给定的套接字上执行 TCP ECHO */
int TCPechod(int sock)
{
	char buf[BUFSIZE];
	int cc;
	while ((cc = read(sock, buf, sizeof(buf)))) {
		if (cc < 0)
			err(1, "TCPechod read");
		if (write(sock, buf, cc) < 0) // cc为 read 读取到的字符数
			err(1, "TCPechod write");
	}
	return 0;
}
/* reaper - clean up zombie children */
void reaper(int sig)
{
	int status;
	while (waitpid((pid_t)-1, &status, WNOHANG) >= 0);
}

UDP

/* UDPechod.c - main */
/* 此处省略预编译指令的#include 部分 */

#define LINELEN 128
int UDPechod(int);
int passivesock(const char *, const char *, const int);
/* main - 迭代UDP服务器为ECHO服务 */
int main(int argc, char *argv[])
{
	char *service = "echo";
	int socketd;
	switch (argc) {
	case 1:
		break;
	case 2:
		service = argv[1];
		break;
	default:
		errx(1, "usage: %s [port]\n", argv[0]);
	}
	socketd = passivesock(service, "UDP", 0);
	while (1) {
		UDPechod(socketd);
	}
}
/* UDPechod - 在给定的套接字上执行 UDP ECHO */
int UDPechod(int sock)
{
	struct sockaddr_storage fromsin;
	char buf[LINELEN+1];
	socklen_t alen;
	alen = sizeof(fromsin);
	if (recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *)&fromsin, &alen) < 0)
		err(1, "UDPechod recvfrom");
	(void) sendto(sock, (char *)&buf, sizeof(buf), 0, (struct sockaddr *)&fromsin, alen);
	return 0;
}

九、多线程 ECHO 服务器实现

源文件 TCPmtechod.c 包含多线程 ECHO 服务器所用的代码,它使用并发的、面向连接的 算法为多个客户提供并发服务。 本题程序调用了自定义例程库函数 passivesock 分配和绑定服务器套接口。

前言知识

程序代码

/* TCPmtechod.c - main, TCPechod, prstats */
/* 此处省略预编译指令的#include 部分 */

#define QLEN 32 /* 连接队列长度最大值 */
#define BUFSIZE 8192 /* 读写缓冲区最大值 */
#define INTERVAL 5 /* secs */
struct {
	pthread_mutex_t st_mutex;
	unsigned int st_concount;
	unsigned int st_contotal;
	unsigned long st_contime;
	unsigned long st_bytecount;
} stats;
void prstats(void);
int TCPechod(int);
int passivesock(const char *, const char *, const int);
int main(int argc, char *argv[])
{
	pthread_t th;
	pthread_attr_t ta;
	int ret;
	char *service = "echo"; /* 服务名或者端口号 */
	struct sockaddr_storage fsin; /* 客户端请求地址 */
	socklen_t alen; /* 客户端地址长度 */
	int msock; /* 主套接字 */
	int ssock; /* 从套接字 */
	switch (argc) {
	case 1:
		break;
	case 2:
		service = argv[1];
		break;
	default:
		errx(1, "usage: %s [port]\n", argv[0]);
	}
	msock = passivesock(service, "tcp", QLEN);
	(void) pthread_attr_init(&ta);
	(void) pthread_attr_setdetachstate(&ta, PTHREAD_CREATE_DETACHED);
	(void) pthread_mutex_init(&stats.st_mutex, 0);
	if ((ret = pthread_create(&th, &ta, (void * (*)(void *))prstats, 0)) < 0)
	errx(1, "pthread_create(prstats): %s\n", strerror(ret));
	while (1) {
		alen = sizeof(fsin);
		ssock = accept(msock, (struct sockaddr *)&fsin, &alen);
		if (ssock < 0) {
			if (errno == EINTR)
				continue;
			err(1, "accept error");
		}
		if ((ret = pthread_create(&th, &ta, (void * (*)(void *))TCPechod, (void *)ssock)) < 0)
			errx(1, "pthread_create: %s\n", strerror(ret));
	}
}
int TCPechod(int sd)
{
	time_t start;
	char buf[BUFSIZE];
	int cc;
	start = time(0);
	(void) pthread_mutex_lock(&stats.st_mutex);
	stats.st_concount++;
	(void) pthread_mutex_unlock(&stats.st_mutex);
	while ((cc = read(sd, buf, sizeof buf))) {
		if (cc < 0)
			err(1, "TCPechod read error");
		if (write(sd, buf, cc) < 0)
			err(1, "TCPechod write error");
		(void) pthread_mutex_lock(&stats.st_mutex);
		stats.st_bytecount += cc;
		(void) pthread_mutex_unlock(&stats.st_mutex);
	}
	(void) close(sd);
	(void) pthread_mutex_lock(&stats.st_mutex);
	stats.st_contime += time(0) - start;
	stats.st_concount--;
	stats.st_contotal++;
	(void) pthread_mutex_unlock(&stats.st_mutex);
	return 0;
}
void prstats(void)
{
	time_t now;
	while (1) {
		(void) sleep(INTERVAL);
		(void) pthread_mutex_lock(&stats.st_mutex);
		now = time(0);
		(void) printf("--- %s", ctime(&now));
		(void) printf("%-32s: %u\n", "Current connections", stats.st_concount);
		(void) printf("%-32s: %u\n", "Completed connections", stats.st_contotal);
		if (stats.st_contotal) {
			(void) printf("%-32s: %.2f (secs)\n",
			"Average complete connection time",
			(float)stats.st_contime /
			(float)stats.st_contotal);
			(void) printf("%-32s: %.2f\n",
			"Average byte count",
			(float)stats.st_bytecount /
			(float)(stats.st_contotal +
			stats.st_concount));
		}
		(void) printf("%-32s: %lu\n\n", "Total byte count", stats.st_bytecount);
		(void) pthread_mutex_unlock(&stats.st_mutex);
	}
}

十、单线程并发 ECHO 服务器实现

源文件 TCPmechod.c 包含单线程并发 ECHO 服务器所用的代码,它使用并发的、面向连 接的算法为多个客户提供并发服务。 本题程序调用了自定义例程库函数 passivesock 分配和绑定服务器套接口。

前言知识

程序代码

/* TCPmechod.c - main, echo */
/* 此处省略预编译指令的#include 部分 */

#define QLEN 32 /* 连接队列的最大长度 */
#define BUFSIZE 8192 /* 读写缓冲区大小 */
int passivesock(const char *, const char *, const int);
int echo(int);
int main(int argc, char *argv[])
{
	char *service = "echo"; /* 服务名称或端口号 */
	struct sockaddr_storage fsin; /* 客户端请求地址 */
	int msock; /* 主服务套接字 */
	fd_set rfds; /* 读取文件描述符集 */
	fd_set afds; /* 活动文件描述符集 */
	socklen_t alen; /* 请求地址长度 */
	int sd, nfds;
	switch (argc) {
	case 1:
		break;
	case 2:
		service = argv[1];
		break;
	default:
		errx(1, "usage: %s [port]\n", argv[0]);
	}
	msock = passivesock(service, "tcp", QLEN);
	nfds = FD_SETSIZE;
	FD_ZERO(&afds);
	FD_SET(msock, &afds);
	while (1) {
		memcpy(&rfds, &afds, sizeof(rfds));
		if (select(nfds, &rfds, (fd_set *)0, (fd_set *)0, (struct timeval *)0) < 0)
			err(1, "select error");
		if (FD_ISSET(msock, &rfds)) {
			int ssock;
			alen = sizeof(fsin);
			ssock = accept(msock, (struct sockaddr *)&fsin, &alen);
			if (ssock < 0)
			err(1, "accept error");
			FD_SET(ssock, &afds);
		}
		for (sd=0; sd<nfds; ++sd)
			if (sd != msock && FD_ISSET(sd, &rfds))
				if (echo(sd) == 0) {
					(void) close(sd);
					FD_CLR(sd, &afds);
				}
	}
}
int echo(int sd)
{
	char buf[BUFSIZE];
	int cc;
	cc = read(sd, buf, sizeof(buf));
	if (cc < 0)
		err(1, "echo read error");
	if (cc && write(sd, buf, cc) < 0)
		err(1, "echo write error");
	return cc;
}

十一、多协议 DAYTIME 服务器的实现

源文件 daytimed.c 包含多协议 DAYTIME 服务器所用的代码,它使用单线程同时为 UDP 和 TCP 提供 DAYTIME 服务。 本题程序调用了自定义例程库函数 passivesock 分配和绑定服务器套接口。

前言知识

程序代码

/* daytimed.c - main, daytime */
/* 此处省略预编译指令的#include 部分 */

int daytime(char []);
int passivesock(const char *, const char *, const int);
#ifndef MAX
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#endif
#define QLEN 32
#define LINELEN 4096
int main(int argc, char *argv[])
{
	char *service = "daytime";
	char buf[LINELEN+1]; /* 一行文本的缓冲区 */
	struct sockaddr_storage fsin;
	socklen_t alen;
	int tsock; /* TCP 主套接字 */
	int usock; /* UDP 套接字 */
	int nfds;
	fd_set rfds; /* 可读取文件描述符 */
	switch (argc) {
	case 1:
		break;
	case 2:
		service = argv[1];
		break;
	default:
		errx(1, "usage: %s [port]\n", argv[0]);
	}
	
	tsock = passivesock(service, "tcp", QLEN);
	usock = passivesock(service, "udp", 0);
	nfds = MAX(tsock, usock) + 1; /* fd 的最大位数 */
	FD_ZERO(&rfds);
	
	while (1) {
		FD_SET(tsock, &rfds);
		FD_SET(usock, &rfds);
		if (select(nfds, &rfds, (fd_set *)0, (fd_set *)0, (struct timeval *)0) < 0)
			err(1, "select error");
		if (FD_ISSET(tsock, &rfds)) {
			int ssock; /* TCP 从套接字 */
			alen = sizeof(fsin);
			ssock = accept(tsock, (struct sockaddr *)&fsin, &alen);
			if (ssock < 0)
				err(1, "accept error");
			daytime(buf);
			(void) write(ssock, buf, strlen(buf));
			(void) close(ssock);
		}
		if (FD_ISSET(usock, &rfds)) {
			alen = sizeof(fsin);
			if (recvfrom(usock, buf, sizeof(buf), 0, (struct sockaddr *)&fsin, &alen) < 0)
				err(1, "recvfrom error");
			daytime(buf);
			(void) sendto(usock, buf, strlen(buf), 0, (struct sockaddr *)&fsin, sizeof(fsin));
		}
	}
}
int daytime(char buf[])
{
	char *ctime();
	time_t now;
	(void) time(&now);
	sprintf(buf, "%s", asctime(localtime(&now)));
	return 0;
}

十二、多服务服务器实现

源文件 superd.c 包含多服务服务器所用的代码,它提供了四种服务:ECHO、CHARGEN、 DAYTIME 和 TIME。 本题程序调用了自定义例程库函数 passivesock 分配和绑定服务器套接口。

前言知识

程序代码

/* superd.c - main, doTCP, reaper */
/* 此处省略预编译指令的#include 部分 */

#define UDP_SERV 0
#define TCP_SERV 1
#define NOSOCK -1 /* 无效的套接字描述符 */
struct service {
	char *sv_name;
	char sv_useTCP;
	int sv_sock;
	void (*sv_func)();
};
void TCPechod(int), TCPchargend(int), TCPdaytimed(int), TCPtimed(int);
int passivesock(const char *, const char *, const int);
void doTCP(struct service *);
static void reaper(int);
struct service svent[] =
{
	{ "echo", TCP_SERV, NOSOCK, TCPechod },
	{ "chargen", TCP_SERV, NOSOCK, TCPchargend },
	{ "daytime", TCP_SERV, NOSOCK, TCPdaytimed },
	{ "time", TCP_SERV, NOSOCK, TCPtimed },
	{ 0, 0, 0, 0 },
};
#ifndef MAX
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#endif /* MAX */
#define QLEN 32 /* 连接队列的最大长度 */
#define LINELEN 4096
extern unsigned short portbase;
int main(int argc, char *argv[])
{
	struct service *psv, /* 服务表指针 */
	*fd2sv[FD_SETSIZE]; /* fd 映射到服务的指针 */
	int fd, nfds;
	fd_set afds, rfds; /* 可以读取的文件描述符 */
	struct sigaction act;
	switch (argc) {
		case 1:
			break;
		case 2:
			portbase = (unsigned short) atoi(argv[1]);
			break;
		default:
			errx(1, "usage: %s [portbase]\n", argv[0]);
	}
	nfds = 0;
	FD_ZERO(&afds);
	for (psv=&svent[0]; psv->sv_name; ++psv) {
		if (psv->sv_useTCP)
			psv->sv_sock = passivesock(psv->sv_name, "tcp", QLEN);
		else
			psv->sv_sock = passivesock(psv->sv_name, "udp", 0);
		fd2sv[psv->sv_sock] = psv;
		nfds = MAX(psv->sv_sock+1, nfds);
		FD_SET(psv->sv_sock, &afds);
	}
	/* (void) signal(SIGCHLD, reaper); */
	/* The sigaction() function supersedes the signal() function,
	and should be used in preference. */
	act.sa_handler = reaper;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	act.sa_flags |= SA_RESTART;
	act.sa_flags |= SA_NODEFER;
	if (sigaction(SIGCHLD, &act, NULL) == -1)
		err(1, "sigaction error");
	while (1) {
		memcpy(&rfds, &afds, sizeof(rfds));
		if (select(nfds, &rfds, (fd_set *)0, (fd_set *)0, (struct timeval *)0) < 0) {
			if (errno == EINTR)
				continue;
			err(1, "select error");
		}
		for (fd=0; fd<nfds; ++fd)
			if (FD_ISSET(fd, &rfds)) {
				psv = fd2sv[fd];
				if (psv->sv_useTCP)
					doTCP(psv);
				else
					psv->sv_func(psv->sv_sock);
			}
	}
}
void doTCP(struct service *psv)
{
	struct sockaddr_storage fsin; /* the from address of a client */
	/* unsigned int alen; */
	socklen_t alen; /* from-address length */
	int fd, ssock;
	alen = sizeof(fsin);
	ssock = accept(psv->sv_sock, (struct sockaddr *)&fsin, &alen);
	if (ssock < 0)
		err(1, "doTCP accept error");
	switch (fork()) {
		case 0:
			break;
		case -1:
			err(1, "doTCP fork error");
		default:
			(void) close(ssock);
		return; /* parent */
	}
	/* child */
	for (fd = FD_SETSIZE; fd >= 0; --fd)
		if (fd != ssock)
			(void) close(fd);
	psv->sv_func(ssock);
	exit(0);
}
static void reaper(int signum)
{
	int status;
	/* while (wait3(&status, WNOHANG, (struct rusage *)0) >= 0) */
	while (waitpid((pid_t)-1, &status, WNOHANG) >= 0)
	/* empty */;
}
/* sv_funcs.c - TCPechod, TCPchargend, TCPdaytimed, TCPtimed */
/* 此处省略预编译指令的#include 部分 */
#define BUFSIZE 8192 /* buffer size for reads and writes */
void TCPechod(int), TCPchargend(int), TCPdaytimed(int), TCPtimed(int);
void TCPechod(int sd)
{
	char buf[BUFSIZE];
	int cc;
	while ((cc = read(sd, buf, sizeof(buf)))) {
		if (cc < 0)
			err(1, "echo read error");
		if (write(sd, buf, cc) < 0)
			err(1, "echo write error");
	}
}
#define LINELEN 4096 /* max text line length */

void TCPchargend(int sd)
{
	char c, buf[LINELEN+2]; /* print LINELEN chars + \r\n */
	c = ' ';
	buf[LINELEN] = '\r';
	buf[LINELEN+1] = '\n';
	while (1) {
		int i;
		for (i=0; i<LINELEN; ++i) {
			buf[i] = c++;
			if (c > '~')
			c = ' ';
		}
		if (write(sd, buf, LINELEN+2) < 0)
			break;
	}
}
void TCPdaytimed(int sd)
{
	char buf[LINELEN], *ctime();
	time_t now;
	(void) time(&now);
	sprintf(buf, "%s", asctime(localtime(&now)));
	(void) write(sd, buf, strlen(buf));
}
#define UNIXEPOCH 2208988800UL /* UNIX epoch, in UCT secs */
void TCPtimed(int sd)
{
	time_t now;
	(void) time(&now);
	now = htonl((unsigned long)(now + UNIXEPOCH));
	(void) write(sd, (char *)&now, sizeof(now));
}

十三、使用 ECHO 服务的并发客户实现

源文件 TCPtecho.c 包含使用 ECHO 服务的并发客户所用的代码。 本题程序调用了自定义例程库函数 connectsock 分配和绑定服务器套接口。

前言知识

程序代码

/* TCPtecho.c - main, TCPtecho, reader, writer, mstime */
/* 此处省略预编译指令的#include 部分 */
int TCPtecho(fd_set *, int, int, int);
int reader(int, fd_set *);
int writer(int, fd_set *);
long mstime(unsigned long *);
int connectsock(const char *, const char *);
#ifndef MIN
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#endif /* MIN */
#define BUFSIZE 8192 /* buffer size for reads and writes */
#define CCOUNT 64*1024 /* default character count */
char *hname[FD_SETSIZE]; /* fd to host name mapping */
int rc[FD_SETSIZE], wc[FD_SETSIZE]; /* read/write character counts */
char buf[BUFSIZE]; /* read/write data buffer */
int main(int argc, char *argv[])
{
	int ccount = CCOUNT;
	int i, hcount, maxfd, fd;
	int one = 1;
	fd_set afds;
	hcount = 0;
	maxfd = -1;
	for (i=1; i<argc; ++i) {
		if (strcmp(argv[i], "-c") == 0) {
			if (++i < argc && (ccount = atoi(argv[i])))
			continue;
			errx(1, "usage: %s [ -c count ] host1 host2...\n",
			argv[0]);
		}
		/* else, a host */
		fd = connectsock(argv[i], "echo");
		
		if (ioctl(fd, FIONBIO, (char *)&one))
			err(1, "ioctl error");
			
		if (fd > maxfd)
			maxfd = fd;
			
		hname[fd] = argv[i];
		++hcount;
		FD_SET(fd, &afds);
	}
	exit(TCPtecho(&afds, maxfd+1, ccount, hcount));
}
int TCPtecho(fd_set *pafds, int nfds, int ccount, int hcount)
{
	fd_set rfds, wfds; /* read/write fd sets */
	fd_set rcfds, wcfds; /* read/write fd sets 副本 */
	int fd, i;
	for (i=0; i<BUFSIZE; ++i) /* echo data */
		buf[i] = 'D';
		
	memcpy(&rcfds, pafds, sizeof(rcfds));
	memcpy(&wcfds, pafds, sizeof(wcfds));
	
	for (fd=0; fd<nfds; ++fd)
		rc[fd] = wc[fd] = ccount;
		
	(void) mstime((unsigned long *)0); /* set the epoch */
	while (hcount) {
		memcpy(&rfds, &rcfds, sizeof(rfds));
		memcpy(&wfds, &wcfds, sizeof(wfds));
		if (select(nfds, &rfds, &wfds, (fd_set *)0, (struct timeval *)0) < 0)
			err(1, "TCPtecho select error");
		for (fd=0; fd<nfds; ++fd) {
			if (FD_ISSET(fd, &rfds))
				if (reader(fd, &rcfds) == 0)
					hcount--;
					
			if (FD_ISSET(fd, &wfds))
				writer(fd, &wcfds);
		}
	}
	return 0;
}
int reader(int fd, fd_set *pfdset)
{
	unsigned long now;
	int cc;
	cc = read(fd, buf, sizeof(buf));
	
	if (cc < 0)
		err(1, "reader read error");
		
	if (cc == 0)
		errx(1, "reader read: premature end of file\n");
		
	rc[fd] -= cc;
	
	if (rc[fd])
		return 1;
	(void) mstime(&now);
	printf("%s: %d ms\n", hname[fd], (int) now);
	(void) close(fd);
	FD_CLR(fd, pfdset);
	return 0;
}
int writer(int fd, fd_set *pfdset)
{
	int nchars;
	nchars = write(fd, buf, MIN((int)sizeof(buf), wc[fd]));
	
	if ( nchars < 0)
		err(1, "writer read error");
		
	wc[fd] -= nchars;
	if (wc[fd] == 0) {
		(void) shutdown(fd, 1);
		FD_CLR(fd, pfdset);
	}
	return 0;
}
long mstime(unsigned long *pms)
{
	/* static struct timeval epoch; */
	/* struct timeval now; */
	static struct timespec epoch;
	struct timespec now;
	/* if (gettimeofday(&now, (struct timezone *)0)) */
	if (clock_gettime(CLOCK_REALTIME, &now))
		err(1, "mstime clock_gettime error");
		
	if (!pms) {
		epoch = now;
		return 0;
	}
	*pms = (now.tv_sec - epoch.tv_sec) * 1000;
	*pms += (now.tv_nsec - epoch.tv_nsec + 500000)/ 1000000;
	return *pms;
}
  • 15
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风落_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值