前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >网络编程:socket 编程

网络编程:socket 编程

作者头像
全栈程序员站长
发布2022-09-07 15:48:38
2.7K0
发布2022-09-07 15:48:38
举报
文章被收录于专栏:全栈程序员必看

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

socket 编程

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

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

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

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

-osi 七层:

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

<span role="heading" aria-level="2">网络编程:socket 编程
<span role="heading" aria-level="2">网络编程:socket 编程

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

socket概念(socket也是套接字)

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

套接字分为2种:

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

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

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

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

用于网络编程

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

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

<span role="heading" aria-level="2">网络编程:socket 编程
<span role="heading" aria-level="2">网络编程:socket 编程

例如:

服务端:

代码语言: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 底层工作原理

<span role="heading" aria-level="2">网络编程:socket 编程
<span role="heading" aria-level="2">网络编程: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 收发消息原理刨析

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

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

服务端同样如此。

<span role="heading" aria-level="2">网络编程:socket 编程
<span role="heading" aria-level="2">网络编程: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()

客户端代码如上,不变。

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

<span role="heading" aria-level="2">网络编程:socket 编程
<span role="heading" aria-level="2">网络编程:socket 编程
<span role="heading" aria-level="2">网络编程:socket 编程
<span role="heading" aria-level="2">网络编程:socket 编程

基于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 在自己这段的缓冲区为空时,就收一个空

<span role="heading" aria-level="2">网络编程:socket 编程
<span role="heading" aria-level="2">网络编程:socket 编程

基于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()

实操结果如下:

<span role="heading" aria-level="2">网络编程:socket 编程
<span role="heading" aria-level="2">网络编程:socket 编程

粘包

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

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

<span role="heading" aria-level="2">网络编程:socket 编程
<span role="heading" aria-level="2">网络编程:socket 编程

解决粘包

方式一:比较低端一些

服务端:

代码语言: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()

总结:

<span role="heading" aria-level="2">网络编程:socket 编程
<span role="heading" aria-level="2">网络编程:socket 编程
<span role="heading" aria-level="2">网络编程:socket 编程
<span role="heading" aria-level="2">网络编程:socket 编程
<span role="heading" aria-level="2">网络编程:socket 编程
<span role="heading" aria-level="2">网络编程:socket 编程

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)

作业

<span role="heading" aria-level="2">网络编程:socket 编程
<span role="heading" aria-level="2">网络编程:socket 编程

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档