Python网络编程篇之socket

1 socket

插座?呵呵,想多了,翻译过来意思是套接字!

A network socket is an internal endpoint for sending or receiving data at a single node in a computer network. Concretely, it is a representation of this endpoint in networking software (protocol stack), such as an entry in a table (listing communication protocol, destination, status, etc.), and is a form of system resource.

socket是一种进程间通信机制(inter process communication ,IPC),提供一种供应用程序访问通信协议的操作系统调用,并且通过将socket与Unix系统文件描述符相整合,使得网络读写数据(或者服务调用)和读写本地文件一样容易。很显然,这货已经离插座越来越远了,已经完全不再是硬件上的物件,而是一序列的“指令” ,按汉语的理解,已经具备了“套接”(建立网络通讯或进程间通讯)和“字”(可交互的有序指令串)的概念。
2 socket address :主机-端口对
如果一个套接字像一个电话插孔——允许通信的一些基础设施,name主机名和端口号就像区号和电话号码的组合。
有效的端口号范围0-65535,尽管小于1024的端口号预留给了系统,Linux和mac os 可以在/etc/services文件中找到预留端口号的列表。
A socket address is the combination of an IP address and a port number, much like one end of a telephone connection is the combination of a phone number and a particular extension. Sockets need not have an address (for example for only sending data), but if a program binds a socket to an address, the socket can be used to receive data sent to that address. Based on this address, internet sockets deliver incoming data packets to the appropriate application process or thread.
有两种类型的套接字:基于文件的和面向网络的
AF:address family地址家族
AF_UNIX 面向文件
AF_INET面向网络

参数一:地址簇

  socket.AF_INET IPv4(默认)
  socket.AF_INET6 IPv6

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

参数二:类型

  socket.SOCK_STREAM  流式socket , for TCP (默认)
  socket.SOCK_DGRAM   数据报式socket , for UDP

  socket.SOCK_RAW 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
  socket.SOCK_RDM 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
  socket.SOCK_SEQPACKET 可靠的连续数据包服务

参数三:协议

  0  (默认)与特定的地址家族相关的协议,如果是 0 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议

3.面向连接的套接字也称为虚拟电路或流套接字

面向连接的通信提供序列化的、可靠的和不重复的数据交付,并且没有记录边界,实现这种连接的主要协议是TCP(传输控制协议)。

创建TCP套接字必须使用SOCK_STREAM作为套接字类型。

无连接的传输无法保证传输的内容的顺序性、可靠性。无连接传输的优势是没有维护虚拟电路连接带来的开销,从而拥有更低的成本。实现无连接的主要协议是UDP(用户数据报协议),创建UDP套接字必须使用SOCK_DGRAM作为套接字类型。

4.Socket 参数介绍

sk.bind(address)
  s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。

sk.listen(backlog)
  开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。
      backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5
      这个值不能无限大,因为要在内核中维护连接队列

sk.setblocking(bool)
  是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。

sk.accept()
  接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。
  接收TCP 客户的连接(阻塞式)等待连接的到来

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

sk.connect_ex(address)
  同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061

sk.close()
  关闭套接字

sk.recv(bufsize[,flag])
  接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。

sk.recvfrom(bufsize[.flag])
  与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。

sk.send(string[,flag])
  将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。

sk.sendall(string[,flag])
  将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。

      内部通过递归调用send,将所有内容发送出去。

sk.sendto(string[,flag],address)
  将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。

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

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

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

sk.fileno()
  套接字的文件描述符

 

5.创建socket

socket.socket(AddressFamily, Type) 

函数 socket.socket 创建⼀个 socket, 返回该 socket 的描述符, 该函数带有两个参数:

Address Family: 可以选择 AF_INET( ⽤于 Internet 进程间通信) 或者AF_UNIX( ⽤于同⼀台机器进程间通信) ,实际⼯作中常⽤AF_INET

Type: 套接字类型, 可以是 SOCK_STREAM( 流式套接字, 主要⽤于TCP 协议) 或者 SOCK_DGRAM( 数据报套接字, 主要⽤于 UDP 协议)创建⼀个tcp socket( tcp套接字)

5.1创建⼀个tcp sockettcp套接字)

TCP通信需要建立一个可靠连接的过程,而且通信双方以流的形式发送数据。

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket Created'

tcp_server

# -*- coding: utf-8 -*-
# 2017/11/25 16:31
import socket
import threading
import time
def dealClient(sock, addr):
    #第四步:接收传来的数据,并发送给对方数据
    print('Accept new connection from %s:%s...' % addr)
    sock.send(b'Hello,I am server!')
    while True:
        data = sock.recv(1024)
        time.sleep(1)
        if not data or data.decode('utf-8') == 'exit':
            break
        print('-->>%s!' % data.decode('utf-8'))
        sock.send(('Loop_Msg: %s!' % data.decode('utf-8')).encode('utf-8'))
    #第五步:关闭套接字
    sock.close()
    print('Connection from %s:%s closed.' % addr)

if __name__=="__main__":
    #第一步:创建一个基于IPv4和TCP协议的Socket
    # 套接字绑定的IP(127.0.0.1为本机ip)与端口
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('127.0.0.1', 9999))
    #第二步:监听连接
    s.listen(5)
    print('Waiting for connection...')
    while True:
        # 第三步:接受一个新连接:
        sock, addr = s.accept()
        # 创建新线程来处理TCP连接:
        t = threading.Thread(target=dealClient, args=(sock, addr))
        t.start()

tcp_client

# -*- coding: utf-8 -*-
# 2017/11/25 16:32
import socket
#初始化Socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#连接目标的ip和端口
s.connect(('127.0.0.1', 9999))
# 接收消息
print('-->>'+s.recv(1024).decode('utf-8'))
# 发送消息
s.send(b'Hello,I am a client')
print('-->>'+s.recv(1024).decode('utf-8'))
s.send(b'exit')
#关闭套接字
s.close()

5.2创建⼀个udp socketudp套接字)

使用UDP协议时,不需要建立连接,只需要知道对方的ip和port,就可以直接发数据包,但是不关心是否能到达目的端。

UDP --- 用户数据报协议, 是一个无连接的简单的面向数据报的运输层协议。
UDP不提供可靠性, 它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。
由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,传输速度很快。
UDP是一种面向无连接的协议, 每个数据报都是一个独立的信息,包括完整的源地址或目的的地址,
它在网络上以任何可能的路径传往目的地, 因此能否到达⽬的地, 到达目的地的时间以及内容的正确性都是不能被保证的。

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
print 'Socket Created'

udp_server

# -*- coding: utf-8 -*-
# 2017/11/25 16:38
import socket
#创建Socket,绑定指定的ip和端口
#SOCK_DGRAM指定了这个Socket的类型是UDP。绑定端口和TCP一样。
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('127.0.0.1', 9999))
print('Bind UDP on 9999...')
while True:
    # 直接发送数据和接收数据
    data, addr = s.recvfrom(1024)
    print('Received from %s:%s.' %(addr,data))
    s.sendto(b'Hello',addr)

udp_client

# -*- coding: utf-8 -*-
# 2017/11/25 16:39
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in [b'Hello', b'World']:
    # 发送数据:
    s.sendto(data, ('127.0.0.1', 9999))
    # 接收数据:
    print(s.recv(1024).decode('utf-8'))
s.close()

一下均基于tcp开发:

一对一

server

#__author: greg
#date: 2017/9/16 16:11
import socket
ip_port = ('127.0.0.1',8888)
sk = socket.socket()
sk.bind(ip_port)
sk.listen(5)#最大排队数,能开多少人
print ("服务端启动...")
# conn,addr= sk.accept()
# while True:
#     client_data=conn.recv(1024)
#     if str(client_data,"utf8")=='exit':
#         break
#     print (str(client_data,"utf8"))
#     server_response=input(">>>")
#     conn.sendall(bytes(server_response,"utf8"))
# conn.close()

while True:
    conn,address = sk.accept()
    print(address)
    while True:
        try:
            client_data=conn.recv(1024)
        except:
            print("意外中断")
            break
        print (str(client_data,"utf8"))
        server_response=input(">>>")
        conn.sendall(bytes(server_response,"utf8"))
    conn.close()

client

#__author: greg
#date: 2017/9/16 16:11
import socket
ip_port = ('127.0.0.1',8888)
sk = socket.socket()
sk.connect(ip_port)
print ("客户端启动:")
# while True:
#     inp = input('>>>')
#     sk.sendall(bytes(inp,"utf8"))
#     if inp == 'exit':
#         break
#     server_response=sk.recv(1024)
#     print (str(server_response,"utf8"))
# sk.close()

while True:
    inp=input('>>>')
    if inp=="exit":
        break
    sk.send(bytes(inp,'utf8'))
    data=sk.recv(1024)
    print(str(data,"utf8"))
sk.close()

一对多,简单并发

server

#__author: greg
#date: 2017/9/16 16:27
import socketserver
class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        print ("服务端启动...")
        while True:
            conn = self.request
            print (self.client_address)
            while True:
                client_data=conn.recv(1024)
                print (str(client_data,"utf8"))
                print ("waiting...")
                conn.sendall(client_data)
            conn.close()
            
if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8091),MyServer)
    server.serve_forever()

client

#__author: greg
#date: 2017/9/16 16:27

import socket

ip_port = ('127.0.0.1',8091)
sk = socket.socket()
sk.connect(ip_port)
print ("客户端启动:")
while True:
    inp = input('>>>')
    sk.sendall(bytes(inp,"utf8"))
    if inp == 'exit':
        break
    server_response=sk.recv(1024)
    print (str(server_response,"utf8"))
sk.close()

聊天并发实例

server

#__author: greg
#date: 2017/9/16 20:55
import socketserver
class MyServer(socketserver.BaseRequestHandler):
    def handle(self): #handle父类有handle方法
        print ("服务端启动...")
        while True:
            conn = self.request
            print (self.client_address)
            while True:
                client_data=conn.recv(1024)
                print (str(client_data,"utf8"))
                print ("waiting...")
                server_response=input(">>>")
                conn.sendall(bytes(server_response,"utf8"))
                # conn.sendall(client_data)
            conn.close()
            # print self.request,self.client_address,self.server
            
if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8098),MyServer)
    server.serve_forever()

client

#__author: greg
#date: 2017/9/16 20:54
import socket
ip_port = ('127.0.0.1',8098)
sk = socket.socket()
sk.connect(ip_port)
print ("客户端启动:")
while True:
    inp = input('>>>')
    sk.sendall(bytes(inp,"utf8"))
    server_response=sk.recv(1024)
    print (str(server_response,"utf8"))
    if inp == 'exit':
        break
sk.close()

应用:

传送命令:

cmd_server:

 1 #__author: greg
 2 #date: 2017/9/16 22:21
 3 import socket
 4 ip_port = ('127.0.0.1', 8879)
 5 sk = socket.socket()
 6 sk.connect(ip_port)
 7 print("客户端启动:")
 8 while True:
 9     inp = input('cdm:>>>').strip()
10     if len(inp) == 0:
11         continue
12     if inp == "q":
13         break
14     sk.sendall(bytes(inp, "utf8"))
15     server_response = sk.recv(1024)
16     print(str(server_response, "gbk"))
17     print('receive data size', len(server_response))
18     if inp == 'exit':
19         break
20 sk.close()
View Code

cmd_client

 1 #__author: greg
 2 #date: 2017/9/16 22:21
 3 import socket
 4 ip_port = ('127.0.0.1', 8879)
 5 sk = socket.socket()
 6 sk.connect(ip_port)
 7 print("客户端启动:")
 8 while True:
 9     inp = input('cdm:>>>').strip()
10     if len(inp) == 0:
11         continue
12     if inp == "q":
13         break
14     sk.sendall(bytes(inp, "utf8"))
15     server_response = sk.recv(1024)
16     print(str(server_response, "gbk"))
17     print('receive data size', len(server_response))
18     if inp == 'exit':
19         break
20 sk.close()
View Code

sendall会把数据直接全部发送到客户端,客户端将所有的数据都放到缓冲区,每次recv多少字节取决于recv内的参数,理论不应该超过8k。

所以,并不能一次recv()无限大数据,所以这里我们应该通过循环去接收。

“粘包”, 即服务器端你调用时send 2次,但你send调用时,数据其实并没有立刻被发送给客户端,而是放到了系统的socket发送缓冲区里,等缓冲区满了、或者数据等待超时了,数据才会被send到客户端,这样就把好几次的小数据拼成一个大数据,统一发送到客户端了,这么做的目地是为了提高io利用效率,一次性发送总比连发好几次效率高嘛。 但也带来一个问题,就是“粘包”,即2次或多次的数据粘在了一起统一发送。

我们在这里必须要想办法把粘包分开, 因为不分开,你就没办法取出来服务器端返回的命令执行结果的大小。

首先你是没办法让缓冲区强制刷新把数据发给客户端的。

你能做的,只有一个。就是,让缓冲区超时,超时了,系统就不会等缓冲区满了,会直接把数据发走,因为不能一个劲的等后面的数据呀,等太久,会造成数据延迟了,那可是极不好的。so如果让缓冲区超时呢?

  1. time.sleep(0.5),经多次测试,让服务器程序sleep 至少0.5就会造成缓冲区超时
  2. 不用sleep,服务器端每发送一个数据给客户端,就立刻等待客户端进行回应,即调用 conn.recv(1024), 由于recv在接收不到数据时是阻塞的,这样就会造成,服务器端接收不到客户端的响应,就不会执行后面的conn.sendall(命令结果)的指令,收到客户端响应后,再发送命令结果时,缓冲区就已经被清空了,因为上一次的数据已经被强制发到客户端了。 
#__author: greg
#date: 2017/9/16 22:37
import socketserver
import subprocess
class Myserver(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            conn=self.request
            conn.sendall(bytes("欢迎登录","utf8"))
            while True:
                client_bytes=conn.recv(1024)
                if not client_bytes:break
                client_str=str(client_bytes,"utf8")
                print(client_str)
                command=client_str
                result_str=subprocess.getoutput(command)
                result_bytes = bytes(result_str,encoding='utf8')
                info_str="info|%d"%len(result_bytes)
                conn.sendall(bytes(info_str,"utf8"))
                # conn.recv(1024)
                conn.sendall(result_bytes)
            conn.close()
if __name__=="__main__":
    server=socketserver.ThreadingTCPServer(("127.0.0.1",9998),Myserver)
    server.serve_forever()
big_server
#__author: greg
#date: 2017/9/16 22:36mysql
import socket
ip_port=("127.0.0.1",9998)
sk=socket.socket()
sk.connect(ip_port)
print("客户端启动...")
print(str(sk.recv(1024),"utf8"))
while True:
    inp=input(">>>").strip()
    sk.sendall(bytes(inp,"utf8"))
    basic_info_bytes=sk.recv(1024)
    print(str(basic_info_bytes,"utf8"))
    # sk.send(bytes('ok','utf8'))
    result_length=int(str(basic_info_bytes,"utf8").split("|")[1])
    print(result_length)
    has_received=0
    content_bytes=bytes()
    while has_received<result_length:
        fetch_bytes=sk.recv(1024)
        has_received+=len(fetch_bytes)
        content_bytes+=fetch_bytes
    cmd_result=str(content_bytes,"utf8")
    print(cmd_result)
sk.close()
big_client
#__author: greg
#date: 2017/9/17 0:14
import socket,os

ip_port=("127.0.0.1",8898)
sk=socket.socket()
sk.bind(ip_port)
sk.listen(5)
BASE_DIR=os.path.dirname(os.path.abspath(__file__))
while True:
    print("waiting connect")
    conn,addr=sk.accept()
    flag = True
    while flag:
            client_bytes=conn.recv(1024)
            func,file_byte_size,filename=str(client_bytes,"utf8").split("|",2)
            path=os.path.join(BASE_DIR,'upload',filename)
            has_received=0
            file_byte_size=int(file_byte_size)
            f=open(path,"wb")
            while has_received<file_byte_size:
                data=conn.recv(1024)
                f.write(data)
                has_received+=len(data)
            print("ending")
            f.close()
up_server
#__author: greg
#date: 2017/9/17 0:14
import socket
import re,os,sys
ip_port=("127.0.0.1",8898)
sk=socket.socket()
sk.connect(ip_port)
BASE_DIR=os.path.dirname(os.path.abspath(__file__))
print("客户端启动....")
while True:
    inp=input(">>>").strip() #post|1.jpg
    if inp.startswith("post"):
        method,local_path=inp.split("|",1)
        local_path=os.path.join(BASE_DIR,local_path)
        file_byte_size=os.stat(local_path).st_size
        file_name=os.path.basename(local_path)
        post_info="post|%s|%s"%(file_byte_size,file_name)
        sk.sendall(bytes(post_info,"utf8"))
        has_sent=0
        file_obj=open(local_path,"rb")
        while has_sent<file_byte_size:
            data=file_obj.read(1024)
            sk.sendall(data)
            has_sent+=len(data)
        file_obj.close()
        print("上传成功")
up_client

 

下一篇:socketserver

 

posted @ 2017-11-25 17:14  ninxin18  阅读(1355)  评论(0编辑  收藏  举报