第三十三篇,网络编程TCP协议通讯过程实现和函数接口

网络编程知识点概览



   1.核心知识点


       传输层的两个重要协议
            tcp协议:
                tcp有关的理论概念(面试的时候喜欢问)
                linux中tcp通信流程和接口函数
                tcp单播(点播)
                tcp双向通信
                tcp广播
            udp协议
                udp有关的理论概念
                linux中udp通信流程和接口函数
                udp单向和双向通信
       多路复用
       tcp和udp的对比,三次握手,四次握手

   2.引入网络编程
       温故知新                                                                                                                          
       进程间通信:管道(pipe mkfifo) 信号(默认响应动作,改变响应动作kill和signal/sigqueue和sigaction,忽略信号,屏蔽/阻塞信号sigprocmask) 信号量(semget  semctl  semop) 共享内存(shmget shmat shmdt shmctl) 消息队列(msgget msgsnd msgrecv msgctl)
                   以上通信方法都只能在同一台主机内部进行通信
       网络编程也是属于进程间通信的一种方式,网络编程既可以在同一台主机内部通信,也可以在不同的主机间通信


网络中基本概念


========================================
  1.网络模型
       网络模型:为了方便大家理解整个网络的通信过程,人为把网络通信的过程划分为不同的层次
       通信协议:计算机科学家制定的游戏规则
                 比如:古代打仗的通信协议,敌人来了--》放狼烟
                       计算机网络中常见的通信协议 --》HTTP协议
                                                     FTP协议     文件传输协议
                                                     telnet协议  远程登陆协议
       路由:寻找网络数据传输的最优路径(有专门的算法去寻找最优路径)

       两种常见的网络模型:
            第一种: OSI七层模型              
                   应用层     作用:开发特定的应用程序需要用到其中的部分协议
                              比如:HTTP协议
                                    FTP协议     文件传输协议
                                    telnet协议  远程登陆协议
                   会话层     作用:建立会话,对数据加密,解密
                   表示层     作用:数据的封装,解析    http协议(超文本传输协议)  ftp协议(文件传输协议)  telnet(远程登录)
                   传输层     作用:解决数据在网络中传输的问题  tcp协议和udp协议
                   网络层(ip层)  作用:解决路由(数据采取何种路径发送出去最优)问题   ip协议
                   数据链路层   作用:开发网卡的驱动,需要深入了解这个层次的协议
                   物理层         网络接口,真实的网卡
            第二种:TCP/IP模型
                   依据OSI七层模型演变过来的
                   应用层(把应用层,会话层,表示层三个合并)
                   传输层 
                   网际层
                   网络接口层(把数据链路层和物理层合并)
                   

  2.MAC地址
       你电脑物理网卡的地址,是全世界独一无二
    地址协议族
       ip地址有两种,一种IPV4地址协议族,另外一种是IPV6地址协议族
    IPV4
       32位的IP地址,比如:192.168.22.2(点分十进制IP,三个小数点隔成四个部分,每个部分一个字节,小数点不占用存储空间,仅仅只是写法标记)
    IPV6
       为了解决IPV4地址不够用,扩展位数,128位   

  3.端口号
       作用:为了区分同一台主机内部不同的网络进程
             端口号本质上是个无符号的短整型数字  0---65535之间
             程序员是可以自己指定端口号,但是不能使用1024以内的端口号,因为1024以内的端口号很多都被操作系统占用了
             误解:端口号是不是就是进程的ID号--》不是,两个不同的概念


tcp协议的通信过程和接口函数



  1.tcp协议的通信过程/流程


       客户端的流程
           创建套接字--》绑定自己的ip和端口号--》连接服务器--》收发信息--》关闭套接字
       服务器的流程
           创建套接字--》绑定自己的ip和端口号--》监听--》接受客户端的连接请求--》收发信息--》关闭套接字
        


  2.相关的接口函数

      (1)创建tcp套接字--》买手机         
   #include <sys/socket.h>
            int socket(int domain, int type, int protocol);
                 返回值: 成功 返回套接字的文件描述符
                         失败 -1
                 参数:domain    --》地址协议族类型
                                    ipv4地址协议族 --》AF_INET
                                    ipv6地址协议族 --》AF_INET6
                      type --》套接字的类型
                             tcp套接字/数据流套接字/流式套接字 --》SOCK_STREAM
                             udp套接字/数据报套接字           --》SOCK_DGRAM
                      protocol --》扩展协议,一般设置0,不会理会
      (2)绑定ip和端口号       
   #include <sys/socket.h>
            int bind(int socket, const struct sockaddr *address, socklen_t address_len); //既可以传递ipv4也可以传递ipv6
            假设:int bind(int socket, const struct sockaddr_in *address, socklen_t address_len);  //局限性
            假设:int bind(int socket, const struct sockaddr_in6 *address, socklen_t address_len); //局限性
                 返回值:成功 返回0
                        失败 -1
                   参数:socket  --》套接字文件描述符
                         address --》用来存放你需要绑定的ip和端口号(自己的ip和端口号)
                         struct sockaddr      //通用地址结构体(兼容ipv4和ipv6)
                         {
                            unsigned short sa_family;  //地址协议类型  AF_INET或者AF_INET6
                            char sa_data[14];  //存放要绑定的ip和端口号
                         }
                         struct sockaddr_in   //ipv4地址结构体(专门用来存放ipv4地址)
                         {
                            sin_family; //存放地址协议族 AF_INET
                            struct in_addr sin_addr;  //结构体嵌套,存放你要绑定的ipv4地址
                            sin_port;   //存放你要绑定的端口号
                         }
                         struct in_addr
                         {
                             in_addr_t s_addr;;  //最终用来存放你需要绑定的ip地址
                         } 
                         struct sockaddr_in6  //ipv6地址结构体(专门用来存放ipv6地址)
                         {

                         }
                         address_len --》地址结构体的大小,sizeof
        (3)ip地址和端口号的转换--》大小端

             字节序:反映数据在计算机内存中采用何种存储方式去存储数据,分为两种,大小端
             不同的操作系统采用的字节序是不同的
                 比如:ubuntu采用就是小端序
                       计算机网络中传递的数据采用的是大端序存储
             大端序:数据的高字节存放在低地址,低字节存放在高地址
             小端序:数据的高字节存放在高地址,低字节存放在低地址   
             主机字节序:指的就是小端序
             网络字节序:指的就是大端序
 
      3.1 小端序ip转换成大端序ip
                   
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
          in_addr_t inet_addr(const char *cp);
         返回值:返回转换得到的大端序(网络字节序)ip
                  参数:cp --》字符串ip(小端序ip/主机字节序)
          int inet_aton(const char *cp, struct in_addr *inp);
          返回值:成功 0  失败 -1
                  参数:cp --》字符串ip
                  inp --》存放转换得到的大端序ip
                  代码参考:inet_aton("192.168.22.9",&(bindaddr.sin_addr));

                         

    3.2 小端序端口号转换成大端序端口                 
    uint16_t htons(uint16_t hostshort);
              h -->host  n -->network   s -->short
         返回值:返回转换得到的大端序端口号
          参数:hostshort --》小端序端口号
       3.3 大端序ip转换成小端序ip                   
char *inet_ntoa(struct in_addr in);
            返回值:字符串格式的,小端序的ip
            参数:in --》大端序ip
        3.4 大端序端口号转换成小端序端口
             
uint16_t ntohs(uint16_t netshort);
        (4)连接女朋友 --》连接服务器        
    #include <sys/socket.h>
    int connect(int socket, const struct sockaddr *address, socklen_t address_len);
          返回值: 成功 0
                 失败 -1
          参数:address --》存放对方(服务器)的ip和端口号
     (5)监听
 int listen(int socket, int backlog);
               返回值:成功 0
                      失败 -1
                参数:socket --》套接字文件描述符
                     backlog --》重点,同时最多允许多少个男朋友(客户端)连接我,一般这个值建议你5---10之间就可以了
                     listen(tcpsock,5); //最多允许5个男朋友(客户端)同时连接我(服务器)
   (6)愿意接听--》接受客户端的连接请求
   int accept(int socket, struct sockaddr *address,socklen_t *address_len);
    返回值(重点,重点,重点): 成功 返回新的套接字文件描述符
      之所以要产生新的套接字,原因在于服务器需要区分不同的客户端
      每个客户端连接服务器成功之后,accept都会返回一个新的套接字
            失败 -1
      参数:socket --》套接字文件描述符
           address --》存放对方(客户端)的ip和端口号
           address_len --》地址长度大小,要求是指针


     
     总结验证accept函数的特点
              特点1:如果客户端没有连接服务器,服务器会一直阻塞在accept的位置
                     如果有客户端连接服务器成功,accept就不会阻塞,立马返回新的套接字文件描述符
              特点2:客户端连接成功,accept函数会自动帮你把连接成功的客户端信息(ip和端口号)保存到第二个参数中

        (7)收发信息


              tcp收发信息有两组函数都可以用来收发信息
                    第一组:传统的read/write
                    第二组:使用recv/send       read和recv作用相同   write和send作用相同
              发送信息
                 

  ssize_t send(int socket, const void *buffer, size_t length, int flags); 
                           返回值:成功 发送的字节数
                                   失败 -1
                             参数:前面三个参数跟write一模一样
                                   flags --》默认设置为0


              接收信息
                 

  ssize_t recv(int socket, void *buffer, size_t length, int flags); 
                           返回值:成功 接收的字节数
                                   0 --》对方断开连接了
                                   失败 -1
                             参数:前面三个参数跟read一模一样
                                   flags --》默认设置为0

遇到的问题


   1.ubuntu配置ip不熟
         ubuntu配置ip两种常用方法:
              方法一:使用图形用户界面配置
                      详细配置见截图
              方法二:修改配置文件配置
                      vim /etc/network/interfaces  打开这个文件
                      如果是静态ip,linux网络配置成桥接模式
                             (1) 打开/etc/network/interfaces文件
                                                在这个文件的后面加入如下几句话(静态)
                                                          auto  ens33
                                                          iface ens33 inet static      //设置静态ip
                                                          address 192.168.1.5   //设置ip地址
                                                          gateway 192.168.1.1  //设置网关
                                                          netmask 255.255.255.0  //子网掩码
                                                          dns-nameservers 192.168.120.1  //dns服务器 
                                                打开/etc/resolv.conf文件
                                                            namesever  你自己的DNS服务器地址                                        
                                                
                             (2)重启网络
                                               sudo /etc/init.d/networking force-reload     
                                               sudo /etc/init.d/networking restart   
                      如果是动态ip,linux的网络配置成NAT模式
                             (1) 打开/etc/network/interfaces文件
                                    在这个文件的后面加入如下几句话(动态)
                                                          auto  ens33
                                                          iface ens33 inet dhcp      //设置动态ip
                                                          后面的内容就不需要再写了


   2.绑定失败,地址还在使用这种错误
        问题一:
          Cannot assign requested address  
          原因:绝对是你的ip地址写错了
          解决方法:
               方法一:修改代码中的ip地址,改成正确的ip(啰嗦,每个拿到代码都要修改)
               方法二:linux提供了一个宏定义INADDR_ANY,配合一个函数htonl()函数实现自动配置本地主机上的任意ip地址--》自适应
             

 函数原型:uint32_t htonl(uint32_t hostlong);
  写法一:采用了具体的ip地址 
 bindaddr.sin_addr.s_addr=inet_addr("192.168.22.5");
  写法二:没有采用具体的ip    
bindaddr.sin_addr.s_addr=htonl(INADDR_ANY)

             
                  
        问题二:
          Address already in use 
          原因:ubuntu系统有个机制,当你的程序异常结束的时候,端口号并不会立马释放(大概会延迟30秒钟左右才释放),如果你性子急躁,马上再次运行程序,就会有这种错误提示
          解决方法:
               方法一:耐心等待30秒--》馊主意
               方法二:修改代码(主函数传参),换个端口号 --》略微好一点
               方法三:调用setsockopt()函数去设置套接字可以重复使用端口号
                     

int setsockopt(int socket, int level, int option_name,const void *option_value, socklen_t option_len);
           比如:
           int on=1;
setsockopt(tcpsock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));


      注意:此代码只能在socket之后,bind之前使用
           


           


          

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肖爱Kun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值