前两年, 就买了《TCP/IP网络编程》这本书, 因为自身基础薄弱, 只是蜻蜓点水翻阅了几张。编程
后来工做了这些年, 愈来愈感到瓶颈期已经来临, 再花式的 curd 也俘获不了领导的芳心了。数组
因而, 打算仔细学习下 《TCP/IP网络编程》, 为了让本身更深入记忆, 特作笔记。bash
#include <sys/scoket.h>
int socket(int domain, int type, int protocol)
domain : 套接字中实用的协议族信息
type : 套接字数据传输类型信息
protocol : 计算机通讯中实用的协议信息
复制代码
名称 | 协议族 |
---|---|
PF_INET | IPv4互联网协议族 |
PF_INET6 | IPv6互联网协议族 |
PF_LOCAL | 本地通讯unix协议族 |
... | ... |
2.1 面向连接的套接字类型 (SOCK_STREAM)服务器
传输方式特征:网络
1.1 传输过程数据不会丢失
1.2 按序传输数据
1.3 不存在数据边界
复制代码
这几个特性其实就是咱们常说的 TCP协议。dom
缓冲区概念:socket
收发数据的套接字内部有缓冲(buffer), 简言之就是字节数组. 经过套接字传输的数据将保存到该数组。 所以, 咱们 read、write其实读取缓冲区的内容。tcp
那么当缓冲区满, 会发生什么状况呢。 在ICP/IP网络编程书中介绍, 若是read函数读取的速度比接收数据的速度慢, 则缓冲区有可能填满。 此时套接字将没法再接收数据, 传输端套接字将中止传输。函数
2.2 面向消息的套接字类型 (SOCK_STREAM)学习
传输方式特征:
1. 强调快速传输而非传输顺序
2. 传输数据可能丢失也可能毁损
3. 传输的数据存在数据边界
复制代码
其实就是咱们常说的UDP协议
这里咱们不作选择, 为0便可。
//建立套接字(IPv4协议族, TCP套接字, TCP协议)
int sock = socket(PF_INET, SOCK_STREAM, 0);
复制代码
返回的为 文件描述符, 失败返回-1
#include <sys/socket.h>
int bind(int socketfd, struct sockaddr *myaddr, socklen_t addrlen);
socketfd 要分配的套接字文件描述符
myaddr 存储地址信息的结构体变量地址值
addrlen 第二个结构体变量的长度
复制代码
socketfd 不用多说, 便是咱们的socket函数返回的文件描述符
咱们看到他是一个 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地址
};
复制代码
地址族 | 含义 |
---|---|
AF_INET | IPv4互联网使用的地址族 |
AF_INET6 | IPv6互联网使用的地址族 |
AF_LOCAL | 本地通讯unix使用的地址族 |
... | ... |
16位端口号
32位 ip 地址信息, 以网络字节序保存
无特殊含义, 为与sockaddr 大小保持一致, 写入0 便可。
传递地址信息的长度
//分配内存-构造服务端地址端口
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地址和端口
首先咱们来看下这几个函数的含义
地址族 | 含义 |
---|---|
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);
}
复制代码
设置服务端 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.
复制代码
● 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]));
复制代码
这里面包含上面讲到的一些知识点.
参考资料:
《TCP/IP 网络编程》