Python网络编程

    科技2022-07-13  139

    0x00 UDP- client

    import socket ''' udp client ''' # 创建套接字对象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指定了一个固定的地址. 也可以不指定, os自动分配 client.bind(addr_local) while True: msg = input('输入发送给server的数据:') if msg == 'exit': break client.sendto(msg.encode(), addr_remote) # udp_client一般用recv, 因为一般来说1个client只跟1个server通信 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 server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) addr_local = ('127.0.0.1', 12346) server.bind(addr_local) while True: # udp_server一般是recvfrom, 因为它要获得client的信息,才知道该发给谁 # 因为udp是无连接的 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指定地址,非必要 client.bind(local_addr) # 连接到server 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连接 client_conn, client_addr = server.accept() # recv返回的是数据。为啥?因为这是有连接的, server很清楚数据是谁发来的 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. 服务器可根据这点判断客户端是否丢失连接。
    Processed: 0.015, SQL: 8