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

2.1.1 协议族

协议也有许多的分类,根据socket函数的第一个参数传递套接字中使用的协议分类信息。这类信息被称为协议族。

  1. #include<sys/socket.h>
  2. int socket(int domain, int type, int protocol);
  3. //成功时返回文件描述符,失败时返回-1
名称 协议族
PF_INET IPv4互联网协议族
PF_INET6 IPv6互联网协议族
PF_LOCAL 本地通信的UNIX协议族
PF_PACKET 底层套接字的协议族
PF_IPX IPX Novell协议族

实际上套接字中实际采用的最终协议信息是通过socket函数的第三个参数传递的。在指定的协议族范围内通过第一个参数决定第三个参数

2.1.2 套接字类型

套接字类型指的是套接字的数据传输方式,通过socket函数的第二个参数传递。(第一个参数只是决定了协议族,然而决定了协议族并不能决定传输方式,也就是PF_INET协议族中存在着多种数据传输方式)。

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

socket函数传递第二个参数是SOCK_STREAM,将创建面向连接的套接字。

  • 传输过程中数据不会消失
  • 按序传输数据
  • 传输的数据不存在数据边界
  • 套接字连接必须一一对应

关于不存在数据边界进行解释,传输数据的过程中计算机调用了3次write函数传递了100字节的数据,但接收者只通过了1次read函数进行读取。那么也有可能传递了很多数据,调用了好几次read函数进行读取。也就是说在面向连接的套接字中,这两个函数的调用次数并无太大意义。所以说面向连接的套接字不存在数据边界。

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

socket函数传递第二个参数是SOCK_DGRAM,将创建面向消息的套接字。

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

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

2.1.3 协议的最终选择

首先声明一点传递前两个参数即可创建所需的套接字 ,大部分情况下可以向第三个参数传递0,除非遇到以下情况:同一协议族中存在多个数据传输方式相同的协议。也就是数据传输方式相同,但在该协议族里面的协议不同。所以需要第三个参数进行具体指定协议信息

  1. IPv4协议族中面向连接的套接字
  1. int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  1. 2.**IPv4**协议族中**面向消息**的套接字
int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);

2.1.4 面向连接的套接字:TCP套接字示例

  • hello_server.c —— tcp_server.c:无变化
  • hello_client.c —— tcp_client.c:更改read函数调用方式

修改代码验证传输的数据不存在数据边界。需让write函数的调用次数不同于read函数的调用次数。所以在客户端中多次调用read函数以接受服务器端发送的全部数据。

// tcp_client
#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;
    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);
    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))
    {
        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);
}
#tcp_server
zhang@zhang-virtual-machine:~/Desktop/Ctest$ gcc tcp_server.c -o tcp_server
zhang@zhang-virtual-machine:~/Desktop/Ctest$ gcc tcp_client.c -o tcp_client
zhang@zhang-virtual-machine:~/Desktop/Ctest$ ./tcp_server 9190
#tcp_client
zhang@zhang-virtual-machine:~/Desktop/Ctest$ ./tcp_client 127.0.0.1 9190
Message from server : Hello World! 
Function read call count:207