第2章 套接字类型和协议部署

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

1. 关于协议

如果两个人展开对话,一方使用电话,那么另一方也得使用电话,不能用书信,电话就是两人对话的协议
如果是计算机就是“计算机之间对话必备的通信规则”
简而言之就是为了完成数据交换而定好的约定

2. 创建套接字

#include<sys/socket.h>
int socket(int domain,int type, int protocol)
成功时返回文件描述符,失败返回-1
domin   套接字中使用的协议族(Protocol Family)信息
type    套接字传输类型信息
protocol    计算机间通信中使用的协议信息

3.协议族(Protocol Family)

socket第一个参数即,参数传递套接字中使用的 协议分类信息,此协议分类信息称为 协议族
头文件 sys/socket.h中声明的协议族

名称

协议族

PF_INET

IPv4互联网协议族

PF_INET6

IPv6互联网协议族

PF_LOCAL

本地通信的UNIX协议族

PF_PACKET

底层套接字的协议族

PF_IPX

IPX Novell协议族

笔记注重PF_INET对应的IPv4互联网协议族
套接字实际采用的最终协议信息是socket第三个参数传递。在指定协议族范围内通过第一个参数决定第三个参数

4.套接字类型(Type)

套接字类型是指套接字数据传输方式,通过socket第二个参数传递,这才能决定创建的套接字数据传输方式。也就是说决定了协议族并不能决定数据传输方式,PF_INET协议族中存在多种数据传输方式。

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

·传输方式特征

  1. 传输过程数据不会消失

  2. 按序传输数据

  3. 传输的数据不存在数据边界(Boundary)

  4. 套接字连接必须一一对应(只能同样特性的套接字连接)

·解释
只要传输数据载体本身没问题就不会数据丢失(稳定),较晚传输的数据不会先到达,保证了数据的按序传递,假设100个糖果分批传送,接受者凑齐100个之后才装袋。 这种情况适合write和read函数 :

传输数据的计算机调用了三次write传递100次,但是接收数据的计算机可能会通过一次read就可以接受全部,套接字内有字节数组作为缓冲,套接字传输的数据保存到这个数组内,也就是说收到数据未必立即调用read,只要数组容量够大,则可能在数据填充满后通过一次read读取全部,也可以分为多次。所以讨论write和read的调用次数没意义。所以说面向连接的套接字没数据边界。

·补充:

  1. 面向连接的套接字会根据接收端的状态传输数据,如果传输出错还提供重传服务

  2. 简单概括这种类型的套接字

     可靠的,按序传递的,基于字节的面向连接的数据传输方式套接字。像谁就不多说了。
    

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

·传输方式特征

  1. 强调快速传输而非传输顺序

  2. 传输的数据可能会丢失也可能损毁

  3. 传输的数据有数据边界

  4. 限制每次传输的大小

·解释
面向消息的套接字追求快速,但无法避免数据丢失或者损毁。每次传输有大小限制,只能分批传送,分几次发送就得分几次接收(有数据边界)。也就是说接受数据的次数和传输次数相同。

·补充:

不可靠的、不按序传递的、以数据的高速传输速率为目的套接字。

5.协议的最终选择

  1. socket前面两个参数传递了协议族信息套接字数据传输方式难道还不能决定最终协议?

    一般来说有了前面两个参数就可以创建套接字,第三个可以传递0,但是如果:“同意协议族中存在多个数据传输方式相同的协议”,这种情况下需要指定具体协议信息。

  2. 本书主要基于IPv4展开的

    1. 基于IPv4协议族,SOCK_STREAM面向连接的数据传输,满足条件的只有IPPROTO_TCP。这种套接字称为TCP套接字,如下:

        int tcp_socket = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
      
    2. 基于IPv4协议族,SOCK_DGRAM面向连接的数据传输,满足条件的只有IPPROTO_UDP。这种套接字称为UDP套接字,如下:

       int udp_socket = socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);
      

6.面向连接的套接字:TCP套接字示例

这个示例的服务端与第一章用的代码相同,客户端修改read函数的调用方式,验证

TCP套接字传输的数据不存在数据边界

客户端代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.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;

        if(argc!=3)
        {
                printf("Usage : %s <IP> <port> \n",argv[0]);
                exit(1);
        }

        sock=socket(PF_INET,SOCK_STREAM, 0);//创建TCP套接字。前面两个参数就能确定协议,可省略IPPROTO_TCP
        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!");

       while(read_len=read(sock,&message[idx++],1))//while循环反复调用read函数,每次读取一个字节。如果read返回0说明读取完成,跳出循环
        {
                 if(read_len==-1)
                         error_handling("read() error!");
                str_len+=read_len;//read_len值始终为1,因为每次读取一个字节。出了循环后str_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);
}

运行完服务端和客户端代码之后结果与之前相同:

2.2Windows平台下的实现以及验证

  1. 套接字函数socket

#include<winsock2.h>
SOCKET socket(int af,int type,int protocol);
成功时返回套接字句柄,失败时返回INVALID_SOCKET

参数种类以及含义与Liunx完全一样,只讨论返回值类型。 返回值类型为SOCKET,次结构体用来保存整数型套接字句柄值。实际上socket返回值类型为整型,完全可以通过int类型接收。但是考虑到扩展性,最好用SOCKET数据类型来保存套接字句柄。发生错误时返回的INVALID_SOCKET实际上就是-1,但是这不重要,除非用-1判断是否发生错误,但是这样并不好,如果以后微软要修改INVALID_SOCKET的值,代码需要大面积修改,所以并不好。虽然琐碎但是重要。

  1. 基于Windows的TCP套接字演示

     笔记里就不过多记录了,几乎和之前Windows一样,就只需要修改几处代码
    

TCP套接字示例

服务端

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
void ErrorHandling(char* message);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hSocket;
	SOCKADDR_IN servAddr;

	char message[30];
	int strLen=0;
	int idx=0, readLen=0;

	if(argc!=3)
	{
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}

	if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error!");  
	
	hSocket=socket(PF_INET, SOCK_STREAM, 0);
	if(hSocket==INVALID_SOCKET)
		ErrorHandling("hSocket() error");
	
	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family=AF_INET;
	servAddr.sin_addr.s_addr=inet_addr(argv[1]);
	servAddr.sin_port=htons(atoi(argv[2]));
	
	if(connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr))==SOCKET_ERROR)
		ErrorHandling("connect() error!");
 
	while(readLen=recv(hSocket, &message[idx++], 1, 0))
	{
		if(readLen==-1)
			ErrorHandling("read() error!");
		
		strLen+=readLen;
	}

	printf("Message from server: %s \n", message);  
	printf("Function read call count: %d \n", strLen);

	closesocket(hSocket);
	WSACleanup();
	return 0;
}

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

客户端

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
void ErrorHandling(char* message);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hSocket;
	SOCKADDR_IN servAddr;

	char message[30];
	int strLen=0;
	int idx=0, readLen=0;

	if(argc!=3)
	{
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}

	if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error!");  
	
	hSocket=socket(PF_INET, SOCK_STREAM, 0);
	if(hSocket==INVALID_SOCKET)
		ErrorHandling("hSocket() error");
	
	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family=AF_INET;
	servAddr.sin_addr.s_addr=inet_addr(argv[1]);
	servAddr.sin_port=htons(atoi(argv[2]));
	
	if(connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr))==SOCKET_ERROR)
		ErrorHandling("connect() error!");
 
	while(readLen=recv(hSocket, &message[idx++], 1, 0))
	{
		if(readLen==-1)
			ErrorHandling("read() error!");
		
		strLen+=readLen;
	}

	printf("Message from server: %s \n", message);  
	printf("Function read call count: %d \n", strLen);

	closesocket(hSocket);
	WSACleanup();
	return 0;
}

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

本文章使用limfx的vscode插件快速发布