对应书籍
《tcp/ip网络编程》
第二章 套接字类型与协议设置
第三章 地址族与数据序列
socket函数的三个参数
我们上节调用的创建套接字的函数int sock(int domain, int type, int protocal);, 这一章讨论下这后面的三个参数.
1. domain-协议族(Protocol Family)
一般选择IPv4协议族,其余的用的比较少.
![tcp/ip网络编程[笔记二] - 图1](/uploads/projects/xuwuzhongdezhenyan@cr0cgo/7ca41d573d0cecb509e31c5dbcdd0992.png)
2. type-套接字类型(Type)
主要分成以下两种
套接字类型1: 面向连接的套接字(SOCK_STREAM)
特点:
- 传输过程中数据不会丢失(可靠传输)
- 按序传输数据(保证顺序性)
- 传输的数据不存在数据边界(面向字节流的传输)
套接字类型2: 面向消息的套接字(SOCK_DGRAM)
特点:
- 强调快速传输而非传输顺序
- 传输的数据可能丢失也可能损毁
- 传输的数据有数据边界
- 限制每次传输的大小
3. protocal-协议的最终选择
对于IPv4协议族,前两个参数选好了, 最后一个参数也就确定了, 因为就两种协议TCP和UDP. 这里就不细讲这两个协议啦.
第二章总结
这二章节讲的很少, 就讲述了两种传输方式, 还写了一个代码验证了socket缓冲区的存在. 当然课后题还是得看看:
- 什么是协议?在收发数据中定义协议有何意义.
协议就是“计算机对话必备通信规则”
sockaddr_in结构体详解
我们在第一章编写的服务器代码和客户端代码都用到了这个结构体, 此结构体就是存储套接字的信息.
先来看看具体定义:
/** Socket address, internet style.*/struct sockaddr_in {sa_family_t sin_family; //地址族(Address Family)in_port_t sin_port; //16位TCP/UDP端口号struct in_addr sin_addr; //32位IP地址char sin_zero[8]; //不使用};
成员fin_family:
![tcp/ip网络编程[笔记二] - 图2](/uploads/projects/xuwuzhongdezhenyan@cr0cgo/afd87ff4ac10df289b8621b70abfccea.png)
成员sin_addr
存储IP地址, 以32位无符号整形存储, 采用网络字节序(后面会介绍什么是网络字节序).
/** Internet address (a structure for historical reasons)*/struct in_addr {in_addr_t s_addr;};typedef __uint32_t in_addr_t; /* base type for internet address */
成员sin_zero
无特殊含义, 全部填充成0, 为了保持结构体sockaddr_in和结构体sockaddr长度一致.
我们在调用bind时用到以下代码:
if (bind(serv_sock,(struct sockaddr*) &serv_addr,sizeof(serv_addr))==-1){error_handling((char*)"bind() error");}
我们会发现bind函数的第二个参数实际上是sockaddr,sockaddr长这样:
/** [XSI] Structure used by kernel to store most addresses.*/struct sockaddr {sa_family_t sa_family; /* [XSI] address family */char sa_data[14]; /* [XSI] addr value (actually larger) */};
不难发现, sockaddr_in中的sin_port占2个字节, sin_addr占4个字节, sin_zero占8个字节, , 刚好和sockaddr的sa_data长度一样. 现在明白sockaddr_in为啥后面多了一个无实际意义的sin_zero了吧.
那么有的人可能会问sockaddr为啥要把sa_data长度设置成14个字节呢, 这个是为了以后的扩展性
字节序
CPU向内存保存数据的方式有两种:
- 大端序(Big Endian): 高位字节存放到低位地址
- 小端序(Little Endian): 高位字节存放到高位地址
大家看两幅图就明白了:
![tcp/ip网络编程[笔记二] - 图4](/uploads/projects/xuwuzhongdezhenyan@cr0cgo/14e604d1a39246e3b4e48aacf712fd3d.png)
![tcp/ip网络编程[笔记二] - 图5](https://cdn.nlark.com/yuque/0/2021/png/2318628/1610976870418-1242e7a3-5870-4320-8d56-bea7c2ee073f.png#alt=-w397)
网络字节序和字节序转换
网络字节序就是大端序.
字节序转换函数介绍:
unsigned short htons(unsigned short);unsigned short ntohs(unsigned short);unsigned long htonl(unsigned long);unsigned long ntohl(unsigned long);s指short(两字节), l指long(四字节), h代表主机(host)字节序, n代表网络(network)字节序
网络地址初始化与分配
我们已经详细学习了结构体sockaddr_in, 之后我们来学习初始化和分配的一般流程, 这个在第一章的代码中其实有用过. 我这里只是单独拿出来, 方便记忆.
- 初始化代码
struct sockaddr_in my_addr;char * serv_ip = "121.37.12.39";char * serv_port = "9190"memset(&my_addr, 0, sizeof(my_addr));my_addr.sin_family = AF_INET;my_addr.sin_addr.s_addr = inet_addr(serv_ip);//注意这一行my_addr.sin_port = htons(atoi(serv_port));
上述代码中,有几个地方需要注意:
inet_addr函数
- 函数原型:
#include<arpa/inet.h>in_addr_t inet_addr(const char *string);
- 函数原型:
-
作用: 把点分十进制表示法(Dotted Decimal Notation)转换成网络字节序的整形.
- INADDR_ANY
对于服务器, 当只有一张网卡时, 其实IP地址只有一个, 这时候就可以用这个函数,
把上面代码中第六行改成my_addr.sin_addr.s_addr = htonl(INADDR_ANY).
- 分配代码
//serv_sock是套接字文件描述符, serv_addr是sockaddr_in结构体, 我们假设他们已经事先创建好了bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
只有服务器端才会使用bind, 客户端直接connect, 这是因为我们只用知道服务器端的IP地址和端口号.
第三章总结
这一章在讨论如何向套接字分配地址, 重点讲了结构体sockaddr_in, 讲的还是比较简单. 题目没有什么需要思考的, 都是记忆性知识.
