《TCP IP网络编程》第二章 套接字类型与协议设置

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

2.1 套接字协议及其数据传输特性

关于协议

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

创建套接字
#include<sys/socket.h>

int socket(int domain, int type, int protocol);
			domain		套接字中使用的协议族信息
			type		套接字数据传输类型信息
			protocol	计算机通信中使用的协议信息
协议族(domain)
sys/socket.h中声明的协议族名称协议族
PF_INETIPv4互联网协议族(最常用)

此处只列出最常用的,其他不常用的就不列出了。

套接字类型(type)

套接字类型 == 套接字的数据传输方式。

同一个协议族,如PF_INET协议族中,也存在多种数据传输方式。

两种常见的套接字类型(数据传输方式)如下:

套接字类型1:面向连接的套接字(SOCK_STREAM)

特点:

  • 传输过程中数据不会消失
  • 按序传输数据
  • 传输的数据不存在数据边界
  • 面向连接的套接字只能与另外一个同样特性的套接字连接

总结起来就是:可靠的、按序传递的、基于字节的、面向连接的数据传输方式的套接字

关于不存在数据边界的问题解释:

传输数据的计算机通过3次调用write函数传递了100字节数据,但接收数据的计算机仅通过1次read函数调用就接收了全部100字节。

②收发数据的套接字内部有缓冲(就是字节数组)。通过套接字传输的数据将保存到该数组,因此,收到数据不意味着立马调用read函数。只要不超过数组容量,则有可能在数据填充满缓冲区后通过1次read函数调用读取全部,也有可能分成多次read函数调用进行读取。

③面向连接的套接字中,read函数和write函数的调用次数并无太大意义。也就是,面向连接的套接字不存在数据边界。

套接字缓冲已满是否意味着数据丢失

由于可以调用read从缓冲区读取部分数据,所以,缓冲区并不全是满的。但read函数读取速度若比接收数据的速度慢,那么缓冲区有可能被填满。此时,套接字无法再次接收数据。

但是!!即使这样,也不会发生数据丢失,因为传输端套接字将停止传输。也就是说,面向连接的套接字会根据接收端的状态传输数据,如果传输出错还能提供重传服务。

因此,面向连接的套接字除特殊情况外不会发生数据丢失。

套接字类型2:面向消息的套接字(SOCK_DGRAM)

特点:

  • 强调快速传输而非传输顺序
  • 传输的数据可能丢失也可能损毁
  • 传输的数据有数据边界
  • 限制每次传输的数据大小

有数据边界: 发送者发送两次,接受者也得接收两次。这种特性就是传输的数据有数据边界。

总结起来就是:不可靠的、不按序传递的、以数据的高速传输为目的的套接字。

面向消息的套接字不存在连接的概念。

协议的最终选择
int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);		建立一个TCP套接字

int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);		建立一个UDP套接字
面向连接的套接字:TCP套接字示例

更改hello_client.cread的次数,每次读取一个字节,后来发现共读取了13次。(Hello World!)

2.2 Windows平台下的实现及验证

Windows操作系统的socket函数

函数名和参数名都与Linux平台相同,只有返回值类型稍有不同。

#include<winsock2.h>

SOCKET socket(int af, int type, int protocol);
成功时返回socket句柄,失败时返回INVALID_SOCKET

SOCKET可以看做是保存套接字句柄的一个数据类型,其实socket返回整数,可以通过int接收,但是考虑到以后的扩展性,还是用SOCKET接收。

失败返回值INVALID_SOCKET,是个常数,一般为-1,但是最好还是写成INVALID_SOCKET,否则如果微软更改了这个常数值,那么修改工作量会特别大。

2.3 习题

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

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

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

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

(3)面向消息的套接字的特性?

  1. 传输数据可能丢失
  2. 有数据边界
  3. 以快速传递为目标
  4. 限制每次传递数据的大小
  5. 与面向连接的套接字不同,不存在连接的概念

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

连接指向型TCP套接字不存在数据边界。

因此输入输出函数的响应次数不具有意义。重要的不是函数的响应次数,而是数据的收发量。因此,必须将传输数据的量和接收数据的量制作成编码,保证发送数据的量和接收数据的量是一致的,特别要注意是制作依赖函数响应次数判断代码。

(6)修改代码,使客户端仅用一次read读取服务器端多次write发出的内容:
tcp_serv.c

/*****************************tcp_serv.c*********************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

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 <port>\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

/*****************************tcp_clnt.c*********************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

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 <IP> <port>\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);
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值