本专题内容均来自 Stevens 先生的 Unix 网络编程 卷I

TCP 建立与中止

三次握手


从图中可以看出:

  • 服务端必须准备好接受外来的连接,称之为被动打开。调用的函数为 socket, bind 和 listen 。
  • 客户端主动的像服务端发起连接时,会向服务端发送一个 SYN 分节,它告诉服务端接下来数据的初始序列号,此分节通常不携带数据。调用的函数为 connect 。
  • 服务端收到连接请求后,需要确认客户端的 SYN, 也就是回发一个 ACK, 同时也要发送一个 SYN 分节,其包含了同一连接中发送数据的初始序列号。
  • 客户端随后确认来自服务端的 SYN。

Note:
关于初始序列号,客户端或者服务端在发送 SYN 是都会携带初始序列号,在确认 SYN 的 ACK 中,携带的序列号必须是这个初始序列号加1。如图中所示的 J 和 J+1; K 与 K+1。

tcp中 SYN 的常用选项
(1) MSS 选项(maximum segment size)
(2) 窗口规模选项
(3) 时间戳选项

那么问题来了:为什么采用三次握手而不是两次?
(1)为了防止已失效的连接请求发送到服务端,浪费资源。这种预防是很有必要的,因为这样可以防止很多恶意的网络攻击。
(2)为了同步序列号,实现可靠传输。tcp 是全双工的,如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认。假定通过超时重传策略实现两次握手后双方都确认起时序列号,那么握手时间将增加,连接建立时间将变长。

四次挥手


以客户端主动关闭为例

  • 客户端发送一个 FIN 分节,告知服务端,客户端数据发送完毕,即将关闭。调用 close 函数。
  • 服务端接收到来自客户端的 FIN 后,即可回复确认 ACK, 并将 FIN 放在队列中等候处理。
  • 服务端在处理完这个连接的所有数据后,也会调用 close 关闭,即向客户端发送 FIN 分节。
  • 客户端收到 FIN 分节后,同样回复确认 ACK。此时客户端进入 TIME_WAIT 状态。

Note:
(1) Q: 什么情况下存在 TIME_WAIT 状态?
A: 主动发起关闭连接的一端会存在 TIME_WAIT 状态。
(2) Q: TIME_WAIT 状态存在的意义?
A: 主要有两个理由:
I) 可靠的实现TCP 全双工连接的终止。因为最后的确认 ACK 可能会丢失,如果没有 TIME_WAIT 状态,被动关闭的一方将会超时重传 FIN, 此时主动关闭方已经关闭了,无法回传确认 ACK 了。
II) 允许老的重复分节在网络中消逝。TIME_WAIT 状态持续的时间为 2*MSL, 保证了相同 IP 和端口重复连接时,旧的重复分组已经在网络中消逝。

状态转换图

套接字编程

仅以 IPv4 套接字为例

在头文件 <netinet/in.h>中,POSIX 定义了 IPv4 的套接字地址结构:

struct in_addr{in_addr_t    s_addr;
}struct sockaddr_in{uint8_t           sin_len;sa_family_t       sin_family;in_port_t         sin_port;struct in_addr    sin_addr;char              sin_zero[8];
}

通用套接字地址结构

struct sockaddr{uint8_t      sa_len;sa_family_t  sa_family;     //AF_xxxchar         sa_data[14];
}

绑定套接字函数的 ANSI C 原型为

int bind(int, struct sockaddr*, socklen_t);

意味着在绑定套接字时,需要进行强制类型转换

struct sockaddr_in serv;
bind(sockfd, (struct sockaddr*)&serv, sizeof(serv));

需要注意:套接字数据结构大小的数据类型是 socklen_t,而不是 int。POSIX 建议将 socklen_t 定义为 uint32_t。

字节序

字节序没有标准,不同的系统可能是大端排序,也可能是小端排序的。

什么是大端和小端

如何判断系统的字节序

/**
* @return 0: unknown; 1: big-endian; 2: little-endian
*/
unsigned int host_byte_order()
{union {short s;char c[sizeof(s)];}un;un.s = 0x0102;if(2 == sizeof(short)){if(1 == un.c[0] && 2 == un.c[1]){return 1;}else if(2 == un.c[0] && 1 == un.c[1]){return 2;}}return 0;
}int main()
{printf("%s: ",CPU_VENDOR_OS);unsigned int ret = host_byte_order();if(1 == ret){printf("big-endian\n");}else if(2 == ret){printf("little-endian\n");}else{printf("unknown\n");}return 0;
}


关于 CPU_VENDOR_OS 宏:
随书源码根目录下运行 configure 文件即可生成 config.h文件,config.h文件中会有这个宏定义。

字节序间转换

#include <netinet/in.h>
//返回网络字节序
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);//返回主机字节序
uint16_t ntohs(uint16_t net16bitvalue);
uint32_t ntohl(uint32_t net32bitvalue);

h: host
n: net
l: long
s: short

在网络编程时,统一转成网络字节序。例如端口13的转化

#include "byte_oder.h"
#include <netinet/in.h>int main()
{test_host_byte_order();uint16_t port_n = htons(13);uint16_t port_h = ntohs(port_n);printf("port_n = %d, port_h = %d\n", port_n,port_h);return 0;
}

13 的16进制为 0x0D, 大端排序则为 0x0D00,转换10进制为 3328.

字节操作

#include <string.h>
//将目标字符串指定数目的字节清0
void bzero(void *dest, size_t nbytes);
//从源地址拷贝指定长度的字节到目的地址,与memcpy函数参数位置相反,并且memcpy函数在源字节串与目的字节串有重叠时可能出现不可预料的错误,而bcopy能正确处理。
void bcopy(const void *src,void *dest, size_t nbytes);
int bcmp(const void *ptr1, const void *ptr2, size_t nbytes);

注意:源字节串与目的字节串有重叠时用 memcpy 不安全的,可以使用 bcopy,也可以用 memmove函数代替。

地址转换函数

#include <arpa/inet.h>
/**
* @description 将 strptr 所指的字符串转换成32位网络字节序二进制值,并通过 addrptr指针存储
* @return 成功:1;失败:0
*/
int inet_aton(const char* strptr, struct in_addr *addrptr);/**
* 这是一个被废弃的函数
*
* @description 与 inet_aton 函数功能一样,但是不能像 inet_aton 函数一样对 IP 执行检查,
* 即默认所有 2^32个可能的二进制值都是有效的,出错时返回 INADDR_NONE(通常是32位全1的值),
* 这意味着255.255.255.255不能由此函数处理。
*/
in_addr_t inet_addr(const char* strptr);/**
* @description 将32位网络序二进制IPv4 转换为点分十进制数串。
*/
char* inet_ntoa(struct in_addr inaddr);
#include <arpa/inet.h>
int main()
{struct in_addr addr;int ret = inet_aton("192.168.1.126",&addr);if(ret){printf("192.168.1.126 的网络字节序为: %d\n",addr.s_addr);printf("网络字节序: %d,转换为十进制点分数串: %s\n",addr.s_addr,inet_ntoa(addr));}return 0;
}


以下函数兼容 IPv4 和 IPv6
family 既可以是 AF_INET 也可以是 AF_INET6

#include <arpa/inet.h>
/**
* @description 将 strptr 所指的字符串转换成32位网络字节序二进制值,并通过 addrptr指针存储
* @return 成功:1;失败:0
*/
int inet_pton(int family,const char* strptr,void *addrptr);
/**
* @description 将32位网络序二进制IPv4 addrptr 转换为点分十进制数串 strptr。
* len 为 strptr 空间长度,如果这个长度不够则返回nullptr,并置 errno 为 ENOSPC。
* strptr 不可以为空,必须提前分配好空间。
*/
const char *inet_ntop(int family, const void *addrptr, char* strptr, size_t len);

调用实例

struct in_addr addr;
int ret = inet_pton(AF_INET,"192.168.1.126",(void*)&addr);
if(ret)
{printf("192.168.1.126 的网络字节序为: %d\n",addr.s_addr);
}

套接字函数

基本 TCP 客户、服务器程序的套接字函数如下图



#include <sys/socket.h>
/**
* 当 protocol 设置为 0 时,选择 family 和 type 组合的系统默认值
* 返回套接字描述符(sockfd), 一个非负整数值
*/
int socket(int family, int type, int protocol);int connect(int sockfd, const struct *servaddr, socklen_t addrlen);int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);int listen(int sockfd, int backlog);int accept(int sockfd, const struct sockaddr* cliaddr, socklen_t *addrlen);

这里需要注意的:
listen 函数中的 backlog 参数,这个是已连接队列和未连接完成队列之和,详细不展开,相关知识在第4章第五节。
accept 是一个阻塞函数,从已连接队列中取出已连接 sockect。

目前掌握的知识只能写演示代码,还不足以商用。

Unix 网络编程基础相关推荐

  1. python网络编程基础(线程与进程、并行与并发、同步与异步、阻塞与非阻塞、CPU密集型与IO密集型)...

    python网络编程基础(线程与进程.并行与并发.同步与异步.阻塞与非阻塞.CPU密集型与IO密集型) 目录 线程与进程并行与并发同步与异步阻塞与非阻塞CPU密集型与IO密集型 线程与进程 进程 前言 ...

  2. python网络编程证书_《Python网络编程基础》笔记

    python网络编程基础 ================== Author: lujun9972 Date: 2013-03-08 22:29:20 CST Table of Contents == ...

  3. java 编程原理_Java网络编程 -- 网络编程基础原理

    Hello,今天记录下 Java网络编程 --> 网络编程基础原理. 一起学习,一起进步.继续沉淀,慢慢强大.希望这文章对您有帮助.若有写的不好的地方,欢迎评论给建议哈! 初写博客不久,我是杨展 ...

  4. unix网络编程 str_cli epoll 非阻塞版本

    unix网络编程 str_cli epoll 非阻塞版本 unix网络编程str_cli使用epoll实现讲了使用epoll配合阻塞io来实现str_cli,这个版本是配合非阻塞io. 可以看到采用非 ...

  5. 多实例多进程网络编程PHP,php socket网络编程基础知识(四):多进程

    标签:status   传递   windows   返回   修改   队列   _for   响应   关联 说明 php在web编程时是不需要考虑多进程的,但整个php流程是涉及到多进程的,只不 ...

  6. Unix网络编程 chart

    前言 在最初接触网络这一领域的时候,就是傻傻地抱着一本TCP/IP协议详解来学习,主要学习协议的原理并研究协议相关的算法,大家都知道协议纯理论的学习是比较枯燥和复杂的,看着看着就睡着了.由于项目需要, ...

  7. Linux网络编程基础知识

    Linux网络编程基础知识 1. 协议的概念 1.1 什么是协议 1.2 典型协议 2 网络应用程序设计模式 2.1 C/S模式 2.2 B/S模式 2.3 优缺点 3 分层模型 3.1 OSI七层模 ...

  8. Linux学习之----socket网络编程基础

    分层模型 OSI七层模型 1.物理层:主要定义物理设备标准,如网线的接口类型.光纤的接口类型.各种传输介质的传输速率等.它的主要作用是传输比特流(就是由1.0转化为电流强弱来进行传输,到达目的地后再转 ...

  9. Linux C编程之十六 网络编程基础-socket

    一.协议的概念 1. 什么是协议 从应用的角度出发,协议可理解为"规则",是数据传输和数据的解释的规则. 假设,A.B双方欲传输文件.规定: 第一次,传输文件名,接收方接收到文件名 ...

最新文章

  1. μC-/OS II(一) PC编译环境的搭建
  2. Windows环境下MySQL 8.0 的安装、配置与卸载
  3. 《.NET框架程序设计》第2章 第3章 读后感
  4. Linux中文档去掉windows文本的多余的回车符(^M)
  5. BZOJ3930-莫比乌斯反演+杜教筛
  6. oracle thin和oci 区别
  7. Linux使用RSA实现免密登录(原理)
  8. java注解教程 pdf_Java注解详解
  9. 在BCB中使用多线程实例
  10. 机器学习实战(四)逻辑回归LR(Logistic Regression)
  11. mysql中计算日期整数差
  12. c语言家庭财务管理算法,c语言家庭财务管理报告.doc
  13. 有赞云支付php接口,Erphpdown wordpress插件集成有赞云支付的接口申请方法
  14. HTTPS/数字证书/数字签名
  15. 数据库中存储用户名、密码时如何处理?
  16. cocos creator麻将教程系列(四)—— 达达麻将客户端初始化流程
  17. [python] 使用正则表达式验证email地址是否有效
  18. php怎么爬取亚马逊的数据,php 抓取亚马逊中国产品数据-标题,价格,首图片
  19. 100个中国传统英文词汇,你会用英语表达吗?
  20. 牛客网Java编程题总结

热门文章

  1. 记录一次XordDos(BillGates)木马导致Centos kworker线程占满CPU资源的解决过程
  2. opencv的GPU编程(一)
  3. 破解PHPstorm
  4. window环境安装Hadoop 3.0.0
  5. mysql gtid 复制_MySQL 使用GTID进行复制
  6. 其中n和ch是用户传入的参数,n为[1, 9]的正整数。要求函数按照如样例所示的格式打印出n行由字符ch构成的字符金字塔。注意每个字符后有一个空格。
  7. 用在游戏里的编程语言
  8. CSS3 之高级动画(5)CSS3铅笔玻璃瓶
  9. react学习笔记3
  10. 介绍 Preact Signals