python3基础知识复习 -- 网络(tcp/udp)编程介绍

网络编程介绍


网络通信就是两个进程间在用Python进行网络编程,就是在Python程序本身这个进程内,连接别的服务器进程的通信端口进行通信。

TCP编程

Socket是网络编程的一个抽象概念。通常我们用一个Socket表示“打开了一个网络链接”(要指定目标IP+端口号)

主动发起连接的叫客户端,被动响应连接的叫服务器。

客户端client

client端请求新浪网页的例子:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import socket, ssl

# 创建一个socket, AF_INET为ipv4,AF_NET6为ipv6,SOCK_STREAM指面向流的TCP协议
#s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接,注意参数是一个tuple,网页端口80,<1024是Internet标准服务端口,>1024任意用
#s.connect(('www.sina.com.cn', 80))

# 新浪强制HTTPS协议访问 所以 80端口改443 socket 改 ssl
s = ssl.wrap_socket(socket.socket())
s.connect(('www.sina.com.cn', 443))

# 使用HTTP标准格式发送请求给服务器
s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')

# 接收数据
buffer = []
while True:
    # 每次最多接收1k字节
    d = s.recv(1024)
    if d:
        buffer.append(d)
    else:
        break # 当接收数据为空时,表示接收完毕,退出循环
html = b''.join(buffer) # 拼接为完整byte

# 接收完数据后close连接
s.close()
# 接收到的数据包含HTTP头和网页本身,使用‘\r\n\r\n’分割一下
header, content = html.split(b'\r\n\r\n', 1)
# 打印头部
print(header.decode('UTF-8'))
# 保存网页内容
with open('sina.html', 'wb') as f:
    f.write(content)

NOTE1: 关于ssl.wrap_socket 用法参照从编程角度看SSL协议(1)ssl库–SSLSocket类_薄荷_红茶的博客-CSDN博客

服务器端host

服务器进程首先要绑定一个端口并监听来自其他客户端的连接。如果某个客户端连接过来了,服务器就与该客户端建立Socket连接,随后的通信就靠这个Socket连接了。

所以,服务器会打开固定端口(比如80)监听,每来一个客户端连接,就创建该Socket连接。由于服务器会有大量来自客户端的连接,所以,服务器要能够区分一个Socket连接是和哪个客户端绑定的。一个Socket依赖4项:服务器地址、服务器端口、客户端地址、客户端端口来唯一确定一个Socket。

但是服务器还需要同时响应多个客户端的请求,所以,每个连接都需要一个新的进程或者新的线程来处理.

我们来编写一个简单的服务器程序,它接收客户端连接,把客户端发过来的字符串加上Hello再发回去。

服务器可能有多块网卡,可以绑定到某一块网卡的IP地址上,也可以用0.0.0.0绑定到所有的网络地址,还可以用127.0.0.1绑定到本机地址(客户端必须同时在本机运行才能连接,外部的计算机无法连接进来。)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# TCP Server side

import socket
import threading
import time

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定server IP和指定端口
s.bind(('127.0.0.1', 7777))
# 调用listen()方法开始监听端口,传入的参数指定等待连接的最大数量
s.listen(5)
print('Waiting for connection...')

# 定义tcplink: 连接建立后,服务器首先发一条欢迎消息,然后等待客户端数据,并加上Hello再发送给客户端。如果客户端发送了exit字符串,就直接关闭连接。
def tcplink(sock, addr):
    print("Accpet new connection from %s:%s" % addr) # addr传入的IP:port格式,所以是两个%s
    sock.send(b'Welcome')
    while True:
        data = sock.recv(1024)
        time.sleep(1)
        if not data or data.decode('utf-8') == 'exit':
            break
        sock.send(('hello, %s' % data.decode('utf-8')).encode('utf-8'))
    sock.close() # 记得关闭socket
    print('Disconnect the connection from %s:%s' % addr)

# 服务器程序通过一个永久循环来接受来自客户端的连接,accept()会等待并返回一个客户端的连接:
while True:
    sock, addr = s.accept()
    t = threading.Thread(target=tcplink, args=(sock, addr))
    t.setDaemon(True)
    t.start()
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

# TCP client side

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 7777))
# 接收欢迎语
print(s.recv(1024).decode('utf-8'))

for each in [b'Mike', b'James', b'Brown']:
    # 发送数据
    s.send(each)
    # 接收返回值
    data = s.recv(1024)
    print(data.decode('utf-8'))

s.send(b'exit')
s.close()

先运行server,再运行client, 注意sever是无限循环需要手动干掉的
sever-client

UDP编程

UDP则是面向无连接的协议,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。

使用UDP的通信双方也分为客户端和服务器。服务器首先需要绑定端口,创建Socket时,SOCK_DGRAM指定了这个Socket的类型是UDP。绑定端口和TCP一样,但是不需要调用listen()方法,而是直接接收来自任何客户端的数据

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# UDP server

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('127.0.0.1', 7777))
print('Binding UDP server on port 7777, wating for connection...')

while True:
    # recvfrom()方法返回数据和客户端的地址:端口
    data, addr= s.recvfrom(1024)
    print('Received data from %s:%s' % addr)
    # 直接调用sendto()就可以把数据用UDP发给客户端。发送的是一个tuple,分别是数据和地址
    s.sendto(b'Hello, %s' % data, addr)

客户端使用UDP时,首先仍然创建基于UDP的Socket,然后,不需要调用connect(),直接通过sendto()给服务器发数据:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# UDP Client

import socket

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

for each in [b'Mike', b'John', b'Jackie']:
    # 发送数据 tuple(data, (ip, port))
    s.sendto(each, ('127.0.0.1', 7777))
    # 接收仍然用recv
    print(s.recv(1024).decode('utf-8'))
s.close()

UDP的使用与TCP类似,但是不需要建立连接。此外,服务器绑定UDP端口和TCP端口互不冲突,即大家可以用相同的端口号。

在这里插入图片描述

eg: UDP 实现一个在线聊天室

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# UDP server

import socket
import threading
import os

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('127.0.0.1', 7777))
print('Host is bind at 127.0.0.1:7777')

clients = set() # 用于保存连接的客户端信息,用集合的方式去重

def chat(data, addr):
    for each in clients:
        if each != addr: # 给所有其他成员发送消息
            s.sendto(('### %s:%s>>>' % addr).encode('utf-8')+data, each)

while True: # 无限循环接收和转发msg
    try:
        data, addr= s.recvfrom(1024)
        clients.add(addr)
        content = data.decode('utf-8')
        if content == "Register": # Register功能
            print('%s:%s is regiestered.' % addr)
            s.sendto(('welcome %s:%s to the Fake QQ(127.0.0.1:7777)...' % addr).encode('utf-8') , addr)

            s.sendto(b'### Type "Friends" to get friends list, "Exit" to end this session', addr)
        elif content == "Friends": # 朋友列表功能
            for each in clients:
                if each != addr:
                    s.sendto(('Friends list: %s:%s' % each).encode('utf-8'), addr)
        elif content == "Exit": # 退出时从注册列表中移除client
            print('%s:%s is disconnected'% addr)
            clients.remove(addr)
        else:
            chat(data, addr)
            
    except KeyboardInterrupt:
        print('Server is closed!')
        os._exit(0)
    except Exception as e:
        print(str(e))

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# UDP Client

import socket
import threading
import os
from datetime import datetime

s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
host = ('127.0.0.1', 7777)
s1.sendto(b'Register', host) # 自动注册到sever

def my_input(): # 构建一个generator,每次得到用户输入
    while True:
        try:
            temp = input('### ')
            yield temp
        except Exception as e:
            print(str(e))

m = my_input()
def send_msg(client):
    while True:
        msg = next(m)
        client.sendto(msg.encode('utf-8'), host)
        if msg == 'Exit':
            os._exit(0)
        

def recv_msg(client): # 接收来自server的消息并添加timestamp
    while True:
        try:
            content = client.recv(1024).decode('utf-8')
            dt = datetime.now().strftime('%Y-%m-%d %H:%M')
            print(dt+'\n'+content+'\n')
        except Exception as e:
            print(str(e))

# 发送消息和接收消息各一个进程
t1 = threading.Thread(target=send_msg, args=(s1,))
t2 = threading.Thread(target=recv_msg, args=(s1,))
t1.start()
t2.start()


喜欢就三连哦

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值