基于TCP协议的socket

一、套接字

套接字(socket)是一个抽象层,应用程序可以通过它发送或接受数据,可对其进行像文件一样的打开、读写和关闭等操作。网络套接字是IP地址与端口的组合。

套接字是网络编程中的一种通信机制,是支持TCP/IP得其网络的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单地说就是通信两方的一种约定,用套接字中的相关函数来完成通信过程。

 

二、两种协议

TCP协议

称为流式协议,可靠协议,是一种提供可靠数据传输的通用协议。

UDP协议

数据报协议(自带报头),一个面向无连接的协议。采用该协议不需要两个应用程序先建立连接。不能够提供数据互传,因此还协议传输数据安全新差。

 

三、TCP通信

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

1.socket通信简易版本

服务端

import socket
server = socket.socket()
server.bind(('127.0.0.1',8080))  # 插电话卡  绑定IP地址和端口(把地址绑定到套接字)
server.listen()  # 开机  半连接池

conn,addr = server.accept()  # 接听 (接收客户端链接)
data = conn.recv(1024)  # 听别人说话(接收客户端信息)
print(data)  # 打印客户端信息
conn.send(b'hello girl')  # 给别人回话(向客户端发送信息)

conn.close()  # 挂电话(关闭客户端套接字)
server.close()  # 关机(关闭服务器套接字)
server端

客户端

import socket

client = socket.socket()  # 拿电话(创建套接字)
client.connect(('127.0.0.1',8080))  # 拨号 写对方的ip和port(尝试连接服务器)

client.send(b'hello boy')  # 对别人说话(发送信息)
data = client.recv(1024)  # 听别人说话(接受信息)
print(data)  # 打印数据,也就是所得信息

client.close()  # 挂电话(关闭客户端套接字)
client端

2.通信循环

服务端

import socket
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)

conn,addr = server.accept()
while True:

    data = conn.recv(1024)
    print(data)

    conn.send(data.upper())  # 将客户端传过来的数据转换成大写给客户端
server端

客户端

import socket

client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:  # 利用While 循环来实现
    msg = input('>>>:').encode('utf-8')  # 输入内容
    client.send(msg)  # 将这个内容发送给服务端
    data = client.recv(1024)
    print(data)  # 打印出这个数据
client端

3.连接循环

服务端

import socket
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)  # 5表示允许客户端最大等待数为5


while True:  # 连接循环上一层,下面一层结束了,返回继续执行

    conn,addr = server.accept()  # 等待别人来
    print(addr)  # 打印结果为客户端的ip地址

    while True:
        try:  # 能够有效地解决服务端报错问题
            data = conn.recv(1024)
            print(data)
            if len(data) == 0:
                break

            conn.send(data.upper())
        except ConnectionResetError as e:
            print(e)
            break
    conn.close()
server端

客户端

import socket

client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:
    msg = input('>>>:').encode('utf-8')
    if len(msg) == 0:
        continue
    client.send(msg)
    data = client.recv(1024)
    print(data)
client端

4.代码健壮性补充

问题1

服务端代码

import socket
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)

conn,addr = server.accept()
while True:

    data = conn.recv(1024)
    print(data)

    conn.send(data.upper())

客户端代码

import socket

client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:  # 利用While 循环来实现
    msg = input('>>>:').encode('utf-8')  # 输入内容
    client.send(msg)  # 将这个内容发送给服务端
    data = client.recv(1024)
    print(data)  # 打印出这个数据

1.先启动服务端,在启动客户端正常运行

2.结束客户端后,客户端能够正常退出,服务端会报错

针对于这个问题,就利用异常处理的方式来解决

客户端代码不变,服务端需要添加 Try 的方式来解决,这样服务端也能够正常退出,不会报错

服务端更改后的代码

import socket
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)

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

while True:
    try:  # 能够有效地解决服务端报错问题
        data = conn.recv(1024)
        print(data)
        if len(data) == 0:
            break

        conn.send(data.upper())
    except ConnectionResetError as e:
        print(e)
        break
conn.close()

问题2

1.先启动服务端,在启动客户端正常运行

产生的问题:

会发现点击Enter键的时候,客户端不能够输入内容,服务端也一直在等待,这样就导致了程序一直卡在这了

2.解决方法:

在服务端等待的原基础上,对客户端代码进行修改,需要对msg进行一个判断

更改后的代码

import socket

client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:
    msg = input('>>>:').encode('utf-8')
    if len(msg) == 0:
        continue
    client.send(msg)
    data = client.recv(1024)
    print(data)

进行判断之后就可以输入了

5.TCP粘包问题

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

1.使用struct模块解决数据繁多冗杂

 

由上面两张图可知,打包之前文件大小为48,打包之后为4,无论文件大小是多少,打包结果都是4,

解包之后就会还原成原来文件的大小

 

2.使用struct模块解决TCP粘包问题

借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。因此可以利用这个特点来预先发送数据长度。

服务端

 

import struct
import subprocess
import socket
import json

server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)

while True:
    conn,addr = server.accept()
    while True:
        try:
            cmd = conn.recv(1024)
            if len(cmd) == 0:
                break
            cmd = cmd.decode('utf-8')
            obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            res = obj.stdout.read() + obj.stderr.read()
            d = {'name':'jason','file_size':len(res),'info':'dasdjfhjncksc'}
            json_d = json.dumps(d)
            #1.先制作一个字典的报头
            header = struct.pack('i',len(json_d))
            #2.发送字典报头
            conn.send(header)
            #3.发送字典
            conn.send(json_d.encode('utf-8'))
            #4.发送真实的数据
            conn.send(res)
        except ConnectionRefusedError:
            break
    conn.close()
server端

 

客户端

import socket
import struct
import json
client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:
    msg = input('>>>:').encode('utf-8')
    if len(msg) == 0:
        continue
    client.send(msg)

    #1.先接受字典报头
    header_dict = client.recv(4)
    #2.解析报头,获取字典长度
    dict_size = struct.unpack('i',header_dict)[0]
    #3.接受字典数据
    dict_bytes = client.recv(dict_size)
    dict_json = json.loads(dict_bytes.decode('utf-8'))
    #4.从字典中获取信息
    print(dict_json)
    recv_size = 0
    real_data = b''
    while recv_size < dict_json.get('file_size'):
        data = client.recv(1024)
        real_data += data
        recv_size += len(data)
    print(real_data.decode('gbk'))
client端
原文地址:https://www.cnblogs.com/xiongying4/p/11321253.html