相关含义:
TCP是面向连接的通信,所以在通信之前,客户端与服务器端必须通过三次握手建立连接,然后在通信完毕,还要通过四次挥手断开连接。
三次握手建立连接:server端调用socket(),bind(),listen()创建监听套接字并完成初始化,然后调用accept()阻塞式等待客户连接。客户端创建一个套接字初始化后,调用connect连接server,连接过程:调用connect()发出SYN段并阻塞等待服务器应答(client:我想要连接你),服务器应答一个SYN-ACK(server:好,我准备好了,你连接吧),客户端收到从connect()返回,同时应答一个ACK给server(client:太好了,我连接好了),服务器收到ACK,从accept返回。
数据传输过程:建立连接后,TCP可以提供全双工的通信,server先读再写,client先写在读,用read()和write()阻塞式的等待一个写一个读。一直循环下去。
四次挥手关闭连接:假设Client端发起中断连接请求,也就是发送FIN报文。Server端接到FIN报文后,意思是说"我Client端没有数据要发给你了",但是如果你还有数据没有发送完成,则不必急着关闭Socket,可以继续发送数据。所以你先发送ACK,"告诉Client端,你的请求我收到了,但是我还没准备好,请继续你等我的消息"。这个时候Client端就进入FIN_WAIT状态,继续等待Server端的FIN报文。当Server端确定数据已发送完成,则向Client端发送FIN报文,"告诉Client端,好了,我这边数据发完了,准备好关闭连接了"。Client端收到FIN报文后,"就知道可以关闭连接了,但是他还是不相信网络,怕Server端不知道要关闭,所以发送ACK后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。“,Server端收到ACK后,"就知道可以断开连接了"。Client端等待了2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,我Client端也可以关闭连接了。Ok,TCP连接就这样关闭了!如果一方调用shutdown()则连接处于半关闭状态,仍可接受对方的数据。
相关函数
1.创建套接字
server = socket.socket()
domain:地址类型,ipv4、ipv6、unix的地址类型分别定义为常数AF_INET、AF_INET6、AF_UNIX.
type:socket传输类型,tcp通信是面向字节流的,所以为SOCK_STREAM
在网络通信时,我们的数据要从本主机通过网络发送到对端主机,数据在内存中存放的形式有大端或者小端两种形式,所以在向网络中传输数据是,网络就要按照一定的规定收发数据。TCP/IP协议规定,网络字节流应按照大端字节流,即低地址高字节。
网络数据流的地址规定:先发出的数据是低地址,后发出的数据是高低址,因为网络字节流为大端,也就是先发送数据的高位字节,在发送低位字节。
3.将套接字与socket结构体绑定,socket结构体会指定ip,端口号,还有地址类型
address = ("localhost", 6666)
server.bind(address)
一般服务器的端口号和ip是绑定的,众所周知的,而客户端的端口号可以随机分配的
将sockfd设置为监听套接字,并通过参数2是指明最多可以监听多少个套接字。
4.server.listen(5)(两个参数)
监听套接字的作用:server服务器启动后,会源源不断的有客户端来连接,这时候就需要一个监听套接字,来把一个个来访的socket按顺序存起来,并按顺序交给accept的去处理并返回newsockfd去收发数据,这样做可以保证在server满负荷的处理其他socket时,其他客户端要访问服务器时,可以通过监听队列等待一会,有其他客户端断开连接了,他就可以连接了。
5. conn, addr = server.accept()
阻塞式等待客户端连接,监听套接字一直在监听是否有新连接到来连接,如果有链接则接受对方连接,连接之后由返回值new_sock收发数据。
6.new_sock(1) = accept(监听套接字(2),struct socket_in输出型参数(3),输入输出型参数(4))
当客户端向服务器端返回ACK时候,会返回一个新的socket对象,数据的传输会在这个新返回的对象中进行操作,之前定义的socket对象会继续监听其它访问该服务器的客户端
7. client.connect(address)
client不需要被别人连接,只需要连接别人,所以使用connect来连接服务器
补充:
存在问题:但是在运行的时候发现一个问题,server启动后,然后启动client建立连接后,然后直接ctrl+c终止掉server,无法立即重启,必须等待半分钟才能重新启动
原因:这是因为在四次挥手断开连接时,主动断开的一方会进入TIME_WAIT状态,这是server还没有完全断开连接,还占着8080号端口,所以再次启动时创建监听套接字就无法在绑定上8080号端口。
但是这是不合理的,因为在实际生活中,服务器一旦挂了,不能立即重启,可能会影响许多客户的体验,会造成很大的损失,那么如何解决。
解决:解决这个问题的方法是使用setsockopt()设置socket描述符的,设置选项SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个socket描述符。在server代码的socket()和bind()之间插入。
EG1
注意:1和2是tcp的例子,三次握手建立连接,四次挥手关闭连接
1.客户端传输一个,服务器端接受接受依次数据,并且返回
服务器端:
#无限接受客户端的连接请求,一个客户端可接受1次发送的信息,无挂起
import socket
address = ("localhost", 6666)
server = socket.socket()
server.bind(address)
server.listen()
conn, addr = server.accept()
data = conn.recv(1024)
print("data", data)
conn.send(b"000000099999999")
server.close()
客户端:
#一个客户端可发送1次信息给服务器
import socket
address = ("localhost",6666)
client = socket.socket()
client.connect(address)
name = input(">>").strip()
print(len(name))
client.send(name.encode('utf-8'))
data = client.recv(1024)
print("data:", data)
client.close()
EG2:客户端发送四次信息给服务器端,无限接受客户端的连接请求,但是一个客户端可接受4次发送的信息,最高挂起5个
服务器端:
import socket
address = ("localhost", 6666)
server = socket.socket()
server.bind(address)
server.listen(5)
while True:
conn, addr = server.accept()
count = 1
while count < 4:
data = conn.recv(1024)
print("data",data)
print(len(data))
count += 1
print("*******")
conn.send(b"132456798")
server.close()
客户端:
import socket, time
address = ("localhost",6666)
client = socket.socket()
client.connect(address)
count = 1
while count <4:
if count == 4:
break
name = input(">>").strip()
print(len(name))
client.send(name.encode('utf-8'))
count += 1
print("len(name) == 0")
data = client.recv(1024)
print("data:", data)
client.close()
EG3:udpServer的代码
服务器:
import socket
HOST='192.168.0.37'
PORT=50001
BUFFER=4096
sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
sock.bind((HOST,PORT))
#sock.listen(0)
print('tcpServer listen at: %s:%s\n\r' %(HOST,PORT))
while True:
#client_sock,client_addr=sock.accept()
#print('%s:%s connect' %client_addr)
while True:
recv,client_addr=sock.recvfrom(BUFFER)
if not recv:
break
print('[Client %s:%s said]:%s' % (client_addr[0],client_addr[1],recv))
sock.sendto('tcpServer has received your message',client_addr)
sock.close()
客户端:
import socket
HOST='192.168.0.37'
PORT=50001
BUFFER=4096
sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
sock.connect((HOST,PORT))
sock.send('hello, tcpServer!')
recv=sock.recv(BUFFER)
print('[tcpServer said]: %s' % recv)
sock.close()
补充:你会发现由于udp是非连接的,不需要三次握手,所以不需要进行listen,也不需要accept,直接通信就可以了。还有就是初始化socket的时候,通过指定socket.SOCK_DGRAM来实现初始化的socket是基于udp协议的。如果初始化的是udp协议的socket,就不需要listen,也不存在accept,双方通信的同时指明对方的地址和端口就可以了。
详细:https://blog.csdn.net/dream_1996/article/details/77924466
深度理解:https://blog.csdn.net/gocy015/article/details/45055589