Linux网络编程基础和TCP协议(网络编程一)

18 篇文章 0 订阅
16 篇文章 0 订阅

一、linux中的网络编程

1. 引入

网络编程也是属于进程间通信的一种方式

前面学习:管道,信号,共享内存,消息队列,信号量(共同的特点:只能用于同一台主机内部的不同进程间通信)

网络编程:既能在同一台主机内部通信,也可以在不同主机间通信

2.网络的理论概念

(1)网络分层模型

为了方便大家理解计算机网络的通信过程,人为地把计算机网络分成了七个层次

OSI七层模型,每个层次都有自己的通信协议(游戏规则)

应用层 http协议(网页) ftp协议(文件传输协议) telnet(远程登录访问)

会话层 数据的加密,解密

表示层 前面三个层的作用,提供程序员开发应用程序需要的通信协议

传输层 在计算机网络帮助我们传输数据的,常用的协议的有tcp协议,udp协议

网络层 作用:帮助我们寻找数据传输的最佳路径(路由功能),路由器中有个路由表(存放了所有连接的主机的MAC地址和ip地址,通过算法寻找最佳传输路径) 常用协议:ip协议

数据链路层 作用:开发网卡的驱动就需要用到数据链路层的协议

网络接口层(物理层) 作用:开发网卡的驱动就需要用到物理层的协议

tcp/ip模型,七层简化成了4层

应用层、传输层、网络层(ip层)、物理层

(2)ipv4和ipv6

ipv4: 32位地址 192.168.19.2(点分十进制ip),三个小数点划分成了四个部分,每个部分各占一个字节

ipv6: 解决ipv4地址不够用的问题,占128位

(3)端口号

本质是一个无符号的短整数,取值范围0–65535之间,程序员可以自己指定端口号,但是千万不要使用1024以内的端口号(很多被操作系统占用),作用是为了区分同一台主机内部不同的网络进程

同一台主机内部,端口号一定不能相同,在不同的主机上端口号可以一样

3.tcp协议相关的接口函数

tcp通信基于c/s模式(client/server)

(1) 创建套接字

#include <sys/socket.h>

int socket(int domain, int type, int protocol);
返回值:成功 返回套接字的文件描述符
       失败 -1

参数:domain --- 地址协议类型
                ipv4地址 --- AF_INET或者PF_INET
                ipv6地址 --- AF_INET6或者PF_INET6
     type --- 套接字的类型
              tcp套接字(数据流套接字,流式套接字) --- SOCK_STREAM
              udp套接字(数据报套接字) --- SOCK_DGRAM
     protocol --- 扩展协议,一般设置0

(2) 绑定

#include <sys/socket.h>

int bind(int socket, const struct sockaddr *address, socklen_t address_len);

int bind(int socket, const struct sockaddr_in *address, socklen_t address_len);  //局限性,只能用ipv4地址

int bind(int socket, const struct sockaddr_in6 *address, socklen_t address_len); //局限性,只能用ipv6地址
返回值:  成功 0  失败 -1

参数: socket --- 创建套接字文件描述符
      address --- 保存需要绑定的ip地址和端口号
      struct sockaddr --- 通用地址结构体(兼容ipv4和ipv6)
      {
                  unsigned short  sa_family; //地址协议类型
                  char sa_data[14]; //存放ip和端口号
      } 
      struct sockaddr_in      ipv4地址结构体
      {
                  short   sin_family;  //地址协议类型AF_INET或者PF_INET
                  short   sin_port;   //存放你要绑定的端口号
                  struct in_addr  sin_addr;    //存放你要绑定的ip地址   4字节
                  unsigned char sin_zero[8];//打酱油的,为了跟struct sockaddr大小保持一致
       }
       struct sockaddr_in6    ipv6地址结构体
       struct in_addr
       {
                  unsigned int   s_addr;  //最终用来存放ip地址
       }
       address_len --- 地址结构体的大小  sizeof

tcp服务器端是一定要绑定: 如果不绑定,客户端connect的时候不知道端口号写多少

tcp客户端可以绑定也可以不绑定: 绑定了,相当于程序员自己指定了一个固定的端口号

不绑定,操作系统会自动分配一个端口号(每次运行分配得都不一样,不固定)

linux提供了一个宏定义INADDR_ANY(只能在bind函数中使用)

作用:帮助我们绑定本地主机上的任意一个ip地址

例:server.sin_addr.s_addr =htonl(INADDR_ANY);

回顾:结构体大小规则

64位ubuntu

找到结构体中最大的类型(数组不要理会看数据类型)

最大类型>=8字节    整个结构体就按照8字节对齐
最大类型<8字节     整个结构体就按照最大类型的大小对齐

32位ubuntu

找到结构体中最大的类型(数组不要理会看数据类型)

最大类型>=4字节    整个结构体就按照4字节对齐
最大类型<4字节     整个结构体就按照最大类型的大小对齐    

(3) ip和端口号的转换

大小端:

ubuntu系统(主机字节序):小端序存储数据

计算机网络中(网络字节序):数据采用大端序存放

第一组:小端序ip端口号 -> 大端序ip端口号

小端序存储的ip-> 大端序存储的ip(把主机字节序ip转换成网络字节序ip)

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

in_addr_t inet_addr(const char *cp);
返回值:成功 --- 返回转换得到大端序二进制ip
       失败 --- -1

参数:cp --- 你需要转换的点分十进制小端序ip

主机字节序端口号-> 转换成网络字节序端口号

#include <arpa/inet.h>
                                 
uint16_t htons(uint16_t hostshort)
返回值:成功 --- 返回转换得到的大端序端口号
       失败 --- -1

参数:hostshort --- 小端序端口号

第二组:大端序ip端口号 -> 小端序ip端口号

大端序存储的ip -> 小端序存储的ip

char *inet_ntoa(struct in_addr in);
返回值:转换得到的字符串ip(小端序)
       失败 --- -1

参数:in --- 大端序ip

大端序存储的端口号 -> 小端序存储的端口号

uint16_t ntohs(uint16_t netshort);
返回值:返回转换得到的小端序端口号
       失败 --- -1

参数:netshort --- 大端序端口号

(4)连接

#include <sys/socket.h>

int connect(int socket, const struct sockaddr *address,  socklen_t address_len);
参数: socket --- 创建套接字文件描述符
      address --- 存放对方的ip和端口号
      address_len --- 地址结构体的大小

(5)监听

#include <sys/socket.h>

int listen(int socket, int backlog);
参数:socket --- 创建套接字文件描述符
     backlog --- 最多允许多少个客户端同时连接服务器

比如: listen(tcpsock,5);

(6)愿意接收客户端的连接请求

#include <sys/socket.h>

int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
返回值:成功 --- 返回一个新的套接字(用来跟客户端通信)
       失败 --- -1

参数:socket --- 创建套接字文件描述符
     address --- 存放客户端(对方)的ip和端口号
                 服务器会自动把目前连接成功的那个客户端ip和端口号存放到这个变量中
     address_len --- 地址大小,要求是指针

总结accept的特点:

①accept会阻塞服务器,直到有客户端连接服务器才会解除阻塞

②每连接一个客户端,服务器accept都会返回一个对应的新套接字

(7)收发信息

①read和write

如果通信双方任意一方断开连接(退出了),会导致read不再阻塞,并且read返回值为0

返回值:>0 --- 接收到网络中数据字节数
       ==0 --- 说明对方断开连接了(实际开发的时候可以去判断read的返回值,为0退出你的程序)
       -1 --- 说明read调用失败  

使用情况:

第一种:当成文件IO的函数使用,read读取某个文件里面的内容,如果文件读取完毕,read的返回值就是0

第二种:在网络编程中使用,read返回0表示对方断开连接

②send和recv

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int socket, const void *buffer, size_t length, int flags);  //相当于write
参数:前面三个跟write一样的,最后一个参数设置为0
#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int socket, void *buffer, size_t length, int flags);  //相当于read
返回值:>0 --- 接收到网络中数据字节数
       ==0 --- 说明对方断开连接了(实际开发的时候可以去判断recv的返回值,为0退出你的程序)
       -1 --- 说明recv调用失败  

4. 运行代码出现的问题

问题一: 代码刚退出,立马执行会出现绑定失败

绑定失败!: Address already in use

原因: linux系统中,程序退出了,绑定的端口号并不会立马释放掉,会延迟大概半分钟左右才释放

解决方法一: 更换绑定的端口号,利用主函数传参

解决方法二: linux提供了设置套接字属性的函数(使用频率最高)

#include <sys/socket.h>

int setsockopt(int socket, int level, int option_name,  const void *option_value, socklen_t option_len);
参数:socket --- 创建套接字文件描述符
     level --- SOL_SOCKET
     option_name --- SO_REUSEADDR  //取消端口号绑定时候重复使用的限制
     option_value --- 指针,指向存放选项待设置的新值的缓冲区
     option_len --- option_value缓冲区长度

例:

int on=1;
setsockopt(tcpsocket,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Java.L

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

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

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

打赏作者

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

抵扣说明:

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

余额充值