TCP/IP网络编程C02-套接字类型与协议设置

学习笔记

socket()函数


int socket(int domain, int type, int protocol);       
// domain:采取的协议族,一般为 PF_INET;
//type:数据传输方式,一般为 SOCK_STREAM;
//protocol:使用的协议,一般设为 0 即可。
//成功时返回文件描述符,失败时返回 -1

创建套接字的函数 socket 的三个参数的含义:

  1. domain:使用的协议族。一般只会用到 PF_INET,即 IPv4 协议族。

  2. type:套接字类型,即套接字的数据传输方式。主要是两种:SOCK_STREAM(即 TCP)和 SOCK_DGRAM(即 UDP)。

  3. protocol:选择的协议。一般情况前两个参数确定后,protocol 也就确定了,所以设为 0 即可。

协议族(Protocol Family)

协议就是为了完成数据交换而定好的约定

名称 协议族
PF_INET IPv4互联网协议族
PF_INET6 IPv6互联网协议族
PF_LOCAL 本地通信的UNIX协议族
PF_PACKET 底层套接字的协议族
PF_IPX IPX Novell协议族

套接字类型(Type)

面向连接的套接字(SOCK_STREAM)

SOCK_STREAM 代表的是 TCP 协议,会创建面向连接的套接字,有如下特点:

  1. 可靠传输,传输的数据不会消失。

  2. 按序传输。

  3. 传输的数据没有边界:从面向连接的字节流角度理解。接收方收到数据后放到接收缓存中,用户使用 read 函数像读取字节流一样从中读取数据,因此发送方 write 的次数和接收方 read 的次数可以不一样。

面向消息的套接字(SOCK_DGRAM)

SOCK_DGRAM 代表的是 UDP 协议,会创建面向消息的套接字,有如下特点:

  1. 快速传输。

  2. 传输的数据可能丢失、损坏。

  3. 传输的数据有数据边界:这意味着接收数据的次数要和传输次数相同,一方调用了多少次 write(send),另一方就应该调用多少次 read(recv)。

  4. 限制每次传输的数据大小。

协议的最终选择

因为有这种情况:同一协议族中存在多个数据传输方式相同的协议,所以还需要第三个参数 protocol 来指定具体协议。
但是 PF_INET(IPv4 协议族)下的 SOCK_STREAM 传输方式只对应 IPPROTO_TCP 一种协议,SOCK_DGRAM 传输方式也只对应 IPPROTO_UDP 一种协议,所以参数 protocol 只要设为 0 即可。

int tcp_socket = socket(PF_INET, SOCK_STREAM, 0);
int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);  // 和上面效果一样

习题答案

Q01

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

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

Q02

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

  • 传输过程中数据不会丢失(有快重传和超时重传机制) [[TCP协议下是如何保证数据可靠性#快重传和超时重传]]
  • 按序传输数据
  • 传输的数据不存在数据边界 (Boundary)

Q03

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

  • 传输数据可能丢失
  • 没有数据边界(Boundary)
  • 以快速传递为目标
  • 不限制每次传输数据大小
  • 与面向连接的套接字不同,不存在连接概念

Q04

下列数据适合用哪类套接字进行传输?

  • 演唱会现场直播的多媒体数据(UDP)
  • 某人压缩过的文本文件(TCP)
  • 网上银行用户与银行之间的数据传递(TCP)

Q05

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

TCP套接字,即连接导向型套接字,不存在收发数据的边界。因此,I/O函数的调用次数不具有意义。重要的不是函数的调用次数,而是数据的收发量。因此,必须编写代码,以便发送的数据量和接收的数据量相匹配,特别是不能编写依赖于函数的调用次数的代码。

Q06

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

	for(i=0;i<3000;i++)
		printf("Wait time ‰d \n",i);

让CPU执行多余任务以延迟代码运行的方式称为“ Busy Waiting"。使用得当即可推迟函数调用。

代码如下:

	/*****************************tcp_serv.c*********************************/
	#include 
	#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);  
		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(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 
	#include 
	#include 
	#include 
	#include 
	#include 
	#include 

	void 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);
	}

书本源码

01-tcp_client.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 


/*
domain
type
protocol
*/

void 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;
	
	if(argc!=3){
		printf("Usage : %s  \n", argv[0]);
		exit(1);
	}
	
	sock=socket(PF_INET, SOCK_STREAM, 0);//TCP
	if(sock == -1)
		error_handling("socket() error");
	
	//填写目的端的IP地址和端口
	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!");


	//如果服务端进程关闭了socket连接,那么客户端会接收到服务端发送过来的一个 TCP 协议的 FIN 数据包,然后客户端进程中原本阻塞着等待接收服务端进程数据的 read函数此时就会被唤醒,返回一个值 0。
	//这跟我们前面提到两种文件读到文件末尾返回 EOF(值为-1)的情况有点差别,所以在程序中从 socket 进行读取操作时,判断数据流结束的标志不是 -1 而是 0。
	while(read_len=read(sock, &message[idx++], 1))//每次只读一个字节的消息
	{
		if(read_len==-1)
			error_handling("read() error!");
		
		str_len+=read_len;
	}

	printf("Message from server: %s \n", message);
	printf("Function read call count: %d \n", str_len);
	close(sock);
	return 0;
}

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

/******************** input******************
description:
服务器端发送了13字节的数据,客户端调用13次read函数进行读取。


content:
 ./01-tcp_client 127.0.0.1 9190


*******************************************/



/******************** output******************
description:



content:
Message from server: Hello World! 
Function read call count: 13 

*******************************************/

02-tcp_server.c

#include 
#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");
	

	//填写服务器端的IP地址和端口
	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, sizeof(message));
	close(clnt_sock);//这段代码会发送FIN包
	close(serv_sock);
	return 0;
}

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



content:
./02-tcp_server 9190


*******************************************/



/******************** output******************
description:
向客户端发送"Hello World!"这个消息


content:


*******************************************/

参考链接

  • 《TCP/IP网络编程》课后练习答案第一部分1~5章 尹圣雨_KongJHong的博客-CSDN博客_tcpip网络编程课后答案
  • phoon/TCP-IP-NP: 《TCP/IP网络编程》((韩)尹圣雨) 学习笔记
  • 《TCP/IP网络编程》学习笔记整理 - 从园客博开始 - 博客园
  • riba2534/TCP-IP-NetworkNote: 《TCP/IP网络编程》(韩-尹圣雨)学习笔记
  • hclg/tcp_ip: TCP/IP 网络编程

你可能感兴趣的:(网络,tcp/ip,udp)