Python套接字编程Socket Progaming——1

本篇文章是Network And Web Programing-Socket Programing分类中的第一篇文章,内容主要包含

  1. Socket概念理解
  2. Socket programing介绍
  3. 一个简单的TCP协议的server-client程序
  4. 支持同时处理多个客户端简单server-client连接程序
  5. socket的常用选项使用

理解socket概念

一个socket确定了网络中两个应用的端口之间的唯一连接方式,一个socket包含三个部分:协议方式(TCP, UDP或IP)、IP地址和端口号PORT,端口号是一个整数代表着一个进程,为了唯一确定端口之间连接,还需要指定使用的协议类型(及其信息),这些唯一确定了两个结点之间的连接的信息就是socket,有时候socket和port会视作同义词来使用,但是需要注意两者是不同的

Socket编程

套接字编程是一种在网络中两个结点连接和交流的方式,其中一个socket(结点)绑定并监听一个特定IP的PORT的请求,另一个socket则向这个IP的PORT发送请求形成连接,监听的一方为服务端,主动发送连接请求的一段为客户端

连接百度服务端的代码示例
连接到一个服务端需要知道它的IP和开放连接的端口,连接时IP不能填域名,可以通过ping www.baidu.com获取百度的IP,或者在代码中这样获取

import socket


ip = socket.gethostbyname("www.baidu.com")
print(ip)	# 163.177.151.109

连接到服务端

ip = socket.gethostbyname("www.baidu.com")
port = 80   # 默认开放的端口
ipaddress = (ip, port)

sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.connect(ipaddress)
print(f"Successfully connected to baidu server on port: {ipaddress}")

输出:

Successfully connected to baidu server on port: ('163.177.151.109', 80)

socket包含两个参数
第一个参数涉及到socket支持的IP地址族类,AF_INET表示ipv4类,对于socket只有下面三种族类可用

AF_UNIX 
AF_INET 
AF_INET6 

AF_UNIX族类socket可以提供单个系统之间进程的交流,AF_UNIX族类支持数据流和数据报类型的socket(类型在第二个参数介绍)
AF_INET和AF_INET6族类socket可以提供不同系统之间进程的交流,也支持数据流和数据报类型的socket

第二个参数是socket的类型,SOCK_STREAM表示面向连接TCP的协议,socket的类型取决于两个结点之间传递的数据的性质(例如稳定性、顺序一致性和重复信息的处理方式等),以下是定义在unix系统sys/socket.h文件中标准的socket类型

/*Standard socket types */
#define  SOCK_STREAM             1 /*virtual circuit*/
#define  SOCK_DGRAM              2 /*datagram*/
#define  SOCK_RAW                3 /*raw socket*/
#define  SOCK_RDM                4 /*reliably-delivered message*/
#define  SOCK_CONN_DGRAM         5 /*connection datagram*/

知道怎么建立一个socket连接之后,现在需要怎么使用socket连接发送数据,socket库支持socket使用sendall方法发送数据,客户端可以发送数据,服务端也可以用这个方法发送数据

一个简单的server-client程序

server:
实现包含以下步骤

  • 服务端需要使用bind()方法绑定到一个特定的IP和端口号
  • 使用listen()方法监听这个端口
  • 当接收到连接请求时使用accept()方法初始化一个socket的连接
  • 然后对这个连接使用recv方法接收客户端发送的数据并交给程序处理
  • 最后使用close()方法关闭socket连接
# server.py
import socket


def mian():
    ip = socket.gethostname()
    ipaddress = (ip, 12345)
    # ipaddress = ("", 12345)     # socket bind的ip传入空字符串时让服务器可以监听网络中其他电脑的请求
    sk = socket.socket()
    sk.bind(ipaddress)
    sk.listen(5)       # 指定系统允许的最大连接数,超过时会拒绝新的连接

    while True:
        conn, addr = sk.accept()
        conn.send(b"Got a connection from server")
        data = conn.recv(1024)
        print(f"received data from client: {data}")
        conn.close()



if __name__ == "__main__":
    mian()      

在命令行运行server.py后开启服务,等待客户端的连接请求
client:
包含两个步骤

  • 创建socket对象
  • 连接到指定的ip地址
# client.py
import socket


def main():
    ip = socket.gethostname()
    port = 12345
    ipaddress = (ip, port)
    sk = socket.socket()
    sk.connect(ipaddress)
    sk.send(b"Hello")
    data = sk.recv(1024)
    print(f"receive data from server: {data}")
    sk.close()


if __name__ == "__main__":
    main()

在另外一个命令行运行client.py,就会向server发送请求

# server output:
received data from client: b'Hello'
received data from client: b'Hello'
...

# client output: 
receive data from server: b'Got a connection from server'

使用在socket之上封装的socketserver编写TCP连接以及多线程支持多个客户端连接

server:

# -*- coding:utf-8 -*-
"""
一个简单的应答服务器
socketserver
    TCPServer: 在socket之上封装的方便操作各种类型socket连接的类,默认是TCP连接
BaseRequestHandler: socket响应处理的基类,无实际实现功能
    属性request: 属性是客户端socket,
    属性client_address: 包含服务器绑的定IP和端口号
"""
import socket
import time
from socketserver import TCPServer, BaseRequestHandler


class EchoHandler(BaseRequestHandler):
    def handle(self) -> None:    
        print(f"Got connection from address {self.client_address}")
        self.request: socket.socket
        while True:
            msg = self.request.recv(2048)
            print(f"Message from client: \n{msg}")
            self.request.send(msg)


if __name__ == '__main__':
    server = TCPServer(("", 12345), EchoHandler)       # TCPServer中实现了TCP服务器中的bind、listen和close等基本初始化操作
    server.serve_forever()

client:

# -*- coding:utf-8 -*-
import socket
import time


def connection():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(("localhost", 12345))
    while True:
        msg = input("Input msg and send:\n")
        s.send(msg.encode())
        print(f"Msg sent")
        time.sleep(1)


if __name__ == '__main__':
    connection()

开启server服务之后,在client发送信息

# client
Input msg and send:
a
Msg sent
b
Msg sent
Input msg and send:
# server
Got connection from address ('127.0.0.1', 36087)
Message from client: 
b'a'
Message from client: 
b'b'

这个服务默认一次只能服务一个客户端,如果你尝试再打开一个client进程连接server时,发送消息后server端并不会立马收到消息,而是要等到第一个连接的client断开之后才会收到第二个client发送的消息,而且发送的多条消息会聚集到一条一种
Python套接字编程Socket Progaming——1_第1张图片

让服务器支持同时处理多个客户端

如果想要一个服务器能同时处理和服务多个客户端,可以初始化一个ForkingTCPServerThreadingTCPServer

if __name__ == '__main__':
    # server = TCPServer(("", 12345), EchoHandler)       # TCPServer中实现了TCP服务器中的bind、listen和close等基本初始化操作
    server = ThreadingTCPServer(("", 12345), EchoHandler)
    server.serve_forever()

然后client开启两个进程连接server并发送消息:
Python套接字编程Socket Progaming——1_第2张图片

限制客户端连接数

但是随着客户端的数量增加,非常有必要限制一下客户端连接的数量和缩短连接等待时间,在linux上可以使用iptable限制连接数和sysctl限制TIME_WAIT时间
限制一个IP最多15个连接:

-A INPUT -p tcp -m tcp --dport 12345 --tcp-flags FIN,SYN,RST,ACK SYN -m connlimit --connlimit-above 15 --connlimit-mask 32 --connlimit-saddr -j REJECT --reject-with tcp-reset

TCP连接超时时间设置为15s:

net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15

也可以使用多线程开启多个TCPServer的方式来实现支持处理多个客户端的情况:

if __name__ == '__main__':
    from threading import Thread
    server = ThreadingTCPServer(("", 12345), EchoHandler, bind_and_activate=False)
    # 预先分配好最大的工作线程池,每个服务处理一个连接并限制了客户端连接的最大数量
    nworkers = 10
    for i in range(nworkers):
        t = Thread(target=server.serve_forever)
        t.daemon = True
        t.start()
    server.serve_forever()

socket的一些常用选项

允许socket的bind方法重复使用local address

if __name__ == '__main__':
    server = TCPServer(("", 12345), EchoHandler, bind_and_activate=False)
    # SOL_SOCKET指定选项类型/等级并设定SO_REUSEADDR的值为真指定该socket支持重复使用地址
    server.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    server.server_bind()
    server.server_activate()
    server.serve_forever()

上面这个选项由于经常被使用到,它被放到TCPServerallow_reuse_address属性,因此使用TCPServer时可以直接修改socket重复使用local address的选项:

if __name__ == '__main__':
    ThreadingTCPServer.allow_reuse_address = True
    server = ThreadingTCPServer(("", 12345), EchoHandler)
    server.serve_forever()

ref: Understanding Socket Concept

ref: IP Family

ref: Socket types

ref: Socket Level options

你可能感兴趣的:(Network,And,Web,programing,#,Socket,Programing,网络和web编程,socket编程,Python)