协议与套接字类型

协议与套接字类型

本章主要讲的是套接字类型以及简单的协议

协议概念理解

计算机网络当中有各种各样的协议,每种协议都是为了解决特定问题而出现的。例如,相隔很远的两个人进行通信,如果一方采用电话,则另外一方也必须采用电话而非书信。因此采用电话进行通信就是两个人进行通信的协议。协议的具体内容可以去参照其他解答,这里不再赘述。

套接字

计算机网络编程-基础篇01 当中我们使用了套接字,但是没有深入阐述套接字。因此本篇主要是阐述套接字的使用。

创建套接字

#include<sys/socket.h>
int socket(int domain,int type,int protocol);

domain: 套接字中使用的协议族信息。

type:套接字数据传输类型信息

protocol:计算机间通信中使用的协议信息

接下来我们一一分析该函数的前两个参数。
在这里插入图片描述

协议族(Protocol Family)

头文件sys/socket.h 中声明的协议族

名称协议族
PF_INETIPv4 互联网协议族
PF_INET6IPv6互联网协议族
PF_LOCAL本地通信的UNIX协议族
PF_PACKET底层套接字的协议族
PF_IPXIPX Novell协议族

我们主要记住前两个协议族信息就OK,其他的并不常用亦或是尚未普及。正如表中所述,协议族是许多相关具体协议的集群,而socket函数第三个参数(protocol)就是用来制定第一个参数(协议族)里面的某个具体的协议,第三个参数常用的协议有:IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、等等。一般我们设置第三个参数为0代表选择type类型(第二个参数)默认的协议,除非“同一协议族存在多个传输方式相同的协议”我们才将第三个参数设置成具体的 协议信息。

套接字类型(Type)

套接字类型指的是数据传输的方式,例如:面向连接的套接字(TCP套接字)、面向消息的套接字(UDP套接字)。也就是说,即使我们通过第一个参数指定协议族依然不能确定数据的传输方式,每种协议族都有各种数据传输的方式,因此需要特别指定。接下来我们主要介绍两种主要的传输方式的特点:TCP 、UDP(注意不仅仅有这两种传输方式,但这两种是比较常用的)

面向连接的套接字(SOCK_STREAM)
  • 特点

    1. 传输过程中数据不会丢失(这块内容需要单独分出一块来讲,我会在接下来的博客当中讲)。

    2. 按序传输数据(类似于队列)。

    3. 传输的数据不存在边界。(可以看实例中的代码)

      将数据分100批发送,但是接受者不会因为一个包达到之后接受,而是等到100个包到达之后才接受。在计算机里面的体现就是:传输数据的计算机通过调用100次write函数传递了100字节的数据,但接受者只通过调用一次read函数就把所有数据接受了。

      其实接收者有一个缓冲数组用于存放来自发送者发送的包,只要缓冲数组没满,我们就可以继续接收直到数据接收完毕或者缓冲区满员才调用一次read函数读取(接收)

  • 细节

    1. 套接字连接必须一一对应

      面向连接的套接字只能与另外一个同样特性的套接字连接

综上所述,这种传输方式可以总结出一句话:可靠的、按序传递的、基于字节的面向连接的数据传输方式的套接字

面向消息的套接字(SOCK_DGRAM)
  • 特点

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

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

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

      即:发送方一个个包发送,接收方一个个接收。

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

    综上所述:这种传输方式也可以总结为一句话:不可靠的、不按序传递的、以数据的高速传输为目的的套接字。

实例

本实例是基于前面计算机网络编程-基础篇01 中代码修改而来。具体差别:

hello_server.c :无任何变化

hello_client.c : 更改read函数的调用方式

该实例验证如下特性:

“传输的数据不存在数据边界”

  • 客户端(tcp_client.c)

    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<arpa/inet.h>
    #include<sys/socket.h>
    #include<string.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 <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.c)

    #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 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);
        }
        //调用socket函数创建套接字
        serv_sock=socket(PF_INET,SOCK_STREAM,0);
        if(serv_sock==-1)error_handling("sock() 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]));
        //调用bind()函数分配IP地址以及端口号
        if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)
            error_handling("bind() error");
        //调用listen()函数将套接字转为可接收连接的状态
        if(listen(serv_sock,5)==-1)
            error_handling("listen() error");
        clnt_addr_size=sizeof(clnt_addr);
        //调用accept函数受理连接请求。该函数属于阻塞型函数,直到有连接请求才会执行下面的程序
        clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_addr,&clnt_addr_size);
        if(clnt_sock==-1)
            error_handling("accept() error");
        //连接完成之后,系统将套接字看做为一个文件(句柄为socket返回的文件描述符)
        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);
    }
    

编译运行方式:

gcc tcp_server.c -o tcp_server

gcc tcp_client.c -o tcp_client

运行:

先运行服务端:./tcp_server 9091

再运行客户端:./tcp_client 127.0.0.1 9091

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值