Python Basic - Socket编程-不同主机间交互

SOCKET 编程

SOCKET 即“套接字” 英文翻译叫插座,插座这个词很形象,设置一个插座,然后有需要使用的时候可以使用相应的插头进行对接即可使用这个插座提供的资源。在现实生活中也有很多SOCKET,如电源插座,提供的服务是用来供电的,网线插座是用来连通网络的。那么我们现在讲的SOCKET编程,是一种建立在TCP/IP协议的的基础上的(就是运行在网络的基础上)所以,说的是络的socket,网络的SOCKET

网络上的SOCKET的几大组成(IP+端口+协议)

IP: 就是我们平常所说的IP地址,就是一段点分十进制的数字
端口:tcp/udp都有端口号,每个端口可供一个socket使用。
协议:传输数据包目前常用的有两种协议,TCP/UDP

SOCKET通信过程

在这里插入图片描述

  1. 服务器根据地址类型(ipv4,ipv6),socket类型,协议 创建特定的socket。
  2. 服务器为socket绑定IP地址和端口号
  3. 服务器socket监听端口号请求,随时准备接收客户端发来的连接,此时服务器的socket并没有被打开
  4. 客户端创建socket
  5. 客户端打开socket,根据服务器IP地址和端口号试图连接服务器socket
  6. 服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息,此时socket进入到阻塞状态,阻塞就是accept()方法一直等到客户端返回连接信息后才返回,开始接收下一个客户端连接请求。
  7. 客户端连接成功,向服务器发送连接状态信息。
  8. 服务器accept方法返回,连接成功。
  9. 客户端向socket定入信息(或服务端向socket写入信息)
  10. 服务器读取信息(客户读取信息)
  11. 客户端关闭会话
  12. 服务器端关闭会话

相关方法与参数

方法用途
socket.bind(address)将套接字与IP地址绑定,address的格式取决于地址族,在AF_INET下,以原组(host,port)的形式表示地址
socket.listen(backlog)开始监听入站连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。backlog等于5,表示操作系统内核已经接到了连接请求,但是服务器还没有调用accept时行处理的连接个数最大为5.此值不能无限大,因为要在内核中维护连接队列
socket.setblocking(bool)是否阻塞(默认为True),如果设置为False,那么accept和recv时,一旦没有数据就报错
socket.accept()接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址;接收TCP客户的连接(阻塞式)等待连接的到来
socket.connect(address)连接到(address)的套接字,一般情况下,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
socket.connect_ex(address)同上,只不过会有返回值,连接成功时返回0,连接失败时候返回编码,例如:10061
socket.close()关闭套接字
socket.recv(bufsize[,flag])接受套接字的数据,数据以字符串形式返回,bufsize指定最多可以接收的量,flag提供有关消息的其它信息,通常可以忽略
socket.recvfrom(bufsize[,flag])与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数字的套接字地址。
socket.send(string[,flag])将string中的数据发送到连接的套接字,返回值是要发送的字节数量,该数量可能小于string的字节大小。所以,可以没有将数字一次性发送完成
socket.sendall(string[,flag])将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。内部通过递归调用send,将所有内容发送出去。
socket.sendto(string[,flag],address)将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。
socket.settimeout(timeout)设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚刚创建套接字时设置,因为它们可能用于连接的操作(如client连接最多等待5S)
socket.getpeername()返回套接字的远程地址,返回值通常是元组(ipaddr,port)
socket.getsockname()返回套接字自己的地址,通常是一个元组(ipaddr,port)
socket.fileno套接字的文件描述符

套接字(socket)的形参

def __init__(self, family=-1, type=-1, proto=-1, fileno=None):

family=AF_INNET 是ipv4下的参数 ,用于不同主机之间的通信
family=AF_INNET6 是ipv6下的参数 ,用于不同主机之间的通信
family=AF_UNIX 在unix上不同的里程之间进行通信时使用。linux本地的socket文件。

type=SOCK_STREAM / SOCK_Dgram 流式(对于建立TCP连接的时候) 数据式(对于建立UDP连接)
type=SOCK_ROW 裸套接字

这两个参数都有默认值,所以在写socket.socket() 时候 就会默认创建一个套接字,因为两个最重要的参数都有默认的。

创建一个套接字

import socket
sk = socket.socket()

套接字绑定通信地址(通常是IP地址)

address = (127.0.0.1,8080)
sk.bind(address)

监听套接字,并设置队列长度

sk.listen(3)

最大能排多少队,这里最大能排3个队

服务端配置

import socket

sk = socket.socket()
address = ("127.0.0.1",8080)
sk.bind(address)
sk.listen(3)

print("waiting......")
conn = sk.accept()
print(conn)

阻塞住,避免监听的进程终止。
下面为客户端的代码

客户端进行连接

客户端配置

import socket

sk = socket.socket()
address = ("127.0.0.1",8080)
sk.connect(address)

运行结束后服务端结果

waiting......
(<socket.socket fd=708, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 49335)>, ('127.0.0.1', 49335))

====
客户端连接完成后即终止了,返回值为一个元组的信息。这个元组信息是在服务端上用于与客户端通信的socket,里面包含两个元素,一个是“连接项”,另外一个是客户端的IP地址跟端口
所以让两个变量接收两个值。

稍作修改

import socket

sk = socket.socket()
address = ("127.0.0.1",8080)
sk.bind(address)
sk.listen(3)

print("waiting......")
conn,clientinfo = sk.accept()
print(conn)
print(clientinfo)

输出结果

waiting......
<socket.socket fd=700, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 49724)>
('127.0.0.1', 49724)

测试客户端给服务端发送一个简单的消息

服务器端

import socket

sk = socket.socket()
address = ("127.0.0.1",8080)
sk.bind(address)
sk.listen(3)

print("waiting......")
conn,clientinfo = sk.accept()#accept()方法会将此程序先阻塞住,等待客户端来connect,accept()是用于接收客户端的套接字
print(conn)
print(clientinfo)

conn.send("test")

accept()方法会将此程序先阻塞住,等待客户端来connect,accept()是用于接收客户端的套接字

客户端

import socket

sk = socket.socket()
address = ("127.0.0.1",8080)
sk.connect(address)
sk.send("test")

client端接收数据,正常情况下,recv在没有接收到数据之前也会将会话阻塞住,只有收到数据后才会继续往下执行。

运行结果–报错(需要发送byte类型)

"""
<socket.socket fd=704, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 57833)>
('127.0.0.1', 57833)
Traceback (most recent call last):
  File "E:/Python/Pycharm/Lab/week11/sockettest.py", line 19, in <module>
    conn.send("test")
TypeError: a bytes-like object is required, not 'str'
"""

修改发送和接收byte类型

在python3 里,无论是发送还是接收,都必需了byte类型的数据。
修改一:发送之前将str⇒ bytes类型进行发送

conn.send(bytes("test","utf8"))

修改二:接收数据后转换成str字符串进行展示

print(str(recv_data,"utf8"))

注意问题

真正通信的时候使用的是conn的方法,而不是socket的方法。
在accept()方法接收到后,即建立了一个会话,所以以后的会话都建立在这个会话上面进行。
而且一个服务端会跟很多客户端进行连接,所以每个连接都要其于conn来进行。
所以无论是客户端还是服务端的通信都得通过conn来进行。
但是在客户端是sk.recv,在服务端却是conn.send,这两个是一个东西。
服务端是可以用于跟多个客户端进行通信的,所以服务端会产生多个socket。每个socket对应一个客户端的socket。

close()

这个方法,服务端客户端都有
client:
sk.close()
server:
conn.close()
服务端不能使用sk.close() 一关就全关了,其它client都连接不了了。
sk.close() 只是终止了通信,但是sk这个对象还在,还可以继续往下走。

示例:连续发送消息

服务端

import socket

sk = socket.socket()
address = ("127.0.0.1",8080)
sk.bind(address)
sk.listen(3)

print("waiting......")
conn,clientinfo = sk.accept()
print(conn)
print(clientinfo)

while True:
    input_data = input(">>>>>")

    conn.send(bytes(input_data,"utf8"))

客户端

import socket

sk = socket.socket()
address = ("127.0.0.1",8080)
sk.connect(address)

while True:
    recv_data = sk.recv(1024)
    print(str(recv_data, "utf8"))

运行程序并测试

服务端
waiting......
<socket.socket fd=680, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 58876)>
('127.0.0.1', 58876)
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>?
>>>>>
客户端
你
吃
饭
了
吗
?

示例:对话模式

服务端

import socket

sk = socket.socket()
address = ("127.0.0.1",8080)
sk.bind(address)
sk.listen(3)

print("waiting......")
conn,clientinfo = sk.accept()
print(conn)
print(clientinfo)

while True:
    input_data = input(">>>>>")
    conn.send(bytes(input_data,"utf8"))
    recv_data = conn.recv(1024)
    print("Lina:",str(recv_data,"utf8"))

客户端

import socket

sk = socket.socket()
address = ("127.0.0.1",8080)
sk.connect(address)

while True:
    recv_data = sk.recv(1024)
    print("Bob:",str(recv_data, "utf8"))
    send_data = input(">>>>>")
    sk.send(bytes(send_data,"utf8"))

运行程序测试结果

服务端

waiting......
<socket.socket fd=696, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 59620)>
('127.0.0.1', 59620)
>>>>>你吃饭了吗?
Lina: 吃过了
>>>>>你中午吃的什么?
Lina: 中午吃的米饭
>>>>>好的,下午聊
Lina: 好的,886
>>>>>

客户端

Bob: 你吃饭了吗?
>>>>>吃过了
Bob: 你中午吃的什么?
>>>>>中午吃的米饭
Bob: 好的,下午聊
>>>>>好的,886

问题:发送数据为空的情况

如果只是客户端主动关闭会话连接的话,则发送的是空数据过去的,发送了数据,只是内容为空。
但是如果client直接回车,不就是发送的空数据嘛,会不会直接终止?
结论证明 ,如果直接回车发送一个空数据,服务端根本不会继续往下走。还是阻塞在recv()这里。所以不能让客户端发送空数据,像QQ一样,为什么会提示数据为空无法发送。估计也是同理。

怎么解决发送数据为空的问题

Server端
import socket

sk = socket.socket()
address = ("127.0.0.1",8080)
sk.bind(address)
sk.listen(3)

print("waiting......")
conn,clientinfo = sk.accept()
print(conn)
print(clientinfo)

while True:
    input_data = input(">>>>>")
    while not input_data:
        print("您未输入任何数据,请您重新输入")
        input_data = input(">>>>>")

    conn.send(bytes(input_data,"utf8"))
    recv_data = conn.recv(1024)
    if not recv_data: break
    print("Lina:",str(recv_data,"utf8"))

conn.close()

Client端
import socket

sk = socket.socket()
address = ("127.0.0.1",8080)
sk.connect(address)

while True:
    recv_data = sk.recv(1024)
    print("Bob:",str(recv_data, "utf8"))
    send_data = input(">>>>>")
    while not send_data:
        print("您未输入任何数据,请您重新输入")
        send_data = input(">>>>>")
    sk.send(bytes(send_data,"utf8"))

sk.close()

在用户输入数据时,增加一个循环,如果数据为空,则提示用户重新输入
另外还有一种处理方法,用户一回车,本来是发送的空数据过去,但是对方还是阻塞状态的,还在等待信息进行接收,所以此时此时跟在用户后面可以判断一下,如果用户什么也没有输入,则直接帮用户输入一个数据,将此数据发送给对方。

运行结果
Bob: 1
>>>>>2


Bob: 3
>>>>>您未输入任何数据,请您重新输入
>>>>>您未输入任何数据,请您重新输入
>>>>>

listen()—>用于设置最大排队数

def listen(self, __backlog: int = …)
在形参中可以跟一个数字,该数字可用于设置最大的连接等待数。当超过最大连接数后,会产生一个报错,如下所示:

Traceback (most recent call last):
  File "E:/Python/Pycharm/Lab/week11/socketclient.py", line 11, in <module>
    sk.connect(address)
ConnectionRefusedError: [WinError 10061] No connection could be made because the target machine actively refused it

超过最大等等数后,

第一个会话结束后,第二个会话可以继续产生数据

问题:客户端暴力关闭程序

如果client 直接关闭程序,不正常关闭,则server会进行一个提示:

Traceback (most recent call last):
  File "E:/Python/Pycharm/Lab/week11/sockettest.py", line 26, in <module>
    recv_data = conn.recv(1024)
ConnectionResetError: [WinError 10054] An existing connection was forcibly closed by the remote host

报错后直接退出了。
这样就无法接受其它的会话了。怎么样样处理后可以与后续等待的继续对话呢?
首先得知道是哪条代码造成的
在server程序中,程序处于阻塞状态的是recv()方法,但是客户端莫名其秒的将通道关闭了,所以会产生报错。
所以发生报错的代码为“recv()”

本来是阻塞的,等待conn进行处理,但是client直接搞没了,所以不能继续工作了,
这里需要使用异常处理了,这里需要用到异常处理了:

怎么解决

一个较为完美示例

Server端代码

import socket

sk = socket.socket()
address = ("127.0.0.1",9200)
sk.bind(address)
sk.listen(3)
print("waiting.....")
connection,clientinfo = sk.accept()
print(connection)
print(clientinfo)


while True:
    try:
        print("waiting for message input.....")
        user_recv = connection.recv(1024)
    except ConnectionResetError as e:
        connection.close()
        print(e)
        print(clientinfo,"已经主动关闭了会话")
        connection, clientinfo = sk.accept()#因为上面对connection关闭了,所以这里需要重新来一个connection等待连接
        continue
    if not user_recv:# 当用户输入exit的时候,服务端会接收到一个“空”的内容,所以判断用户终止了会话,所以我们接收一个新的连接。
        print("当前用户已经退出会话。")
        connection.close()
        connection, clientinfo = sk.accept()
        print("新用户建立了一个连接,客户端信息:",clientinfo)
        continue


    print(str(user_recv,"utf8"))

    user_input = input(">>>")

    if user_input == "exit":
        print("exiting")
        continue

    try:
        connection.send(bytes(user_input,"utf8"))
    except ConnectionResetError as e:
        connection.close()
        print(e)
        print(clientinfo,"已经主动关闭了会话")
        connection, clientinfo = sk.accept()#因为上面对connection关闭了,所以这里需要重新来一个connection等待连接
        continue

connection.close()

client代码

import socket

sk = socket.socket()
address = ("127.0.0.1",9200)
sk.connect(address)


while True:
    user_input = input(">>>")
    if user_input == "exit":
        print("exiting....")
        break
    #if not user_input:


    sk.send(bytes(user_input,"utf8"))


    user_recv = sk.recv(1024)
    print(str(user_recv,"utf8"))
sk.close()

不会报错 Linux下,如果强制关闭,则直接认为发送了一个空消息,所以直接捕捉空消息即可。

Shell脚本高级编程教程,希望对你有所帮助。 Example 10-23. Using continue N in an actual task: 1 # Albert Reiner gives an example of how to use "continue N": 2 # --------------------------------------------------------- 3 4 # Suppose I have a large number of jobs that need to be run, with 5 #+ any data that is to be treated in files of a given name pattern in a 6 #+ directory. There are several machines that access this directory, and 7 #+ I want to distribute the work over these different boxen. Then I 8 #+ usually nohup something like the following on every box: 9 10 while true 11 do 12 for n in .iso.* 13 do 14 [ "$n" = ".iso.opts" ] && continue 15 beta=${n#.iso.} 16 [ -r .Iso.$beta ] && continue 17 [ -r .lock.$beta ] && sleep 10 && continue 18 lockfile -r0 .lock.$beta || continue 19 echo -n "$beta: " `date` 20 run-isotherm $beta 21 date 22 ls -alF .Iso.$beta 23 [ -r .Iso.$beta ] && rm -f .lock.$beta 24 continue 2 25 done 26 break 27 done 28 29 # The details, in particular the sleep N, are particular to my 30 #+ application, but the general pattern is: 31 32 while true 33 do 34 for job in {pattern} 35 do 36 {job already done or running} && continue 37 {mark job as running, do job, mark job as done} 38 continue 2 39 done 40 break # Or something like `sleep 600' to avoid termination. 41 done 42 43 # This way the script will stop only when there are no more jobs to do 44 #+ (including jobs that were added during runtime). Through the use 45 #+ of appropriate lockfiles it can be run on several machines 46 #+ concurrently without duplication of calculations [which run a couple 47 #+ of hours in my case, so I really want to avoid this]. Also, as search 48 #+ always starts again from the beginning, one can encode priorities in 49 #+ the file names. Of course, one could also do this without `continue 2', 50 #+ but then one would have to actually check whether or not some job 51 #+ was done (so that we should immediately look for the next job) or not 52 #+ (in which case we terminate or sleep for a long time before checking 53 #+ for a new job).
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值