基于TCP的套接字编程:
socket层的位置:socket在传输层和应用层之间
socket是什么:
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。
socket的底层封装对文件描述符的读写。
套接字分类
- 基于文件类型的套接字家族;套接字家族的名字:AF_UNIX
- 基于网络类型的套接字家族;套接字家族的名字:AF_INET
套接字工作流程
先从服务端说起。服务端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务端的连接就建立了。客户端发送数据请求,服务器端接受请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
socket模块的应用:
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字、s.listen()开始TCP监听、s.accept()被动接受TCP客户的连接,(阻塞式)等待连接的到来。
accept成功之后,会创建一个连接connection对象,以及客户端的ip_port信息。
con.close()关闭这个连接。s.close()关闭服务器。
代码如下:
1 | import socket |
简化代码如下:
1 | import socket |
socket.SOL_SOCKET设置这个让我们可以在套接字级别上设置选项。选项有一些常用取值,常用选项有:
SO_BROADCAST广播消息的能力(只有udp支持广播,并且还必须是在支持广播消息的网络上(例如以太网,令牌环网等))、
SO_DEBUG,仅由TCP支持。当给一个TCP套接字开启本选项时,内核将为TCP在该套接字发送和接受的所有分组保留详细跟踪信息。这些信息保存在内核的某个环形缓冲区中,并可使用trpt程序进行检查。
SO_KEEPALIVE
给一个TCP套接字设置保持存活选项后,如果2小时内在该套接字的任何一方向上都没有数据交换,TCP就自动给对端发送一个保持存活探测分节。这是一个对端必须相应的TCP分节,它会导致以下3种情况之一。
(1)对端以期望的ACK响应。应用进程得不到通知(因为一切正常)。在又经过仍无动静的2小时后,TCP将发出另一个探测分节。
(2)对端以RST响应,它告知本端TCP:对端已崩溃且已重新启动。该套接字的待处理错误被置为ECONNRESET,套接字本身则被关闭。
(3)对端对保持存活探测分节没有任何响应。
如果根本没有对TCP的探测分节的响应,该套接字的待处理错误就被置为ETIMEOUT,套接字本身则被关闭。然而如果该套接字收到一个ICMP错误作为某个探测分节的响应,那就返回响应的错误,套接字本身也被关闭。
本选项的功能是检测对端主机是否崩溃或变的不可达(譬如拨号调制解调器连接掉线,电源发生故障等等)。如果对端进程崩溃,它的TCP将跨连接发送一个FIN,这可以通过调用select很容易的检测到。
本选项一般由服务器使用,不过客户也可以使用。服务器使用本选项时因为他们花大部分时间阻塞在等待穿越TCP连接的输入上,也就是说在等待客户的请求。然而如果客户主机连接掉线,电源掉电或者系统崩溃,服务器进程将永远不会知道,并将继续等待永远不会到达的输入。我们称这种情况为半开连接。保持存活选项将检测出这些半开连接并终止他们。
SO_LINGER
本选项指定close函数对面向连接的协议(例如TCP和SCTP,但不是UDP)如何操作。默认操作是close立即返回,但是如果有数据残留在套接字发送缓冲区中,系统将试着把这些数据发送给对端。
RCVBUF和SNDBUF接收和发送缓冲区
RCVLOWAT和SNDLOWAT接收和发送低水位
SO_RCVTIMEO 和 SO_SNDTIMEO套接字选项
SO_REUSEADDR 和 SO_REUSEPORT 套接字选项
SO_REUSEADDR所有的TCP服务器都应该指定本套接字选项,一个最重要的原因如下:
SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知的端口,即使以前建立的将该端口用作他们的本地端口的连接仍存在。这个条件通常是这样碰到的:
(a)启动一个监听服务器;
(b)连接请求到达,派生一个子进程来处理这个客户;
(c)监听服务器终止,但子进程继续为现有连接上的客户提供服务;
(d)重启监听服务器。
默认情况下,当监听服务器在步骤d通过调用socket,bind和listen重新启动时,由于他试图捆绑一个现有连接(即正由早先派生的那个子进程处理着的连接)上的端口,从而bind调用会失败。但是如果该服务器在socket和bind两个调用之间设置了SO_REUSEADDR套接字选项,那么将成功。所有TCP服务器都应该指定本套接字选项,以允许服务器在这种情况下被重新启动。
这里关于套接字选项不做更多展开,附上链接参考,https://blog.csdn.net/u010144805/article/details/78579771
客户端:p.connect(ip地址,端口号) 连接服务器
代码如下:
1 | import socket |
简化代码如下:
1 | import socket |
- 用户态:专门存放应用程序。内核态:专门存放操作系统的内核。
- 在socket 里面 from socket import * 这样可以减少代码量。
- 当发送回车换行,内容为空,没有必要发所以就会卡顿。当自己这一端 的内核态没东西会卡住recv()。加一个判断是否为空解决。
- 按照socket数据--->内核态----->网卡的顺序发送 send()()都是发送socket数据 send()和recv()都是往自己的内存里面收发。
- 端口号+ip地址+mac地址 = 哪个应用程序+哪台电脑+哪个房间(一一对应) 标示互联网上唯一的一个程序
udp套接字
由于udp是无连接的所以比TCP更简洁。
服务端:recvfrom()接受的结果是发送的信息,和发送方的IP和端口号。sendto(信息,目标主机IP和端口号)
1 | from socket import * |
简化代码如下:
1 | import socket |
客户端:
1 | from socket import * |
简化代码如下:
1 | import socket |
这里和tcp的socket区别可以看出,recv方法和recvfrom方法的一个区别
- recv在自己这端缓冲区为空时,阻塞。
- 而recvfrom在自己这端的缓冲区为空时,就收一个空。
用udp实现两个不同客户端的交流:
服务端:
1 | from socket import * |
客户端1:
1 | from socket import * |
客户端2:
1 | from socket import * |
HTTP三次握手、四次挥手
三次握手(因为刚开始没有数据传输所以可以合并),四次挥手(因为客户端到服务端数据传完可以断开,但是服务端的数据不一定发完,所以不能一次性断开) 握手:
- 客户端发送连接请求(syn)
- 服务器回应确认发送(ack),第一条客户端到服务器端的连接建好,并且向客户端发送连接请求(syn)
- 客户端回应确认发送(ack),第二条服务器端到客户端的连接建好。
挥手(谁先发完,谁就断开连接):
- 客户端发送请求断开连接(seq)
- 服务端回应确认(ack)此时客户端到服务端的链接断开
- 服务端发送请求断开连接(seq)
- 客户端回应确认(ack) 此时服务端到客户端的链接断开
至此双向连接都已断开。
HTTP协议
特点:无状态的协议、基于请求/响应的模式。
POST方式有请求体,而GET方式没有请求体。在请求协议中,空行是用来和请求体分开。
部分参数:
1 | Accpect:接收类型 */* 代表全部接受,q=0.8代表权重 |