python网络编程

一,概念

Internet:是连接网络的网络,任何网络只要支持Internet就可以接入Internet。

IP:每张网卡都有一个固定的32位网络地址IPv4。

IP包:在两个IP之间传递的数据包,不保证送达,不按顺序。

TCP:保证IP包送达,保证顺序,并且具体到是IP中哪两个进程再通信。

Socket: 代表一个连接 需要确定四个东西: 服务器ip+服务器port + 客户端ip+客户端port。

二,TCP通信

来看一个最简单的TCP通信,客户端给服务端发你好,然后服务端给客户端发欢迎

#server
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 9999))           #绑定
s.listen(5)                          #监听
print('等待连接...')
sock, addr = s.accept()             #获取连接
print('连接服务器: 地址 %s ' % str(addr))

print(sock.recv(1024).decode('utf-8')) #读消息

sock.send('欢迎'.encode('utf-8'))    #发消息

sock.close()                        #关闭本次连接
s.close()                           #关闭服务器socket

Output:
等待连接...
连接服务器: 地址 ('127.0.0.1', 50985) 
你好
#client
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1',9999))                   #连接服务器socket

s.send('你好'.encode('utf-8'))                                 #写消息

print(s.recv(1024).decode('utf-8'))             #读消息

s.close()                                       #关闭socket

Output:
欢迎

分析一下流程:

首先建立一个连接,socket,上面说过由四部分唯一确定。

  1. 先看服务端这里,创建一个tcp socket绑定了服务端ip+服务端port。然后开始监听,当客户端连接的时候,就确定了客户端ip+客户端port。那么在服务端已经建立一个连接了。
  2. 再看客户端这里,创建一个tcp socket去连接服务器,这个连接指定了服务器ip+服务器port。然后客户端ip是当前ip,客户端port是随机port。

连接建立后,就应该开始发送数据。

  1. 接收数据使用send方法 内容要用encode()编码
  2. 发送数据使用recv方法 内容要用decode()解码

数据交换完毕,断开连接。

  1. 客户端断开连接。
  2. 服务端断开连接,然后服务器继续运行。

三,UDP通信

#server
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('127.0.0.1', 9999))           #绑定

data, addr = s.recvfrom(1024)  #读消息
print(data.decode('utf-8'))

s.sendto('欢迎'.encode('utf-8'), addr)    #发消息

s.close()                           #关闭服务器socket

Output:
你好
#client
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

s.sendto('你好'.encode('utf-8'),('127.0.0.1',9999))     #写消息

print(s.recv(1024).decode('utf-8'))             #读消息

s.close()                                       #关闭socket

Output:
欢迎

对比一下和TCP的区别:

UDP不需要连接

  1. 服务端创建udp socket之后,只需要绑定ip和port。
  2. 客户端创建udp socket即可。

发送数据

  1. 客户端给服务端发送数据 sendto方法指定服务器ip+服务器port 这样就唯一确定一个连接。
  2. 服务端给客户端发送数据 recvfrom方法可以获取发送数据的ip+port。这样服务端也可以唯一确定一个连接。

关闭连接

  1. 客户端断开连接。
  2. 服务端不需要操作做。

四,多线程+socket

只需要改动服务端即可:

import socket, threading

def linkThread(sock, addr):
    print('连接服务器: 地址 %s 所在线程 %s' % (str(addr), threading.current_thread()))
    print(sock.recv(1024).decode('utf-8'))  # 读消息
    sock.send('欢迎'.encode('utf-8'))  # 发消息
    sock.close()  # 关闭本次连接

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 9999))           #绑定
s.listen(5)                          #监听
print('等待连接...')

while True:
    sock, addr = s.accept()             #获取连接
    threading.Thread(target=linkThread, args=(sock, addr) ).start() #开一个线程去处理连接

s.close()                           #关闭服务器socket

Output:
等待连接...
连接服务器: 地址 ('127.0.0.1', 62417) 所在线程 <Thread(Thread-1, started 6820)>
你好
连接服务器: 地址 ('127.0.0.1', 62418) 所在线程 <Thread(Thread-2, started 3424)>
你好
连接服务器: 地址 ('127.0.0.1', 62419) 所在线程 <Thread(Thread-3, started 14428)>
你好
连接服务器: 地址 ('127.0.0.1', 62420) 所在线程 <Thread(Thread-4, started 14640)>
你好
连接服务器: 地址 ('127.0.0.1', 62421) 所在线程 <Thread(Thread-5, started 9468)>
你好
连接服务器: 地址 ('127.0.0.1', 62422) 所在线程 <Thread(Thread-6, started 3952)>
你好

可以看到,在服务端加上多线程,客户端可以不做任何修改无感知的继续去完成之前的功能。

五,交互

前面的是线程交互,你一句我一句。现在来看如何达到无序交互。针对每一个连接开两个线程,一个读,一个写即可。

#server
import socket, threading

def linkSend(sock, addr):
    print('连接服务器: 地址 %s 所在线程 %s' % (str(addr), threading.current_thread()))
    while True:
        msg = input()
        sock.send(msg.encode('utf-8'))  # 发消息

def linkRecv(sock, addr):
    while True:
        print(sock.recv(1024).decode('utf-8'))  # 读消息

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 9999))           #绑定
s.listen(5)                          #监听
print('等待连接...')

while True:
    sock, addr = s.accept()             #获取连接
    threading.Thread(target=linkSend, args=(sock, addr)).start() #开启写线程
    threading.Thread(target=linkRecv, args=(sock, addr)).start() #开启读线程

s.close()                           #关闭服务器socket

Output:
等待连接...
连接服务器: 地址 ('127.0.0.1', 62489) 所在线程 <Thread(Thread-1, started 3032)>
你好
不错呦
#client
import socket, threading

def linkSend(sock):
    while True:
        msg = input()
        sock.send(msg.encode('utf-8'))  # 写消息

def linkRecv(sock):
    while True:
        print(sock.recv(1024).decode('utf-8'))  # 读消息

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1',9999))                   #连接服务器socket

threading.Thread(target=linkSend, args=(s,)).start()    #发送线程
threading.Thread(target=linkRecv, args=(s,)).start()    #接收线程

Output:
你好
不错呦

六,交互式连接怎么关闭?

上面的例子问题很明显,双方都没有关闭连接,这个连接会被一直占用着。

参考TCP四次挥手的过程,我设计了如下这个关闭TCP连接的方式。

客户端在输入exit后,关闭socket的输出流,服务端收到exit后,关闭socket的输入流。这是模仿TCP四次挥手的前两次。但是要注意,这个时候是半连接,服务端还可以向客户端发消息。

服务端主动关闭socket,然后客户端的recv方法就会返回空,也关闭即可。这里不优雅的就是,最好能够在服务端关闭socket输入流之后关联的在写方法中关闭socket。

关于TCP和Socket可以参考这篇文章https://blog.csdn.net/ctrl_qun/article/details/52518479

#server
import socket, threading

dict = {}

def linkSend(sock, addr):
    print('连接服务器: 地址 %s 所在线程 %s' % (str(addr), threading.current_thread()))
    while dict[sock]:
        msg = input()
        if not dict[sock]:    #这里要判断主要是因为input()阻塞在这里
            break
        sock.send(msg.encode('utf-8'))  # 发消息
    sock.close()

def linkRecv(sock, addr):
    while dict[sock]:
        msg = sock.recv(1024).decode('utf-8')  # 读消息
        print(msg)
        if msg == 'exit':
            dict[sock] = False
    sock.shutdown(socket.SHUT_RD)  #关闭读数据

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 9999))           #绑定
s.listen(5)                          #监听
print('等待连接...')

while True:
    sock, addr = s.accept()             #获取连接
    dict[sock] = True           #标志初始化
    threading.Thread(target=linkSend, args=(sock, addr)).start() #开启写线程
    threading.Thread(target=linkRecv, args=(sock, addr)).start() #开启读线程

s.close()                           #关闭服务器socket

Output:
等待连接...
连接服务器: 地址 ('127.0.0.1', 62606) 所在线程 <Thread(Thread-1, started 5708)>
你好
欢迎
exit
随便...这句话只是用来让input不要阻塞在那里的
#client
import socket, threading

def linkSend(sock):
    while True:
        msg = input()
        sock.send(msg.encode('utf-8'))  # 写消息
        if msg == 'exit':
            break
    sock.shutdown(socket.SHUT_WR)   #不能调用 sock.close() 因为服务器还可能发送数据

def linkRecv(sock):
    while True:
        data = sock.recv(1024)
        if not data:                #当服务器也关闭后 客户端就可以关闭socket了
            break
        print(data.decode('utf-8'))  # 读消息
    sock.close()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1',9999))                   #连接服务器socket

threading.Thread(target=linkSend, args=(s,)).start()    #发送线程
threading.Thread(target=linkRecv, args=(s,)).start()    #接收线程

#s.close()                                       #关闭socket

Output:
你好
欢迎
exit

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值