TCP/IP网络编程

原创
2020/12/05 15:29
阅读数 146

套接字协议

socket

  • 协议族:PF_INET(IPv4互联网协议族)、PF_INET6(IPv6互联网协议族)
  • 套接字类型:SOCK_STREAM(面向连接的套接字)、SOCK_DGRAM(面向消息的套接字)

可靠的、按序传递的、基于字节的面向连接的数据传输方式的套接字

面向连接的套接字会根据接收端的状态传输数据,如果传输出错还会提供重传服务。因此,面向连接的套接字除特殊情况外不会发生数据丢失。

不可靠的、不按序传递的、以数据的高速传输为目的的套接字

  • socket函数的第三个参数,该参数决定最终采用的协议。 大多数情况下可以向第三个参数传递0,除非遇到以下这种情况:同一协议族中存在多个数据传输方式相同的协议

IPv4协议族中面向连接的套接字 的实现:

int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)

IPv4协议族中面向消息的套接字的实现:

int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)

IP地址和端口号

IP是Internet Protocol(网络协议)的简写,是为了收发网络数据而分配给计算机的值。端口号并非赋予计算机的值,而是为了区分程序中创建的套接字而分配套接字的序号(为了更好的区分同一台机器上不同程序创建的套接字)。

通过NIC(Network Interface Card, 网络接口卡)接收的数据内有端口号,操作系统正是参考此端口号把数据传输给相应端口的套接字。 端口号就是在同一操作系统内为区分不同套接字而设置的,因此无法将一个端口号分配给不同套接字。 总之,数据传输目标地址同时包含IP地址和端口号,只有这样,数据才会被传输到最终的目的应用程序(应用程序套接字)。

结构体描述IP和端口信息:

struct sockaddr_in
{
	sa_family_t               sin_family;    // 地址族(Address Family)
	unit16_t                    sin_port;      // 16位TCP/UDP端口号
	struct in_addr           sin_addr;      // 32位IP地址
	char                          sin_zero[8];   //不使用
};

该结构体中提到的in_addr定义如下,用来存放32位IP地址。

struct in_addr
{
	in_addr_t                    s_addr;       // 32位IPv4地址
};

字节序与网络字节序

  • 大端序:高位字节存放到低位地址。与人类正常计数思路一致。
  • 小端序:高位字节存放到高位地址。 网络字节序统一为大端序。 Intel系列CPU以小端序方式保存数据。

字节序转换

unsigned short htons(unsigned short);  // 把short类型数据从主机字节序转化为网络字节序
unsigned short ntohs(unsigned short); // 把short类型数据从网络字节序转化为主机字节序
unsigned long htons(unsigned long);
unsigned long ntohl(unsigned long);

通常,以s作为后缀的函数中,s代表2个字节的short,因此用于端口号转换;以l作为后缀的函数中,l代表4个字节,因此用于IP地址转换。

TCP与IP

IP层解决数据传输中的路径选择问题,只需按照此路径传输数据即可。TCP和UDP层以IP层提供的路径信息为基础完成实际的数据传输,故该层又称传输层(Transport)。

listen函数即可生成服务器套接字,这时处于等待状态,listen函数的第二个参数决定了等候室的大小。等候室称为连接请求等待队列,准备好服务器套接字和连接请求等待队列后,这种课接收连接请求的状态称为等待连接请求状态。listen函数的第二个参数值与服务器端的特性有关,像频繁接收请求的web服务器端至少应为15. accept函数时若等待队列为空,则accept函数不会返回,直到队列中出现新的客户端连接。他会一直等待,直到队列有信息。

服务器端调用listen函数后创建连接请求等待队列,之后客户端即可请求连接。客户端调用connect函数发送请求连接。

客户端的IP地址和端口在调用connect函数时自动分配,无需调用标记的bind函数进行分配。操作系统自动使用主机的IP。

注意:

  1. 客户端只能等到服务器端调用listen函数后才能调connect函数。
  2. 客户端调用connect函数前,服务器端有可能率先调用accept函数。当然,此时服务器端在调用accept函数时进入阻塞(blocking)状态,直到客户端调connect函数为止。

TCP内部工作原理

分为3部分:

  • 与对方套接字建立连接

这里就是著名的三次握手,确认建立连接的过程。

shake1 套接字A: "你好,套接字B。我这儿有数据要传给你,建立连接吧。"
【SYN】 SEQ: 1000, ACK: -
该消息中SEQ为1000,ACK为空,而SEQ为1000的含义如下:
“现传递的数据包序号为1000,如果接收无误,请通知我向您传递1001号数据包。”

shake2 套接字B: "好的,我这边已就绪"
【SYN+ACK】 SEQ: 2000, ACK: 1001
此时SEQ为2000,ACK为1001,而SEQ为2000的含义如下:
"现传递的数据包序号为2000,如果接收无误,请通知我向您传递2001号数据包。"
而ACK 1001的含义如下:
“刚才传输的SEQ为1000的数据包接收无误,现在请传递SEQ为1001的数据包。”

shake3 套接字A: "谢谢你受理我的请求"
【ACK】 SEQ: 1001, ACK: 2001
"已正确收到传输的SEQ为2000的数据包,现在可以传输SEQ为2001的数据包。"

shake2中,对主机A首次传输的数据包的确认消息(ACK 1001)和为主机B传输数据做准备的同步消息(SEQ 2000)捆绑发送,因此,此种类型的消息又称SYN+ACK。收发数据前向数据包分配序号,并向对方通报此序号,这都是为防止数据丢失所做的准备。

  • 与对方套接字进行数据交换

  • 断开与对方套接字的连接 如果对方还有数据需要传输时直接断掉连接会出问题,所以断开连接时需要双方协商。
套接字A: "我希望断开连接。"
套接字B: "喔,是吗? 请稍后。"

套接字B: "我也准备就绪,可以断开连接。"
套接字A: "好的,谢谢合作。"

先由套接字A向套接字B传递断开连接的消息,套接字B发出确认收到的消息,然后向套接字A传递可以断开连接的消息,套接字A同样发出确认消息。

这是著名的四次挥手,此过程经历4个阶段。FIN表示断开连接,也就是说,双方各发送1次FIN消息后断开连接。向主机A传递了两次ACK 5001,也许这会让各位感到困惑。其实,第二次FIN数据包中的ACK 5001只是因为接收ACK消息后未接收数据而重传的。

展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部