zoukankan      html  css  js  c++  java
  • Linux基础(05)socket编程

     Linux的核心思想之一 "一切皆文件" 

    内容 , socket在Linux内核的实现的代码及TCP和UDP的实现

                  网络编程常用头文件:  https://blog.csdn.net/jx232515/article/details/51912700

    1.  例如本地文件对一个字符串的"aaaaaaa"的读写是 open()返回的句柄 fd 作为载体, 调用 write 和 read 进行读写

      而网络文件的和本地文件差不多但多了一些特性  用socket()返回的句柄 socket 传输读写数据前 要再创建一个sockaddr_in 对象 设置好协议族family 地址ip 端口prot 后 把设置好的sockaddr_in 对象利用 bind() 和 socket 绑定 ; 再用 listen()监听socket 有哪些连接和设置最大监听数量

        完成上述一系列后可以调用 write 和 read 进行读写 , 也有一些特殊的: accept()  connect()  (setsocketopt()设置sendbuff和recvbuff的size)


    2.内核的socket就是这样一个简单的结构 , 里面每种结构都是函数指针或变量的集合 , 

          方便实现每种协议的接口函数名和最终目的相同,但是实现过程却不同的面向对象思想

    在Linux内核里 ops是一些函数的集合如accept() connect()等 , file里是一些write() read() 等 , sk是 bind() listen() 等;

           每种协议栈的接口函数名是相同的但是内部实现是不同的


    3.在创建cocket对象时要告诉内核你要使用哪种协议 , socket()第一个参数添加ipv4/ipv6协议 第三个参数添加TCP/UDP协议也可以为0(默认具体依赖第二个参数) , 第二个参数,如果是流套接字的SOCK_ STREAM默认是TCP协议的 , 如果是数据包套接字SOCK_DGRAM默认是UDP ,还有个原始套接字SOCK_RAW是使用其他协议,具体要给第三个参数

    套接字分为三类;

    流式socket(SOCK_STREAM):提供可靠,面向连接的通信流;它使用TCP协议,从而保证了数据传输的正确性和顺序性。

    数据报socket(SOCK_DGRAM):数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,无序的,并且不保证可靠,无差错的。它使用的数据报协议是UDP。

    原始socket:原始套接字允许对底层协议如TP或ICMP进行直接访问,它功能强大但使用复杂,主要用于一些协议的开发。


    4.TCP/IP协议

      4.1分层概念 : mac  ip  tcp

        物理层 MAC(源MAC 目标MAC 长度和类型L/T )------>ARP 解决链路和物理连接问题

        网络层 IP  解决远程通信问题

        传输层 TCP/UDP ---->端口(对应了本机的某个进程) 决定了数据传给哪个进程

        应用层 HTTP HTTPS FTP TELNET SSH

      4.2 ip格式 点分十进制 二进制

          网络字节序(大端)0x12 0x34 0x56 x078 本地字节序(小端) 0x78 0x56 0x34 0x12

          case 1 : 点分十进制 转换 网络字节序的二进制  有个专用函数  in_addr_t inet_addr(const char* cp);

          case2 : 网络字节序的二进制 转换 点分十进制          char* inet_ntoa(struct in_addr_t in);

      4.3子网掩码 用于确定网段的范围  24位能用0~255个ip地址  28位能用14个IP地址和一个255广播地址

          24 == 32位二进制中有 24位是1 剩下的8位是0 (一个字节2位)

          255.255.255.0  == 11111111.11111111.11111111.00000000

      4.4 拆包和封包过程


    5.TCP编程原理和流程

     

    socket()的返回值: 成功 >0 (返回文件描述符的序号) , 失败 -1 ( 创建失败 INVALID_SOCKET也是-1 )

    bind()的返回值: 成功 0 , 失败 <0 (可用WSAGETLASTERROR 函数取错误码)

    listen()    : 成功0 , 失败 <0 (可用WSAGETLASTERROR 函数取错误码)

    accept()    : 成功>0(返回文件描述符的序号一般从3开始) , 失败 <0 (可用WSAGETLASTERROR 函数取错误码)

    connect()  : 成功 0  , 失败 <0 (可用WSAGETLASTERROR 函数取错误码)

    recv/recvfrom() : 成功 >0 (返回字符串的len)  , =0 (断开连接) ,失败 <0 (可用WSAGETLASTERROR 函数取错误码)

    send/sendto() :成功 >0 (返回字符串的len)  , =0 (断开连接) ,失败 <0 (可用WSAGETLASTERROR 函数取错误码)

    recv和recvfrom的区别在于recvfrom可以获取或设置对端的信息(明确发送或接收的目标), send和sendto同理

    sendto可以在参数中指定发送的目标地址 , 适用于发送未建立连接的UDP数据包 , send需要socket已建立连接,sendto可用于无连接的socket

    对于有连接的socket,两者一样,sendto最后两个参数没用

    int sendto(int s, const void * msg, int len, unsigned int flags, const struct sockaddr * to, int tolen);

    int send( SOCKET s, const char FAR *buf, int len, int flags );

     

    阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回;  connect 和  accept 都是阻塞函数

      5.1 TCP服务端

    #include<stdio.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<string.h>
    #include<unistd.h>
    #include <arpa/inet.h>
    #include<pthread.h>
    
    /*
    面向对象三步:创建对象 设置对象 分配对象
    */
    int main( int argc, char* argv[] )
    {
        //1. creat socket
        int sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
        if(sock < 0)
        {
            printf("create socket fail");
            return 1;
        }
        //2.set socket
        //设置本地协议族和地址端口    设置时用sockaddr_in                  绑定    或使用时强转sockaddr*
        struct sockaddr_in local;
        local.sin_family = AF_INET;        //协议族
        //htons: host to network 把本地端口转换成网络的 ntohs 相反
        local.sin_port = htons(8881);    //端口
        //inet_addr点分十进制转换网络二进制 
        local.sin_addr.s_addr = inet_addr("127.0.0.1");    //源地址 inet_addr()点分十进制(255.255.255.0))转换成网络字节序的二进制(1111.1111.1111.0)
        
        //3.分配对象
        if(bind(sock,(struct sockaddr *)&local,sizeof(local)) < 0)    //将源信息local绑定到套接字socket上
        {
            perror("bind error
    ");
            close(sock);
            return 2;
        }
        printf("bind success
    ");
        //4.连接
        if(listen(sock,10) < 0)        //接受连接和设置最大连接数
        {
            perror("listen error
    ");
            close(sock);
            return 3;
        }
        printf("Listen success
    ");
        
        struct sockaddr_in peer;    //保存对端信息
        socklen_t len = sizeof(peer);
        //5.监听
        int fd = accept(sock,(struct sockaddr *)&peer ,&len);    //阻塞函数直到有客户端连接也可以选择不保存第二第三可以为NULL
        if(fd < 0)
        {
            perror("accept error
    ");
            close(sock);
            return 4;
        }
        printf("accept success
    ");
    
        char recvbuff[128];
        //6.开始通信
        while(1)
        {
             memset(recvbuff,0,sizeof(recvbuff));
            //recv(fd, recvbuff, sizeof(recvbuff),0);
             read(fd, recvbuff, sizeof(recvbuff));    //读取accept返回的对端数据
             if(strcmp(recvbuff,"exit
    ")==0)
                 break;
             fputs(recvbuff, stdout);
            ///send(fd, recvbuff, sizeof(recvbuff),0);
             write(fd, recvbuff, sizeof(recvbuff));    //往对端写入数据
         }
        
        close(fd);
        close(sock);    
        return 0;
    }

      5.2客户端 

    #include<stdio.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<string.h>
    #include<unistd.h>
    #include <arpa/inet.h>
    #include<pthread.h>
    
    int main( int argc, char* argv[] )
    {
        //1. creat socket
        int sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
        if(sock < 0)
        {
            perror("socket error
    ");
            return 1;
        }
        printf("socket success!
    ");
        //2.set socket
        struct sockaddr_in local;
        local.sin_family = AF_INET;
        local.sin_port = htons(8881);
        local.sin_addr.s_addr = inet_addr("127.0.0.1");
        
        if(connect(sock , (struct sockaddr* )&local , sizeof(local)) < 0)    //连接服务器
        {
            perror("connect error
    ");
            close(sock);
            return 2;
        }
        printf("connect success!
    ");
    
    
        char recvbuff[128];
        char sendbuff[128];
        while(fgets(sendbuff,sizeof(sendbuff),stdin))
        {
            write(sock,sendbuff,strlen(sendbuff));
            if(0 == strcmp(sendbuff,"exit
    "))
                break;
            read(sock,recvbuff,sizeof(recvbuff));
            fputs(recvbuff,stdout);
    
            memset(sendbuff, 0,sizeof(sendbuff));
            memset(recvbuff, 0,sizeof(recvbuff));
        }
        close(sock);
        return 0;
    }

     


    6.UDP协议

      客户端不需要绑定是因为在使用sendto()

      sendto() 和 recvfrom() 返回值 为整型,如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR。

      recvfrom() 用serversock接收客户端的数据包和ip信息并另存为buff 和 最后两个参数

        s:套接字, 

        buff:接收数据缓冲区 ,

        bufflen:缓冲区长度 , 

        flags:调用操作方式(一般是0) ,

        from:指针(保存发送端的信息), 

        fromlen:保存的长度 (socklen_t类型的变量的首地址) &len

      sendto()

        s:套接字, 

        buff:发送数据缓冲区 ,

        bufflen:缓冲区长度 , 

        flags:调用操作方式(一般是0) ,

        from:指针(指向发送端信息), 

        fromlen:发送的长度 (sizeof() 或传入 socklen_t类型的变量) len

      数据类型socklen_t和int具有相同的长度.否则就会破坏 BSD套接字层的填充  转换使用

    6.1 服务端

    #include <stdio.h>
    #include <string.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <arpa/inet.h>
    
    int  main( int argc, char* argv[] )
    {    int serverSock = socket(AF_INET,SOCK_DGRAM,0);
    
        struct sockaddr_in local;
        local.sin_family = AF_INET;
        local.sin_port = htons(8888);
        local.sin_addr.s_addr = inet_addr("127.0.0.1");
    
        printf("socket success
    ");
        int bindr = bind(serverSock,(struct sockaddr*)&local,sizeof(local));
        if( bindr < 0)
        {
            perror("bind error");
            close(serverSock);
            return 1;
        }
        printf("bind success
    ");
    
        struct sockaddr_in clientSock;
        socklen_t len = sizeof(clientSock);
        char buff[128];
        while(1)
        {
            memset(buff,0,sizeof(buff));
          //recv()和accept()的结合体 recvfrom(serverSock,buff,
    sizeof(buff),0,(struct sockaddr*)&clientSock,&len); fputs(buff,stdout);
          //send()和connect()的结合体 sendto(serverSock,buff,
    sizeof(buff),0,(struct sockaddr*)&clientSock,len); }
       close(serverSock);
       return 0; }

    6.2客户端

    #include <stdio.h>
    #include <string.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <arpa/inet.h>
    
    int  main( int argc, char* argv[] )
    {    //1.create socket
        int clientsock = socket(AF_INET , SOCK_DGRAM , 0);
        //2.set socket
        struct sockaddr_in local;
        local.sin_family = AF_INET;
        local.sin_port = htons(8888);
        local.sin_addr.s_addr = inet_addr("127.0.0.1");
        if(clientsock < 0)
        {
            perror("socket perror");
         close(clientsock);
    return -1; } printf("socket Success "); socklen_t len; char buf[128] ; while(1) { printf("aaa:"); fgets(buf,sizeof(buf),stdin);     //往8888端口发送buf if(sendto(clientsock,buf,sizeof(buf),0,(struct sockaddr*)&local,sizeof(local)) < 0) { perror("sendto error");
           close(clientsock);
    return 1; } socklen_t len = sizeof(local); recvfrom(clientsock,buf,sizeof(buf),0,(struct sockaddr*)&local,&len); fputs(buf,stdout); memset(buf,0,sizeof(buf)); } close(clientsock); return 0; }

     


    7.进阶

    • udp广播数据包(广播只能是udp实现)

        路由器不转发广播数据包, 交换机会转发广播数据包 . 广播只能在一个广播域(局域网)中传播 , 而不能跨网段传播

        能够在组播组里进行传播 , 且路由器可以进行组播数据包转发, 能跨局域网传播(必须是同一个组播组)

        广播组播的区别: 都是用setsockopt开启,但是组播有ip 广播没有

    • 如何识别广播包

        MAC: 广播包的目标mac 是全ff  FF:FF:FF:FF:FF:FF

        IP:  当前网段的最后一个地址 x.x.x.255


    8.socket选项设置与读取函数

      int setsockopt()  是否开启广播等功能

               详解 https://blog.csdn.net/a493203176/article/details/70053137

          SOCKET sock, 指向一个打开的套接口描述字

          int level,  指定选项的类型。
               SOL_SOCKET: 基本套接口
               IPPROTO_IP: IPv4套接口
               IPPROTO_IPV6: IPv6套接口
               IPPROTO_TCP: TCP套接口

          int optname, 选项的名称

          char* optval, 是一个指向变量的指针 类型:整形,套接口结构, 其他结构类型:linger{}, timeval{ } 默认为0(false)不开启广播 , 1(ture)为开启

          int optlen optval 的大小      

      int getsockopt()  用法 https://www.cnblogs.com/wangshuo/archive/2011/04/20/2022279.html

          sockfd:一个标识套接口的描述字。
          level:选项定义的层次。支持的层次有SOL_SOCKET、IPPROTO_TCP。
          optname:需获取的套接口选项。
          optval:指针,指向存放所获得选项值的缓冲区。
          optlen:指针,指向optval缓冲区的长度值。
     
     
    总结: TCP:server: socket()-----绑定bind()----监听listen()----接受连接并保存对端accept()-----读取数据recv()-----发送数据send()
         client:  socket()------------------------------------发起连接connect()------------------发送数据send()----接收数据recv()
        UDP:server: socket()----绑定bind()-----accept和recv的结合体recvfrom()-----connect和send的结合体sendto()
          client: socket()---------------------connect和send的结合体sendto()-----accept和recv的结合体recvfrom()
    setsockopt()设置socket_fd的选项开启或关闭一些功能或更改缓冲区, 比如开启UDP的广播功能等
  • 相关阅读:
    容斥原理算法总结(bzoj 2986 2839)
    网络流系列算法总结(bzoj 3438 1061)
    bzoj 2746: [HEOI2012]旅行问题 AC自动机fail树
    bzoj 3283: 运算器 扩展Baby Step Giant Step && 快速阶乘
    计算几何考场绘图技巧
    bzoj 1845: [Cqoi2005] 三角形面积并 扫描线
    bzoj 3784: 树上的路径 堆维护第k大
    BZOJ 1231: [Usaco2008 Nov]mixup2 混乱的奶牛
    BZOJ 1112: [POI2008]砖块Klo
    BZOJ 1003: [ZJOI2006]物流运输trans DP+最短路
  • 原文地址:https://www.cnblogs.com/yxnrh/p/11494700.html
Copyright © 2011-2022 走看看