TCPIP协议-网络编程基础概念篇

TCP/IP协议-网络编程基础篇

创建套接字(socket)

#include <sys/scoket.h>
int socket(int domain, int type, int protocol)
domain : 套接字中实用的协议族信息
type : 套接字数据传输类型信息
protocol : 计算机通信中实用的协议信息

domain参数 协议族

名称 协议族
PF_INET IPv4互联网协议族
PF_INET6 IPv6互联网协议族
PF_LOCAL 本地通信unix协议族
······ ······

type参数 套接字类型

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

传输方式特征:
1.1 传输过程数据不会丢失
1.2 按序传输数据
1.3 不存在数据边界
这几个特性其实就是我们常说的 TCP协议。
缓冲区概念:
收发数据的套接字内部有缓冲(buffer), 简言之就是字节数组. 通过套接字传输的数据将保存到该数组。因此, 我们 read、write其实读取缓冲区的内容。
那么当缓冲区满, 会发生什么情况呢。在ICP/IP网络编程书中介绍, 如果read函数读取的速度比接收数据的速度慢, 则缓冲区有可能填满。此时套接字将无法再接收数据, 传输端套接字将停止传输。

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

传输方式特征:

  1. 强调快速传输而非传输顺序
  2. 传输数据可能丢失也可能毁损
  3. 传输的数据存在数据边界
    其实就是我们常说的UDP协议。

protocol参数 协议最终选择

通常不作选择,为0即可。

最终使用TCP链接模式

//创建套接字(IPv4协议族, TCP套接字, TCP协议)
int sock = socket(PF_INET, SOCK_STREAM, 0);

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

#include <sys/socket.h>
int bind(int socketfd, struct sockaddr *myaddr, socklen_t addrlen);
socketfd 要分配的套接字文件描述符
myaddr  存储地址信息的结构体变量地址值
addrlen 第二个结构体变量的长度

socketfd 参数

socket函数返回的文件描述符

myaddr 参数

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 即可。

addrlen参数

传递地址信息的长度

最终我们使用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 结构体的数据, 其中介绍几个点.
INADDR_ANY 会自动获取当前服务器的IP
我们看到使用到了 htonl、htons 函数,构造IP地址和端口

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

地址族 含义
htons 把short型数据从主机字节序转化为网络字节序
htonl 把long型数据从主机字节序转化为网络字节序
ntohs 把short型数据从网络字节序转化为主机字节序
ntohl 把long型数据从网络字节序转化为主机字节序

数据传输采用的网络字节序, 那在传输前应直接把数据转换成网络字节序, 接收的数据也需要转换城主机字节序再保存上面这句话是有问题的, 原因是数据收发过程中是有自动转换机制的.除了 socketaddr_in 结构体变量手动填充数据转换外, 其他情况不需要考虑字节序问题。

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

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

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

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

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

整数 大端字节表示 小端字节表示
0x12345678 0x12 0x34 0x56 0x78 0x78 0x56 0x34 0x12

大字节序更符合我们的阅读习惯。但是我们的主机使用的是哪种字节序取决于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

//成功时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]));  

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

posted @ 2021-07-19 15:31  wzr777  阅读(162)  评论(0)    收藏  举报