python学习之 socket

本文深入解析了Socket编程的基础概念,包括套接字的工作原理、不同类型的Socket及其应用场景,并提供了Python Socket编程的实际示例。
部署运行你感兴趣的模型镜像

Socket的中文名

套接字(socket):源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字。其用于标识客户端请求的服务器和服务

它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

socket的作用:

区分不同应用程序进程间的网络通信和连接,主要有3个参数:通信的IP地址、使用的传输层协议(TCP或UDP)和使用的端口号。Socket原意是 “插座”。通过将这3个参数结合起来,与一个“插座”Socket绑定,应用层就可以和传输层通过套接字接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。(需要源IP,源端口,目的IP,目的端口,协议才能准确区分不同的应用程序(端口号也称作应用程序编号))

Socket可以看成在两个程序进行通讯连接中的一个端点,是连接应用程序和网络 驱动程序的桥梁,Socket在应用程序中创建,通过绑定与网络驱动建立关系。此后,应用程序送给Socket的数据,由Socket交给网络驱动程序向网络上发送出去。计算机从网络上收到与该Socket绑定IP地址和 端口号相关的数据后,由网络驱动程序交给Socket,应用程序便可从该Socket中提取接收到的数据,网络应用程序就是这样通过Socket进行数据的发送与接收的。

官方文档:

socket编程howto:http://python.usyiyi.cn/documents/python_278/howto/sockets.html#socket-howto

Sockets

I’m only going to talk about INET sockets, but they account for at least 99% of the sockets in use.And I’ll only talk about STREAM sockets - unless you really know what you’re doing (in which case this HOWTO isn’t for you! ), you’ll get better behavior and performance from a STREAM socket than anything else. I will try to clear up the mystery of what a socket is, as well as some hints on how to work with blocking and non-blocking sockets. But I’ll start by talking about blocking sockets.You’ll need to know how they work before dealing with non-blocking sockets.(本文只涉及Inet socket,但是它已经包含了99%的socket使用)

(关于socket 的 block 和 non-block:By default, socket operations are blocking: when the thread calls a method like connect orrecv, it pauses until the operation completes.2 block的意义是socket在调用一个方法后会一直等待这个socket操作完成才会进行下一步操作)

Part of the trouble with understanding these things is that “socket” can mean a number of subtly different things, depending on context. So first, let’s make a distinction between a “client” socket - an endpoint of a conversation, and a “server” socket, which is more like a switchboard operator(接线员). The client application (your browser, for example) uses “client” sockets exclusively; the web server it’s talking to uses both “server” sockets and “client” sockets.(浏览器只使用client socket,而服务器同时使用client socket和server scoket)


Creating a Socket

Roughly speaking, when you clicked on the link that brought you to this page, your browser did something like the following:(当你在访问这个链接的时候,浏览器做了以下的事情)

#create an INET, STREAMing socket
s = socket.socket(
    socket.AF_INET, socket.SOCK_STREAM)
#now connect to the web server on port 80
# - the normal http port
s.connect(("www.mcmillan-inc.com", 80))

When the connect completes, the socket s can be used to send in a request for the text of the page.The same socket will read the reply,and then be destroyedThat’s right, destroyed. Client sockets are normally only used for one exchange (or a small set of sequential exchanges).(当这个连接完成的时候,这个叫做s的socket就可以用来发送获取页面文本的请求,这个socket也会读取回复。在这之后,这个socke就会被销毁。注意,每个socket一般在进行一次信息交换后就会被销毁)

What happens in the web server is a bit more complex. First, the web server creates a “server socket”:(server socket的创建更加麻烦一些)

#create an INET, STREAMing socket
serversocket = socket.socket(
    socket.AF_INET, socket.SOCK_STREAM)
#bind the socket to a public host,
# and a well-known port
serversocket.bind((socket.gethostname(), 80))
#become a server socket
serversocket.listen(5)

A couple things to notice: we used socket.gethostname()  so that the socket would be visible to the outside worldIf we had used s.bind(('localhost', 80)) or s.bind(('127.0.0.1', 80)) we would still have a “server” socket, but one that was only visible within the same machine. s.bind(('', 80))specifies that the socket is reachable by any address the machine happens to have(我们应该用socket.gethostname()来创建server socket,这样能使得服务器在网络上是可见的)

A second thing to note: low number ports are usually reserved for “well known” services (HTTP, SNMP etc). If you’re playing around, use a nice high number (4 digits).(很多小于3位数的端口已经被保留作为一些特定用途,个人用途最好使用4digts以上的端口)

Finally, the argument to listen tells the socket library that we want it to queue up as many as 5 connect requests (the normal max) before refusing outside connections. If the rest of the code is written properly, that should be plenty.(listen方法告诉server socket它能够同时处理5个connet请求,剩余的请求会被服务器拒绝)

Now that we have a “server” socket, listening on port 80, we can enter the mainloop of the web server:

while 1:
    #accept connections from outside
    (clientsocket, address) = serversocket.accept()
    #now do something with the clientsocket
    #in this case, we'll pretend this is a threaded server
    ct = client_thread(clientsocket)
    ct.run()

There’s actually 3 general ways in which this loop could work - dispatching a thread to handleclientsocket, create a new process to handle clientsocket, or restructure this app to use non-blocking sockets,(server socket一般有3种方式处理请求:分配一个线程处理clientsocket,创建一个进程处理clentsocket,或者让这个软件使用non-blocking sockets)


  The important thing to understand now is this: this is all a “server” socket does.  It doesn’t send any data.  It doesn’t receive any data.  It just produces “client” sockets.  Each clientsocket is created in response to some other “client” socket doing a connect()to the host and port we’re bound to.  As soon as we’ve created that clientsocket, we go back to listening for more connections.  The two “clients” are free to chat it up - they are using some dynamically allocated port which will be recycled when the conversation ends.(对于server socket,它只是做监听这件事,并不接受任何数据,也不发送任何数据。服务器socket只会在其他client socket发出connect请求的时候创建一个clent socket。我们可以通过这个clientsocket自由交换数据)

IPC (Inter-Process Communication,进程间通信)

If you need fast IPC between two processes on one machine, you should look into whatever form of shared memory the platform offers. A simple protocol based around shared memory and locks or semaphores is by far the fastest technique.

If you do decide to use sockets, bind the “server” socket to 'localhost'On most platforms, this will take a shortcut around a couple of layers of network code and be quite a bit faster.(在一台机器上进行进程间通信的时候,你可以使用socket的localhost )

Using a Socket

The first thing to note, is that the web browser’s “client” socket and the web server’s “client” socket are identical beasts. That is, this is a “peer to peer” conversation.(首先要注意的是,你的浏览器使用的client socket和服务器的clent socket是同样的东西,这是一种对等网络


Now there are two sets of verbs to use for communication. 
You can use send and recv, or you can transform your client socket into a file-like beast and use read and write. (现在我们有两种使用socket 通信的方式 send 和 recv,或者你也可以把socket 转换为一种类似文件IO的对象,并使用熟悉的文件IO操作read和write)

 The latter is the way Java presents its sockets. I’m not going to talk about it here, except to warn you that you need to useflush on sockets. These are buffered “files”, and a common mistake is to write something, and then read for a reply. Without a flush in there, you may wait forever for the reply, because the request may still be in your output buffer.

(后一种文件IO的方式是JAVA对待socket的方式。如果你要使用这种方式,我需要提醒你需要对socket使用flush操作,因为这种操作是使用缓冲区的。常见的错误是你向socket write了一些内容,然后使用read等待回复,在使用flush之前,你可能永远无法收到回复,因为你wrte的内容还停留在缓冲区里。)

Now we come to the major stumbling block of sockets - send and recv operate on the network buffers. They do not necessarily handle all the bytes you hand them (or expect from them), because their major focus is handling the network buffers. In general, they return when the associated network buffers have been filled (send) or emptied (recv)They then tell you how many bytes they handled. It is your responsibility to call them again until your message has been completely dealt with.(现在,让我们看看socket最让人头疼的部分,send和recv操作。send和recv操作)

When a recv returns 0 bytes, it means the other side has closed (or is in the process of closing) the connection. You will not receive any more data on this connection. Ever. You may be able to send data successfully; I’ll talk more about this later.(当socket的recv方法返回0字节的时候,这个socket已经被销毁了,虽然你仍然可以发送数据)

A protocol like HTTP uses a socket for only one transfer. The client sends a request, then reads a reply. That’s it. The socket is discarded. This means that a client can detect the end of the reply by receiving 0 bytes.(很多协议比如http协议都只用socket做一次数据交换,交换完成之后,这个socket就被销毁了)

But if you plan to reuse your socket for further transfers, you need to realize that there is no EOT on a socket. I repeat: if a socket send or recv returns after handling 0 bytes, the connection has been broken. If the connection has not been broken, you may wait on a recv forever, because the socket will not tell you that there’s nothing more to read (for now). Now if you think about that a bit, you’ll come to realize a fundamental truth of sockets: messages must either be fixed length (yuck),or be delimited (shrug), or indicate how long they are (much better), or end by shutting down the connectionThe choice is entirely yours, (but some ways are righter than others).(但如果你想要再次使用这个socket继续传输,你需要明白当socket 的 send 和recv都返回0的时候,这个socket已经失效了。否则的话,recv要么会返回完整长度的数据,要么会返回部分长度的数据,要么返回数据的长度)

一个send模型

class mysocket:
    '''demonstration class only
      - coded for clarity, not efficiency
    '''

    def __init__(self, sock=None):
        if sock is None:
            self.sock = socket.socket(
                socket.AF_INET, socket.SOCK_STREAM)
        else:
            self.sock = sock

    def connect(self, host, port):
        self.sock.connect((host, port))

    def mysend(self, msg):
        totalsent = 0
        while totalsent < MSGLEN:
            sent = self.sock.send(msg[totalsent:])
            if sent == 0:
                raise RuntimeError("socket connection broken")
            totalsent = totalsent + sent

    def myreceive(self):
        chunks = []
        bytes_recd = 0
        while bytes_recd < MSGLEN:
            chunk = self.sock.recv(min(MSGLEN - bytes_recd, 2048))
            if chunk == '':
                raise RuntimeError("socket connection broken")
            chunks.append(chunk)
            bytes_recd = bytes_recd + len(chunk)
        return ''.join(chunks)
大牛博客:http://yangrong.blog.51cto.com/6945369/1339593/

Python 提供了两个基本的 socket 模块。

   第一个是 Socket,它提供了标准的 BSD Sockets API。

   第二个是 SocketServer, 它提供了服务器中心类,可以简化网络服务器的开发。

下面讲的是Socket模块功能

1、Socket 类型

套接字格式:

socket(family,type[,protocal]) 使用给定的地址族、套接字类型、协议编号(默认为0)来创建套接字。

socket类型

描述

socket.AF_UNIX

只能够用于单一的Unix系统进程间通信

socket.AF_INET

服务器之间网络通信

socket.AF_INET6

IPv6

socket.SOCK_STREAM

流式socket , for TCP

socket.SOCK_DGRAM

数据报式socket , for UDP

socket.SOCK_RAW

原始套接字,普通的套接字无法处理ICMPIGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。

socket.SOCK_SEQPACKET

可靠的连续数据包服务

创建TCP Socket

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

创建UDP Socket

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


2、Socket 函数

注意点:

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

2)服务端与客户端不能直接发送列表,元组,字典。需要字符串化repr(data)

socket函数

描述

服务端socket函数

s.bind(address)

将套接字绑定到地址AF_INET,以元组(host,port)的形式表示地址.

s.listen(backlog)

开始监听TCP传入连接。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。

s.accept()

接受TCP连接并返回(conn,address,其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。

客户端socket函数

s.connect(address)

连接到address处的套接字。一般address的格式为元组(hostname,port,如果连接出错,返回socket.error错误。

s.connect_ex(adddress)

功能与connect(address)相同,但是成功返回0,失败返回errno的值。

公共socket函数

s.recv(bufsize[,flag])

接受TCP套接字的数据。数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。

s.send(string[,flag])

发送TCP数据。将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。

s.sendall(string[,flag])

完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常

s.recvfrom(bufsize[.flag])

接受UDP套接字的数据。与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。

s.sendto(string[,flag],address)

发送UDP数据。将数据发送到套接字,address是形式为(ipaddrport)的元组,指定远程地址。返回值是发送的字节数。

s.close()

关闭套接字。

s.getpeername()

返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。

s.getsockname()

返回套接字自己的地址。通常是一个元组(ipaddr,port)

s.setsockopt(level,optname,value)

设置给定套接字选项的值。

s.getsockopt(level,optname[.buflen])

返回套接字选项的值。

s.settimeout(timeout)

设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect()

s.gettimeout()

返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None

s.fileno()

返回套接字的文件描述符。

s.setblocking(flag)

如果flag0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。

s.makefile()

创建一个与该套接字相关连的文件

3、socket编程思路

TCP服务端:

创建套接字,绑定套接字到本地IP与端口

   # socket.socket(socket.AF_INET,socket.SOCK_STREAM) , s.bind()

开始监听连接                   #s.listen()

进入循环,不断接受客户端的连接请求              #s.accept()

然后接收传来的数据,并发送给对方数据         #s.recv() , s.sendall()

传输完毕后,关闭套接字                     #s.close()


TCP客户端:

创建套接字,连接远端地址

       # socket.socket(socket.AF_INET,socket.SOCK_STREAM) , s.connect()

连接后发送数据和接收数据          # s.sendall(), s.recv()

传输完毕后,关闭套接字          #s.close()

4、Socket编程之服务端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
root@yangrong: / python # catday5-socket-server.py
#!/usr/bin/python
import  socket    #socket模块
import  commands    #执行系统命令模块
HOST = '10.0.0.245'
PORT = 50007
s =  socket.socket(socket.AF_INET,socket.SOCK_STREAM)    #定义socket类型,网络通信,TCP
s.bind((HOST,PORT))    #套接字绑定的IP与端口
s.listen( 1 )          #开始TCP监听
while  1 :
        conn,addr = s.accept()    #接受TCP连接,并返回新的套接字与IP地址
        print 'Connected by' ,addr     #输出客户端的IP地址
        while  1 :
                 data = conn.recv( 1024 )     #把接收的数据实例化
                cmd_status,cmd_result = commands.getstatusoutput(data)    #commands.getstatusoutput执行系统命令(即shell命令),返回两个结果,第一个是状态,成功则为0,第二个是执行成功或失败的输出信息
                 if  len (cmd_result.strip())  = = 0 :    #如果输出结果长度为0,则告诉客户端完成。此用法针对于创建文件或目录,创建成功不会有输出信息
                         conn.sendall( 'Done.' )
                 else :
                        conn.sendall(cmd_result)    #否则就把结果发给对端(即客户端)
conn.close()      #关闭连接

5、Socket编程之客户端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
root@yangrong: / python # catday5-socket-client.py
#!/usr/bin/python
import  socket
HOST = '10.0.0.245'
PORT = 50007
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)       #定义socket类型,网络通信,TCP
s.connect((HOST,PORT))        #要连接的IP与端口
while  1 :
        cmd = raw_input ( "Please input cmd:" )        #与人交互,输入命令
        s.sendall(cmd)       #把命令发送给对端
        data = s.recv( 1024 )      #把接收的数据定义为变量
         print  data          #输出变量
s.close()    #关闭连接

6、执行效果:

服务端执行效果:

000003975.jpg


客户端执行效果:

000008836.jpg


7、程序缺限:

这是一个简单的socket通信,里面存在一些bug

1.在客户端输入回车,会挂死。

2.服务端返回的数据大于1024,客户端显示不全。

3.单进程,如果多个客户端连接,要排队,前一个断开,后一个客户端才能通信。


不想把代码写的太复杂,简单的说下解决方案:

问题1.在客户端上判断输入为空,要求重新输入。

问题2.在客户端上循环接收,直到接收完。但有没有完客户端是不知道的,需要服务端发一个结束符。

问题3.在服务端导入SocketServer模块,使得每建立一个连接,就新创建一个线程。实现多个客户端与服务端通信。多线程通信原理如下图:

000109234.jpg






官方文档对于一些变量和方法的解释

socket. AF_UNIX socket. AF_INET(IPV4) socket. AF_INET6(IPV6)

这些常量代表地址簇(和协议),用于 socket() 函数的第一个参数。例如常量 AF_UNIX 没有被定义,那么这个协议就不会被支持了。


socket. SOCK_STREAM(TCP协议,保证数据一定能传到对方) socket. SOCK_DGRAM(广播协议) socket. SOCK_RAW socket. SOCK_RDM socket. SOCK_SEQPACKET

这些常量用于设置 socket 的类型,用于 socket() 函数的第二个参数。(只有SOCK_STREAMSOCK_DGRAM较为常用)


socket. socket ( [ family [type [proto ] ] ] )

Create a new socket using the given address family, socket type and protocol number. The address family should be AF_INET (the default), AF_INET6 orAF_UNIXThe socket type should be SOCK_STREAM (the default), SOCK_DGRAM or perhaps one of the other SOCK_ constants. The protocol number is usually zero and may be omitted in that case.


exception  socket. timeout

当通过主调用settimeout()开启某个socket连接的超时选项后,如果这个连接产生了超时行为,那么这个exception就会被引发。所产生的值通常就是很精确的“timed out”字符串。

exception  socket. error

这个异常由socket related errors引发, 伴随产生的信息会有一个字符串告诉我们什么东西出错了,同时会用一个元组(errno, string)来返回系统调用中导致的错误,与os.error的返回结果类似。可以查看errno模块的相关信息,这个模块包含由操作系统底层定义的error code的名字。

Socket 对象有如下方法。

socket. accept ( )

Accept a connection. The socket must be bound to an address and listening for connections. The return value is a pair (conn, address) where conn is anew socket object usable to send and receive data on the connection, and address is the address bound to the socket on the other end of the connection.

socket. bind ( address )

Bind the socket to addressThe socket must not already be bound. (The format of address depends on the address family — see above.)(意思就是address是上面所提到的AF_INET协议族格式或者其它格式,例如“192.168.1.1”,80)

socket. close ( )

Close the socket. All future operations on the socket object will fail. The remote end will receive no more data (after queued data is flushed). Sockets are automatically closed when they are garbage-collected.

socket. connect ( address )

Connect to a remote socket at address(The format of address depends on the address family — see above.)


socket. getsockname ( )

Return the socket’s own address. This is useful to find out the port number of an IPv4/v6 socket, for instance. (The format of the address returned depends on the address family — see above.)


socket. send ( string [flags ] )

Send data to the socket. The socket mustbe connected to a remote socket. The optional flags argument has the same meaning as for recv() above.Returns the number of bytes sent. Applications are responsible for checking that all data has been sent; if only some of the data was transmitted, the application needs to attempt delivery of the remaining data. For further information on this concept, consult the Socket Programming HOWTO.









您可能感兴趣的与本文相关的镜像

Python3.9

Python3.9

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值