zoukankan      html  css  js  c++  java
  • 网络编程之socket

    主要内容:

    • 1.基于TCP协议下的socket通信流程
    • 2.基于UDP协议下的socket通信流程
    • 3.粘包现象

    1.基于TCP协议下的socket通信流程

    (1)TCP和UDP的对比

    TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;文件传输程序。

    UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文(数据包),尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。

    TCP 和UDP下socket差异对比图

     

    (2) TCP协议下的socket通信流程

    具体的通信流程

    • 先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。
    • 在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。
    • 客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字
    sk.listen()          #监听链接
    conn,addr = sk.accept() #接受客户端链接
    ret = conn.recv(1024)  #接收客户端信息
    print(ret)       #打印客户端信息
    conn.send(b'hi')        #向客户端发送信息
    conn.close()       #关闭客户端套接字
    sk.close()        #关闭服务器套接字(可选)
    
    tcp_server.py
    tcp-服务端
    import socket
    sk = socket.socket()           # 创建客户套接字
    sk.connect(('127.0.0.1',8898))    # 尝试连接服务器
    sk.send(b'hello!')
    ret = sk.recv(1024)         # 对话(发送/接收)
    print(ret)
    sk.close()            # 关闭客户套接字
    
    tcp_client.py
    tcp-客户端

    socket绑定IP和端口时可能出现下面的问题:

     

    解决方法:

    #加入一条socket配置,重用ip和端口
    import socket
    from socket import SOL_SOCKET,SO_REUSEADDR
    sk = socket.socket()
    sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #在bind前加,允许地址重用
    sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字
    sk.listen()          #监听链接
    conn,addr = sk.accept() #接受客户端链接
    ret = conn.recv(1024)   #接收客户端信息
    print(ret)              #打印客户端信息
    conn.send(b'hi')        #向客户端发送信息
    conn.close()       #关闭客户端套接字
    sk.close()        #关闭服务器套接字(可选)
    
    解决办法
    View Code

    但是如果你加上了上面的代码之后还是出现这个问题:OSError: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试那么只能换端口了,因为你的电脑不支持端口重用

    tcp 下一个服务端与多个客户端 的通信

    import socket
    server = socket.socket()
    ip_port = ("192.168.15.51",8009)
    
    server.bind(ip_port)
    server.listen()
    print("请进行登录验证")
    
    conn,addr = server.accept()
    #可以接受消息
    flag = 0
    while not flag:
         from_client_msg = conn.recv(1024)
         with open("user_info012",mode = "r+",encoding = "utf-8") as f:
             for line in f:
                 if line.strip() == from_client_msg.decode("utf-8"):
                     conn.send("登录成功".encode("utf-8"))
                     flag = 1
                     break
             else:
                 conn.send("账号或密码错误".encode("utf-8"))
    
    conn.close()
    server.close()
    server
    import socket
    client = socket.socket()
    server_ip_port = ("192.168.15.51",8009)
    client.connect(server_ip_port)
    
    #发消息
    while 1:
        username, password = input("请输入你的账号"),input("请输入你的密码")
        msg = username + "|" + password
        client.send(msg.encode("utf-8"))
        if msg == 'good|bye':
            break
        from_server_msg = client.recv(1024).decode("utf-8")
        print(from_server_msg)
        if from_server_msg =="登录成功":
            break
    client.close()
    client

    当只有一个客户端时候是可以正常通信的,但是当再起客户端时候服务端不能够收到消息,并且当第一客户端断开连接后,服务端随之关闭,并会报错.原因是:

    tcp属于长连接,长连接就是一直占用着这个链接,这个连接的端口被占用了,第二个客户端过来连接的时候,他是可以连接的,但是处于一个占线的状态,就只能等着去跟服务端建立连接,除非一个客户端断开了(优雅的断开可以,如果是强制断开就会报错,因为服务端的程序还在第一个循环里面),然后就可以进行和服务端的通信了.

    修改后的

    import socket
    server = socket.socket()
    ip_port = ("192.168.15.51",8009)
    
    server.bind(ip_port)
    server.listen()
    print("请进行登录验证")
    
    while 1:
        conn,addr = server.accept()
        #可以接受消息
        flag = 0
        while not flag:
             from_client_msg = conn.recv(1024)
             with open("user_info012",mode = "r+",encoding = "utf-8") as f:
                 for line in f:
                     if line.strip() == from_client_msg.decode("utf-8"):
                         conn.send("登录成功".encode("utf-8"))
                         flag = 1
                         break
                 else:
                     conn.send("账号或密码错误".encode("utf-8"))
    
        print("准备断开连接....")
        conn.close()
    
    # server.close()
    server
    import socket
    client = socket.socket()
    server_ip_port = ("192.168.15.51",8009)
    client.connect(server_ip_port)
    
    #发消息
    while 1:
        username, password = input("请输入你的账号"),input("请输入你的密码")
        msg = username + "|" + password
        client.send(msg.encode("utf-8"))
        if msg == 'good|bye':
            break
        from_server_msg = client.recv(1024).decode("utf-8")
        print(from_server_msg)
        if from_server_msg =="登录成功":
            break
    client.close()
    client

    2.基于UDP协议下的socket通信流程

    3.粘包现象

    3.1 缓冲区

    每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。
    
    write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。
    
    TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。
    
    read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。
    
    这些I/O缓冲区特性可整理如下:
    
    1.I/O缓冲区在每个TCP套接字中单独存在;
    2.I/O缓冲区在创建套接字时自动生成;
    3.即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
    4.关闭套接字将丢失输入缓冲区中的数据。
    
    输入输出缓冲区的默认大小一般都是 8K,可以通过 getsockopt() 函数获取:
    
    1.unsigned optVal;
    2.int optLen = sizeof(int);
    3.getsockopt(servSock, SOL_SOCKET, SO_SNDBUF,(char*)&optVal, &optLen);
    4.printf("Buffer length: %d
    ", optVal);
    
    socket缓冲区解释
    缓冲区释义

    3.2 windows下cmd窗口调用系统指令

     a.首先ctrl+r,弹出左下角的下图,输入cmd指令,确定

     b.在打开的cmd窗口中输入dir(dir:查看当前文件夹下的所有文件和文件夹),你会看到下面的输出结果。

         另外还有ipconfig / ipconfig-all(查看当前电脑的网络信息),windows没有ls这个指令(ls在linux下是查看当前文件夹下所有文件和文件夹的指令,和windows下的dir是类似的),那么没有这个指令就会报错.

     3.3 粘包现象(两种)

    (1)subprocess模块

    import subprocess
    cmd = input('请输入指令>>>')
    res = subprocess.Popen(
        cmd,                     #字符串指令:'dir','ipconfig',等等
        shell=True,              #使用shell,就相当于使用cmd窗口
        stderr=subprocess.PIPE,  #标准错误输出,凡是输入错误指令,错误指令输出的报错信息就会被它拿到
        stdout=subprocess.PIPE,  #标准输出,正确指令的输出结果被它拿到
    )
    print(res.stdout.read().decode('gbk'))
    print(res.stderr.read().decode('gbk'))
    
    subprocess的简单使用
    subprocess

     注意:

    • 如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码且只能从管道里读一次结果,PIPE称为管道。
    • subprocess的stdout.read()和stderr.read(),拿到的结果是bytes类型,所以需要转换为字符串打印出来看。

    (2)粘包现象一

    发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据也很小,会合到一起,产生粘包)

    import socket
    server = socket.socket()
    ip_port = ("192.168.15.51",8001)
    server.bind(ip_port)
    
    server.listen()
    conn,addr = server.accept()
    from_client_msg1 = conn.recv(1024).decode("utf-8")
    from_client_msg2 = conn.recv(1024).decode("utf-8")
    
    print('msg1:',from_client_msg1)
    print('msg2:',from_client_msg2)
    
    conn.close()
    server.close()
    #结果
    #msg1: hahahehe
    #msg2:
    server
    import socket
    client = socket.socket()
    server_ip_port = ("192.168.15.51",8001)
    client.connect(server_ip_port)
    client.send("haha".encode("utf-8"))
    client.send("hehe".encode("utf-8"))
    
    client.close()
    client

    有时间间隔后的client (此时服务端收到的结果不会粘到一起)

    import socket
    import time
    client = socket.socket()
    server_ip_port = ("192.168.15.51",8001)
    client.connect(server_ip_port)
    client.send("haha".encode("utf-8"))
    time.sleep(2)
    client.send("hehe".encode("utf-8"))
    
    client.close()
    
    #服务端显示内容
    #msg1: haha
    #msg2: hehe
    client-添加发送时间间隔

    (3)粘包现象二

    接收方没有及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

    import socket
    import subprocess
    server = socket.socket()
    ip_port = ("192.168.15.51",8006)
    server.bind(ip_port)
    server.listen(3)
    
    while 1:
        conn,addr = server.accept()
        #可以接收消息
        flag = 0
        while not flag:
            from_client_cmd = conn.recv(1024).decode("utf-8")
            sub_obj = subprocess.Popen(from_client_cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            server_cmd_msg = sub_obj.stdout.read()
            server_cmd_err = sub_obj.stderr.read()
    
            conn.send(server_cmd_msg)
            conn.send(server_cmd_err)
    
        conn.close()
    server
    import socket
    client = socket.socket()
    server_ip_port = ("192.168.15.51",8006)
    client.connect(server_ip_port)
    
    while 1:
         cmd = input("请输入要执行的指令(输入Q退出)>>>>>")
         if cmd.upper() == "Q":
             break
         else:
             client.send(cmd.encode("utf-8"))
             from_server_msg =client.recv(1024).decode("gbk")
             print(from_server_msg)
    
    client.close()
    client

    3.4粘包现象的解决方案

       问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端发一个确认消息给发送端,然后发送端再发送过来后面的真实内容,接收端再来一个死循环接收完所有数据

    (1)粘包解决方案一

    import socket
    import subprocess
    server = socket.socket()
    ip_port = ("192.168.15.51",8002)
    server.bind(ip_port)
    server.listen(3)
    while 1:
        conn,addr = server.accept()
        flag=0
        while not flag:
            #来自客户端的指令
            from_client_cmd = conn.recv(1024).decode("utf-8")
            print(from_client_cmd)
            sub_obj = subprocess.Popen(from_client_cmd,shell=True,stderr=subprocess.PIPE,stdout=subprocess.PIPE)
            server_cmd_msg = sub_obj.stdout.read()
            server_cmd_err = sub_obj.stderr.read()
    
            msg1_len = str(len(server_cmd_msg))
            print("指令返回de正确信息的长度>>>",msg1_len)
    
            msg2_len = str(len(server_cmd_err))
            print("指令返回de正确信息的长度>>>", msg2_len)
    
            conn.send(msg1_len.encode("gbk"))
            from_client_ack = conn.recv(1024).decode("utf-8")
            print("客户端对是否发送消息的确认>>>",from_client_ack)
            if from_client_ack == 'ok':
                conn.send(server_cmd_msg)
            else:
                continue
    
        conn.close()
    server
    import socket
    client = socket.socket()
    server_ip_port = ip_port = ("192.168.15.51",8002)
    client.connect(server_ip_port)
    
    flag = 0
    while not flag:
        cmd = input("请输入要执行的指令>>>")
        client.send(cmd.encode("utf-8"))
        from_server_msg_len = client.recv(1024).decode("gbk")
        print('服务端将要发送信息的长度',from_server_msg_len)
        client.send("ok".encode("utf-8"))
    
        from_server_stdout = client.recv(int(from_server_msg_len)).decode("gbk")
        print('收到的正确信息:', from_server_stdout)
    
    client.close()
    server
  • 相关阅读:
    BackgroundWorker原理剖析
    委托异步调用时BeginInvoke的陷阱处理
    线程静态在对象缓存中的妙用
    值得珍藏的.NET源码,不保存就没机会了
    .NET 4.5.1 参考源码索引
    .NET 4.5 参考源码索引
    .NET 4.0 参考源码索引
    WWF3.5SP1 参考源码索引
    WCF3.5 SP1 参考源码索引
    .NET 3.5.1 参考源码索引
  • 原文地址:https://www.cnblogs.com/wcx666/p/9804272.html
Copyright © 2011-2022 走看看