《Unix 网络编程》04:基本TCPSocket编程
基本Socket编程
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | https://lymtics.top |
作者 | LYMTICS(樵仙) | https://lymtics.top |
联系方式 | me@tencent.ml | me@tencent.ml |
原文标题 | 《Unix 网络编程》04:基本TCPSocket编程 | 《Unix 网络编程》04:基本TCPSocket编程 |
原文地址 | https://www.cnblogs.com/lymtics/p/16319917.html | https://www.cnblogs.com/lymtics/p/16319917.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
本系列文章导航:《Unix 网络编程》笔记
socket
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | https://lymtics.top |
作者 | LYMTICS(樵仙) | https://lymtics.top |
联系方式 | me@tencent.ml | me@tencent.ml |
原文标题 | 《Unix 网络编程》04:基本TCPSocket编程 | 《Unix 网络编程》04:基本TCPSocket编程 |
原文地址 | https://www.cnblogs.com/lymtics/p/16319917.html | https://www.cnblogs.com/lymtics/p/16319917.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
创建一个 socket,指定期望的通信协议,得到一个 socket descriptor(套接字描述符)。
#include <sys/socket.h>
int socket(int family, int type, int protocal);
其中:
- family 指明协议族
- type 指明套接字类型
- protocal 设为某个协议类型(或设为 0,以选定前两个变量的组合下该值的默认值)
如下是他们的取值和可用组合:
connect
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | https://lymtics.top |
作者 | LYMTICS(樵仙) | https://lymtics.top |
联系方式 | me@tencent.ml | me@tencent.ml |
原文标题 | 《Unix 网络编程》04:基本TCPSocket编程 | 《Unix 网络编程》04:基本TCPSocket编程 |
原文地址 | https://www.cnblogs.com/lymtics/p/16319917.html | https://www.cnblogs.com/lymtics/p/16319917.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
建立连接:
#include <sys/socket.h>
int connect(int sockfd, const sockaddr *servaddr, socklen_t addrlen);
其中:
- sockfd 是上一小节中创建的 socket 描述符
- servaddr 指向套接字地址结构
- addrlen 是 servaddr 结构的长度
Client 在 connect 前无需进行 bind,由系统确定源 IP 地址,并选择一个临时端口作为源端口。
TCP 下一些错误的情况:
TCP 调用 connect 会触发三次握手:
-
发送方先发送一个 SYN,过一段时间没有收到响应则再发一个……直到若干次重试后仍没有收到 SYN 分节,则返回
E.TIMED.OUT
错误 -
若 SYN 响应为 RST,说明该服务器主机在我们指定的端口没有进程在等待与之连接
这是一种硬错误,一收到 RST 就返回 ECONNREFUESD 错误
-
若 SYN 在中间某个路由器上引发一个 destination unreachable 错误,则认为是一种软错误,客户端继续尝试(类似第一种类型),直到失败,返回一个 E.NET.UNREACH 或 E.HOST.UNREACH
bind
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | https://lymtics.top |
作者 | LYMTICS(樵仙) | https://lymtics.top |
联系方式 | me@tencent.ml | me@tencent.ml |
原文标题 | 《Unix 网络编程》04:基本TCPSocket编程 | 《Unix 网络编程》04:基本TCPSocket编程 |
原文地址 | https://www.cnblogs.com/lymtics/p/16319917.html | https://www.cnblogs.com/lymtics/p/16319917.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr * myaddr, socklen_t addrlen);
第二个参数是一个指向特定于协议的地址结构的指针。
关于绑定的若干说明:
端口:
- 一般来说,客户端无需进行 bind,由内核分配临时端口;
- 而服务器则需要 bind 一个众所周知端口来让别人访问。(这个规则的例外是 RPC 服务器)
IP 地址:
- 对于客户端来说,可以自己指定一个自己的网络接口的地址,也可以由内核进行绑定
- 对于服务器来说,一般是在连接到来时才确定绑定的地址,如果手动设置则意味着只接受该地址的请求
设置通配地址
// IPv4
struct sockaddr_in servaddr;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
// IPv6
struct sockaddr_in6 serv;
serv.sin6_addr = in6addr_any;
listen
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | https://lymtics.top |
作者 | LYMTICS(樵仙) | https://lymtics.top |
联系方式 | me@tencent.ml | me@tencent.ml |
原文标题 | 《Unix 网络编程》04:基本TCPSocket编程 | 《Unix 网络编程》04:基本TCPSocket编程 |
原文地址 | https://www.cnblogs.com/lymtics/p/16319917.html | https://www.cnblogs.com/lymtics/p/16319917.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
只能由 TCP Server 调用,把套接字转换为被动套接字,指示内核接收该套接字的连接请求(默认为主动套接字),此时 socket 状态由 CLOSED 转换为 LISTEN。
本函数的调用时机:socket 和 bind 之后,accept 之前
#include <sys/socket.h>
int listen(int sockfd, int backlog);
backlog 的说明
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | https://lymtics.top |
作者 | LYMTICS(樵仙) | https://lymtics.top |
联系方式 | me@tencent.ml | me@tencent.ml |
原文标题 | 《Unix 网络编程》04:基本TCPSocket编程 | 《Unix 网络编程》04:基本TCPSocket编程 |
原文地址 | https://www.cnblogs.com/lymtics/p/16319917.html | https://www.cnblogs.com/lymtics/p/16319917.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
两个队列:
如图所示,为三次握手的场景:
- 服务器会在接收到第一次握手的 SYN 时将该请求的相关信息保存到一个未完成连接队列中,并返回第二次握手的信息
- 当客户端返回第三次握手的信息后,会将该请求的相关信息从未完成队列移动到已完成连接队列
- 当进程调用 accept 时,已完成连接队列的队头项将返回给进程,或者如果该队列为空,则进入睡眠,直到新的一项放入该队列才唤醒该线程
backlog 规定了内核应该为相应套接字排队的最大连接个数。
backlog 的若干说明
- 这个参数曾被规定为两个队列总和的大小,尽管其没有被明确定义
- Berkeley 的实现给其增设了一个模糊因子:把他乘以 1.5 以得到队列的最大长度
- 之前,backlog 的值总是 5,因为这是 4.2BSD 支持的最大值,如今此值自然不够用;当前许多系统允许修改其最大值
- 当一个客户 SYN 到达时,如果已经满了,则什么也不做;因为这种状态一般都是暂时的,通过此来触发客户端的重传机制从而解决暂时的问题;如果发送 RST,则客户端对其理解有歧义,而且也会加重网络负担,客户端也会触发错误处理机制。
SYN 泛洪攻击
Nginx 的配置参数中有
backlog
这个参数
accept
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | https://lymtics.top |
作者 | LYMTICS(樵仙) | https://lymtics.top |
联系方式 | me@tencent.ml | me@tencent.ml |
原文标题 | 《Unix 网络编程》04:基本TCPSocket编程 | 《Unix 网络编程》04:基本TCPSocket编程 |
原文地址 | https://www.cnblogs.com/lymtics/p/16319917.html | https://www.cnblogs.com/lymtics/p/16319917.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
accept 函数由 TCP server 调用,用于从已完成连接队列队头返回下一个已完成连接;如果该队列为空,则睡眠(阻塞)。
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
这个函数的三个参数:
- 第一个传入的是监听套接字的描述符
- 第二个和第三个参数传入的是结果的存放的地方,建立连接后,函数会把相关的信息保存到该指针指向的结构中。如果我们不感兴趣则可以设为 null。原书中把 Value-Result Arguments 翻译为值结果参数,属实是有点不好理解。
函数的返回值是已连接套接字描述符(或错误信息-1)。
fork/exec
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | https://lymtics.top |
作者 | LYMTICS(樵仙) | https://lymtics.top |
联系方式 | me@tencent.ml | me@tencent.ml |
原文标题 | 《Unix 网络编程》04:基本TCPSocket编程 | 《Unix 网络编程》04:基本TCPSocket编程 |
原文地址 | https://www.cnblogs.com/lymtics/p/16319917.html | https://www.cnblogs.com/lymtics/p/16319917.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
fork
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | https://lymtics.top |
作者 | LYMTICS(樵仙) | https://lymtics.top |
联系方式 | me@tencent.ml | me@tencent.ml |
原文标题 | 《Unix 网络编程》04:基本TCPSocket编程 | 《Unix 网络编程》04:基本TCPSocket编程 |
原文地址 | https://www.cnblogs.com/lymtics/p/16319917.html | https://www.cnblogs.com/lymtics/p/16319917.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
fork
函数是 Unix 中派生新进程的唯一方法。关于这个函数的具体内容在操作系统相关课程中有过学习。
该函数的一个特性是:父进程调用 fork 前打开的所有描述符在 fork 返回之后和子线程共享,因此我们通常会:
- 父进程调用 accept 后调用 fork,然后关闭这个 Socket
- 子进程接着读写这个 Socket
fork 的典型用法:
- 创建自身的副本,让副本处理某项工作,如网络服务器
- 创建自身的副本,然后调用 exec 执行另一个程序,如 Shell
exec
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | https://lymtics.top |
作者 | LYMTICS(樵仙) | https://lymtics.top |
联系方式 | me@tencent.ml | me@tencent.ml |
原文标题 | 《Unix 网络编程》04:基本TCPSocket编程 | 《Unix 网络编程》04:基本TCPSocket编程 |
原文地址 | https://www.cnblogs.com/lymtics/p/16319917.html | https://www.cnblogs.com/lymtics/p/16319917.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
exec
是存放在硬盘上的可执行程序文件被 Unix 执行的唯一方法;有 6 个不同的 exec 函数,他们的参数有所不同:
- 传入的第一个参数是文件名还是路径名
- 新程序的参数一一列出还是指针数组引用
- 把调用进程的环境传递给新程序还是指定新的环境
一般来说,只有 execve
是内核中的系统调用,其他 5 个都是调用 execve
的库函数。
并发服务器
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | https://lymtics.top |
作者 | LYMTICS(樵仙) | https://lymtics.top |
联系方式 | me@tencent.ml | me@tencent.ml |
原文标题 | 《Unix 网络编程》04:基本TCPSocket编程 | 《Unix 网络编程》04:基本TCPSocket编程 |
原文地址 | https://www.cnblogs.com/lymtics/p/16319917.html | https://www.cnblogs.com/lymtics/p/16319917.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
上述服务器只能在同一时间服务一个客户。可以采用 fork 的方法来让子进程处理具体的请求,而父进程专心监听请求的到来。
大致框架
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | https://lymtics.top |
作者 | LYMTICS(樵仙) | https://lymtics.top |
联系方式 | me@tencent.ml | me@tencent.ml |
原文标题 | 《Unix 网络编程》04:基本TCPSocket编程 | 《Unix 网络编程》04:基本TCPSocket编程 |
原文地址 | https://www.cnblogs.com/lymtics/p/16319917.html | https://www.cnblogs.com/lymtics/p/16319917.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
listenfd = Socket(...);
Bind(listenfd, ...);
Listen(listenfd, LISTENQ);
for (;;) {
connfd = Accept(listenfd, ...);
if ( (pid = Fork()) == 0) {
Close(listenfd);
doSomeService(connfd);
Close(connfd);
exit(0);
}
Close(connfd);
}
引用计数的理解
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | https://lymtics.top |
作者 | LYMTICS(樵仙) | https://lymtics.top |
联系方式 | me@tencent.ml | me@tencent.ml |
原文标题 | 《Unix 网络编程》04:基本TCPSocket编程 | 《Unix 网络编程》04:基本TCPSocket编程 |
原文地址 | https://www.cnblogs.com/lymtics/p/16319917.html | https://www.cnblogs.com/lymtics/p/16319917.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
每个文件或套接字都有一个引用计数,引用计数在文件表项中维护。它是当前打开着的引用该文件或套接字的描述符的个数。
当 fork 时,原本父进程持有的两个描述符 listenfd、connfd 的引用值都会 +1,而调用 close 方法只会让相应的引用值 -1。
该套接字真正的清理和资源释放要到其引用计数值到达 0 时才发生。
因此,父进程 Close(connfd)
和子进程 Close(listenfd)
都不会导致套接字真正被关闭。
如果我们确实想触发关闭流程,可以改用 shutdown
函数。
另外,如果不关闭的话,会造成资源的泄漏,耗尽可用的描述符。
close
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | https://lymtics.top |
作者 | LYMTICS(樵仙) | https://lymtics.top |
联系方式 | me@tencent.ml | me@tencent.ml |
原文标题 | 《Unix 网络编程》04:基本TCPSocket编程 | 《Unix 网络编程》04:基本TCPSocket编程 |
原文地址 | https://www.cnblogs.com/lymtics/p/16319917.html | https://www.cnblogs.com/lymtics/p/16319917.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
作用:关闭 Socket,终止 TCP 连接。
- 一个 TCP Socket 的默认行为是把该 Socket 标记成已关闭,然后立即返回到调用进程
- 该 Socket 描述符不能再发送新的消息
- 尝试发送已经在发送队列的消息
- TCP 终止
#include <unistd.h>
int close(int sockfd);
getsockname/getpeername
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | https://lymtics.top |
作者 | LYMTICS(樵仙) | https://lymtics.top |
联系方式 | me@tencent.ml | me@tencent.ml |
原文标题 | 《Unix 网络编程》04:基本TCPSocket编程 | 《Unix 网络编程》04:基本TCPSocket编程 |
原文地址 | https://www.cnblogs.com/lymtics/p/16319917.html | https://www.cnblogs.com/lymtics/p/16319917.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
这两个函数返回与某个套接字关联的本地协议地址或外地协议地址:
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
后两个参数用来接收结果
getsockname 的若干应用场景:
-
客户端,没有 bind 端口,可以用 getsockname 获取内核赋予该连接的本地 IP 和端口号
-
客户端以端口号 0 为参数调用 bind,让内核选择端口号,可以用 getsockname 返回本地端口号
-
获取某个套接字的地址簇
int main(int argc, char** argv) { int sockfd, n; char recvline[MAXLINE + 1]; socklen_t len; struct sockaddr_storage ss; if (argc != 3) err_quit("usage: daytimetcpcli <hostname/IPaddress> <service/port#>"); sockfd = Tcp_connect(argv[1], argv[2]); len = sizeof(ss); Getpeername(sockfd, (SA*)&ss, &len); printf("connected to %s\n", Sock_ntop_host((SA*)&ss, len)); while ((n = Read(sockfd, recvline, MAXLINE)) > 0) { recvline[n] = 0; /* null terminate */ Fputs(recvline, stdout); } exit(0); }
-
服务端,accept成功返回后,可以调用 getsockname 获取该连接的本地 IP 和端口号
getpeername 的若干应用场景:
服务端,accept 进程后并 fork、exec 一个子进程进行处理,执行 exce 后内存映射会被替换成真正的服务器程序(如 Telnet),包含客户端地址的套接字地址结构就此丢失,不过已连接的套接字描述符跨 exec 开放;可以调用 getpeername 获取客户的 IP 地址和端口号(如下图所示)