网络编程:socket 编程

2022-09-07 15:48:38 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

socket 编程

-客户端/服务器架构 :即 C/S架构

1,硬件C/S 架构(打印机)

2, 软件C/S 架构(web服务)

C/S架构与socket的关系:socket就是为了完成C/S架构的开发

-osi 七层:

应用层–运输层–网络层–链路层–物理层

socket 抽象层在应用层和运输层之间

socket概念(socket也是套接字)

socket是应用层和TCP/IP协议中间通信的软件层,它是一组接口,在设计模式中,socket其实就是一个门面模式,它把复杂的TCP/IP协议封装隐藏在socket接口后,让socket去组织数据,以符合指定协议,所以只需遵循socket规定去编程就可以。

套接字分为2种:

-基于文件型的套接字家族 AF_UNIX

用于一台机器的不同程序之间

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

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

用于网络编程

通过网络来实现2个程序通讯

socket 基于tcp运行流程图如下:

例如:

服务端:

代码语言:javascript复制
import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#socket.SOCK_STREAM 是基于流的通讯方式,也就是TCP
#socket.AF_INET 代表是网络嵌套家族类型

phone.bind(('192.168.1.4',8000))
#括号内写IP地址 端口 自己电脑IP是 192.168.1.4 ,监听端口是8000

phone.listen(5) #代表同时可以接5个电话

conn,addr =phone.accept()
msg=conn.recv(1024)# 收消息 ,1024代表可以接收多少字节的信息
print('客户端发来的消息是',msg)
conn.send(msg.upper())#发消息

conn.close()
phone.close()

客户端

代码语言:javascript复制
import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

phone.connect(('192.168.1.4',8000))

phone.send('hello'.encode('utf-8'))#发消息
data =phone.recv(1024) #收消息
print('收到服务端发来的消息',data)

运行结果是:

服务端:客户端发来的消息是 b’hello’

客户端:收到服务端发来的消息 b’HELLO’

socket 底层工作原理

客户端服务端循环发送接收消息

服务端代码:

代码语言:javascript复制
from socket import *

ip_import =('192.168.1.3',8000)
back_log = 5
buffer_size = 1024
代码语言:javascript复制
tcp_server = socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_import)
tcp_server.listen(back_log)
代码语言:javascript复制
conn,add = tcp_server.accept()
print('双向链接是',conn)
print('客户端的地址是',add)

while True:
    msg = conn.recv(buffer_size)
    print('客户发来的是',msg.decode('utf-8'))
    conn.send(msg.upper())

conn.close()
tcp_server.close()

服务端运行结果是:

双向链接是 <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘192.168.1.3’, 8000), raddr=(‘192.168.1.3’, 52951)> 客户端地址是 (‘192.168.1.3’, 52951) 客户发来的是 nihao 客户发来的是 hi 客户发来的是 how are you

客户端代码:

代码语言:javascript复制
from socket import *

ip_port=('192.168.1.3',8000)
back_log=5
buffer_size=1024

tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
代码语言:javascript复制
while True:
 client_msg = input('>>:').strip()
 if not client_msg: continue
 tcp_client.send(client_msg.encode('utf-8'))
 print('客户端已发送消息')
 data = tcp_client.recv(buffer_size)
 print('收到服务端发来的消息',data.decode('utf-8'))

客户端结果如下:

>>:nihao 收到服务端发来的消息 NIHAO >>:hi 收到服务端发来的消息 HI >>:how are you 收到服务端发来的消息 HOW ARE YOU

注意:如果输入可以是空格,但不可以是空,是空的话会继续要求输入。

socket 收发消息原理刨析

客户端发消息:是从应用程序发送到用户态内存,然后发送到内核态内存然后再通过网卡发出

客户端消息:是从网卡进入内核态内存然后发送到用户态内存

服务端同样如此。

服务端多次接收双向连接

windows 系统和linux/mac系统下,socket 不同之处:

断开客户端,windows 系统下服务端会报错,而linux/mac系统下,服务端接收的是空

windows 系统下:

服务端:from socket import *

代码语言:javascript复制
ip_port =('192.168.1.3',8001)
back_log = 5
buffer_size = 1024

tcp_server = socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(back_log)
while True:
 conn,add = tcp_server.accept()
 print('接收的链接是',conn)
 print('地址是',add)

 while True:
 print('服务端开始运行了')
 try:
 data = conn.recv(buffer_size)
           
 print('客户发来的是',data.decode('utf-8'))
 conn.send(data.upper())
 print('服务端已发送',data.upper())
 except Exception:
 break
    conn.close()
tcp_server.close()

客户端

代码语言:javascript复制
from socket import *

ip_port=('192.168.1.3',8001)
back_log=5
buffer_size=1024

tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)

while True:
    client_msg = input('>>:').strip()
    if not client_msg: continue
    tcp_client.send(client_msg.encode('utf-8'))
    print('客户端已发送消息')
    data = tcp_client.recv(buffer_size)
    print('收到服务端发来的消息',data.decode('utf-8'))

tcp_client.close()

linux 系统下:

代码语言:javascript复制
from socket import *

ip_port =('192.168.1.3',8001)
back_log = 5
buffer_size = 1024

tcp_server = socket(AF_INET,SOCK_STREAM)
代码语言:javascript复制
tcp_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
#在bind 之前进行socket 设置,使其不卡在time_wait占用地址
代码语言:javascript复制
tcp_server.bind(ip_port)
tcp_server.listen(back_log)
while True:
    conn,add = tcp_server.accept()
    print('接收的链接是',conn)
    print('地址是',add)

    while True:
        print('服务端开始运行了')
        try:
            data = conn.recv(buffer_size)
            if not data: break
            print('客户发来的是',data.decode('utf-8'))
            conn.send(data.upper())
            print('服务端已发送',data.upper())
        except Exception:
            break
    
tcp_server.close()

客户端代码如上,不变。

总结:服务端,客户端基本要求:

基于udp 的套接字

udp没有连接

服务端

代码语言:javascript复制
from socket import *
ip_port = ('192.168.1.5',8080)
buffer_size = 1024


udp_server = socket(AF_INET,SOCK_DGRAM) #SOCK_DGRAM 数据报
udp_server.bind(ip_port)


while True:
    data,addr = udp_server.recvfrom(buffer_size)
    print(data.decode('utf-8'))
    udp_server.sendto(data.upper(),addr)
代码语言:javascript复制
udp_server.close()
代码语言:javascript复制

客户端

代码语言:javascript复制
from socket import *
ip_port = ('192.168.1.5',8080)
buffer_size = 1024


udp_client = socket(AF_INET,SOCK_DGRAM) #SOCK_DGRAM 数据报

while True:
    msg = input('>>:').strip()
    udp_client.sendto(msg.encode('utf-8'),ip_port)

    data,addr = udp_client.recvfrom(buffer_size)
    print(data.decode('utf-8'))
代码语言:javascript复制
udp_client.close()

运用基于udp的套接字,来制作时间服务器(ntp),代码如下:

时间服务端:

代码语言:javascript复制
from socket import *
import time
ip_port = ('192.168.1.5',8080)
buffer_size = 1024

time_server = socket(AF_INET,SOCK_DGRAM)
time_server.bind(ip_port)

while True:
    data,addr = time_server.recvfrom(buffer_size)
    if not data:
 fmt = '%Y-%m-%d-%X'
 else:
 fmt = data.decode('utf-8')

    back_time = time.strftime(fmt)
    time_server.sendto(back_time.encode('utf-8'),addr)
代码语言:javascript复制
time_server.close()

时间客户端:

代码语言:javascript复制
from socket import *
ip_port = ('192.168.1.5',8080)
buffer_size = 1024


time_client = socket(AF_INET,SOCK_DGRAM) #SOCK_DGRAM 数据报

while True:
    msg = input('>>:').strip()
    time_client.sendto(msg.encode('utf-8'),ip_port)

    data,addr = time_client.recvfrom(buffer_size)
    print('服务器的标准时间是:',data.decode('utf-8'))
代码语言:javascript复制
time_client.close()

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

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

基于tcp实现远程命令

subprocess 模块

代码:变量名=subprocess.Popen(命令,shell=True,

            stderr=subprocess.PIPE,

            stdin=subprocess.PIPE,

            stdout=subprocess.PIPE)

代码语言:javascript复制

将命令结果封装在管道(PIPE)中,stdin 代表输入,stdout 代表输出,stderr代表报错。

想要读取内容:代码:变量名.stdrr.read()

服务端

代码语言:javascript复制
from socket import *
import subprocess

ip_port = ('192.168.1.5',8080)
back_log = 5
buffer_size = 1024

tcp_sever = socket(AF_INET,SOCK_STREAM)
代码语言:javascript复制
tcp_sever.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) #设置socket,使其不卡在time wait,占用地址
代码语言:javascript复制
tcp_sever.bind(ip_port)
tcp_sever.listen(back_log)

while True:
    conn,addr = tcp_sever.accept()
    print('新的客户端连接',addr)

    while True:
        try:
            #收消息
            cmd = conn.recv(buffer_size)
            if not cmd:break
            print('收到客户端的命令',cmd)

            #执行命令,得到命令的结果cmd_res
            res = subprocess.Popen(cmd.decode('utf-8'),shell=True,
 stderr=subprocess.PIPE,
 stdin=subprocess.PIPE,
 stdout=subprocess.PIPE)
            err = res.stderr.read()

 if err:
 cmd_res = err
 else:
 cmd_res = res.stdout.read()
            #发消息
       if not cmd_res:
代码语言:javascript复制
        cmd_res = '操作成功'.encode('utf-8') #此代码代表,如果cmd_res 为空的话,也会显示结果。
代码语言:javascript复制
            conn.send(cmd_res)
        except Exception as e:
            print(e)
            break

tcp_sever.close()

客户端

代码语言:javascript复制
from socket import *

ip_port = ('192.168.1.5',8080)
back_log = 5
buffer_size = 1024

tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)

while True:
    cmd = input('>>:').strip()
    if not cmd:continue
 if cmd == 'quit':break

    tcp_client.send(cmd.encode('utf-8'))
    cmd_res = tcp_client.recv(buffer_size)
    print('命令的执行结果是',cmd_res.decode('utf-8'))

tcp_client.close()

实操结果如下:

粘包

注意:只有tcp 会粘包,udp不会粘包。

粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据造成的。

解决粘包

方式一:比较低端一些

服务端:

代码语言:javascript复制
from socket import *
import subprocess

ip_port = ('192.168.1.5',8080)
back_log = 5
buffer_size = 1024

tcp_sever = socket(AF_INET,SOCK_STREAM)
tcp_sever.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
tcp_sever.bind(ip_port)
tcp_sever.listen(back_log)

while True:
    conn,addr = tcp_sever.accept()
    print('新的客户端连接',addr)

    while True:
        try:
            #收消息
            cmd = conn.recv(buffer_size)
            if not cmd:break
            print('收到客户端的命令',cmd)

            #执行命令,得到命令的结果cmd_res
            res = subprocess.Popen(cmd.decode('utf-8'),shell=True,
                                   stderr=subprocess.PIPE,
                                   stdin=subprocess.PIPE,
                                   stdout=subprocess.PIPE)
            err = res.stderr.read()

            if err:
                cmd_res = err
            else:
                cmd_res = res.stdout.read()
            #发消息
            if not cmd_res:
                cmd_res = '操作成功'.encode('utf-8')

            length =len(cmd_res)
 conn.send(str(length).encode('utf-8'))
 client_ready = conn.recv(buffer_size)
 if client_ready == b'ready':
 conn.send(cmd_res)
        except Exception as e:
            print(e)
            break

tcp_sever.close()

客户端

代码语言:javascript复制
from socket import *

ip_port = ('192.168.1.5',8080)
back_log = 5
buffer_size = 1024

tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)

while True:
    cmd = input('>>:').strip()
    if not cmd:continue
    if cmd == 'quit':break

    tcp_client.send(cmd.encode('utf-8'))

    length= tcp_client.recv(buffer_size)
 tcp_client.send(b'ready')
 length = int(length.decode('utf-8'))

 recv_msg = b''
 recv_size = 0
 while recv_size < length:
 recv_msg  =tcp_client.recv(buffer_size)
 recv_size = len(recv_msg)

    print('命令的执行结果是',recv_msg.decode('utf-8'))

tcp_client.close()

方式二:比较高端一些

服务端:

代码语言:javascript复制
from socket import *
import subprocess
import struct

ip_port = ('192.168.1.2',8080)
back_log = 5
buffer_size = 1024

tcp_sever = socket(AF_INET,SOCK_STREAM)
tcp_sever.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
tcp_sever.bind(ip_port)
tcp_sever.listen(back_log)

while True:
    conn,addr = tcp_sever.accept()
    print('新的客户端连接',addr)

    while True:
        try:
            #收消息
            cmd = conn.recv(buffer_size)
            if not cmd:break
            print('收到客户端的命令',cmd)

            #执行命令,得到命令的结果cmd_res
            res = subprocess.Popen(cmd.decode('utf-8'),shell=True,
                                   stderr=subprocess.PIPE,
                                   stdin=subprocess.PIPE,
                                   stdout=subprocess.PIPE)
            err = res.stderr.read()

            if err:
                cmd_res = err
            else:
                cmd_res = res.stdout.read()
            #发消息
            if not cmd_res:
                cmd_res = '操作成功'.encode('utf-8')

            length =len(cmd_res)
 conn.send(struct.pack('i',length))
            conn.send(cmd_res)
        except Exception as e:
            print(e)
            break

tcp_sever.close()

客户端:

代码语言:javascript复制
from socket import *
import struct
from functools import partial

ip_port = ('192.168.1.2',8080)
back_log = 5
buffer_size = 1024

tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)

while True:
    cmd = input('>>:').strip()
    if not cmd:continue
    if cmd == 'quit':break

    tcp_client.send(cmd.encode('utf-8'))

    length_data= tcp_client.recv(4)
 length = struct.unpack('i',length_data)[0]

 recv_msg = ''.join(iter(partial(tcp_client.recv, buffer_size), b''))

    print('命令的执行结果是',recv_msg.decode('utf-8'))

tcp_client.close()

总结:

socket 并发socketserver

socketserver-tcp套接字 并发

并发:即多个客户端与服务端的同时交互

服务端:

代码语言:javascript复制
import socketserver


class MyServer(socketserver.BaseRequestHandler):
 def handle(self):
 # print('conn is:', self.request) # self.request相当于conn
 # print('addr is:', self.client_address) # self.client_address相当于addr

 while True:
 try:
 # 收消息
 data = self.request.recv(1024)
 if not data: break
 print('收到客户端的消息是:', data,self.client_address)

 # 发消息
 self.request.send(data.upper())
 except Exception as e:
 print(e)
 break

if __name__ == '__main__':
 s = socketserver.ThreadingTCPServer(('192.168.1.2',8081),MyServer)
 s.serve_forever()

客户端:

代码语言:javascript复制
from socket import *

ip_port = ('192.168.1.2', 8081)
buffer_size = 1024

tcp_client = socket(AF_INET, SOCK_STREAM)
tcp_client.connect(ip_port)

while True:
    msg = input('>>: ').strip()
    if not msg: continue
    tcp_client.send(msg.encode('utf-8'))
    print('客户端已经发送消息')

    data = tcp_client.recv(buffer_size)
    print('收到服务端发来的消息', data.decode('utf-8'))

tcp_client.close()

socketserver-udp套接字 并发

对于tcp来说,self.request = conn

但是对于udp来说,self.request =(data,udp的套接字对象

服务端:

代码语言:javascript复制
import socketserver

class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
 #self.request[0] 是data
 #self.request[1]是udp的socket套接字
 #self.client_address 是conn
 #收消息
 print('收到客户端的消息是',self.request[0],self.client_address)
 #发消息
 self.request[1].sendto(self.request[0].upper(),self.client_address)


if __name__ == '__main__':
 s=socketserver.ThreadingUDPServer(('192.168.1.2',8082),MyServer) #多线程
 s.serve_forever()

客户端:

代码语言:javascript复制
from socket import *
ip_port=('192.168.1.2',8082)
buffer_size=1024

udp_client=socket(AF_INET,SOCK_DGRAM) #数据报

while True:
    msg=input('>>: ').strip()
    udp_client.sendto(msg.encode('utf-8'),ip_port)

    data,addr=udp_client.recvfrom(buffer_size)
    # print(data.decode('utf-8'))
    print(data)

认证客户端合法性

服务端

代码语言:javascript复制
from socket import *
import os
import hmac
secret_key = b'ni hao ma'

def conn_auth(conn):
    '''
    认证客户端连接
    :param conn:
    :return:
    '''
    print('开始验证连接的合法性')
    msg = os.urandom(32)
    conn.sendall(msg)
    h=hmac.new(secret_key,msg)#加严
    digest=h.digest()
    respone=conn.recv(len(digest))
    return hmac.compare_digest(respone,digest)#对比respone 与digest 是否一致



def data_handler(conn, bufize=1024):
    if not conn_auth(conn):
        print('该连接不合法,关闭')
        conn.close()
        return
    print('连接合法,开始通信')

    while True:
        data = conn.recv(bufize)
        if not data: break
        conn.send(data.upper())



def server_handle(ip_port,bufize,backlog=5):
    '''
        只处理连接
        :param ip_port:
        :param bufize:
        :param backlog:
        :return:
        '''
    tcp_socket_server = socket(AF_INET, SOCK_STREAM)
    tcp_socket_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    # 在bind 之前进行socket 设置,使其不卡在time_wait占用地址
    tcp_socket_server.bind(ip_port)
    tcp_socket_server.listen(backlog)
    while True:
        conn, addr = tcp_socket_server.accept()
        print('conn是%s,addr是%s' % (conn, addr))
        data_handler(conn, bufize)
    tcp_socket_server.close()

if __name__ == '__main__':
    ip_port =('192.168.1.2',8080)
    bufize = 1024
    server_handle(ip_port,bufize)

客户端:

代码语言:javascript复制
from socket import *
import os,hmac

secret_key = b'ni hao ma'

def conn_auth(conn):

    '''
        验证客户端到服务器的连接合法性
        :param conn:
        :return:
        '''
    msg = conn.recv(32)
    h = hmac.new(secret_key, msg)
    digest = h.digest()
    conn.sendall(digest)


def client_handler(ip_port,bufsize=1024):
    tcp_socket_client = socket(AF_INET, SOCK_STREAM)
    tcp_socket_client.connect(ip_port)
    conn_auth(tcp_socket_client)

    while True:
        #发消息
        cmd = input('>>:').strip()
        if not cmd: continue
        if cmd == 'quit': break
        tcp_socket_client.send(cmd.encode('utf-8'))
        #收消息
        data=tcp_socket_client.recv(bufsize)
        print(data.decode('utf-8'))
    tcp_socket_client.close()

if __name__ == '__main__':
    ip_port = ('192.168.1.2', 8080)
    bufize = 1024
    client_handler(ip_port,bufize)

作业

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/154421.html原文链接:https://javaforall.cn

0 人点赞