python3 socket编程

一、ip地址

  作用:用来在网络中标记一台电脑;在本地局域网中是唯一的。

 

二、端口

  端口就好比一间房子的们,是出入这间房子的必经之路。

    端口号不是随意分配的,而是按照一定的规定进行分配。

    端口分类:

    知名端口(Well Known Ports):

      也就是是众所周知的端口号,范围是从0到1023;

      一般情况下,如果一个程序需要使用知名端口需要有root权限。

      (比如80端口是给配饰HTTP服务

         211端口是给FTP服务)

    动态端口(Dynamic Ports):

      范围是从1024到65535;

      之所以称为动态端口,是因为它一般不固定分配某种服务,而是动态分配;

      动态分配是指当一个系统程序或应用程序程序需要网络通信时,它向主机申请一个端口,主机从可用的端口号中分配一个供它使用;

      当这个程序关闭时,同时也就释放了所占用的端口号。

    查看端口号:

      netstat -an

    

三、socket(套接字)

   1.Socket是应用层与TCP/IP协议族通信(传输层)的中间软件抽象层,它是一组接口。socket是传输层一下的一种封装;

  2.基于网络类型的套接字家族:套接字家族的名字:AF_INET;

  3.客户端怎么通过互联网找到服务端?

     通过ip地址中隐藏的Mac地址找到服务器在哪个网段,而其中的Mac地址是找到具体的哪台电脑。

  4.详见网络通信原理:http://www.cnblogs.com/linhaifeng/articles/5937962.html(转自别人)

  5.套接字分类:

    (1)面向连接的套接字(虚拟电路或流套接字)——意味着在进行通信之前必须先建立一个连接

       实现该种连接类型的主要协议是传输控制协议(缩写TCP)

    (2)无连接的套接字(数据报类型套接字)——在通信开始之前并不需要建立连接

       实现该种连接类型的主要协议是用户数据报协议(缩写UDP)

 

四、tcp

  1.tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端;

  2.TCP特点:

    2.1 面向连接

      通信双方必须先建立连接才能进行数据的传输,双方都必须为该连接分配必要的系统内核资源,以管理连接的状态和连接上的传输。

      双方间的数据传输都可以通过这一个连接进行。  

      完成数据交换后,双方必须断开此连接,以释放系统资源。

      这种连接是一对一的,因此TCP不适用于广播的应用程序,基于广播的应用程序请使用UDP协议。

    2.2.可靠传输

      1)TCP采用发送应答机制

        TCP发送的每个报文段都必须得到接收方的应答才认为这个TCP报文段传输成功。

      2)超时重传

        发送端发出一个报文段之后就启动定时器,如果在定时时间内没有收到应答就重新发送这个报文段;

        TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的包发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。

      3)错误校验

        TCP用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。

      4) 流量控制和阻塞管理

        流量控制用来避免主机发送得过快而使接收方来不及完全收下。

  3.TCP与UDP的不同点

    面向连接(确认有创建三方交握,连接已创建才作传输。);

    有序数据传输;

    重发丢失的数据包;

    舍弃重复的数据包;

    无差错的数据传输;

    阻塞/流量控制。

  4.tcp注意点

    tcp服务器一般情况下都需要绑定,否则客户端找不到这个服务器;

    tcp客户端一般不绑定,因为是主动链接服务器,所以只要确定好服务器的ip、port等信息就好,本地客户端可以随机;

    tcp服务器中通过listen可以将socket创建出来的主动套接字变为被动的,这是做tcp服务器时必须要做的;

    当客户端需要链接服务器时,就需要使用connect进行链接,udp是不需要链接的而是直接发送,但是tcp必须先链接,只有链接成功才能通信;

    当一个tcp客户端连接服务器时,服务器端会有1个新的套接字,这个套接字用来标记这个客户端,单独为这个客户端服务;

    listen后的套接字是被动套接字,用来接收新的客户端的链接请求的,而accept返回的新套接字是标记这个新客户端的;

    关闭listen后的套接字意味着被动套接字关闭了,会导致新的客户端不能够链接服务器,但是之前已经链接成功的客户端正常通信;

    关闭accept返回的套接字意味着这个客户端已经服务完毕;

    当客户端的套接字调用close后,服务器端会recv解堵塞,并且返回的长度为0,因此服务器可以通过返回数据的长度来区别客户端是否已经下线;

   5.tcp三次握手和四次握手

    三次握手:

 

 

 

    四次挥手:

 

  【摘自别人https://blog.csdn.net/xisuo002/article/details/79161609】

  所谓四次挥手就是终止TCP连接,断开一个TCP连接需要客户端和服务端发送四次数据包以确定断开连接。 
    1.Client发送一个数据包,用来关闭Client到server的数据传输,Client进入最后的等待状态。 
    2.server接到数据包经过判断后,发送确认信息给Client,自己进入等待关闭状态。 
    3.server发送一个数据包给Client,用来关闭server到Client的数据传输 
    4.Client接收到数据经过判断后,自己进入Time_wait状态,server接收数据经过判断无误后,server进入关闭状态,Client等待2MLS(Maxmum segment lifetime),它是任何报文在网络丢弃前在网络内的最长时间,过了这个时间,Client就自动关闭了。 

  SYN:同步信号(Synchronize) 
  ACK:确认字符(Acknowledge Character) 
  RST:重连位(Reset) 
  FIN:对方终止发送数据(Finally)

 

 

  

 

  服务端和客户端

 1 from socket import *
 2 tcp_server=socket(AF_INET,SOCK_STREAM)
 3 
 4 ip_port=("127.0.0.1",8080)
 5 back_log=5#半连接池
 6 buffer_size=1024#缓冲池
 7 tcp_server.bind(ip_port)#绑定(主机,端口号)到套接字
 8 tcp_server.listen(back_log)#开始TCP监听
 9 while True:#循环不停连接
10     conn,addr=tcp_server.accept()#服务端阻塞, 被动接受TCP客户的连####接,(阻塞式)等待连接的到来
11     while True:#循环不停接受发送消息
12         data=conn.recv(buffer_size)
13         print("客户端发来的消息是",data.decode('utf-8'))
14         conn.send(data.upper())
15     conn.close()
16 tcp_server.close()
View Code
 1 __author__ = 'Administrator'
 2 from socket import *
 3 ip_port=('127.0.0.1',8080)
 4 back_log=5
 5 buffer_size=1024
 6 tcp_client=socket(AF_INET,SOCK_STREAM)
 7 tcp_client.connect(ip_port)
 8 while True:##循环不停接受发送消息
 9     msg=input('>>>>>>>>')
10     #if not msg:continue
11     tcp_client.send(msg.encode('utf-8'))
12     print('客户端已经发送消息了')
13     data=tcp_client.recv(buffer_size)
14     print("客户端收到服务端返回的消息",data.decode('utf-8'))
15 tcp_client.close()
View Code

   当客户端发空的时候: 

 1 __author__ = 'Administrator'
 2 from socket import *
 3 ip_port=('127.0.0.1',8080)
 4 back_log=5
 5 buffer_size=1024
 6 tcp_client=socket(AF_INET,SOCK_STREAM)
 7 tcp_client.connect(ip_port)
 8 while True:##循环不停接受发送消息
 9     msg=input('>>>>>>>>')
10     #if not msg:continue
11     tcp_client.send(msg.encode('utf-8'))
12     print('客户端已经发送消息了')
13     data=tcp_client.recv(buffer_size)
14     print("客户端收到服务端返回的消息",data.decode('utf-8'))
15 tcp_client.close()
16 
17 #####################################
18 >>>>>>>>
19 客户端已经发送消息了
View Code 
 1 __author__ = 'Administrator'
 2 from socket import *
 3 tcp_server=socket(AF_INET,SOCK_STREAM)
 4 
 5 ip_port=("127.0.0.1",8080)
 6 back_log=5
 7 buffer_size=1024
 8 tcp_server.bind(ip_port)
 9 tcp_server.listen(back_log)
10 while True:#循环不停连接
11     conn,addr=tcp_server.accept()#服务端阻塞
12     while True:#循环不停接受发送消息
13         data=conn.recv(buffer_size)
14         print("客户端发来的消息是",data.decode('utf-8'))
15         conn.send(data.upper())
16     conn.close()
17 tcp_server.close()
18 ########################
19 此时服务端根本没反应
View Code

  反之:

    recv在自己这段的缓冲区为空时,阻塞;

    当recv接收到客户端的数据时和客户端close时都会解堵塞

 

  【注意】如果出现addre alrea in use的错误:

    这个是服务端仍然存在四次挥手的time_wait状态在占用地址;

    解决办法:就是它在bind前加:tcp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) 

__author__ = 'Administrator'
import socket

def main():
    buffer_size=1024
    #  1.创建套接字
    tcp_client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

    #  2.连接服务器
    dest_ip=input("请输入要链接的服务器的ip:")
    dest_port=int(input("请输入要链接的服务器的端口:"))
    server_addr=(dest_ip,dest_port)
    tcp_client.connect(server_addr)

    #  3.发送数据/接收数据
    send_data=input("请输入要发送的内容:")
    tcp_client.send(send_data.encode("utf-8"))

    #  4.关闭套接字
    tcp_client.close()

if __name__ == '__main__':
    main()
View Code
__author__ = 'Administrator'
import  socket

def main():
    #  1.创建套接字
    tcp_server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

    #  2.bind绑定ip和port
    tcp_server.bind(("127.0.0.1",8082))

    #  3.listen使套接字变为被动链接,这样就可以接收别人的链接了——监听
    tcp_server.listen(128)#128是可以同时链接的并发量,暂时这么理解

    while True:####该循环是为多个客户端服务

        #  4.aacept等待客户端的链接,为客户端服务
        #  accept产生的新的套接字用来为客户端服务(后面的都是用这个)
        new_client_socket,client_addr=tcp_server.accept()
        while True:####该循环多次为同一个客户端服务多次
            #  5.recv/send接收发送数据
            #  recv,recvform前者只有数据,后面有来自,表明里面还有ip和port
            recv_data=new_client_socket.recv(1024)
            print(recv_data.decode("utf-8"))

            #  如果recv解堵塞:有两种
            #  (1)客户端发来数据
            #  (2)客户端调用close,recv会解堵塞
            if recv_data:
                # 发送数据
                send_back_msg=input("请输入要返回的信息:")
                new_client_socket.send(send_back_msg.encode("utf-8"))
            else:
                break

        #  关闭accept返回的套接字,意味着不会再为这个客户端服务
        new_client_socket.close()

    #  如果将监听套接字关闭了,那么会导致不能再次等待新客户端的到来,及xxxxx.accept会失败。
    tcp_server.close()


if __name__ == '__main__':
    main()
View Code

 

 

  下载文件:

[在目录下准备一个a.txt文件]

__author__ = 'Administrator'
import  socket

def main():
    #  1.创建套接字
    tcp_client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

    #  2.获取服务器的ip和端口
    dest_ip=input("请输入下载服务器的ip:")
    dest_port=int(input("请输入下载服务器的port:"))

    server_addr=(dest_ip,dest_port)
    #  3.连接服务器
    tcp_client.connect(server_addr)

    #  4.获取下载文件名字
    dowdload_file=input("请输入要下载的文件名字:")

    #  5.将文件名发送给服务器
    tcp_client.send(dowdload_file.encode("utf-8"))
    #  6.接收文件中的数据
    recv_data=tcp_client.recv(1024)

    if recv_data:
        #  7.保存接收到的数据到一个文件中
        with open("[新]"+dowdload_file,"wb") as f:
            f.write(recv_data)

    #  8.关闭套接字
    tcp_client.close()

if __name__ == '__main__':
    main()
View Code

 

__author__ = 'Administrator'
import  socket
def send_file_2_client(new_client_socket,client_addr):
    1#  1.接收客户端需要下载的文件名
    filename=new_client_socket.recv(1024).decode("utf-8")
    print("客户端[%s]需要下载的文件是%s:" %(str(client_addr),filename))

    file_content=None
    #  2.打开这个文件,读取数据
    try:
        f=open(filename,"rb")
        file_content=f.read()
        f.close()
    except Exception as r:
        print("没有要下载的文件%s" %filename)

    # 3.发送文件数据发送给客户端
    if file_content:
        new_client_socket.send(file_content)


def main():
    #  1.创建套接字
    tcp_server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

    #  2.bind绑定ip和port
    tcp_server.bind(("127.0.0.1",8082))

    #  3.listen使套接字变为被动链接,这样就可以接收别人的链接了——监听
    tcp_server.listen(128)

    #  4.aacept等待客户端的链接,为客户端服务
    #  accept产生的新的套接字用来为客户端服务(后面的都是用这个)
    while True:
        new_client_socket,client_addr=tcp_server.accept()

        #  5.调用发送文件函数,完成客服端服务
        send_file_2_client(new_client_socket,client_addr)


        #  关闭套接字
        new_client_socket.close()
    tcp_server.close()


if __name__ == '__main__':
    main()
View Code

 

 

  6.基于tcp实现远程执行命令

View Code
View Code

  tcp发送消息为空时候break;

  其他的异常可以增加try......except;

View Code
View Code

>>: ping www.baidu.com
命令的执行结果是 
正在 Ping www.a.shifen.com [180.97.33.107] 具有 32 字节的数据:
来自 180.97.33.107 的回复: 字节=32 时间=35ms TTL=55
来自 180.97.33.107 的回复: 字节=32 时间=35ms TTL=55
来自 180.97.33.107 的回复: 字节=32 时间=33ms TTL=55
来自 180.97.33.107 的回复: 字节=32 时间=34ms TTL=55

180.97.33.107 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 33ms,最长 = 35ms,平均 = 34ms

>>:

新的客户端链接 ('127.0.0.1', 57023)
收到客户端的命令 b'ping www.baidu.com'

 

 

五、udp

  1.发送数据流程

    创建套接字;

    发送数据;

    关闭套接字。

  2.接收数据流程

    创建套接字;

    绑定(本地自己的信息)ip地址和端口;

    接收数据;

    关闭套接字。

 

  3.udp无需连接,udp是无链接的,先启动哪一端都不会报错

 1 __author__ = 'Administrator'
 2 from socket import *
 3 ip_port=('127.0.0.1',8080)
 4 back_log=5
 5 buffer_size=1024
 6 udp_server=socket(AF_INET,SOCK_DGRAM)
 7 udp_server.bind(ip_port)
 8 while True:
 9     data,addr=udp_server.recvfrom(buffer_size)
10     print('udp服务端接收消息了')
11     print(data)
12     print(addr)
13     udp_server.sendto(data.upper(),addr)
14     print('udp服务端发送消息了')
View Code
 1 __author__ = 'Administrator'
 2 from socket import *
 3 ip_port=('127.0.0.1',8080)
 4 back_log=5
 5 buffer_size=1024
 6 udp_client=socket(AF_INET,SOCK_DGRAM)
 7 while True:
 8     msg=input('>>>>>>>>').strip()
 9     udp_client.sendto(msg.encode('utf-8'),ip_port)
10     print('udp客户端发送消息了')
11     data,addr=udp_client.recvfrom(buffer_size)
12     print(data)
13     print('udp客户端接收消息了')
View Code

 

结果显示:

>>>>>>>>ing
udp客户端发送消息了
b'ING'
udp客户端接收消息了
>>>>>>>>

 

udp服务端接收消息了
b'ing'
('127.0.0.1', 62066)
udp服务端发送消息了

 

【注意】:

 

recvfrom在自己这段的缓冲区为空时,就收一个空。

 

>>>>>>>>
udp客户端发送消息了
b''
udp客户端接收消息了
>>>>>>>>

 

 

 

udp服务端接收消息了
b''
('127.0.0.1', 65161)
udp服务端发送消息了

 

__author__ = 'Administrator'

import socket

def main():
    buffer_size=1024
    #  1.创建套接字
    udp_server=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    #  2.绑定一个本地信息
    localaddr=("127.0.0.1",8082)
    udp_server.bind(localaddr)
    while True:
        #  3.接收数据
        #  recv_data这个变量中存储的是一个元祖(接收到的数据,(发送方的ip,port))
        recv_data=udp_server.recvfrom(buffer_size)
        recv_msg=recv_data[0]
        recv_addr=recv_data[1]

        #  4.打印接收到的数据
        print(recv_msg.decode("utf-8"))
        print(recv_addr)
    #  5.关闭套接字
    udp_server.close()


if __name__ == '__main__':
    main()
View Code

 

__author__ = 'Administrator'

import socket

def udp_main():
    ip_port=("127.0.0.1",8082)
    #  创建一个套接字
    udp_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

    #  循环接收数据
    while True:
        #  从键盘获取数据
        send_data=input("请输入您的数据:")

        #  如果输入字符串0,退出程序
        if send_data=="0":
            break

        #  可以使用套接字收发数据,字符串转成字节(计算机)
        #udp_socket.sendto(b"hahaha hahah",("127.0.0.1",8082))
        udp_socket.sendto(send_data.encode("utf-8"),ip_port)
        #  关闭套接字
    udp_socket.close()
    print("测试程序是否运行")
if __name__ == '__main__':
    udp_main()
View Code 

  同一个套接字既可以发也可以收:

      recvfrom在为空的时候会堵塞;

     单工:数据只在一个方向上传输,不能实现双方通信(比如电视);

     半双工:允许数据在两个方向上传输,但是同一时间数据只能在一个方向上传输,其实际上是切换的单工(比如对讲机);

     全双工:允许数据在两个方向上同时传输(比如手机电话)。

     socket套接字是全双工。

 

__author__ = 'Administrator'
import socket

def send_msg(udp_socket):
    dest_ip=input("请输入对方的ip:")
    dest_port=int(input("请输入对方的端口号:"))
    send_data=input("请输入要发送的信息:")
    udp_socket.sendto(send_data.encode("utf-8"),(dest_ip,dest_port))

def recv_msg(udp_socket):
    recv_data=udp_socket.recvfrom(1024)
    print("%s:%s" %(recv_data[0].decode("utf-8"),str(recv_data[1])))

def main():
    #  创建套接字
    udp_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

    #  绑定信息
    udp_socket.bind(("",8092))

    #  循环来处理事情
    while True:
        print("1:发送信息")
        print("2:接受信息")
        print("3:退出系统")
        op=input("请输入您想要进行的操作:")
        if  op=="1":
            #  发送
            send_msg(udp_socket)
        elif op=="2":
            #  接收并显示
            recv_msg(udp_socket)
        elif op=="3":
            #  接收并显示
            break
        else:
            print("请重新输入")

if __name__ == '__main__':
    main()



#########################
1:发送信息
2:接受信息
3:退出系统
请输入您想要进行的操作:1
请输入对方的ip:127.0.0.1
请输入对方的端口号:8092
请输入要发送的信息:hhhhhh
1:发送信息
2:接受信息
3:退出系统
请输入您想要进行的操作:2
hhhhhh:('127.0.0.1', 8092)
1:发送信息
2:接受信息
3:退出系统
请输入您想要进行的操作:
View Code

 

 

 

 

 

posted @ 2018-10-06 12:49  小猪猪猪  阅读(200)  评论(0)    收藏  举报