day7 socket网络编程基础

Socket

Socket是什么?

    下面来看一下网络的传输过程:

   

    上面图片显示了网络传输的基本过程,传输是通过底层实现的,有很多底层,我们写传输过程的时候,要知道所有的过程那就太复杂了,socket为我们封装了底层的传输流程,让我们直接可以在socket上直接实现数据交换。

    socket本质:对底层网络协议的封装。

    socket实现数据的发送和接收,通过什么建立连接呢?下面看一幅简单的图片:

      

    在计算机上,我们运行了很多线程,我们如何实现数据的定向交换呢?如何实现客户端和服务器的连接呢?连接我们可以通过IP地址进行连接,连接上之后发送给那个程序呢?这时候我们就要通过port(端口号)进行指明,因为实现连接的过程是通过IP+端口号(port)进行连接。

    客户端

 

import socket
while True:
    s = socket.socket()
    '''生成一个socket连接'''
    s.connect(("localhost",6970))   #建立连接,连接本地,端口号是6969端口

    message = input("请输入您要发送的信息:").encode('utf-8')
    if message == "quit":
        break
    s.sendall(message)   #python3上只能传输二进制字节
    data = s.recv(1024)      #接收服务器端传来的内容
    print(data)

s.close()   #关闭连接

 

    上面代码就是客户端,通过客户端发送数据到服务器,实现交互,客户端加上了一个while循环,能够实现多次交互,我们知道,正常情况下,交互一次就退出了,通过While循环,让客户端不停的产生新的连接,就能不断与客户端进行数据交换。

    下面是客户算发送的数据,只能以字符的形式进行发送,所以发送的是字符,汉字看不到,进行转换了。而且不知道为什么,输入空之后,客户端没有响应,静止不动了:

请输入您要发送的信息:dasfda
b'dasfda'
请输入您要发送的信息:不能发送汉字吗
b'\xe4\xb8\x8d\xe8\x83\xbd\xe5\x8f\x91\xe9\x80\x81\xe6\xb1\x89\xe5\xad\x97\xe5\x90\x97'
请输入您要发送的信息:放大法是否对
b'\xe6\x94\xbe\xe5\xa4\xa7\xe6\xb3\x95\xe6\x98\xaf\xe5\x90\xa6\xe5\xaf\xb9'
请输入您要发送的信息:dfafdas
b'dfafdas'
请输入您要发送的信息:dfasdfa
b'dfasdfa'
请输入您要发送的信息:dfasfd
b'dfasfd'
请输入您要发送的信息:afdasdfas
fb'afdasdfas'
请输入您要发送的信息:dasfda
b'fdasfda'
请输入您要发送的信息:dfa
fdab'dfa'
请输入您要发送的信息:
b'fda'
请输入您要发送的信息:afasfda
b'afasfda'
请输入您要发送的信息:afdasfd
b'afdasfd'
请输入您要发送的信息:afdasdfa
b'afdasdfa'
请输入您要发送的信息:

服务器

 

import socket

'''生成socket实例'''
s = socket.socket()
s.bind(("localhost",6970))    #绑定本地IP和6969端口号
s.listen(10)     #监听客户端发送的信息,一旦有客户端发送过来连接,就接收,现在是等待状态,防止堵塞
print("连接建立完毕,正在等待数据.......")
while True:
    conn,addr = s.accept()    #接收数据,accept()会接收两个数据,一个是连接conn,一个是地址addr(IP和端口号)
    print("Addr:",addr)
    data = conn.recv(1024)   #通过连接接收数据
    print(data)
    conn.send(data)      #发送数据,把接收到的信息发送

conn.close()
s.close()

 

     上面是服务器的代码,我们也使用了一个循环,让服务器一直挂着,等待客户端发送数据,不停的接收:

    下面是服务器接收的数据:

连接建立完毕,正在等待数据.......
Addr: ('127.0.0.1', 51924)
b'dasfda'
Addr: ('127.0.0.1', 51926)
b'\xe4\xb8\x8d\xe8\x83\xbd\xe5\x8f\x91\xe9\x80\x81\xe6\xb1\x89\xe5\xad\x97\xe5\x90\x97'
Addr: ('127.0.0.1', 51928)
b'\xe6\x94\xbe\xe5\xa4\xa7\xe6\xb3\x95\xe6\x98\xaf\xe5\x90\xa6\xe5\xaf\xb9'
Addr: ('127.0.0.1', 51930)
b'dfafdas'
Addr: ('127.0.0.1', 51932)
b'dfasdfa'
Addr: ('127.0.0.1', 51934)
b'dfasfd'
Addr: ('127.0.0.1', 51936)
b'afdasdfas'
Addr: ('127.0.0.1', 51938)
b'fdasfda'
Addr: ('127.0.0.1', 51940)
b'dfa'
Addr: ('127.0.0.1', 51942)
b'fda'
Addr: ('127.0.0.1', 51944)
b'afasfda'
Addr: ('127.0.0.1', 51946)
b'afdasfd'
Addr: ('127.0.0.1', 51948)
b'afdasdfa'
Addr: ('127.0.0.1', 51950)

    可以看出,数据进行了多次的交互,并且接收了数据,是以字符编码形式进行接收的。

客户端
import socket

client = socket.socket()    #声明socket类型,同时生成socket连接对象
client.connect(("localhost",6969))

client.send("我要下载A片".encode("utf-8"))     #在python3中只能发送字节
data = client.recv(1024)    #默认接收数据的大小,定义1024个字节
print("recv:",data.decode())

client.close()    #关闭连接


服务器
#服务器端
import socket

server = socket.socket()
server.bind(("localhost",6969))   #绑定要监听端口
server.listen()    #监听

print("我要开始等电话了")
conn,addr = server.accept()    #等待接收,conn对方的连接,addr对方的地址,接收会接收到一个IP编号和地址
#conn就是客户端连过来而在服务器端为其生成的一个连接实例
print(conn,addr)

print("电话来了")
data = conn.recv(1024)    #接收的数据大小,不能直接调用server接收,打电话新的进来,可以切换
print("recv:",data)
conn.send(data.upper())

server.close()

    下面来看一个循环的代码:

#服务器端
import socket

server = socket.socket()
server.bind(("localhost",6969))   #绑定要监听端口
server.listen()    #监听

print("我要开始等电话了")
while True:
    conn,addr = server.accept()    #等待接收,conn对方的连接,addr对方的地址,接收会接收到一个IP编号和地址
    #conn就是客户端连过来而在服务器端为其生成的一个连接实例
    print(conn,addr)

    print("电话来了")
    data = conn.recv(1024)    #接收的数据大小,不能直接调用server接收,打电话新的进来,可以切换
    print("recv:",data)
    conn.send(data.upper())

server.close()

    首先启动服务器端,等待客户端发送数据:

    客户端:

import socket

client = socket.socket()    #声明socket类型,同时生成socket连接对象
client.connect(("localhost",6969))

while True:
    msg = input(">>:").strip()      #输入空会卡主,我们知道,QQ也是不允许用户输入空的,会提示输入为空
    client.send(msg.encode("utf-8"))     #在python3中只能发送字节
    data = client.recv(1024)    #默认接收数据的大小,定义1024个字节
    print("recv:",data.decode())

client.close()    #关闭连接

    客户端输入:

    >>:我
  recv: 我
  >>:要

    上面可以看出,客户端输入第一次正常,第二次卡住了,只能进行一次通讯,说明客户端的实现一次通讯之后终止了。

    服务端接收:

    我要开始等电话了
  <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 6969), raddr=     ('127.0.0.1', 58460)> ('127.0.0.1', 58460)
  电话来了
  recv: b'\xe6\x88\x91'

    可以看出,服务器端第一次接收数据之后,也卡住了,服务器端卡主是由于没有新的连接进来,连接并没有终端,而只是客户端自己断开,找不到新的连接挂在哪里。

    下面来修改服务器代码:

 

#服务器端
import socket

server = socket.socket()
server.bind(("localhost",7071))   #绑定要监听端口
server.listen()    #监听

print("我要开始等电话了")
conn, addr = server.accept()  # 等待接收,conn对方的连接,addr对方的地址,接收会接收到一个IP编号和地址
# conn就是客户端连过来而在服务器端为其生成的一个连接实例
print(conn, addr)
print("电话来了")

while True:
    data = conn.recv(1024)    #接收的数据大小,不能直接调用server接收,打电话新的进来,可以切换
    # if not data:
    #     break
    print("recv:",data)
    conn.send(data.upper())

server.close()

 

    现在重新启动服务器,然后启动客户端,在客户端输入交互内容,如下:

客户端输入呢绒:
>>:wo 
recv: WO
>>:yao
recv: YAO
>>:zixue
recv: ZIXUE


服务器端接收内容:
我要开始等电话了
<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=('127.0.0.1', 51508)> ('127.0.0.1', 51508)
电话来了
recv: b'wo'
recv: b'yao'
recv: b'zixue

    现在显示一切都是那么完美,现在来断开客户端,看会怎样,如下:

    ecv: b''
  recv: b''
  recv: b''
  recv: b''
  recv: b''

    ......

    客户端断开之后,服务器端并没有断开,由于服务器端在循环接收消息,由于没有客户端发送消息,因为接收到的消息一直是空的,并且服务器端server = socket.accept()并没有执行接收过程,因为一直在运行循环。

    下面我们来验证服务器端的情况,看客户端断了之后,服务器端是如何执行的,为此我们需要简单修改一下服务器端代码,如下:

#服务器端
import socket

server = socket.socket()
server.bind(("localhost",7071))   #绑定要监听端口
server.listen()    #监听

print("我要开始等电话了")
conn, addr = server.accept()  # 等待接收,conn对方的连接,addr对方的地址,接收会接收到一个IP编号和地址
# conn就是客户端连过来而在服务器端为其生成的一个连接实例
print(conn, addr)
print("电话来了")

count = 0
while True:
    data = conn.recv(1024)    #接收的数据大小,不能直接调用server接收,打电话新的进来,可以切换
    # if not data:
    #     break
    print("recv:",data)
    conn.send(data.upper())
    count += 1
    if count >= 10:
        break

server.close()

  运行结果如下:

  我要开始等电话了
  <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=     ('127.0.0.1', 51532)> ('127.0.0.1', 51532)
  电话来了
  recv: b'wo'
  recv: b'yao'
  recv: b'\xe8\x87\xaa\xe5\xad\xa6'
  recv: b'\xe5\xbe\x80'
  recv: b''
  recv: b''
  recv: b''
  recv: b''
  recv: b''
  recv: b''

    从上面代码可以看出,当客户端端口之后,服务器端并没有断开,由于连接断开了,服务器一直在循环接收数据。(在Windows上,如果客户端断开,服务器端也会断开)

    下面来修改一下代码,让客户端断开,服务器端也断开。

    如下:

#服务器端
import socket

server = socket.socket()
server.bind(("localhost",7071))   #绑定要监听端口
server.listen()    #监听

print("我要开始等电话了")
conn, addr = server.accept()  # 等待接收,conn对方的连接,addr对方的地址,接收会接收到一个IP编号和地址
# conn就是客户端连过来而在服务器端为其生成的一个连接实例
print(conn, addr)
print("电话来了")

while True:
    data = conn.recv(1024)    #接收的数据大小,不能直接调用server接收,打电话新的进来,可以切换
    if not data:       #加入一个判断,如果接收为空,则断开服务器
        break
    print("recv:",data)
    conn.send(data.upper())

server.close()

    上面代码中,对服务器端代码进行了简单完善,让客户端断开的时候,服务器端也断开,如何实现呢?只需要判断客户端接收到空的时候终止既可以。然后启动服务器,启动客户端进行数据传输如下:

客户端输入:
/usr/bin/python3.5 /home/zhuzhu/第七天/socket_client.py
>>:woyao
recv: WOYAO
>>:自学
recv: 自学
>>:Traceback (most recent call last):
  File "/home/zhuzhu/第七天/socket_client.py", line 7, in <module>
    msg = input(">>:").strip()      #输入空会卡主,我们知道,QQ也是不允许用户输入空的,会提示输入为空
KeyboardInterrupt


服务器端接收:
我要开始等电话了
<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=('127.0.0.1', 51548)> ('127.0.0.1', 51548)
电话来了
recv: b'woyao'
recv: b'\xe8\x87\xaa\xe5\xad\xa6'

    从上面结果可以看出,客户端断开时候,服务器端也断开了,那么如何实现客户端断开,服务器端接收新的客户端的消息,只断开当前连接,重新生成一个新的连接。如下:

服务器端

 

 

#服务器端
import socket

server = socket.socket()
server.bind(("localhost",7071))   #绑定要监听端口
server.listen()    #监听
while True:
    print("我要开始等电话了")
    conn, addr = server.accept()  # 等待接收,conn对方的连接,addr对方的地址,接收会接收到一个IP编号和地址
    # conn就是客户端连过来而在服务器端为其生成的一个连接实例
    print(conn, addr)
    print("电话来了")

    while True:
        data = conn.recv(1024)    #接收的数据大小,不能直接调用server接收,打电话新的进来,可以切换
        if not data:
            break
        print("recv:",data)
        conn.send(data.upper())

server.close()

 

    上面代码进行了两层嵌套,内层嵌套是如果接收消息为空,则断开本次连接,外层循环是内存连接断开之后,重新生成一个新的连接,因此我们首先启动服务器端,现在同时打开三个客户端如下:

    启动服务器:

    我要开始等电话了
  <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=     ('127.0.0.1', 51568)> ('127.0.0.1', 51568)
  电话来了

    客户端1:

    /usr/bin/python3.5 /home/zhuzhu/第七天/socket_client.py
  >>:

    客户端2:

    /usr/bin/python3.5 /home/zhuzhu/第七天/socket_client.py
  >>:

 

    客户端3:

    /usr/bin/python3.5 /home/zhuzhu/第七天/socket_client.py
  >>:

    首先在客户端1发送消息,如下:

    >>:11
  recv: 11

    服务器:

    /usr/bin/python3.5 /home/zhuzhu/第七天/socket_server.py
  我要开始等电话了
  <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=       ('127.0.0.1', 51568)> ('127.0.0.1', 51568)
  电话来了
  recv: b'11'

    可以看到,服务器接收到了客户端1发送的消息,在客户端2和客户端3发送消息,如下:

    客户端2:

    >>:shibushi

    客户端3:
    /usr/bin/python3.5 /home/zhuzhu/第七天/socket_client.py
  >>:接收不到吗?  
    服务器端:

    /usr/bin/python3.5 /home/zhuzhu/第七天/socket_server.py
  我要开始等电话了
  <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=     ('127.0.0.1', 51568)> ('127.0.0.1', 51568)
  电话来了
  recv: b'11'

    可以看到,服务器并没有接收到客户端2和客户端3的信息,现在终端客户端1,如下:

    客户端1:

    >>:11
  >>:Traceback (most recent call last):
   File "/home/zhuzhu/第七天/socket_client.py", line 7, in <module>
    msg = input(">>:").strip()      #输入空会卡主,我们知道,QQ也是不允许用户输入空的,会提示输入为空
  KeyboardInterrupt

    服务器:

    我要开始等电话了
  <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=     ('127.0.0.1', 51568)> ('127.0.0.1', 51568)
  电话来了
  recv: b'11'
  我要开始等电话了
  <socket.socket fd=5, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1',     7071), raddr=     ('127.0.0.1', 51570)> ('127.0.0.1', 51570)
  电话来了
  recv: b'shibushi'

     从上面可以看出,客户端1断开后,服务器与客户端1的连接也断开了,生成了一个新的连接,连接客户端2,并接收客户端2发送过来的消息。

    因此我们得出结论,上面代码实现了客户端断开后,服务器端重新挂起,等待新的连接的任务。

    当所有客户端中断后,如下:

 

/usr/bin/python3.5 /home/zhuzhu/第七天/socket_server.py
我要开始等电话了
<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=('127.0.0.1', 51568)> ('127.0.0.1', 51568)
电话来了
recv: b'11'
我要开始等电话了
<socket.socket fd=5, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=('127.0.0.1', 51570)> ('127.0.0.1', 51570)
电话来了
recv: b'shibushi'
我要开始等电话了
<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=('127.0.0.1', 51572)> ('127.0.0.1', 51572)
电话来了
recv: b'\xe6\x8e\xa5\xe6\x94\xb6\xe4\xb8\x8d\xe5\x88\xb0\xe5\x90\x97\xef\xbc\x9f'
recv: b'shibush'
recv: b'duankaiyixai'
recv: b'exit'
我要开始等电话了

 

    可以看出,当客户端所有连接断开后,服务器端还在等待消息。等待新的连接介入。

    上面客户端代码是有缺陷的,因为send()是不能发送空的,我们发送空就会让客户端卡主,如下:

    客户端输入:

    >>:eoyoa
  recv: EOYOA
  >>:dfasfd
  recv: DFASFD
  >>:                        #发送空,卡住了

    从上面客户端执行代码可以看出,确实当前卡主了,现在来完善一下客户端,让当用户输入为空的时候,跳过本次过程,告诉用户不能发送空,重新输入:

import socket

client = socket.socket()    #声明socket类型,同时生成socket连接对象
client.connect(("localhost",7071))

while True:
    msg = input(">>:").strip()      #输入空会卡主,我们知道,QQ也是不允许用户输入空的,会提示输入为空
    if not msg:
        print("对不起,发送消息不能为空!")
        continue
    client.send(msg.encode("utf-8"))     #在python3中只能发送字节,send()不能发送空
    data = client.recv(1024)    #默认接收数据的大小,定义1024个字节
    print("recv:",data.decode())

client.close()    #关闭连接

    上面代码中,客户端加入了一个判断,当用户输入发送消息为空时,告诉用户不能为空,并跳过本次循环(我们知道,QQ聊天也是不能发送空消息的),运行如下:

    客户端输入:

    /usr/bin/python3.5 /home/zhuzhu/第七天/socket_client.py
  >>:dasfd
  arecv: DASFD
  >>:dfasfd
  recv: ADFASFD
  >>:
  对不起,发送消息不能为空!
  >>:发顺丰达
  recv: 发顺丰达                         #不小心打了一个广告
  >>:
  对不起,发送消息不能为空!
  >>:

    可以看出,上述代码实现了不能发送为空的功能,如果想要空的时候退出,可以把continue改成break,当然这是非主流做法了。任何聊天工具不会让用户输入空就终止聊天的。

posted @ 2017-08-03 07:12  (野生程序员)  阅读(454)  评论(0编辑  收藏  举报