网络编程

tcp协议和udp协议

用于应用程序之间的通信。如果说ip地址和mac地址帮我们确定唯一的一台机器,那么我们怎么找到一台机器上的一个软件呢?

端口

  我们知道,一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等,这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。实际上是通过“IP地址+端口号”来区分不同的服务的。

TCP协议

  当应用程序希望通过 TCP 与另一个应用程序通信时,它会发送一个通信请求。这个请求必须被送到一个确切的地址。在双方“握手”之后,TCP 将在两个应用程序之间建立一个全双工 (full-duplex) 的通信。

  这个全双工的通信将占用两个计算机之间的通信线路,直到它被一方或双方关闭为止。

DP协议

  当应用程序希望通过UDP与一个应用程序通信时,传输数据之前源端和终端不建立连接。

  当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。

tcp和udp的对比

TCP---传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。 
UDP---用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快

现在Internet上流行的协议是TCP/IP协议,该协议中对低于1024的端口都有确切的定义,他们对应着Internet上一些常见的服务。这些常见的服务可以分为使用TCP端口(面向连接)和使用UDP端口(面向无连接)两种。 
说到TCP和UDP,首先要明白“连接”和“无连接”的含义,他们的关系可以用一个形象地比喻来说明,就是打电话和写信。两个人如果要通话,首先要建立连接——即打电话时的拨号,等待响应后——即接听电话后,才能相互传递信息,最后还要断开连接——即挂电话。写信就比较简单了,填写好收信人的地址后将信投入邮筒,收信人就可以收到了。从这个分析可以看出,建立连接可以在需要痛心地双方建立一个传递信息的通道,在发送方发送请求连接信息接收方响应后,由于是在接受方响应后才开始传递信息,而且是在一个通道中传送,因此接受方能比较完整地收到发送方发出的信息,即信息传递的可靠性比较高。但也正因为需要建立连接,使资源开销加大(在建立连接前必须等待接受方响应,传输信息过程中必须确认信息是否传到及断开连接时发出相应的信号等),独占一个通道,在断开连接钱不能建立另一个连接,即两人在通话过程中第三方不能打入电话。而无连接是一开始就发送信息(严格说来,这是没有开始、结束的),只是一次性的传递,是先不需要接受方的响应,因而在一定程度上也无法保证信息传递的可靠性了,就像写信一样,我们只是将信寄出去,却不能保证收信人一定可以收到。 
TCP是面向连接的,有比较高的可靠性, 一些要求比较高的服务一般使用这个协议,如FTP、Telnet、SMTP、HTTP、POP3等。
而UDP是面向无连接的,使用这个协议的常见服务有DNS、SNMP、QQ等。对于QQ必须另外说明一下,QQ2003以前是只使用UDP协议的,其服务器使用8000端口,侦听是否有信息传来,客户端使用4000端口,向外发送信息(这也就不难理解在一般的显IP的QQ版本中显示好友的IP地址信息中端口常为4000或其后续端口的原因了),即QQ程序既接受服务又提供服务,在以后的QQ版本中也支持使用TCP协议了。

互联网协议与osi模型

互联网协议按照功能不同分为osi七层或tcp/ip五层或tcp/ip四层

每层运行常见物理设备

每层运行常见的协议

3.socket概念

socket层

理解socket

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

3.套接字(socket)的发展史

套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。 

基于文件类型的套接字家族

套接字家族的名字:AF_UNIX

unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信

基于网络类型的套接字家族

套接字家族的名字:AF_INET

(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)

4.tcp协议和udp协议

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

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

我知道说这些你们也不懂,直接上图。

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

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

 黏包成因
在发送端 由于两条消息发送的间隔时间很短,且两条消息本身也很短,在发送之前被合成一条消息
在接收端 由于接收不及时导致两条先后到达的信息在接收端黏在了一起
黏包的本质
信息与信息之间没有边界
tcp协议 流式传输

import struct
ret = struct.pack("i",1024)
print(ret)
ret2 = struct.unpack("i",ret)
print(ret2)


----->
b'x00x04x00x00'
(1024,)
cur.execute("""insert into userinfo1 (username,password) values ('%s','%s')"""%(username,pwd))

注意   %s 要加 引号
import json
import socket
import sys
import hashlib
from pymysql import connect



def get_md5(username, password):
    """密码加密"""
    md5 = hashlib.md5(username.encode('utf-8'))
    md5.update(password.encode("utf-8"))
    return md5.hexdigest()


def login(opera, username, pwd):
    """pymysql 操作"""
    conn = connect(host="localhost", port=3306, user='luyizhou', password='luyizhou89', database="socket_test")
    cur = conn.cursor()
    if opera == "regist":
        pwd = get_md5(username, pwd)
        cur.execute("""insert into userinfo1 (username,password) values ('%s','%s');""" % (username, pwd))
        conn.commit()
        return {'opt': 'regist', 'result': True}
    elif opera == 'login':
        cur.execute(f"select password from userinfo1 where username = '{username}';")
        ret = cur.fetchone()   #获取的是元组
        if ret == None:
            return {'opt':"login","result":"账户不存在"}
        elif pwd == ret[0]:
             return {'opt':'login','result':True}
        return {'opt':"login","result":"密码错误"}



sk= socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()
while True:
    conn,addr = sk.accept()
    msg = conn.recv(1024).decode('utf-8')
    dic_msg = json.loads(msg)
    if hasattr(sys.modules[__name__],dic_msg['operate']):
        ret = getattr(sys.modules[__name__],dic_msg['operate'])(dic_msg)
        content = json.dumps(ret).encode('utf-8')
        conn.send(content)
    conn.close()
sk.close()
server:
import json
import socket
import struct
import hashlib
def get_md5(usr,pwd):
    md5 = hashlib.md5(usr.encode('utf-8'))
    md5.update(pwd.encode('utf-8'))
    return md5.hexdigest()

def login(conn):
    msg = conn.recv(1024).decode('utf-8')
    dic = json.loads(msg)
    with open('userinfo', encoding='utf-8') as f:
        for line in f:
            username, password = line.strip().split('|')
            if username == dic['user'] and password == get_md5(dic['user'], dic['passwd']):
                res = json.dumps({'flag': True}).encode('utf-8')
                conn.send(res)
                return True
        else:
            res = json.dumps({'flag': False}).encode('utf-8')
            conn.send(res)
            return False

def upload(conn):
    len_bytes = conn.recv(4)
    num = struct.unpack('i', len_bytes)[0]
    str_dic = conn.recv(num).decode('utf-8')
    dic = json.loads(str_dic)

    with open(dic['filename'], 'wb') as f:
        while dic['filesize']:
            content = conn.recv(2048)
            f.write(content)
            dic['filesize'] -= len(content)

sk = socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()
while True:
    try:
        conn,addr = sk.accept()
        ret = login(conn)
        if ret:
            upload(conn)
    except Exception as e:
        print(e)
    finally:
        conn.close()
sk.close()


client:
import os
import json
import socket
import struct

def upload(sk):
    # 上传文件
    file_path = input('>>>')
    filename = os.path.basename(file_path)
    filesize = os.path.getsize(file_path)
    dic = {'filename': filename, 'filesize': filesize}
    bytes_dic = json.dumps(dic).encode('utf-8')

    len_bytes = struct.pack('i', len(bytes_dic))
    sk.send(len_bytes)
    sk.send(bytes_dic)

    with open(file_path, 'rb') as f:
        while filesize > 2048:
            content = f.read(2048)
            sk.send(content)
            filesize -= 2048
        else:
            content = f.read()
            sk.send(content)

usr = input('username :')
pwd = input('password :')
dic = {'operate':'login','user':usr,'passwd':pwd}
bytes_dic = json.dumps(dic).encode('utf-8')
sk = socket.socket()
sk.connect(('127.0.0.1',9001))
sk.send(bytes_dic)

res = sk.recv(1024).decode('utf-8')
dic = json.loads(res)
if dic['flag']:
    print('登录成功')
    upload(sk)
else:
    print('登录失败')

sk.close()
import time
import socketserver
server:
class Myserver(socketserver.BaseRequestHandler):
    def handle(self):
        conn = self.request
        for i in range(200):
            conn.send(('hello%s'%i).encode('utf-8'))
            print(conn.recv(1024))
            time.sleep(0.5)


server = socketserver.ThreadingTCPServer(('127.0.0.1',9001),Myserver)
server.serve_forever()


固定类方法   handle方法  self.request相当于conn   固定写法 
其他函数 可以写到类方法中  其并发运行的是handle方法
hmac  模块
import os
import hmac

hmac = hmac.new(b'alex sb',os.urandom(32))
print(hmac.digest())
server:
import os
import hashlib
import socket

secret_key = b'alex sb'
#os.urandom(32) 给每一客户端发送一个随机的字符串,来保证即使数据被拦截你也不能使用这个消息
sk = socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()

conn,addr = sk.accept()
rand = os.urandom(32)
conn.send(rand)

sha = hashlib.sha1(secret_key)
sha.update(rand)
res = sha.hexdigest()

ret = conn.recv(1024).decode('utf-8')
if ret == res:
    print('是合法的客户端')
else:
    print('不是合法的客户端')
    conn.close()


client:
import socket
import hashlib

secret_key = b'alexsb'
sk = socket.socket()
sk.connect(('127.0.0.1',9001))

rand = sk.recv(32)

sha = hashlib.sha1(secret_key)
sha.update(rand)
res = sha.hexdigest()

sk.send(res.encode('utf-8'))

sk.close()
server:
import os
import hmac
import socket

secret_key = b'alex sb'
#os.urandom(32) 给每一客户端发送一个随机的字符串,来保证即使数据被拦截你也不能使用这个消息
sk = socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()

conn,addr = sk.accept()
rand = os.urandom(32)
conn.send(rand)

hmac = hmac.new(secret_key,rand)
res = hmac.digest()

ret = conn.recv(1024)
if ret == res:
    print('是合法的客户端')
else:
    print('不是合法的客户端')
    conn.close()



client:
import hmac
import socket


secret_key = b'alex sb'
sk = socket.socket()
sk.connect(('127.0.0.1',9001))

rand = sk.recv(32)

hmac = hmac.new(secret_key,rand)
res = hmac.digest()

sk.send(res)

sk.close()
原文地址:https://www.cnblogs.com/qj696/p/12307006.html