python网络编程(socket)

socket介绍

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。

建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。

Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原义那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务。

Socket非常类似于电话插座。以一个国家级电话网为例,电话的通话双方相当于相互通信的2个进程,区号是它的网络地址;区内一个单位的交换机相当于一台主机,主机分配给每个用户的局内号码相当于Socket号。任何用户在通话之前,首先要占有一部电话机,相当于申请一个Socket;同时要知道对方的号码,相当于对方有一个固定的Socket。然后向对方拨号呼叫,相当于发出连接请求(假如对方不在同一区内,还要拨对方区号,相当于给出网络地址)。假如对方在场并空闲(相当于通信的另一主机开机且可以接受连接请求),拿起电话话筒,双方就可以正式通话,相当于连接成功。双方通话的过程,是一方向电话机发出信号和对方从电话机接收信号的过程,相当于向Socket发送数据和从socket接收数据。通话结束后,一方挂起电话机相当于关闭Socket,撤消连接。

socket编程

        socket协议 

TCP: 面向连接的通信协议

建立连接:三次握手

(1)  第一次握手:Client端(客户端)调用connect函数调用,系统为Client随机分配一个端口,连同传入connect中的参数(Server的IP和端口),这就形成了一个连接四元组,客户端发送一个带SYN:同步序列编号(Synchronize Sequence Numbers)标志的TCP报文到服务器。这是三次握手过程中的报文1。connect调用让Client端的socket处于SYN_SENT(请求链接)状态,等待服务器确认。 

注:如果连接成功了就变为ESTABLISHED(确定的),此时SYN_SENT状态非常短暂。

(2)第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态; 

注:在TCP/IP协议中,如果接收方成功的接收到数据,那么会回复一个ACK数据。

注:  ACK (Acknowledgement)即是确认字符,在数据通信中,接收站发给发送站的一种传输类控制字符。表示发来的数据已确认接收无误。
(3) 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户器和客务器进入ESTABLISHED状态,完成三次握手。连接已经可以进行读写操作。

断开连接:四次挥手

(1) 当client想要关闭它与server之间的连接。client(某个应用进程)首先调用close主动关闭连接,这时TCP发送一个FIN M;client端处于FIN_WAIT1状态。 

注:FIN是用来扫描保留的端口,发送一个FIN包(或者是任何没有ACK或SYN标记的包)到目标的一个开放的端口,然后等待回应。许多系统会返回一个复位标记。

(2) 当server端接收到FIN M之后,执行被动关闭。对这个FIN进行确认,返回给client ACK。当server端返回给client ACK后,client处于FIN_WAIT2状态,server处于CLOSE_WAIT状态。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据; 

(3) 一段时间之后,当server端检测到client端的关闭操作(read返回为0)。接收到文件结束符的server端调用close关闭它的socket。这导致server端的TCP也发送一个FIN N;此时server的状态为LAST_ACK。 

(4) 当client收到来自server的FIN后 。 client端的套接字处于TIME_WAIT状态,它会向server端再发送一个ack确认,此时server端收到ack确认后,此套接字处于CLOSED状态。这样每个方向上都有一个FIN和ACK。

UDP:无连接的协议

UDP:协议传输容易丢包,成本低,但是速度快.

1. 通信步骤

服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

被动:当服务器开启,不会主动访问客户端,只是被动等待请求

阻塞:当有一个用户与服务端发起通信的时候,这个时候信道阻塞,其他用户无法访问

注:TCP发送数据时,已经建立好TCP连接,所以不需要指定地址,UDP是面向无连接的,所以每次发送要指定是发送给谁

服务端与客户端之间发送的数据必须是字符串

2. 通信方式

单  工 通信双方只有一条信道,且通信身份不可逆。 BB机

半双工 通信双方只有一条信道,且通信身份可逆。   对讲机

全双工 通信双方只有多条信道,且通信身份可逆。   手机

socket tcp

TCP服务端     

1、创建套接字

sock = socket.socket(socket.AF_INET,sokcet.SOCK_STREAM)

2、绑定套接字到本地IP与端口

sock.bind(())

3、开始监听

sock.listen()

4、接受客户端的连接请求

sock.accept()

5、接收传来的数据,并发送给客户端数据

sock.recv()  sock.send()                                                           

6、传输完毕,关闭套接字

sock.close()

 

案例:

#导入套接字
import socket
#创建一个套接字,并且以tcp连接
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#绑定套接字,参数为双元素元组,如果写空代表本地所有ip
#第二个参数是端口 0-65525
sock.bind(('',9000))
#监听,最大端口为5
sock.listen(5)
#接收连接请求
#conntent 用来接收请求用户的消息和发送对该用户的的消息功能
#address 是请求用户的身份(ip,port)
content,address = sock.accept()
print('%s:%s id connected……'%address)
#发送数据,数据必须是字节的形式
content.send('hello'.encode())
#接收数据参数是字节的形式
print(content.recv(521))
#关闭套接字
sock.close()

注:AF_INET(又称 PF_INET)是 IPv4 网络协议的套接字类型

 

 

TCP 客户端

1.  创建套接字

sock = socket.socket(socket.AF_INET,sokcet.SOCK_STREAM)

2.  连接服务端的地址和端口

sock.connect(())

3.  连接后发送数据和接受数据

sock.recv()  sock.send()

4.  传输完毕,关闭套接字

sock.close()

案例:

import socket
#创建一个套接字
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#套接字连接
sock.connect(('127.0.0.1',9000))
#接收数据
print(sock.recv(521))
#发送数据
sock.send('Hi'.encode())
#关闭套接字
sock.close()

socket UDP

为方便理解,假设为服务端

1.创建套接字(SOCK_DGRAM数据报)

sock = socket.socket(socket.AF_INET,sokcet.SOCK_DGRAM)

2.绑定套接字到本地IP与端口

sock.bind(())

3.发送接收数据(需要指定发送给和接收具体的地址端口)

sock.sendto()

sock.recvfrom()

4.传输完毕,关闭套接字

sock.close()

创建server端

案例:

import socket

#创建一个个套接字 UDP

sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

#绑定端口

sock.bind(('',9099))

#接收信息,data是客户端发送的信息,address是ip和端口

data,address = sock.recvfrom(521)

#发送数据,到指定地址

sock.sendto('server'.encode(),('127.0.0.1',8001))

#打印信息和地址

print(data.decode(),address)

#关闭套接字

sock.close()

为方便理解,假设为客户端

1、创建套接字

sock = socket.socket(socket.AF_INET,sokcet.SOCK_DGRAM)

2、绑定套接字到本地IP与端口

sock.bind(())

3、发送接收数据(需要指定发送给谁)

sock.sendto()

sock.recvfrom()

4、传输完毕,关闭套接字

sock.close()

案例:

import socket

#创建一个套接字

sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

#绑定端口

sock.bind(('127.0.0.1',8001))

#发送信息到一个地址

sock.sendto('client'.encode(),('127.0.0.1',9099))

#得到服务端发送的信息

data,address = sock.recvfrom(521)

#打印响应的信息和地址

print(data.decode(),address)

sock.close()

TCP/UDP通信

1. TCP 服务端

import socket
print('正在连接中……')
def dealclient(sock,addr):
    info = sock.recv(1024).decode()
    while info != 'exit':
        print('客户端:'+info)
        send_mes = input('>>>')
        sock.send(send_mes.encode())
        if send_mes =='exit':
            break
        info = sock.recv(1024).decode()
    sock.close()
if __name__ == '__main__':
    s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('127.0.0.1', 9001))
    s.listen(1)
    sock, addr=s.accept()
    dealclient(sock,addr)


2. TCP 客户端

import socket
s= socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('127.0.0.1',9001))
print('已经建立连接……')
info = ''
while info != 'exit':

  send_mes=input('>>>')
  s.send(send_mes.encode())
  if send_mes =='exit':
    break

  info = s.recv(1024).decode()
  print('服务器:' + info)
s.close()

3. UDP server

import socket
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.bind(('127.0.0.1',9002))
print('wait ....')
while True:
    data,addr = s.recvfrom(1024)
    print('客户端:'+data.decode())
    info = input('>>>')
    if data.decode() == 'exit':
        break
    s.sendto(info.encode(),addr)
s.close()
4.UDP client

import socket

s= socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.connect(('127.0.0.1',9002))
print('connection……')
info = ''
while info != 'exit':
  send_mes=input('>>>')
  s.sendall(send_mes.encode())
  if send_mes =='exit':
    break
  info = s.recv(1024).decode()
  print('服务端:' + str(info))
s.close()

TCP和UDP对比:

1.基于连接与无连接;
2.对系统资源的要求(TCP较多,UDP少);
3.UDP程序结构较简单;
4.流模式与数据报模式 ;5.TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。

SocketServer

SocketServer是socket的再封装。

每个客户端连接服务器时,SocketServer会在服务器上创建一线程或者进程,专门负责处理客户端

Socketserver 是在socket的基础上python编写的用于编写web服务的基础框架

SocketServer内部使用 IO多路复用,以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进程” 专门负责处理当前客户端的所有请求。

在py2.x版本当中为 SocketServer

在py3.x版本当中为 socketserver

SocketServer中类分为三种类型

一是Server类:BaseServer/TCPServer/UDPServer用来接收客户的请求。TCPServer处理TCP请求,UDPServer处理UDP请求。BaserServer是基类,不能直接使用。TCPServer继承自BaseServer,UDPServer继承自TCPServer。暂时不明白为什么UDPServer要继承自TCPServer,后面再说。

二是Handler类:BaseRequestHandler/DatagramRequestHandler/StreamRequestHandler用来处理每一个客户请求。一般用使用BaseRequestHandler就行。

但StreamRequestHandler/DatagramRequestHandler提供了一些特别的功能,前者用来处理流式(TCP)请求,后者处理数据报(UDP)请求。Server每收到一个客户请求就会创建一个Handler类示例来处理该请求。默认情况下,TCPServer/UDPServer是单进程单线程的模型,依次处理每个客户请求,一个请求处理完毕才能接着处理下一个请求。

三是MixIn类:ForkingMixIn/ThreadingMixIn用来为Server提供多进程/多线程并发处理能力的。ForkingMixIn是多进程模型,ThreadingMixin是多线程模型。这里特别巧妙的是,你只要创建一个类,同时继承Server类和MixIn类就能自动获得并发处理请求的能力。该模块本身就直接提供了这种类。

服务端代码:

import socketserver
#创建一个类,继承BaseRwequsetHanler
class MYHandle(socketserver.BaseRequestHandler):
    #类似于析构函数__init__
    def setup(self):
        print('myhandle is start ')
    #用来处理逻辑
    def handle(self):
        #self.server 当前服务
        #self.client_address 客户端的身份(ip.port
        #self.request 用来接收和发送数据
        print(self.server)
        print('%s:%s is connect '%self.client_address)
        recv = self.request.recv(521)
        print(recv)
        self.request.send('i am server '.encode())
#类似于__del__
    def finish(self):
        print('myhandle is stop ')
if __name__ == '__main__':
    #第一个参数来绑定ip
    #第二个位开启对象
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8000),MYHandle)
    #开启服务,永远
    server.serve_forever()

客户端代码(客户端还是使用socket)

import socket
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect(('127.0.0.1',8000))
sock.send('client2'.encode())
print(sock.recv(521).decode())

sock.close()

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值