剛看了反應堆模式的原理,特意復習了socket編程,本文主要介紹python的基本socket使用和select使用,主要用於了解socket通信過程
一、socket模塊
socket
— Low-level networking interface
This module provides access to the BSD socket interface. It is available on all modern Unix systems, Windows, MacOS, and probably additional platforms.
更多詳細信息請看官方文檔 https://docs.python.org/3/library/socket.html
1、Socket類型
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 |
原始套接字,普通的套接字無法處理ICMP、IGMP等網絡報文,而SOCK_RAW可以;其次,SOCK_RAW也可以處理特殊的IPv4報文;此外,利用原始套接字,可以通過IP_HDRINCL套接字選項由用戶構造IP頭。 |
socket.SOCK_SEQPACKET |
可靠的連續數據包服務 |
創建TCP Socket: |
|
創建UDP Socket: |
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) |
2、socket函數
服務器端 Socket 函數
Socket 函數 | 描述 |
---|---|
s.bind(address) | 將套接字綁定到地址,在AF_INET下,以tuple(host, port)的方式傳入,如s.bind((host, port)) |
s.listen(backlog) | 開始監聽TCP傳入連接,backlog指定在拒絕鏈接前,操作系統可以掛起的最大連接數,該值最少為1,大部分應用程序設為5就夠用了 |
s.accpet() | 接受TCP鏈接並返回(conn, address),其中conn是新的套接字對象,可以用來接收和發送數據,address是鏈接客戶端的地址。 |
客戶端 Socket 函數
Socket 函數 | 描述 |
---|---|
s.connect(address) | 鏈接到address處的套接字,一般address的格式為tuple(host, port),如果鏈接出錯,則返回socket.error錯誤 |
s.connect_ex(address) | 功能與s.connect(address)相同,但成功返回0,失敗返回errno的值 |
公共 Socket 函數
Socket 函數 | 描述 |
---|---|
s.recv(bufsize[, flag]) | 接受TCP套接字的數據,數據以字符串形式返回,buffsize指定要接受的最大數據量,flag提供有關消息的其他信息,通常可以忽略 |
s.send(string[, flag]) | 發送TCP數據,將字符串中的數據發送到鏈接的套接字,返回值是要發送的字節數量,該數量可能小於string的字節大小 |
s.sendall(string[, flag]) | 完整發送TCP數據,將字符串中的數據發送到鏈接的套接字,但在返回之前嘗試發送所有數據。成功返回None,失敗則拋出異常 |
s.recvfrom(bufsize[, flag]) | 接受UDP套接字的數據u,與recv()類似,但返回值是tuple(data, address)。其中data是包含接受數據的字符串,address是發送數據的套接字地址 |
s.sendto(string[, flag], address) | 發送UDP數據,將數據發送到套接字,address形式為tuple(ipaddr, port),指定遠程地址發送,返回值是發送的字節數 |
s.close() | 關閉套接字 |
s.getpeername() | 返回套接字的遠程地址,返回值通常是一個tuple(ipaddr, port) |
s.getsockname() | 返回套接字自己的地址,返回值通常是一個tuple(ipaddr, port) |
s.setsockopt(level, optname, value) | 設置給定套接字選項的值 |
s.getsockopt(level, optname[, buflen]) | 返回套接字選項的值 |
s.settimeout(timeout) | 設置套接字操作的超時時間,timeout是一個浮點數,單位是秒,值為None則表示永遠不會超時。一般超時期應在剛創建套接字時設置,因為他們可能用於連接的操作,如s.connect() |
s.gettimeout() | 返回當前超時值,單位是秒,如果沒有設置超時則返回None |
s.fileno() | 返回套接字的文件描述 |
s.setblocking(flag) | 如果flag為0,則將套接字設置為非阻塞模式,否則將套接字設置為阻塞模式(默認值)。非阻塞模式下,如果調用recv()沒有發現任何數據,或send()調用無法立即發送數據,那么將引起socket.error異常。 |
s.makefile() | 創建一個與該套接字相關的文件 |
3、socket異常
Exception | 解釋 |
socket.error | 由Socket相關錯誤引發 |
socket.herror | 由地址相關錯誤引發 |
socket.gaierror | 由地址相關錯誤,如getaddrinfo()或getnameinfo()引發 |
socket.timeout | 當socket出現超時時引發。超時時間由settimeout()提前設定 |
二、socket編程
1、基於TCP(面向連接)的Socket編程(C++)
(5. 關閉套接字(closesocket)
3、socket tcp 編程實例,c/s程序
#!/bin/env python # -*- coding:utf8 -*- """ server.py """ import socket host = ('10.1.32.80', 33333) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 網絡通信, TCP流 s.bind(host) s.listen(5) # listen 5 client print "i'm waiting for connection..." while True: conn, addr = s.accept() # connection and ip address print 'connected by', addr while True: data = conn.recv(1024) print "receive from %s:%s" % (addr, data) conn.sendall("server receive your messages, good bye.") conn.close() break # s.close()
#!/bin/env python # -*- coding:utf8 -*- import socket """ client.py """ host = ('10.1.32.80', 33333) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 網絡通信, TCP流 s.connect(host) while True: msg = raw_input("Please input message: ") try: s.sendall(msg) except socket.error: print "i'm die, bye bye~" break data = s.recv(1024) print data if "good bye" in data: break s.close()
三、基於select的網絡編程
1、select介紹
2、使用select編程,聊天室程序如下。運行多個client,則可互相聊天,輸入"exit"即可退出
服務器代碼
#!/bin/env python #-*- coding:utf8 -*- """ server select """ import sys import time import socket import select import logging import Queue g_select_timeout = 10 class Server(object): def __init__(self, host='10.1.32.80', port=33333, timeout=2, client_nums=10): self.__host = host self.__port = port self.__timeout = timeout self.__client_nums = client_nums self.__buffer_size = 1024 self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server.setblocking(False) self.server.settimeout(self.__timeout) self.server.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) #keepalive self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #端口復用 server_host = (self.__host, self.__port) try: self.server.bind(server_host) self.server.listen(self.__client_nums) except: raise self.inputs = [self.server] #select 接收文件描述符列表 self.outputs = [] #輸出文件描述符列表 self.message_queues = {}#消息隊列 self.client_info = {} def run(self): while True: readable , writable , exceptional = select.select(self.inputs, self.outputs, self.inputs, g_select_timeout) if not (readable or writable or exceptional) : continue for s in readable : if s is self.server:#是客戶端連接 connection, client_address = s.accept() #print "connection", connection print "%s connect." % str(client_address) connection.setblocking(0) #非阻塞 self.inputs.append(connection) #客戶端添加到inputs self.client_info[connection] = str(client_address) self.message_queues[connection] = Queue.Queue() #每個客戶端一個消息隊列 else:#是client, 數據發送過來 try: data = s.recv(self.__buffer_size) except: err_msg = "Client Error!" logging.error(err_msg) if data : #print data data = "%s %s say: %s" % (time.strftime("%Y-%m-%d %H:%M:%S"), self.client_info[s], data) self.message_queues[s].put(data) #隊列添加消息 if s not in self.outputs: #要回復消息 self.outputs.append(s) else: #客戶端斷開 #Interpret empty result as closed connection print "Client:%s Close." % str(self.client_info[s]) if s in self.outputs : self.outputs.remove(s) self.inputs.remove(s) s.close() del self.message_queues[s] del self.client_info[s] for s in writable: #outputs 有消息就要發出去了 try: next_msg = self.message_queues[s].get_nowait() #非阻塞獲取 except Queue.Empty: err_msg = "Output Queue is Empty!" #g_logFd.writeFormatMsg(g_logFd.LEVEL_INFO, err_msg) self.outputs.remove(s) except Exception, e: #發送的時候客戶端關閉了則會出現writable和readable同時有數據,會出現message_queues的keyerror err_msg = "Send Data Error! ErrMsg:%s" % str(e) logging.error(err_msg) if s in self.outputs: self.outputs.remove(s) else: for cli in self.client_info: #發送給其他客戶端 if cli is not s: try: cli.sendall(next_msg) except Exception, e: #發送失敗就關掉 err_msg = "Send Data to %s Error! ErrMsg:%s" % (str(self.client_info[cli]), str(e)) logging.error(err_msg) print "Client: %s Close Error." % str(self.client_info[cli]) if cli in self.inputs: self.inputs.remove(cli) cli.close() if cli in self.outputs: self.outputs.remove(s) if cli in self.message_queues: del self.message_queues[s] del self.client_info[cli] for s in exceptional: logging.error("Client:%s Close Error." % str(self.client_info[cli])) if s in self.inputs: self.inputs.remove(s) s.close() if s in self.outputs: self.outputs.remove(s) if s in self.message_queues: del self.message_queues[s] del self.client_info[s] if "__main__" == __name__: Server().run()
客戶端代碼
#!/usr/local/bin/python # *-* coding:utf-8 -*- """ client.py """ import sys import time import socket import threading class Client(object): def __init__(self, host, port=33333, timeout=1, reconnect=2): self.__host = host self.__port = port self.__timeout = timeout self.__buffer_size = 1024 self.__flag = 1 self.client = None self.__lock = threading.Lock() @property def flag(self): return self.__flag @flag.setter def flag(self, new_num): self.__flag = new_num def __connect(self): client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #client.bind(('0.0.0.0', 12345,)) client.setblocking(True) client.settimeout(self.__timeout) client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #端口復用 server_host = (self.__host, self.__port) try: client.connect(server_host) except: raise return client def send_msg(self): if not self.client: return while True: time.sleep(0.1) #data = raw_input() data = sys.stdin.readline().strip() if "exit" == data.lower(): with self.__lock: self.flag = 0 break self.client.sendall(data) return def recv_msg(self): if not self.client: return while True: data = None with self.__lock: if not self.flag: print 'ByeBye~~' break try: data = self.client.recv(self.__buffer_size) except socket.timeout: continue except: raise if data: print "%s\n" % data time.sleep(0.1) return def run(self): self.client = self.__connect() send_proc = threading.Thread(target=self.send_msg) recv_proc = threading.Thread(target=self.recv_msg) recv_proc.start() send_proc.start() recv_proc.join() send_proc.join() self.client.close() if "__main__" == __name__: Client('10.1.32.80').run()
四、多路IO復用介紹和區別