socket

Ip地址分类

Ip地址由网络地址和主机地址组成分为5类:

  • 1.A类IP地址
    一个A类IP地址由1字节的网络地址和3字节主机地址组成,
    网络地址的最高位必须是“0”(位运算), 地址范围从1.0.0.0 到126.0.0.0。
    可用的A类网络有126个,每个网络能容纳1千多万个主机。

  • 2.B类IP地址
    一个B类IP地址由2个字节的网络地址和2个字节的主机地址组成,
    网络地址的最高位必须是“10”,地址范围从128.0.0.0到191.255.255.255。
    可用的B类网络有16382个,每个网络能容纳6万多个主机 。

  • 3.C类IP地址
    一个C类IP地址由3字节的网络地址和1字节的主机地址组成,网络地址的
    最高位必须是“110”。范围从192.0.0.0到223.255.255.255。C类网络
    可达209万余个,每个网络能容纳254个主机。

  • 4.D类地址用于多点广播(Multicast)。
    D类IP地址第一个字节以“lll0”开始,它是一个专门保留的地址。
    它并不指向特定的网络,目前这一类地址被用在多点广播(Multicast)中。
    多点广播地址用来一次寻址一组计算机,它标识共享同一协议的一组计算机。

  • 5.E类IP地址
    以“llll0”开始,为将来使用保留。
    全零(“0.0.0.0”)地址对应于当前主机。全“1”的
    IP地址(“255.255.255.255”)是当前子网的广播地址。
    在IP地址3种主要类型里,各保留了3个区域作为私有地址,
    其地址范围如下:
    A类地址:10.0.0.0~10.255.255.255

私有网络

  • iPv4地址枯竭为何还能支撑?
    这就要说起NAT(Network Address Translation)网络地址转换技术
    当我们在家或者在公司上网时,你的电脑肯定有一个类似192.168.0.1的地址,
    这种地址属于私网地址,不属于公共互联网地址,每一个小的局域网都会使用
    一个网段的私网地址,在与外界联系时变成公网,这样几十台上百台电脑只需
    要一个公网地址。甚至还能私网套私网NAT套NAT一层一层,节约公网ip数量
    但是NAT也有很多限制:私网访问公网虽然方便,但是公网访问私网就很困难,
    很多服务受到限制,也影响了网络处理的效率。

具体百度iPv4和ipv6

  • 私有ip
    1
    2
    3
    10.0.0.0 – 10.255.255.255
    172.16.0.0 – 172.31.255.255
    192.168.0.0 – 192.168.255.255

Port端口

当你发送一段信息给别的电脑ip时,你这段信息主要是要访问别的电脑的哪个进程呢?如何确定是哪个进程呢?
当进程运行时,会告诉自身电脑给它的信息要从哪个端口传入,如:7788,7890等等

1
2
3
4
5
Dest ip:192.168.1.2
Src ip:192.168.1.3
Dest port:7788
Src port:4567
Content:你好
  • 端口号
    0到2^16(65535)

  • 知名端口号 不能随便用
    0到1023
    80端口分配给HTTP
    21端口分配给FTP

  • 大于1024随便用
    动态端口 Dynamic ports
    1024到65535

Socket简介

socket是网络通信的必须物,Socket是应用层
与TCP/IP协议族通信的中间软件抽象层,它是一组接口。
在设计模式中,Socket其实就是一个门面模式,它把复
杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,
一组简单的接口就是全部。

1
2
3
4
5
6
7
8
9
import socket
# ipv4协议
创建tcp连接
# s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# udp连接
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 可以使用套接字收发数据
#关闭套接字
s.close()
  • ubuntu和windows通讯
    1
    2
    3
    4
    5
    6
    7
    8
    vim缩进:shift V选择,v缩退 >缩进
    ctrl n补全
    如果要实现ubuntu和我们windows的通讯
    如ip:172.154.2.12和192.168.33.23的通讯
    改成桥接模式,sudo dhclient等待分配ip
    然后ip会自动变为和另一个ip同一个网段
    ping 192.168.33.53
    10.167.2.111

socket简单使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import socket
# ipv4协议
创建tcp连接
# s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# udp连接
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
# 键盘获取数据
data = input("请输入你要发送的数据: ")
if data == 'exit':
break
# 可以使用套接字收发数据
s.sendto(data.encode('utf-8'), ('10.167.2.90', 8080))
#关闭套接字
s.close()
  • 想要一个程序有一个固定的端口接收数据

    1
    2
    3
    4
    5
    6
    7
    8
    from socket import *
    s = socket(AF_INET, SOCK_DGRAM)
    local_addr = ('', 7788)
    s.bind(local_addr)
    recv_data = s.recvfrom(1024)
    print(recv_data)
    print(recv_data[0].decode('utf-8'))
    s.close()
  • 绑定发送数据端口
    不绑定端口会随机端口分配!

    1
    2
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.bind(('', 8080))

udp聊天器

  • 单工:收音机只能收不能发,只能单向
  • 半双工:对讲机,我在收的时候发不了,发的时候收不了,同一时刻单向
  • 全双工:手机,同一时刻既可以发,也可以收

socket套接字是全双工 但现在没有多进程线程或协程的情况下,是半双工

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import socket
def send_msg(s):
""" 发送消息"""
dest_ip = input('请输入对方的ip:')
dest_port = int(input("请输入对方的port:"))
send_data = input("请输入要发送的消息:")
s.sendto(send_data.encode('utf-8'), (dest_ip,dest_port ))

def recv_msg(s):
""" 接收数据"""
# 接收并显示
recv_data = s.recv_from(1024)
print("%s : %s" % (str(recv_data[1]), recv_data[0].decode('utf-8')

def main():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定ip 端口
s.bind(("", 7788))
while True:
# 发送
send_msg(s)
# 接收数据
recv_msg(s)
if __name __ == '__main__':
main()

TCP通信模型

  • udp通信模型
    在通信开始前,不需要建立相关连接,
    只需要发送数据即可,类似于生活中写信
    udp发的消息可能丢失,不安全,简单

  • tcp类似于打电话,稳定,面向连接

tcp模型简介

  • 1.通信双方必须先建立连接

    • 1.创建连接
    • 2.数据传送
    • 3.终止连接
  • 2.发送应答机制:
    当主机发送数据之后,接收方会告诉主机是否接收到数据,(没收到会显示超时上传)
    网购都是tcp,qq:tcp,抢票tcp。保证数据可靠

  • 3.tcp严格区分服务器和客户端

tcp客户端构建流程

  • 1.创建套接字
  • 2.目的信息
  • 3.链接服务器 多了这个
  • 4.显示用户输入数据
  • 5.接收对方发来数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import socket
    def main():
    # 1.创建tcp套接字
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 2.链接服务器
    server_ip = input('请输入服务器的ip:')
    server_port = int(input("请输入服务器的port:"))
    server_addr = (server_ip, server_port)
    tcp_socket.connect(server_addr)
    # 3.发送/接收数据
    send_data = input('请输入要发送的数据:')
    tcp_socket.send(send_data.encode('utf-8'))
    # 4.关闭套接字
    tcp_socket.close()

    if __name__ == '__main__':
    main()

tcp服务器创建流程

  • 1.套接字
  • 2.ip port
  • 3.listen使套接字变为可以被动链接
  • 4.accept等待客户端的链接
  • 5.recv/send接收发送的数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    import socket
    # tcp服务器端
    def main():
    # 1.买个手机(创建套接字)
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 2.拆入手机卡(绑定本地信息)
    tcp_server_socket.bind(("", 7890) )

    # 3.将手机设置为响铃(让默认套接字由主动变为被动)
    tcp_server_socket.listen(128)
    while True:
    # 4.等待电话到来(等待客户端链接 accept)
    print('================1================')
    client_socket, client_addr = tcp_server_socket.accept()
    print('================2================')
    print("一个新的客户端已经到来%s" % str(client_addr))
    while True:
    #接收客户端发送的数据
    try:
    recv_data = client_socket.recv(1024)
    except:
    break
    # 客户端调用close或者发出退出信息
    if str(recv_data.decode('utf-8')) == '退出' or not recv_data:
    break
    print("客户端发送过来的请求是%s" % str(recv_data.decode('utf-8')))
    # 回送一部分数据给客户端
    client_socket.send("shadiao========OK=========shadiao".encode('utf-8'))

    # 关闭套接字
    client_socket.close()
    print('服务完毕')
    tcp_server_socket.close()

    if __name__ == '__main__':
    main()

TCP文件下载器

服务器端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import socket
def send_file_2_client(client_addr, client_socket):
while True:
# 接收客户端需要的文件名
try:
file_name = client_socket.recv(1024)
except:
break
# 客户端调用close或者发出退出信息
if str(file_name.decode('utf-8')) == '退出' or not file_name:
break
print("客户端(%s)需要的文件是:%s" % (str(client_addr),
str(file_name.decode('utf-8'))))
# 打开文件读取数据
file_content = None
try:
f = open(file_name, 'rb')
file_content = f.read()
f.close()
except Exception as ret:
print("没有要下载的文件(%s)" % file_name)
if file_content:
# 发送对应的数据给客户端
client_socket.send(file_content)

def main():
# 1.买个手机(创建套接字)
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2.拆入手机卡(绑定本地信息)
tcp_server_socket.bind(("", 7890) )

# 3.将手机设置为响铃(让默认套接字由主动变为被动)
tcp_server_socket.listen(128)
while True:
# 4.等待电话到来(等待客户端链接 accept)
print('================1================')
client_socket, client_addr = tcp_server_socket.accept()
print('================2================')
print("一个新的客户端已经到来%s" % str(client_addr))
#调用发送文件函数为客户端服务
send_file_2_client(client_addr, client_socket)
# 关闭套接字
client_socket.close()
print('服务完毕')
tcp_server_socket.close()

if __name__ == '__main__':
main()

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from socket import *
import sys

def main():
tcp_client_socket = socket(AF_INET, SOCK_STREAM)
server_ip = input("请输入服务器ip:")
server_port = int(input("请输入服务器port:"))

tcp_client_socket.connect((server_ip, server_port))

file_name = input("请输入要下载的文件名:")

tcp_client_socket.send(file_name.encode("utf-8"))

recv_data = tcp_client_socket.recv(1024)
if recv_data:
with open("[接收]" + file_name, "wb") as f:
f.write(recv_data)

tcp_client_socket.close()

if __name__ == '__main__':
main()

简单TCP文件上传器

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import socket

client = socket.socket()
client.connect(('localhost', 9999))

while True:
cmd = input('file name <<:')
if len(cmd) == 0:
continue
client.send(cmd.encode('utf-8'))
server_size = client.recv(1024).decode()
file_size = 0
if server_size != '0':
f = open('2'+cmd , 'wb')
while file_size < int(server_size) :
print(file_size, server_size)
data = client.recv(1024)
if data:
file_size += len(data)
f.write(data)
else:
print('err')
continue
# print(data)
f.close()
print('file over')

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import errno
import selectors
import socket
import os, time


def accept(sock, mask):
conn, addr = sock.accept()
conn.setblocking(False)
sel.register(conn, selectors.EVENT_READ, read)

def read(conn, mask):
file_name = conn.recv(1024).decode()
if os.path.isfile(file_name):
file_size = os.stat(file_name).st_size
conn.send(str(file_size).encode('utf-8'))
print(file_size)
# print(conn.recv(1024))
with open(file_name, 'rb') as f:
for line in f:
if line != '':
print(line)
conn.send(line)
print('over')
else:
conn.send(b'0')
if __name__ == '__main__':
sel = selectors.DefaultSelector()
sock = socket.socket()
sock.bind(('localhost', 9999))
sock.listen()
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)
while True:
events = sel.select()
for key, mask in events:
callaback = key.data
callaback(key.fileobj, mask)

TCP注意点

  • 1.tcp服务器一般情况下都需要绑定:否则客户端找不到服务器
  • 2.客户端为什么不绑定?因为是主动连接服务器,所以只要确定
    服务器ip,port信息就行,本地客户端随机就行,还可以多开
  • 3.tcp通过lisent将套接字socket由主动变为被动,这是tcp服务
    器必须要做的
  • 4.当客户端需要链接服务器是,需要使用connect进行链接,而udp
    不需要,tcp只要有链接成功才能通信
  • 5.当tcp客户端链接服务器后,服务器会用一个新的套接字来标记客
    户端,单独为此客户服务
  • 6.listen后的套接字是被动套接字,专门用来接收客户端的链接请求,
    而accept返回的新的套接字是标记这个客户端的服务
  • 7.关闭listen后的套接字意味着被动套接字关闭了,会导致新的客户
    端不能够链接服务器,但是之前链接成功的可以正常通信
  • 8.关闭accept这个套接字意味着这个客户端服务完毕
  • 9.当客户端套接字close,服务器端会recv解阻塞,并且返回recv长
    度为0,因此服务器可以通过返回数据的长度来区别客户端是否已经下线

多线程

没有多任务就不能同时执行,单核cpu的时间片轮转
单核cpu只能同一时间执行一个任务,但只要够快,就像多线程一样

  • 并行:真的多任务

  • 并发:假的多任务
    任务数多于cpu的核数,就是并发

    1
    2
    threading
    threading.enumerate()查看线程

    两种方法调用线程

  • 1.直接调用函数名

    1
    2
    t1 = treading.Thread(target=sing)
    t1.start()
  • 2.创建类继承thread

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class MyThread(threading.Thread): 必须继承threading
    def run(self): 必须有run方法
    /…..
    t = MyThread()
    t.start()这个start自动调用run
    多线程共享全局变量
    !!全局变量不一定要加global
    全局变量改了地址指向,需要global
    若[1, 2]列表append,指向地址未变,不需要global
    [1, 2] + [100, 200] 就变了
    指向地址变化用global,仅仅修改了指向空间中的数据
    不用global

    t1 = treading.Thread(target=sing, args=(,))一定是一个元组
    target指定这个线程去哪个函数执行代码
    args指定调用函数的时候,传递什么数据过去

共享变量问题

大家都用同一个变量,资源竞争,比如两个线程同时写一个变量
如果出现资源竞争,就会出错

  • 线程在执行如g_num += 1时,会分为三步:
    • 1.获取g_num的值
    • 2.把获取的值+1
    • 3.把第二步的结果存储到g_num中
      当线程同时调用,g_num都是一起加,还没开始存储
  • 如何解决共享变量资源竞争的问题

    • 原子性:
      我做的时候你不做,要么做完,要么不做

    • 同步协调:
      就像走路,一左一右协调行走

    • 互斥锁X:
      当多个线程同时修改一个数据时,需要进行同步控制
      某个线程进行修改共享数据时,先将其锁定,此时资源锁定状态,
      其他线程不可修改,直到该线程释放资源,将资源的状态变为非锁定。

  • 锁方案一

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    mutex = threading.Lock()
    上锁
    mutex.acquire()
    释放锁
    mutex.release()
    import threading
    import time

    g_num = 0

    def test1(num):
    global g_num
    # 上锁,若之前没上锁,上锁成功,若之前上锁了,会堵塞在这儿,直到这个锁被解开
    mutex.acquire()
    for i in range(num):
    g_num += 1
    # 解锁
    mutex.release()
    print("-------------in test1 g_num=%d------" % g_num)

    def test2(num):
    global g_num
    mutex.acquire()
    for i in range(num):
    g_num += 1
    # 解锁
    mutex.release()
    print("-------------in test2 g_num=%d------" % g_num)

    # 创建一个互斥锁 默认未上锁
    mutex = threading.Lock()

    def main():
    t1 = threading.Thread(target=test1, args=(1000000,))
    t2 = threading.Thread(target=test2, args=(1000000,))
    t1.start()
    t2.start()
    time.sleep(2)
    print("-------------in main Thread g_num=%d------" % g_num)

    if __name__ == '__main__':
    main()
  • 锁方案二
    上锁的代码越少越好

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    import threading
    import time

    g_num = 0

    def test1(num):
    global g_num
    # 上锁,若之前没上锁,上锁成功,若之前上锁了,会堵塞在这儿,直到这个锁被解开
    mutex.acquire()
    for i in range(num):
    g_num += 1
    # 解锁
    mutex.release()
    print("-------------in test1 g_num=%d------" % g_num)

    def test2(num):
    global g_num
    mutex.acquire()
    for i in range(num):
    g_num += 1
    # 解锁
    mutex.release()
    print("-------------in test2 g_num=%d------" % g_num)

    # 创建一个互斥锁 默认未上锁
    mutex = threading.Lock()

    def main():
    t1 = threading.Thread(target=test1, args=(1000000,))
    t2 = threading.Thread(target=test2, args=(1000000,))
    t1.start()
    t2.start()
    time.sleep(1)
    print("-------------in main Thread g_num=%d------" % g_num)


    if __name__ == '__main__':
    main()

互斥锁死锁问题

如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。
如在线程A中acquireB 同时在B中acquireA就会出现死锁无法走动

  • 解决死锁:

    • 1.程序设计时尽量避免
    • 2.添加超时时间
  • 银行家算法:
    银行10单位,c1 9 c2 3 c3 8, 如何借款,先给c1 2, c2 2, c3 4
    c2还钱 c3 4, c3还钱 c1 7

分享到