第一章 理解网络编程和套接字



(1) 套接字在网络编程中的作用是什么?为什么称它为套接字?

网络编程就是编写程序让两台联网的计算机相互交换数据。在我们不需要考虑物理连接的情况下,我们只需要考虑如何编写传输软件。操作系统提供了名为“套接字”,套接字是网络传输传输用的软件设备。
socket英文原意是插座:我们把插头插到插座上就能从电网获得电力供给,同样,为了与远程计算机进行数据传输,需要连接到Internet,而变成中的“套接字”就是用来连接该网络的工具。

(2) 在服务器端创建套接字后,会依次调用listen函数和accept函数。请比较并说明两者作用

listen:将套接字转为可接受连接方式(监听套接字)
accept:受理连接请求,并且在没有连接请求的情况调用该函数,不会返回(阻塞)。直到有连接请求为止
二者存在逻辑上的先后关系

(3)Linux中,对套接字数据进行I/O时可以直接使用I/O相关函数;而在Windows中则不可以。原因为何?

Linux把套接字也看作是文件,所以可以用文件I/O相关函数;而Windows要区分套接字和文件,所以设置了特殊的函数。

(4)创建套接字后一般会给它分配地址,为什么?为了完成地址分配需要调用哪些函数?

要在网络上区分来自不同机器的套接字,所以需要地址信息。分配地址是通过bind()函数实现。

(5)Linux中的文件描述符与Windows的句柄实际上非常类似。请以套接字为对象说明他们的含义。

Linux的文件描述符是为了区分指定文件而赋予文件的整数值(相当于编号)。Windows的文件描述符其实也是套接字的整数值,其目的也是区分指定套接字。

(6)底层文件I/O函数与ANSI标准定义的文件I/O函数之间有何区别?

ANSI标准定义的输入、输出函数是与操作系统(内核)无关的以C标准写成的函数;相反,底层文件I/O函数是操作系统直接提供的。
标准I/O分为全缓冲,行缓冲,不缓冲三种形式;文件I/O为不带缓冲的I/O。
文件I/O主要针对文件操作,它操作的是文件描述符;标准I/O针对的是控制台,它操作的是字符流。对于不同设备得特性不一样,必须有不同api访问才最高效。

(7)参考本书给出的示例low_open.c和low_read.c,分别利用底层文件I/O和ANSI标准I/O编写文件复制程序。可任意指定复制程序的使用方法

先执行书上代码创建data.txt并写入内容"Let's go!\n"
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

void error_handling(char* message);

int main(void)
{
	int fd;
	char buf[] = "Let's go!\n";

	fd = open("data.txt", O_CREAT | O_WRONLY | O_TRUNC);
	if (fd == -1)
		error_handling("open() error!");
	printf("file descriptor: %d \n", fd);

	if (write(fd, buf, sizeof(buf)) == -1)
		error_handling("write() error!");

	close(fd);
	return 0;
}

void error_handling(char* message)
{  fputs(message, stderr);  fputc('\n', stderr);  exit(1);
}
</fcntl.h></stdlib.h></stdio.h>
用底层I/O编写:
        int fd1,fd2;
	int count = 0;
	char buf[20];

	fd1 = open("data.txt", O_RDONLY);
	if (fd1 == -1)
		error_handling("open() error!");

	fd2= open("data_copy.txt", O_CREAT | O_WRONLY );
	if (fd2 == -1)
		error_handling("open() error!");

	while ((count = read(fd1, buf, 20)) > 0)      //每次读取20个字符
	{
		write(fd2, buf, count);
	}

	close(fd1);
	close(fd2);
	return 0;
用ANSI标准I/O编写:
	FILE* fin;    // 源文件
	FILE* fout;   // 目标文件
	char buf[20]; //缓冲区设置为20
	int count;  


	fin = fopen("data.txt", "r");
	if (NULL == fin) 	
		error_handling("open() error!");

	fout = fopen("data_copy", "aw");
	if (NULL == fout) 	
		error_handling("open() error!");
	
	while ((count = fread(buf, 1, 20, fin)) > 0)
	{
		fwrite(buf, 1, count, fout);
	}

	fclose(fin);
	fclose(fout);
	return 0;

第二章:套接字类型与协议设置

(1)什么是协议?在收发数据中定义协议有何意义?

协议就是为了完成数据交换而定好的规则。因此,定义协议意味着对数据传输所必需的的规则进行定义。

(2)面向连接的TCP套接字传输特性有3点,请分别说明。

1.传输过程中数据不会丢失
2.按序传输数据
3.传输的数据不存在数据边界(Boundary)

(3)下面哪些是面向消息的套接字的特性?

a、c、e
a.传输数据可能丢失
c.以快速传递为目的
e.与面向连接的套接字不同,不存在连接的概念

(4)下列数据适合用哪些套接字传输?并给出原因

UDP 、TCP 、TCP
1.演唱会讲究实时性,稍微丢包也能够有算法补救(顶多画质下降),所以用UDP更好
2.文本文件讲究可靠传输,所以用TCP
3.支付这种敏感数据更需要可靠传输,所以用TCP

(5)何种类型的套接字不存在数据边界?这类套接字接收数据时需要注意什么?

连接指向型TCP套接字不存在数据边界。因此输入输出函数的响应次数不具有意义。重要的不是函数的响应次数,而是数据的收发量。需要保证在接收套接字的缓冲区填充满之前就从buffer里读取数据。也就是,在接收套接字内部,写入buffer的速度要小于读出buffer的速度。

(6)tcp_server.c和tcp_client.c中需要多次调用read函数读取服务器调用1次write函数传递的字符串。更改程序,使服务器端多次调用(次数自拟)write函数传输数据,客户端调用1次read函数进行读取。为达到这一目的,客户端需延迟调用read函数,因为客户端要等待服务器端传输所有数据。

/*****************************tcp_serv.c*********************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include#include#includevoid error_handling(char *message);

int main(int argc, char *argv[])
{
	int serv_sock;
	int clnt_sock;

	struct sockaddr_in serv_addr;
	struct sockaddr_in clnt_addr;
	socklen_t clnt_addr_size;

	char message[]="Hello World!";
	
	if(argc!=2){
		printf("Usage : %s\n", argv[0]);
		exit(1);
	}
	
	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
	if(serv_sock == -1)
		error_handling("socket() error");
	
	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family=AF_INET;
	serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_addr.sin_port=htons(atoi(argv[1]));
	
	if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1)
		error_handling("bind() error"); 
	
	if(listen(serv_sock, 5)==-1)
		error_handling("listen() error");
	
	clnt_addr_size=sizeof(clnt_addr);  
	clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size);
	if(clnt_sock==-1)
		error_handling("accept() error");  
	
	write(clnt_sock, message, 4);            //多次次调用write函数传输数据 write(clnt_sock, message+4, 4);
	write(clnt_sock, message+8, 4);
	write(clnt_sock, message+12, sizeof(message)-12);

	close(clnt_sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

/*****************************tcp_clnt.c*********************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include#include#includevoid error_handling(char *message);

int main(int argc, char* argv[])
{
	int sock;
	struct sockaddr_in serv_addr;
	char message[30];
	int str_len=0;
	int idx=0, read_len=0, i;
	
	if(argc!=3){
		printf("Usage : %s\n", argv[0]);
		exit(1);
	}
	
	sock=socket(PF_INET, SOCK_STREAM, 0);
	if(sock == -1)
		error_handling("socket() error");
	
	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family=AF_INET;
	serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_addr.sin_port=htons(atoi(argv[2]));
		
	if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1) 
		error_handling("connect() error!");

	for(i=0; i<100; i++) //busy waiting!! printf("Wait time %d \n", i); read(sock, message, sizeof(message)); printf("Message from server: %s \n", message); close(sock); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }</unistd.h></string.h></stdlib.h></stdio.h></unistd.h></string.h></stdlib.h></stdio.h>

第三章 地址族与数据序列

(1)IP地址族IPv4和IPv6有何区别?在何种背景下诞生了IPv6?

IPV4是4字节地址族,IPV6是16字节地址族。IPV6的诞生是为了应对2010年前后IP地址耗尽的问题而提出的标准。

(2)通过IPV4网络ID、主机ID及路由器的关系说明向公司局域网中的计算机传输数据的过程

向目标主机传输数据,首先向目标IP所属的网络传输数据。此时使用的是IP地址中的网络ID。数据传到路由器,路由器将参照IP地址的主机号查找路由表,从对应的网口发送数据,数据经过路由器转发最终送到目标主机。

(3)套接字地址分为IP地址和端口号。为什么需要IP地址和端口号?或者说,通过IP可以区分哪些对象?通过端口号可以区分哪些对象?

IP地址是为了区分网络上的主机。端口号是区分同一主机下的不同的SOCKET,以确保程序进程都能准确收发数据。

(4)请说明IP地址分类方法,并说出下面这些IP地址的分类

C类、A类、B类
根据第一字节格式分类:

(5)计算机通过路由器或交换机连接到互联网。请说出路由器和交换机的作用

路由器:是连接两个或多个网络的硬件设备,在网络间起网关的作用,是读取每一个数据包中的地址然后决定如何传送的专用智能性的网络设备。(作用于网络层,寻址,转发(依靠 IP 地址)
交换机:是一种用于电(光)信号转发的网络设备。(一般作用于链路层,过滤,转发(依靠 MAC 地址))

(6)什么是知名端口?其范围是多少?知名端口中具有代表性的HTTP合同FTP端口号各是多少?

“知名端口(Well-known PROT)”是指预定分配给特定操作的端口。其范围是0~1023,其中最知名的端口是HTTP:80端口和TCP:21。

(7)题目大概意思是:为什么bind中第二个参数是sockaddr,但是传入的是sockaddr_in

bind函数第二个参数类型sockaddr结构体,很难分配IP地址和端口号的空间大小(因为结构体sockaddr并非只为IPv4设计,所以要兼容),所以sockaddr结构体内将IP地址和端口号合并到一起,用一个成员 sa_data 表示。因此对于不同类型网络的IP地址和PORT号的分配是可先通过sockaddr_in完成,之后再强制转换成sockaddr类型。(因为两个结构体的长度相同,都是16字节,强制转换类型时不会丢失字节,也没有多余的字节)

(8)请解释大端序、小端序、网络字节序,并说明为何需要网络字节序

小端序是把高位字节存储到高位地址上;大端序是把高位字节存储到低位地址上。因为保存栈的方式有差异,所以对网络传输数据的过程制定了标准,这就是“网路字节序”。而且,在网络字节序中,数据传输的标准是“大端序”。

(9)大端计算机希望将4字节整型数据12传到小端序计算机。请说出数据传输过程中发生的字节序变换过程

因为网络字节序的顺序标准是“大端序”,所以大端序的计算机在网络传输中不需要先转换字节顺序,直接传输。但是接受数据的是小端序计算机,因此,要经过网络转本地序的过程,再保存到存储设备上。

(10)怎么表示回送地址?其含义是什么?如果向回送地址传输数据将会发生什么情况?

回送地址表示计算机本身,为127.0.0.1。因此,如果将数据传送到IP地址127.0.0.1,数据不进行网络传输而是直接返回。

第四章 基于TCP的服务器端/客户端(1)

(1)请说明TCP/IP的4层协议栈,并说明TCP和UDP套接字经过的层级结构差异

链路层—>IP层—>TCP层—>应用层
链路层—>IP层—>UDP层—>应用层

(2)请说出TCP/IP协议栈中链路层和IP层的作用,并给出两者关系。

数据链路层通过各种控制协议,将有差错的物理信道变为无差错的、能可靠传输数据帧的数据链路。为IP 层提供数据传送服务。
网络层通过路由选择算法,为分组选择最适当的路径,实现两个端系统之间的数据透明传送。

(3)为何需要把TCP/IP协议栈分成4层(或7层)?结合开放式系统回答

ARPANET 的研制经验表明,对于复杂的计算机网络协议,其结构应该是层次式的。
分层的好处:隔层之间是独立的,灵活性好,结构上可以分隔开,易于实现和维护,能促进标准化工作。

(4)客户端调用connect函数向服务器端发送连接请求。服务器端调用哪个函数后,客户端可以调用connect函数?

调用listen函数设置好服务端监听套接字后。

(5)什么时候创建连接请求等待队列?它有何作用?与accept有什么关系

调用listen函数时创建了连接请求等待队列。它是存储客户端连接请求信息的空间。accept函数调用后,将从连接请求队列中取出连接请求信息,并与相应客户端建立连接。

(6)客户端中为何不需要调用bind函数分配地址?如果不调用bind函数,那何时、如何向套接字分配IP地址和端口号?

客户端是请求连接的程序,不是一个接收连接的程序。所以,服务器的地址信息是更重要的因素,没有必要通过bind函数明确地分配地址信息。但是,要想和服务器通信,必须将自己的地址信息分配到套接字上,因此,在connect函数调用时,自动把IP地址和端口号输入到套接字上。

(7)把第1章的hello_server.c和hello_server_win.c改成迭代服务器端,并理由客户端测试更改是否准确

#include  #include  #include  #include  #include  #include  void error_handling(char *message); int main(int argc, char *argv[]) { int serv_sock; int clnt_sock; struct sockaddr_in serv_addr; struct sockaddr_in clnt_addr; socklen_t clnt_addr_size; char message[]="Hello World!"; if(argc!=2){ printf("Usage : %s\n", argv[0]); exit(1);
    }
    
    serv_sock=socket(PF_INET, SOCK_STREAM, 0); if(serv_sock == -1)
        error_handling("socket() error"); memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_addr.sin_port=htons(atoi(argv[1])); if( bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1 )
        error_handling("bind() error"); if( listen(serv_sock, 5)==-1 )
        error_handling("listen() error");
    
    clnt_addr_size=sizeof(clnt_addr); while(1)
    {
        clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size); if(clnt_sock==-1) break;  
        
        write(clnt_sock, message, sizeof(message));
        close(clnt_sock);
    }
    close(serv_sock); return 0;
}  void error_handling(char *message) { fputs(message, stderr);
    fputc('\n', stderr); exit(1);
}

第五章 基于TCP的服务器端/客户端(2)

(1)请说明TCP套接字连接设置的三次握手过程。尤其是3次数据交换过程每次收发的数据内容


初始状态客户端处于 Closed 的状态,服务端处于 Listen 状态,进行三次握手。
  • 第一次握手:客户端给服务端发一个 SYN 报文段,并指明客户端的初始化序列号 ISN(c)。此时客户端处于 SYN_SENT 状态。(在SYN报文段中同步位SYN=1,初始序号seq=x)SYN=1的报文段不能携带数据,但要消耗掉一个序号。
  • 第二次握手:服务器收到客户端的 SYN 报文段之后,会以自己的 SYN 报文段作为应答,并且也是指定了自己的初始化序列号 ISN(s)。同时会把客户端的 ISN(c) + 1 作为ACK 的值,表示自己已经收到了客户端的 SYN报文,此时服务器处于 SYN_RCVD 的状态。(在SYN ACK报文段中SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y)
  • 第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN(s) + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接。(在ACK报文段中ACK=1,确认号ack=y+1,序号seq=x+1)ACK报文段可以携带数据,不携带数据则不消耗序号(第二次握手的既是ACK也是SYN报文段所以不可以)

(2)TCP是可靠的数据传输协议,但在通过网络通信的过程可能丢失数据。请通过ACK和SEQ说明TCP通过何种机制保证丢失数据的可靠传输。

seq用于给数据字节编号,ack用于确认收到编号的信息。每次传输信息时同时发送seq(表示发送数据最后一个字节的编号),收到数据的主机以seq为基础回复发送数据的主机ack(表示已收到数据的最后一个字节的编号+1)。通过这种机制,传输数据的主机就知道数据是否被正确接收。在不正确传输时,可以重传数据。

(3)TCP套接字中调用write和read函数时数据如何移动?结合I/O缓冲进行说明

当write函数被调用时,数据就向端口的输出缓冲区移动。然后经过网络传输传输到对方主机套接字的输入缓冲。这样,输入缓冲中存储的数据通过read函数的响应来读取。

(4)对方主机的输入缓冲剩余50字节空间时,若本方主机通过write函数请求传输70字节,问TCP如何处理这种情况?

通过TCP流量控制机制,对方主机会把输入缓冲大小传送给本方主机。因此即使要求传送70字节的数据,本方主机也不会传输超过50字节数据,剩余的部分保存在传输方的输出缓冲中,等待对方主机的输入缓冲有空余空间时再传输剩余数据(这种交换缓冲区多余空间信息的协议被称为滑动窗口协议)

(5)第2章示例tcp_server.c(第一章的hello_server.c)和tcp_client.c中,客户端接收服务器端传输的字符串后便退出。现更改程序,使服务器端和客户端各传送1次字符串。考虑到使用TCP协议,所以传输字符串前先以4字节整数型方式传递字符串长度。另外,不限制字符串传输顺序及种类,但必须进行3次数据交换。

/**********************************sendrecv_serv.c***********************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include#include#includevoid error_handling(char *message);

int main(int argc, char *argv[])
{
	int serv_sock;
	int clnt_sock;
	int str_len, i;

	struct sockaddr_in serv_addr;
	struct sockaddr_in clnt_addr;
	socklen_t clnt_addr_size;

	char msg1[]="Hello client!";
	char msg2[]="I'm server.";
	char msg3[]="Nice to meet you.";
	char* str_arr[]={msg1, msg2, msg3};        //指针数组,保存三个字符串指针
	char read_buf[100];
	
	if(argc!=2){
		printf("Usage : %s\n", argv[0]);
		exit(1);
	}
	
	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
	if(serv_sock == -1)
		error_handling("socket() error");
	
	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family=AF_INET;
	serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_addr.sin_port=htons(atoi(argv[1]));
	
	if(bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)
		error_handling("bind() error"); 
	
	if(listen(serv_sock, 5)==-1)
		error_handling("listen() error");
	
	clnt_addr_size=sizeof(clnt_addr);  
	clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size);
	if(clnt_sock==-1)
		error_handling("accept() error");  
	
	for(i=0; i<3; i++) { str_len=strlen(str_arr[i])+1; write(clnt_sock, (char*)(&str_len), 4); write(clnt_sock, str_arr[i], str_len); read(clnt_sock, (char*)(&str_len), 4); read(clnt_sock, read_buf, str_len); puts(read_buf); } close(clnt_sock); close(serv_sock); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } /***************************************recvsend_clnt.c***************************/ #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include#include#includevoid error_handling(char *message);

int main(int argc, char* argv[])
{
	int sock;
	struct sockaddr_in serv_addr;

	char msg1[]="Hello server!";
	char msg2[]="I'm client.";
	char msg3[]="Nice to meet you too!";
	char* str_arr[]={msg1, msg2, msg3};         //指针数组,保存三个字符串指针
	char read_buf[100];

	int str_len, i;
	
	if(argc!=3){
		printf("Usage : %s\n", argv[0]);
		exit(1);
	}
	
	sock=socket(PF_INET, SOCK_STREAM, 0);
	if(sock == -1)
		error_handling("socket() error");
	
	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family=AF_INET;
	serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_addr.sin_port=htons(atoi(argv[2]));
	
	if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1) 
		error_handling("connect() error!");

	for(i=0; i<3; i++) { read(sock, (char*)(&str_len), 4); //因为服务端是先写,所以客户端对应要先读 read(sock, read_buf, str_len); puts(read_buf); str_len=strlen(str_arr[i])+1; write(sock, (char*)(&str_len), 4); write(sock, str_arr[i], str_len); } close(sock); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }</unistd.h></string.h></stdlib.h></stdio.h></unistd.h></string.h></stdlib.h></stdio.h>

(6)创建收发文件的服务器端/客户端

/**********************************file_serv.c***********************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include#include#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int serv_sd, clnt_sd;
	FILE * fp;
	char buf[BUF_SIZE];
	char file_name[BUF_SIZE];
	int read_cnt;
	
	struct sockaddr_in serv_adr, clnt_adr;
	socklen_t clnt_adr_sz;
	
	if(argc!=2) {
		printf("Usage: %s\n", argv[0]);
		exit(1);
	}
	
	serv_sd=socket(PF_INET, SOCK_STREAM, 0);   
	
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_adr.sin_port=htons(atoi(argv[1]));
	
	bind(serv_sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
	listen(serv_sd, 5);
	
	clnt_adr_sz=sizeof(clnt_adr);    
	clnt_sd=accept(serv_sd, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
	
	read(clnt_sd, file_name, BUF_SIZE);   
	fp=fopen(file_name, "rb");           //尝试打开客户端请求的文件
	if(fp!=NULL)              //如果文件存在,则传送给客户端
	{
		while(1)
		{
			read_cnt=fread((void*)buf, 1, BUF_SIZE, fp);
			if(read_cnt#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include#include#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int sd;
	FILE *fp;
	
	char buf[BUF_SIZE];
	char file_name[BUF_SIZE];
	int read_cnt;
	struct sockaddr_in serv_adr;
	if(argc!=3) {
		printf("Usage: %s\n", argv[0]);
		exit(1);
	}
	
	printf("Input file name: ");  
	scanf("%s", file_name);               //输入请求服务端传输文件的文件名
	fp=fopen(file_name, "wb");

	sd=socket(PF_INET, SOCK_STREAM, 0);   
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_adr.sin_port=htons(atoi(argv[2]));

	connect(sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
	write(sd, file_name, strlen(file_name)+1);	

	while((read_cnt=read(sd, buf, BUF_SIZE))!=0)        //如果服务端存在该文件,则打印服务端传来的文件
		fwrite((void*)buf, 1, read_cnt, fp);        
	
	fclose(fp);
	close(sd);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}</unistd.h></string.h></stdlib.h></unistd.h></string.h></stdlib.h></stdio.h>

第六章 基于UDP的服务器端/客户端

(1)UDP为什么比TCP速度快?为什么TCP数据传输可靠而UDP数据传输不可靠?

UDP是面向报文、无连接的传输层协议。UDP尽最大努力交付数据但不保证可靠传输。
TCP是面向字节流、有连接的传输层协议。TCP保证可靠传输。
TCP可靠和速度慢的原因:TCP有流量控制机制,保证了消息可靠传输,但因其复杂性从而限制了消息的传输(体现在速度上)

(2)下列不属于UDP特点的是?

b、c、e
b.UDP是无连接的,所以只需要一个套接字,发送完一个接着再发送令一个就行
c.UDP套接字使用的端口号可以和TCP套接字使用的端口号一样,因为UDP和TCP本身就存在区别,所以不需要区别端口号
e.争对UDP可以调用connect函数,但是只是保持连接,提高了效率,但是并不会进行三次握手(从三次握手内容分析也可知道是争对TCP用的)

(3)UDP数据包向对方主机的UDP套接字传递过程中,IP和UDP分别负责哪些部分?

路由器通过IP负责链路选择,应用程序通过UDP负责端到端的传输。

(4)UDP一般比TCP快,但根据交换数据的特点,其差异可大可小。请说明何种情况下UDP的性能优于TCP

TCP于UDP传输过程最大不同就是TCP要先建立连接,数据传输结束还要断开连接。所以在传输数据少,又要频繁传输数据的情况下,UDP简单轻巧的优势就凸显出来了。

(5)客户端TCP套接字调用connect函数时自动分配IP和端口号。UDP中不调用bind函数,那何时分配IP和端口号?

首次调用sendto函数发送数据时,操作系统给其套接字自动分配IP和端口号

(6)TCP客户端必须调用connect函数,而UDP中可以选择性调用。请问,在UDP中调用connect函数有哪些好处?

每当以UDP套接字为对像调用sendto函数时,都要经过以下过程:
第一阶段:向UDP套接字注册目标和端口号
第二阶段:数据传输
第三阶段:删除UDP套接字中注册的IP和端口号
如果调用connect函数,就可以忽略每次传输数据时反复进行的第一阶段和第三阶段。然而,调用connect函数并不意味着经过连接过程,只是将IP地址和端口号绑定在UDP套接字上。这样connect函数使用后,还可以用write、read函数进行数据处理,不需要sendto、recvfrom函数指定目的地址。

(7)请参考本章给出的uecho_sever.c和uecho_client.c,编写示例使服务器端和客户端轮流收发消息。收发的消息均要输出到控制台窗口

/********************************uchar_server.c***********************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include#include#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int serv_sock;
	char message[BUF_SIZE];
	int str_len;
	socklen_t clnt_adr_sz;
	
	struct sockaddr_in serv_adr, clnt_adr;
	if(argc!=2){
		printf("Usage : %s\n", argv[0]);
		exit(1);
	}
	
	serv_sock=socket(PF_INET, SOCK_DGRAM, 0);
	if(serv_sock==-1)
		error_handling("UDP socket creation error");
	
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_adr.sin_port=htons(atoi(argv[1]));
	
	if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
		error_handling("bind() error");
	
	clnt_adr_sz=sizeof(clnt_adr);
	while(1) 
	{
		str_len=recvfrom(serv_sock, message, BUF_SIZE, 0, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);        //客户端先发,服务端先收
		message[str_len]=0;
		printf("Message from client: %s", message);

		fputs("Insert message(q to quit): ", stdout);
		fgets(message, sizeof(message), stdin);
		if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))	
			break;

		sendto(serv_sock, message, strlen(message), 0, (struct sockaddr*)&clnt_adr, clnt_adr_sz);
	}	
	close(serv_sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}


/********************************uchar_client.c***********************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include#include#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int sock;
	char message[BUF_SIZE];
	int str_len;
	socklen_t adr_sz;
	
	struct sockaddr_in serv_adr, from_adr;
	if(argc!=3){
		printf("Usage : %s\n", argv[0]);
		exit(1);
	}
	
	sock=socket(PF_INET, SOCK_DGRAM, 0);   
	if(sock==-1)
		error_handling("socket() error");
	
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_adr.sin_port=htons(atoi(argv[2]));
	
	while(1)
	{
		fputs("Insert message(q to quit): ", stdout);
		fgets(message, sizeof(message), stdin);     
		if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))	
			break;
		  
		sendto(sock, message, strlen(message), 0, (struct sockaddr*)&serv_adr, sizeof(serv_adr));        //客户端先发,服务端先收
		adr_sz=sizeof(from_adr);
		str_len=recvfrom(sock, message, BUF_SIZE, 0, (struct sockaddr*)&from_adr, &adr_sz);   

		message[str_len]=0;
		printf("Message from server: %s", message);
	}	
	close(sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}</unistd.h></string.h></stdlib.h></stdio.h></unistd.h></string.h></stdlib.h></stdio.h>

第七章 优雅地断开套接字连接

(1)解释TCP中“流”的概念。UDP中能否形成流?请说明原因

TCP的流是指,两台主机通过套接字建立连接后进入可交换数据的状态,也称为“流形成的状态”。UDP是基于报文面向无连接的,不存在流。

(2)Linux中的close函数或Windows中的closesocket函数属于单方面断开连接的方法,有可能带来一些问题。什么是单方面断开连接?什么情况下会出现问题?

单方面的断开连接意味着套接字无法再接发数据。但是如果对方数据还没发完,而己方断开连接,就会造成问题。

(3)什么是半关闭?针对输出流执行半关闭的主机处于何种状态?半关闭会导致对方主机接收什么信息?

主机在结束发送后还能接收来自另一端数据的能力。这就是所谓的半关闭主机半关闭会使其发送最后一个报文段时附带一个EOF,告诉对方主机自己没有数据要发了,但还是可以接收对方主机传送的数据。

第八章 域名及网络地址

(1)下列关于DNS说法错误的是?

b、d
b.DNS是域名到IP地址的转换,路由器根据IP进行路径选择
d.DNS本身和操作系统无关

(2)阅读如下对话,并说明东秀的解决方案是否可行。

如果网络上没有特别的限制,可以将与本地网络相连的DNS服务器指定为其他完好的DNS服务器。因此,东秀提议的方法可能成为解决方法。也就是说,静洙可以不去网吧。

(3)在浏览器地址栏输入www.orentec.co.kr,并整理出网页显示过程。假设浏览器访问的默认DNS服务器中并没有关于www.orentec.co.kr的IP地址信息。

步骤1:计算机向默认DNS服务器询问IP地址
步骤2:默认DNS服务器没有IP地址信息,因此向DNS主机发出询问
步骤3:DNS查询服务器向更上级的DNS服务器查询
步骤4:DNS查询服务器将查到的域名对应的IP地址逐级返还给主机
步骤5:网络浏览器根据该IP访问对应的网站
步骤6:网站服务器响应该HTTP请求,浏览器得到html代码
步骤7:浏览器解析html代码后,网页就显示出来了

第九章 套接字的多种可选项

(1)下列关于Time-wait状态的说法错误的是?

a、c、d
a.应该是发生在率先请求断开连接的那一端,所以都有可能
c.正好说反了,应该是与请求连接过程中的SYN消息的传输顺序无关,而是哪一端先主动断开连接(即发送第一个FIN报文段),Time-wait状态就发生在哪一端
d.Time-wait状态有其存在的必要。(1.保证第四次握手客户端发送的最后一个 ACK报文段 能够到达服务端,可靠地实现了TCP全双工连接的终止;2.防止“已失效的连接请求报文段”出现在本连接中)

(2)TCP_NODELAY可选项与Nagle算法有关,可通过它禁止Nagle算法。请问何时应考虑禁用Nagle算法?结合收发数据的特性给出说明

根据传输数据的特性,网络流量未受太大影响时,不使用Nagle算法要比使用它时传输速度快。
例如“传输大文件数据”。将文件数据传入输出缓冲不会花太多时间。因此,即便不使用Nagle算法,也会在装满输出缓冲时传输数据包。这不仅不会增加数据包的数量,反而会在无需等待ACK的前提下连续传输,因此可以大大提高传输速度。

第十章 多进程服务器端

(1)下列关于进程的说法错误的是?

c、d
c.进程之间不存在嵌套关系,进程之间是独立的
d.

(2)调用fork函数将创建子进程,以下关于子进程描述错误的是?

a、c、d
a.子进程运行结束,父进程未调用 wait() 或 waitpid() 函数回收子进程的资源,子进程就会变成僵尸进程(得不到回收,而且又占用cpu资源),所以父进程要是先结束就无法回收子进程。
c.进程之间是独立的,不会共享对方的资源
d..程序是顺序执行的,应该是执行调用fork之后的代码

(3)创建子进程时将复制父进程的所有内容,此时的复制对象也包含套接字文件描述符。编写程序验证复制的文件描述符整数值是否与原文件描述符整数值相同。

#include <stdio.h>
#include <unistd.h>
#include <sys socket&#46;h="">

int main(int argc, char *argv[])
{
	pid_t pid;
	int sockfd=socket(PF_INET, SOCK_STREAM, 0);
	
	pid=fork();		
	
	if(pid==0)
		printf("Child sock fd: [%d] \n", sockfd);
	else
		printf("Parent sock fd: [%d] \n", sockfd);
	return 0;
}

显示
Parent sock fd: [3] 
Child sock fd: [3] </sys></unistd.h></stdio.h>

(4)请说明进程变为僵尸进程的过程及预防措施

僵尸进程是子进程。在子进程结束时,其返回值会传到操作系统,直到返回值被其父进程接收为止前,子进程会一直作为僵尸进程存在。所以,为了防止这种情况的发生,父进程必须明确接收子进程结束时的返回值。

(5)编写程序使其每隔1秒输出简单字符串,并适用于上述时间处理器注册代码

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void ctrl_handler(int sig);

int main(int argc, char *argv[])
{
	struct sigaction act;
	act.sa_handler=ctrl_handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags=0;
	sigaction(SIGINT, &act, 0);

	while(1)
	{
		sleep(1);
		puts("Have a nice day~");
	}

	return 0;
}


void ctrl_handler(int sig)
{
	char ex;
	fputs("Do you want exit(Y to exit)? ", stdout);
	scanf("%c", &ex);
	if(ex=='y' || ex=='Y')
		exit(1);
}

</signal.h></stdlib.h></stdio.h>