前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >TCP/IP网络编程-前三章学习笔记

TCP/IP网络编程-前三章学习笔记

作者头像
用户2825413
发布2019-07-16 10:59:49
6980
发布2019-07-16 10:59:49
举报

开篇语

前两年, 就买了《TCP/IP网络编程》这本书, 由于自身基础薄弱, 只是走马观花翻阅了几张。

后来工作了这些年, 越来越感到瓶颈期已经来临, 再花式的 curd 也俘获不了领导的芳心了。

于是, 打算仔细学习下 《TCP/IP网络编程》, 为了让自己更深刻记忆, 特做笔记。

创建套接字(socket)

#include <sys/scoket.h>

int socket(int domain, int type, int protocol)

domain : 套接字中实用的协议族信息
type : 套接字数据传输类型信息
protocol : 计算机通信中实用的协议信息
1. domain参数 协议族

名称

协议族

PF_INET

IPv4互联网协议族

PF_INET6

IPv6互联网协议族

PF_LOCAL

本地通信unix协议族

2. type参数 套接字类型

2.1 面向链接的套接字类型 (SOCK_STREAM)

传输方式特征:

1.1 传输过程数据不会丢失
1.2 按序传输数据
1.3 不存在数据边界

这几个特性其实就是我们常说的 TCP协议。

缓冲区概念:

收发数据的套接字内部有缓冲(buffer), 简言之就是字节数组. 通过套接字传输的数据将保存到该数组。 因此, 我们 read、write其实读取缓冲区的内容。

那么当缓冲区满, 会发生什么情况呢。 在ICP/IP网络编程书中介绍, 如果read函数读取的速度比接收数据的速度慢, 则缓冲区有可能填满。 此时套接字将无法再接收数据, 传输端套接字将停止传输。

2.2 面向消息的套接字类型 (SOCK_STREAM)

传输方式特征:

1. 强调快速传输而非传输顺序
2. 传输数据可能丢失也可能毁损
3. 传输的数据存在数据边界

其实就是我们常说的UDP协议

3. protocol参数 协议最终选择

这里我们不做选择, 为0即可。

4. 最终我们使用TCP链接模式写法
//创建套接字(IPv4协议族, TCP套接字, TCP协议)
int sock = socket(PF_INET, SOCK_STREAM, 0);

返回的为 文件描述符, 失败返回-1

向套接字分配网络地址(bind)

#include <sys/socket.h>

int bind(int socketfd, struct sockaddr *myaddr, socklen_t addrlen);

socketfd 要分配的套接字文件描述符
myaddr  存储地址信息的结构体变量地址值
addrlen 第二个结构体变量的长度
1. socketfd 参数

socketfd 不用多说, 即是我们的socket函数返回的文件描述符

2. myaddr 参数

我们看到他是一个 sockaddr 结构体的指针类型。

sockaddr结构体:

struct sockaddr {
    __uint8_t       sa_len; 
    sa_family_t     sa_family; //地址组
    char            sa_data[14]; //地址信息
}; 

在sa_data一个成员里,包含了ip、port的地址信息, 这样写起来很麻烦, 所以有了新的结构体 sockaddr_in (IP和端口进行了拆分)

sockaddr_in结构体

struct sockaddr_in {
    __uint8_t       sin_len;
    sa_family_t     sin_family; //地址族
    in_port_t       sin_port; // TCP/UDP端口号
    struct  in_addr sin_addr; //IP地址
    char            sin_zero[8];
};

在上面的结构体中, 又嵌套了 in_addr 结构体,记录 IP 地址

struct in_addr {
    in_addr_t s_addr; //32位IPv4地址
};
结构体 sockaddr_in 的成员分析
成员 sin_family

地址族

含义

AF_INET

IPv4互联网使用的地址族

AF_INET6

IPv6互联网使用的地址族

AF_LOCAL

本地通信unix使用的地址族

成员 sin_port

16位端口号

成员 sin_addr

32位 ip 地址信息, 以网络字节序保存

成员 sin_zero

无特殊含义, 为与sockaddr 大小保持一致, 写入0 即可。

3. addrlen参数

传递地址信息的长度

4. 最终我们使用bind绑定地址方式
//分配内存-构造服务端地址端口
memset(&serv_addr, 0, sizeof(serv_addr));
//IPv4中的地址族
serv_addr.sin_family = AF_INET;
//32位的IPv4地址, INADDR_ANY表示当前ip
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//16位tcp/udp端口号
serv_addr.sin_port = htons(atoi(argv[1]));  

//分配地址
if (bind(serv_sock, (struct sockaddr*) &serv_addr,sizeof(serv_addr) )==-1){
    printf("bind() error");
    exit(0); 
}

bind函数之前, 构造了 sockaddr_in 结构体的数据, 其中介绍几个点.

  1. INADDR_ANY 会自动获取当前服务器的IP
  2. 我们看到使用到了 htonl、htons 函数,构造IP地址和端口

为什么构造结构体地址时候使用了 htonl、htons对IP、端口进行了转换

首先我们来看下这几个函数的含义

地址族

含义

htons

把short型数据从主机字节序转化为网络字节序

htonl

把long型数据从主机字节序转化为网络字节序

ntohs

把short型数据从网络字节序转化为主机字节序

ntohl

把long型数据从网络字节序转化为主机字节序

数据传输采用的网络字节序, 那在传输前应直接把数据转换成网络字节序, 接收的数据也需要转换城主机字节序再保存 上面这句话是有问题的, 原因是数据收发过程中是有自动转换机制的.

除了 socketaddr_in 结构体变量手动填充数据转换外, 其他情况不需要考虑字节序问题。

说了这么多字节序, 那到底什么是网络字节序,什么是主机字节序

1.主机字节序:主机内部内存中数据的处理方式。

2.网络字节序:网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用big endian(大端)排序方式。

天啦撸, 大端又是啥, 我们从两种网络字节顺序说起

字节序:是指整数在内存中保存的顺序。

cpu向内存保存数据字节序有两种实现方式:

  • 小端字节序(little endian):低字节数据存放在内存低地址处,高字节数据存放在内存高地址处。
  • 大端字节序(bigendian):高字节数据存放在低地址处,低字节数据存放在高地址处。

图例:

大字节序更符合我们的阅读习惯。但是我们的主机使用的是哪种字节序取决于CPU,不同的CPU型号有不同的选择。

当我们两台计算机是需要网络通信时, 规范统一约定为大端序进行通讯处理.

客户端代码分析

我们在服务端设置ip时候, 使用了 INADDR_ANY 会自动获取当前服务器的IP, 我们看下客户端的连接代码

struct sockaddr_in serv_addr;
struct sockaddr_in clnt_addr;

char message[30];
//创建套接字(IPv4协议族, TCP套接字, TCP协议)
int sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == -1 ){
    printf("socket() error");
    exit(1);
}

//分配内存-构造服务端地址端口
memset(&serv_addr, 0, sizeof(serv_addr));
//IPv4中的地址族
serv_addr.sin_family = AF_INET;
//32位的IPv4地址
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
//16位tcp/udp端口号
serv_addr.sin_port = htons(atoi(argv[2]));  

if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))) {
    printf("connect() error");
    exit(1);
}

int length = read(sock, message, sizeof(message)-1); 
if (length==-1){
    printf("read() error");
    exit(1);
}
知识点1

设置服务端 serv_addr.sin_addr.s_addr 地址, 使用了函数 inet_addr

int_addr_t inet_addr(const char * string);
//成功时32位大端序整数值, 失败时返回 INADDR_NONE.

例:

printf("%d",inet_addr("192.168.2.1"));
//output: 16951488
printf("%d",inet_addr("192.168.2.256"));
//output: -1

相同功能函数, 只是简化了向 serv_addr.sin_addr.s_addr 赋值操作

int inet_aton(const char *string, struct in_addr * addr);
//成功时返回1(true) 失败时返回0(false)
inet_aton(addr, &addr_inet.sin_addr)

其他函数:

char * inet_ntao(struct in_addr adr);
//成功时返回转换的字符串地址值, 失败时返回-1.
知识点2

● atoi():将字符串转换为整型值。

● atol():将字符串转换为长整型值。

printf("%d",atoi("123"));
//output : 123

对比服务端、客户端构造地址代码

服务端

serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//16位tcp/udp端口号
serv_addr.sin_port = htons(atoi(argv[1]));  

客户端

//32位的IPv4地址
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
//16位tcp/udp端口号
serv_addr.sin_port = htons(atoi(argv[2]));  

这里面包含上面讲到的一些知识点.

  1. 服务端因为使用INADDR_ANY实际等于 inet_addr("0.0.0.0"), 获取本机的IP地址
  2. 因为客户端接收了字符串IP地址, 所以使用了显示 inet_addr, 返回32位大端序整型数值
  3. htons 将短整型转换为网络字节序, 对于端口来说是比较合适的, 而对于IP类转换的整型数值, 一般需要 htonl 进行转换

参考资料:

《TCP/IP 网络编程》

https://blog.csdn.net/stalin_/article/details/80337915

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-05-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 呆呆熊的技术路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 开篇语
  • 创建套接字(socket)
    • 1. domain参数 协议族
      • 2. type参数 套接字类型
        • 3. protocol参数 协议最终选择
          • 4. 最终我们使用TCP链接模式写法
          • 向套接字分配网络地址(bind)
            • 1. socketfd 参数
              • 2. myaddr 参数
                • 结构体 sockaddr_in 的成员分析
                  • 成员 sin_family
                  • 成员 sin_port
                  • 成员 sin_addr
                • 成员 sin_zero
                  • 3. addrlen参数
                    • 4. 最终我们使用bind绑定地址方式
                    • 为什么构造结构体地址时候使用了 htonl、htons对IP、端口进行了转换
                      • 说了这么多字节序, 那到底什么是网络字节序,什么是主机字节序
                        • 天啦撸, 大端又是啥, 我们从两种网络字节顺序说起
                        • 客户端代码分析
                          • 知识点1
                            • 知识点2
                            • 对比服务端、客户端构造地址代码
                            相关产品与服务
                            腾讯云代码分析
                            腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档