网络编程

基于tcp的socket基本写法

服务端

import socket

server = socket.socket()

server.bind(('127.0.0.1',8080))

server.listen()

coon,addr = server.accept()

ret = coon.recv(1024)
print(ret)

coon.send(b'hi')

coon.close()

server.close()

客户端

import socket

client = socket.socket()

client.connect(('127.0.0.1',8080))

client.send(b'hello')

res = client.recv(1024)

print(res)

client.close()



服务端
import socket

server = socket.socket()
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #避免服务器重启的时候报address already in use

server.bind(('127.0.0.1',8080))
server.listen()

coon,addr = server.accept()

print(addr)

ret = coon.recv(1024)
print(ret)
coon.send(b'hi')


ret = coon.recv(1024)
print(ret.decode('utf-8'))
coon.send(bytes('雪菜肉丝面加个蛋',encoding='utf-8'))

coon.close()

server.close()

客户端

import socket
client = socket.socket()
client.connect(('127.0.0.1',8080))

client.send(b'hello')
res = client.recv(1024)
print(res)

client.send(bytes('早上吃什么?'.encode('utf-8')))
res = client.recv(1024)
print(res.decode('utf-8'))

client.close()

服务端
import socket

server = socket.socket()
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #避免服务器重启的时候报address already in use
server.bind(('127.0.0.1',8080))
server.listen()

coon,addr = server.accept()
print(addr)

while True:
ret = coon.recv(1024).decode('utf-8')
if ret == 'bye':
break
print(ret)
info = input('>>: ').strip()
coon.send(bytes(info,encoding='utf-8'))

coon.close()

server.close()

客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1',8080))

while True:
msg = input(':>> ').strip()
client.send(bytes(msg,encoding='utf-8'))
res = client.recv(1024).decode('utf-8')
print(res)
if res == 'bye':
client.send(b'bye')
break


client.close()


服务端
import socket

server = socket.socket()
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #避免服务器重启的时候报address already in use
server.bind(('127.0.0.1',8080))
server.listen()

while True:
coon,addr = server.accept()
print(addr)

while True:
ret = coon.recv(1024).decode('utf-8')
if ret == 'bye':
break
print(ret)
info = input('>>: ').strip()
coon.send(bytes(info,encoding='utf-8'))

coon.close()

server.close()

客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1',8080))

while True:
msg = input(':>> ').strip()
client.send(bytes(msg,encoding='utf-8'))
res = client.recv(1024).decode('utf-8')
print(res)
if res == 'bye':
client.send(b'bye')
break


client.close()


基于udp的socket基本写法

服务端
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)

sk.bind(('127.0.0.1',8090))

msg,addr = sk.recvfrom(1024)

print(msg.decode('utf-8'))

sk.sendto(b'bye',addr)

sk.close()

# 在UDP的server中不需要监听也不需要建立连接
# 在启动服务之后只能被动的等待客户端发消息过来
# 客户端发消息的同时还会自动带地址过来
# 消息回复的时候 不仅需要发送消息 还需要把自己的地址发送过去

服务端
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)

ip_port = ('127.0.0.1',8090)

sk.sendto(b'hello',ip_port)

ret,addr = sk.recvfrom(1024)

print(ret.decode('utf-8'))

sk.close()


udp版本的qq
服务端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)

sk.bind(('127.0.0.1',8080))

while True:
msg,addr = sk.recvfrom(1024)
print(msg.decode('utf-8'))
info = input('杨颖>>: ')
info = ('33[35m来自杨静的消息:%s33[0m'%info).encode('utf-8')
if not info:continue
if info == 'bye':break
sk.sendto(info,addr)

  客户端

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)

ip_port = ('127.0.0.1',8080)

while True:
info = input('张仁国:>>: ')
info = ('33[31m来自张仁国的消息: %s33[0m'%info).encode('utf-8')

if not info:continue
if info == 'bye':break
sk.sendto(info,ip_port)
msg,addr = sk.recvfrom(1024)
print(msg.decode('utf-8'))

sk.close()


粘包问题解决方案基于UDP协议的套接字的粘包问题及解决
客户端
import socket

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


# 不用发连接,就只用send,send里面只能写数据,发给谁?
# 所以用sendto,第一个写要发的数据,第二个写到底要发给谁(服务端的ip跟端口号)
client.sendto('hello'.encode('utf-8'),('127.0.0.1',8090))

# 客户端收消息
data,server_addr = client.recvfrom(1024)
print(data,server_addr)

client.close()

# 客户端怎么循环跟服务端差不多
while True:
# 但是如果写成client.sendto('hello'.encode('utf-8'),('127.0.0.1',8090))这样就是死循环了
# 所以应该改成让用户输入
msg = input('>>: ').strip()
client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8090))

data,server_addr = client.recvfrom(1024)
print(data,server_addr)

client.close()
# 如果不输入,就是空,也不会卡住,为什么?
# 原因是输了空,就是个空字符串,发送个空,服务端在那里收空,udp叫数据报协议,每个数据都自带个报头,
# 空不是空数据,其实是带着报头出去了,服务端回也回了个空,空的大写还是个空



服务端

# 基于套接字,先导入socket
import socket
# 拿到套接字对象,传参数
server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# SOCK_DGRAM 数据报协议指的就是UDP
# 什么是数据报?怎么能称之为一个报文? ---->有报头还有数据部分
# 基于tcp,他是数据流意味着他只发真实数据,会有粘包问题。为了解决粘包问题,在每一段数据之前都加了报头
# 于是解决了粘包的问题,解决的关键点就在于精髓就在于接收端会根据报头把完整的数据一次性给收干净了

# 数据报协议说明每次发都是自带一个报文,接收端呢每次都是完整的给接受完
# UDP协议没有粘包问题

# 拿到服务端绑定ip跟端口
server.bind(('127.0.0.1',8090))

# 绑定完ip跟端口需要监听,listen是监听连接请求,udp没有连接所以不需要
# 没有监听,accept是建立连接,所以也不需要

# 收消息 1024也是最大接收1024个bytes
# res = server.recvfrom(1024)
# print(res)



# 整体是先运行服务端,等着收消息,然后启动客户端发消息
# (b'hello', ('127.0.0.1', 53386))

# 收到的是个元祖 b'hello'是数据 ('127.0.0.1', 53386)是个小元祖是客户端的ip跟端口号(回消息的时候有用)

# 所以接收消息可以写成:
data,client_addr = server.recvfrom(1024)
print(data,client_addr)


# 在tcp协议中必须是服务端先启动客户端然后才能发起连接,而在udp协议的特点:不建连接,先启动客户端也没关系


# 如果要建立完整的通信,不仅要收而且要回消息
server.sendto(data.upper(),client_addr)
# 这个对应客户端在收

server.close()


# 但这样写就不叫一个套接字通信循环,通信循环怎么写?代码如下
# 循环的收发消息
while True:
data,client_addr = server.recvfrom(1024)
print(data,client_addr)

server.sendto(data.upper(),client_addr)
server.close

-----------------------------------------------------------------
基于UDP协议的套接字通讯循环的原理分析

服务端
# 在写tcp协议的时候,发现服务端启动了,但当同时来多个客户端的时候,每个客户端都发一条消息,不能每个客户端都得到立即的回复
# 会感觉到明显的等,也就是说基于TCP协议的套接字让客户感觉不到并发的这么一个效果
# 怎么叫并发?
# 并发简单的说就是给人的感觉看起来像是同时处理的

# 并发编程之后会回来再总结并发!!!


# tcp协议不行,udp协议行不行?写代码验证一下

import socket

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

server.bind(('127.0.0.1',9099))


while True:
data,client_addr = server.recvfrom(1024)
print('得到的信息:%s 得到的连接 %s'%(data,client_addr))
#客户端一输入消息发送,服务端会立马收到,立马给客户端回过去,这个速度太快了,客户端二,客户端三都是的,服务端会立马回到等收消息的状态
# 如果这是三个客户同时敲下回车键,给这三个人的感觉也是同时收到,因为速度太快了。像并发但这个并发是有局限性的,如果1万个人那地一万个会
# 明显感觉到等待,或者回的数据量比较大的时候,所以此时准备并不是基于udp的实现的并发

# 还是要研究多进程多线程!!!

server.sendto(data.upper(),client_addr)


客户端
import socket

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


while True:
msg = input('>>: ').strip()
client.sendto(msg.encode('utf-8'),('127.0.0.1',9099))

data,server_addr = client.recvfrom(1024)
print('得到的信息:%s 得到的连接 %s'%(data,server_addr))


UDP没有粘包问题

服务端
import socket

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

s.bind(('127.0.0.1',8099))

data,client_addr = s.recvfrom(1024)
print('第一次',data,client_addr)

data,client_addr = s.recvfrom(1024)
print('第二次',data,client_addr)

data,client_addr = s.recvfrom(1024)
print('第三次',data,client_addr)

s.sendto(data.upper(),client_addr)

s.close()


# 结果:
# 第一次 b'hello' ('127.0.0.1', 61493)
# 第二次 b'python' ('127.0.0.1', 61493)
# 第三次 b'world' ('127.0.0.1', 61493)


# 如果对于TCP协议来说你这么发,三个会合成一条送过去,在第一次收就已经收完了,但在udp协议中
# 服务器每一次都是一次完美的接收,根本不与其他人重复,没有粘包问题,并且如果只发送两条消息,
# 第一次 b'hello' ('127.0.0.1', 65207)
# 第二次 b'python' ('127.0.0.1', 65207)
# 就在原地等,所以说udp每次发送都可以说是完美的数据报,所以每次接收都对应唯一的recvfrom完完整整的接收完

# 第三次即使发空,其实数据也不是空,也有报头,也有对应的去收,所以udp没有粘包问题

# 基于udp协议传输文件,上传下载,如果文件一个T,一次发不完,要源源不断的发,一旦中间丢一次整个文件就废掉了
# 上网买东西的时候,比方说要付款,这种数据就不能用udp协议,一定要发送个数据,等到对方回应了才算完成


# udp发包对方不收,这个数据也就没了
# 每次发送对应的收的数据量都是大于当时发的数据量,所以能收完整了,那问题是小于当时发的,剩下的就丢了,下一次收就收下一个包

# 在Windows中会报错 Mac 或 Linux 不会报错每一次就收自己对应的,不会收上一次的残留,每次不要超过512,udp协议不能传大数据


客户端
import socket

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

c.sendto(b'hello',('127.0.0.1',8099))
c.sendto(b'python',('127.0.0.1',8099))
# c.sendto(b'world',('127.0.0.1',8099))

data,sever_addr = c.recvfrom(1024)


c.close()


-------------------------------------socketserver  TCP 协议的
服务端

# udp,跟tcp协议的套接字之后怎么能实现并发呢?提到并发离不开进程跟线程,怎么不用进程跟线程就实现并发的效果呢?
# Python有个非常好的优点,好多功能都是通过模块封装好了,自己不一定了解进程跟线程就能实现并发的效果。
# socketserver模块 实现并发的套接字

# 基于tcp协议的

# 先导入模块
import socketserver
# 是因为什么原因没实现并发,一方面建连接,一方面基于连接通讯

# 服务端先有个连接循环,不停地从半连接池取出连接请求建立连接,但是连接循环并不是连接循环,建完连接还要通讯

# 解决方案
# 一个人不停地连接,建立好之后,专门分配好另外一个人去跟建立好连接的这个人去通讯,作为连接的建立者可以不停地去建立新的连接
# 整体并发效果就有了,并发的是建立好连接



# if __name__ == '__main__':
# socketserver模块下面有个ThreadingTCPServer线程的意思括号括起来,里面传参数,传三个,服务端的address,
# 一个类里request是一些请求,还有绑定并绑定等于True,绑定并激活就是bind和listen。看一下博客看源码。
# 会拿到一个结果server
# server = socketserver.ThreadingTCPServer(('127.0.0.1',8090),类,绑定并激活)
# server下面会有一个serve_forever()--->永远对外提供服务,就是在做循环,处理的是循环建立连接
# server.serve_forever()#代表连接循环
# 每建立一个连接就会启动一个线程,专门与刚刚建立好的连接做通讯循环的事

# 怎么解决通讯循环,这里有个类,需要创建一个类,其实每建立一个连接,就会启动一个线程,然后该线程就相当于一个服务员,服务员专门做一件事就是功能
# 这个功能需要放到对象的方法里面去

# 从头开始分析:
# 绑定并激活,bind_and_activate = True,就是拿到套接字对象,绑定服务端ip跟端口号,进行listen操作
# listen完了之后开始while True,进行循环的accept,这一步被serve_forever()封装好了,循环的accept,每accept成功一次
# 直接去跟建立好的通讯,通讯不完不能建新连接。
# 现在是每建立成功一次连接,就立马启动一个线程就相当于招了一个服务员,然后调这个类实例化一次去得到一个对象
# 然后把一些刚刚建好的连接的信息,封装到这个对象里面去,然后出发这个对象里面的某些方法去执行
# 这些方法应该写上通讯循环功能,通讯循环需要参数,参数从哪里来?套接字是面向对象的思想,参数都丢到封装好的对象里面去了
# 并且这个对象是一个容器,里面存了一堆数据,捆绑了一堆方法,对象是数据与功能的结合体,用对象的这个思想,整合程度更高

# 创建一个类

# class 起一个接收用户给我通讯的一些信息,起什么名无所谓,但是这个类一定要继承socketserver模块里面有个叫 BaseRequestHandler
# 为什么?因为你不可能所有功能自己重写啊,大部分要继承别人的,继承这个类,注意了,类内部必须有一个handle方法
# class Myhandler(socketserver.BaseRequestHandler):
# # 为什么必须有handle,一个字都不能差。因为你用别人的功能,别人会自动到handle里面去调
# def handle(self):
# # 在这个方法内,我们应该通讯循环
#
# # 这个有个self,就是对象,对象必然捆绑了一堆数据以及方法,如何查看?
# # .的方式就能查看
# # self.client_address #客户端的ip跟端口
# # self.request 就是conn
# # 打印一下看属性的值是什么
# print(self.client_address)
# print(self.request)


class MyHandler(socketserver.BaseRequestHandler):
def handle(self):
while True:
# 该捕捉异常还是要捕捉异常
try:
data = self.request.recv(1024)
if not data:break
print(data)
self.request.send(data.upper())
except ConnectionRefusedError:
break




if __name__ == '__main__':
server = socketserver.ThreadingTCPServer(('127.0.0.1',8090),MyHandler,bind_and_activate=True)
server.serve_forever()








互斥锁

可以将执行任务的部分只涉及到修改共享数据的代码变成串行。
join :是将执行任务的所有代码整体串行。

抢票软件简单版代码:
有一个放数据的文件和模拟多个人的软件





 









原文地址:https://www.cnblogs.com/zhangrenguo/p/9918320.html