0x00 UDP- client
import socket
'''
udp client
'''
client
= socket
.socket
(socket
.AF_INET
, socket
.SOCK_DGRAM
)
addr_remote
= ('127.0.0.1', 12346)
addr_local
= ('127.0.0.1', 12345)
client
.bind
(addr_local
)
while True:
msg
= input('输入发送给server的数据:')
if msg
== 'exit':
break
client
.sendto
(msg
.encode
(), addr_remote
)
msg
= client
.recv
(1024)
print('从server接收的数据:' + msg
.decode
())
client
.close
()
解读:
udp的socket应当用socket.SOCK_DGRAM可以给客户端bind一个固定的地址,但没必要client发送数据时用sendto, 指定addr_remote,这样才知道要发送给谁client接受数据时可以用recv, 也可以用recvfrom。但用recv比较舒服。因为client一般只与一个server通信,sendto的时候就指定了addr_remote, 没必要用recvfrom获得server的地址信息。recvfrom的返回值是一个二元组,第一个元素是传过来的byte信息,第二个元素是连接的对方的地址.recv的返回值是一个byte信息.
0x01 UDP- server
import socket
'''
udp server
'''
server
= socket
.socket
(socket
.AF_INET
, socket
.SOCK_DGRAM
)
addr_local
= ('127.0.0.1', 12346)
server
.bind
(addr_local
)
while True:
msg
,client
= server
.recvfrom
(1024)
print('从client接收的数据:'+msg
.decode
())
resp
= input('请输入要发送给客户端的信息:')
if resp
== 'exit':
break
server
.sendto
(resp
.encode
(), client
)
server
.close
()
解读:
server的recv是带from的,这样才能拿到client的信息,sendto传递给client消息。sendto是必须的。因为udp是无连接的,不用sendto就不知道发给谁。server的bind是必须的,不然client不知道去哪找server.
emm, 似乎上面的交流模式有点NT?你也许发现了,这样的写法,如果send之后没有recv,那么就会傻傻的阻塞。所以咱们的QQ用这种半双工的形式来做,不会被骂死吗? 其实socket不是半双工,而是全双工。只不过recv是阻塞的,所以体现不出来socket全双工的特点。我们改造一下代码,弄2个线程分别进行收发就OK了。
0x02 udp多线程client
import threading
import socket
client
= socket
.socket
(socket
.AF_INET
, socket
.SOCK_DGRAM
)
remote
= ('127.0.0.1', 12346)
client
.bind
(('', 12345))
def send():
for i
in range(1000):
client
.sendto
(input().encode
(),remote
)
def receive():
for i
in range(1000):
msg
= client
.recv
(1024)
print(msg
.decode
())
def main():
t1
= threading
.Thread
(target
=send
)
t2
= threading
.Thread
(target
=receive
)
t1
.start
()
t2
.start
()
t1
.join
()
t2
.join
()
if __name__
== '__main__':
main
()
0x03 udp多线程server
import threading
import socket
server
= socket
.socket
(socket
.AF_INET
, socket
.SOCK_DGRAM
)
remote
= ('127.0.0.1', 12345)
server
.bind
(('', 12346))
def send():
for i
in range(100):
server
.sendto
(input().encode
(), remote
)
def receive():
for i
in range(1000):
msg
= server
.recv
(1024)
print(msg
.decode
())
def main():
t1
= threading
.Thread
(target
=send
)
t2
= threading
.Thread
(target
=receive
)
t1
.start
()
t2
.start
()
t1
.join
()
t2
.join
()
main
()
题外话
tcp和udp可以占用同一个端口吗? - 当然没问题。 协议已经可以标识哪种套接字了,所以端口号就不用加以区分了(对于同一个协议来说)。 TCP严格区分客户端和服务器。 因为服务器需要有个欢迎套接字。啥是欢迎套接字?其实这个跟ftp原理比较像,可以认为欢迎套接字就是ftp的控制套接字。真正的数据传输还需要另外的套接字。
0x04:TCP-client
import socket
def main():
client
= socket
.socket
(socket
.AF_INET
, socket
.SOCK_STREAM
)
local_addr
= ('', 12345)
remote_addr
= ('', 12346)
client
.bind
(local_addr
)
client
.connect
(remote_addr
)
client
.send
(input().encode
())
msg
= client
.recv
(1024).decode
()
print(msg
)
client
.close
()
main
()
0x05:TCP server
import socket
def main():
server
= socket
.socket
(socket
.AF_INET
, socket
.SOCK_STREAM
)
local_addr
= ('', 12346)
server
.bind
(local_addr
)
server
.listen
(5)
client_conn
, client_addr
= server
.accept
()
msg
= client_conn
.recv
(1024)
print(msg
.decode
())
client_conn
.send
(input().encode
())
client_conn
.close
()
server
.close
()
当client发送的数据为空的时候,则代表client关闭了。(不能主动发送为空的数据,只能是client关闭时,自动发给server为空的数据, 所以可以凭借此认定client丢失连接)。 client发送数据为空时,server的recv会解阻塞的。
0x06: listen参数的作用
listen参数代表了可建立socket连接的排队的个数。
0x07: tcp注意点总结
tcp服务器一般情况下需要绑定,否则client不好找服务器。tcp客户端一般不绑定,但也可以绑定。tcp服务器的listen可以使socket创建出来的主动套接字变成被动套接字,这是服务器必须要做的。客户端需要连接服务器时,要用connect进行连接。因为tcp是有连接的。当一个客户端连接服务器时,服务端会产生一个新的套接字来标志这个客户端。listen后的套接字是被动套接字,是用来接收客户端的连接请求用的。accept返回的新套接字是标识client的。关闭listen后的套接字意味着新的客户端不能连接到服务器了。但是已经连接好的客户端正常通信。关闭accept返回的套接字意味着跟这个客户端断开连接了。客户端的套接字close()之后,服务器端会recv解阻塞,长度为0. 服务器可根据这点判断客户端是否丢失连接。