第十一篇:网络编程
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第十一篇:网络编程相关的知识,希望对你有一定的参考价值。
Socket模块
C/S架构
一、什么是C/S架构
1、服务器是一个软件或硬件,用于提供客户需要的“服务”
2、硬件上,客户端常见的就是平时所使用的PC机,服务器常见的有联想、DELL等厂商生产的各种系列服务器
3、软件上,服务器提供的服务主要是程序的运行,数据的发送与接收、合并、升级或其它的程序或数据的操作
二、套接字
1、套接字是一种具有“通讯端点”概念的计算机网络数据结构
2、套接字起源于20世纪70年代加利福尼亚大学伯克利分校版本的Unix
3、一种套接字是Unix套接字,其“家族名”为AF_UNIX
4、另一种套接字是基于网络的,“家族名”为AF_INET
5、如果把套接字比做电话的插口,那么主机与端口就像区号与电话号码的一对组合
三、面向连接与无连接
1、无论你使用哪一种地址家族,套接字的类型只有两种。一种是面向连接的套接字,另一种是无连接的套接字
2、面向连接的主要协议就是传输控制协议TCP,套接字类型为SOCK_STREAM
3、无连接的主要协议是用户数据报协议UDP,套接字类型为SOCK_DGRAM
4、python中使用socket模块中的socket函数实现套接字的创建
socket函数与方法
一、创建TCP服务器
创建TCP服务器的主要步骤如下:
1. 创建服务器套接字:s = socket.socket()
2. 绑定地址到套接字:s.bind()
3. 启动监听:s.listen()
4. 接受客户连接:s.accept()
5. 与客户端通信:recv()/send()
6. 关闭套接字:s.close()
二、创建TCP客户端
创建TCP客户端的步骤主要如下:
1. 创建客户端套接字:cs = socket.socket()
2. 尝试连接服务器:cs.connect()
3. 与服务器通信:cs.send()/cs.recv()
4. 关闭客户端套接字:cs.close()
三、创建UDP服务器
? 创建UDP服务器的主要步骤如下:
1. 创建服务器套接字:s = socket.socket()
2. 绑定服务器套接字:s.bind()
3. 接收、发送数据:s.recvfrom()/ss.sendto()
4. 关闭套接字:s.close()
四、创建UDP客户端
? 创建UDP客户端的步骤主要如下:
1. 创建客户端套接字:cs = socket.socket()
2. 与服务器通信:cs.sendto()/cs.recvfrom()
3. 关闭客户端套接字:cs.close()
xinetd
配置xinetd服务
一、什么是xinetd
1、xinetd可以统一管理很多服务进程,它能够:
1.1 绑定、侦听、和接受来对自服务器每个端口的请求
1.2 有客户端访问时,调用相应的服务器程序响应
1.3 节约了系统内存资源
1.4 同时响应多个客户端的连接请求
2、Windows系统没有该功能
3、多数UNIX系统使用的是inetd实现相同的功能
二、配置文件解析
编写xinetd程序
一、使用标准输入输出
1、当使用xinetd的时候,它通过两个方法传递socket:如文件描述符0和1
2、它们和文件描述符一样,分别代表标标准输入和标准输出
3、因为标准输出sys.stdout默认是被缓冲的,所以为了实时性,需要使用到sys.stdout.flush()函数
4、通过这种方法,服务器端程序既可以当成网络服务器程序使用,也可以像普通的脚本程序一样执行
#!/usr/bin/env python
import sys
print ‘Welcome.‘
print ‘Please enter a string: ‘
sys.stdout.flush()
line = sys.stdin.readline().strip()
print ‘You enterd %d characters‘ % len(line)
二、使用socket对象
1、通过调用socket.fromfd()可以建立socket对象
2、建立socket对象需要使用xinetd传递给程序的文件描述符
3、fromfd()函数需要文件数量和一些标准的参数,这些参数与前一章内容相同
4、文件描述符可以通过fileno()函数得到
#!/usr/bin/env python
import sys
import time
import socket
s = socket.fromfd(sys.stdin.fileno(), socket.AF_INET,
socket.SOCK_STREAM)
s.sendall(‘Welcome!\\n‘)
s.sendall(‘you are connected from %s.\\n‘ % str(s.getpeername()))
s.sendall(‘The local time is: %s.\\n‘ % time.ctime())
forking
forking工作原理
一、什么是forking
1、fork(分岔)在Linux系统中使用非常广泛
2、当某一命令执行时,父进程(当前进程)fork出一个子进程
3、父进程将自身资源拷贝一份,命令在子进程中运行时,就具有和父进程完全一样的运行环境
二、进程的生命周期
1、父进程fork出子进程并挂起
2、子进程运行完毕后,释放大部分资源并通知父进程,这个时候,子进程被称作僵尸进程
3、父进程获知子进程结束,子进程所有资源释放
三、僵尸进程
1、僵尸进程没有任何可执行代码,也不能被调度
2、如果系统中存在过多的僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程
3、对于系统管理员来说,可以试图杀死其父进程或重启系统来消除僵尸进程
forking编程
一、forking编程基本思路
1、需要使用os模块
2、os.fork()函数实现forking功能
3、python中,绝大多数的函数只返回一次,os.forking将返回两次
4、对fork()的调用,针对父进程返回子进程的PID;对于子进程,返回PID0
5、因为所有的父子进程拥有相同的资源,所以在编写程序时要避免资源冲突
pid = os.fork() #实现forking
if pid: #在父进程中关闭子进程连接
close_child_conn #接着处理其他的连接请求
handle_more_conn
else: #子进程关闭父进程连接,响应当
close_parent_conn #前的用户连接
process_this_conn
二、使用轮询解决zombie问题
1、父进程通过os.wait()来得到子进程是否终止的信息
2、在子进程终止和父进程调用wait()之间的这段时间,子进程被称为zombie(僵尸)进程
3、如果子进程还没有终止,父进程先退出了,那么子进程会持续工作。系统自动将子进程的父进程设置为init进程,init将来负责清理僵尸进程
4、python可以使用waitpid()来处理子进程
5、waitpid()接受两个参数,第一个参数设置为-1,表示与wait()函数相同;第二参数如果设置为0表示挂起父进程,直到子程序退出,设置为1表示不挂起父进程
6、waitpid()的返回值:如果子进程尚未结束则返回0,否则返回子进程的PID
#!/usr/bin/env python
import os, time
def reap():
result = os.waitpid(-1, os.WNOHANG) #WNOHANG即值为1
print ‘Reaped child process %d‘ % result[0]
pid = os.fork()
if pid:
print ‘In parent. Sleeping 15s...‘
time.sleep(15)
reap()
time.sleep(5)
print ‘parent done‘
else:
print ‘In child. Sleeping 5s...‘
time.sleep(5)
print ‘Child terminating.‘
三、forking服务器
1、在网络服务器中,forking被广泛使用
2、如果服务器需要同时响应多个客户端,那么forking是解决问题最常用的方法之一
3、父进程负责接受客户端的连接请求
4、子进程负责处理客户端的请求
多线程
多线程工作原理
一、多线程的动机
1、在多线程(MT)编程出现之前,电脑程序的运行由一个执行序列组成,执行序列按顺序在主机的中央处理器(CPU)中运行
2、无论是任务本身要求顺序执行还是整个程序是由多个子任务组成,程序都是按这种方式执行的
3、即使子任务相互独立,互相无关(即,一个子任务的结果不影响其它子任务的结果)时也是这样
4、如果并行运行这些相互独立的子任务可以大幅度地提升整个任务的效率
二、多线程任务的工作特点
1、它们本质上就是异步的,需要有多个并发事务
2、各个事务的运行顺序可以是不确定的,随机的,不可预测的
3、这样的编程任务可以被分成多个执行流,每个流都有一个要完成的目标
4、根据应用的不同,这些子任务可能都要计算出一个中间结果,用于合并得到最后的结果
三、什么是进程
1、计算机程序只不过是磁盘中可执行的、二进制(或其它类型)的数据
2、进程(有时被称为重量级进程)是程序的一次执行
3、每个进程都有自己的地址空间、内存以及其它记录其运行轨迹的辅助数据
4、操作系统管理在其上运行的所有进程,并为这些进程公平地分配时间
四、什么是线程
1、线程(有时被称为轻量级进程)跟进程有些相似。不同的是,所有的线程运行在同一个进程中,共享相同的运行环境
2、线程有开始,顺序执行和结束三部分
3、线程的运行可能被抢占(中断),或暂时的被挂起(也叫睡眠),让其它的线程运行,这叫做让步
4、一个进程中的各个线程之间共享同一片数据空间,所以线程之间可以比进程之间更方便地共享数据以及相互通讯
5、线程一般都是并发执行的,正是由于这种并行和数据共享的机制使得多个任务的合作变为可能
6、需要注意的是,在单CPU 的系统中,真正的并发是不可能的,每个线程会被安排成每次只运行一小会,然后就把CPU 让出来,让其它的线程去运行
多线程编程
一、多线程相关模块
1、thread和threading模块允许程序员创建和管理线程
2、thread模块提供了基本的线程和锁的支持,而threading提供了更高级别、功能更强的线程管理功能
3、推荐使用更高级别的threading模块
4、只建议那些有经验的专家在想访问线程的底层结构的时候,才使用thread模块
二、传递函数给Thread类
1、多线程编程有多种方法,传递函数给threading模块的Thread类是介绍的第一种方法
2、Thread对象使用start()方法开始线程的执行,使用join()方法挂起程序,直到线程结束
#!/usr/bin/env python
import threading
import time
nums = [4, 2]
def loop(nloop, nsec): #定义函数,打印运行的起止时间
print ‘start loop %d, at %s‘ % (nloop, time.ctime())
time.sleep(nsec)
print ‘loop %d done at %s‘ % (nloop, time.ctime())
def main():
print ‘starting at: %s‘ % time.ctime()
threads = []
for i in range(2): #创建两个线程,放入列表
t = threading.Thread(target = loop, args = (0, nums[i]))
threads.append(t)
for i in range(2):
threads[i].start() #同时运行两个线程
for i in range(2):
threads[i].join() #主程序挂起,直到所有线程结束
print ‘all Done at %s‘ % time.ctime()
if __name__ == ‘__main__‘:
main()
三、传递可调用类给Thread类
1、传递可调用类给Thread类是介绍的第二种方法
2、相对于一个或几个函数来说,由于类对象里可以使用类的强大的功能,可以保存更多的信息,这种方法更为灵活
#!/usr/bin/env python
import threading
import time
nums = [4, 2]
class ThreadFunc(object): #定义可调用的类
def __init__(self, func, args, name = ‘‘):
self.name = name
self.func = func
self.args = args
def __call__(self):
apply(self.func, self.args)
def loop(nloop, nsec): #定义函数,打印运行的起止时间
print ‘start loop %d, at %s‘ % (nloop, time.ctime())
time.sleep(nsec)
print ‘loop %d done at %s‘ % (nloop, time.ctime())
def main():
print ‘starting at: %s‘ % time.ctime()
threads = []
for i in range(2):
t = threading.Thread(target = ThreadFunc(loop, (i, nums[i]),loop.__name__))
threads.append(t) #创建两个线程,放入列表
for i in range(2):
threads[i].start()
for i in range(2):
threads[i].join()
print ‘all Done at %s‘ % time.ctime()
if __name__ == ‘__main__‘:
main()
四、含有线程的服务器
1、多数的线程服务器有同样的结构
2、主线程是负责侦听请求的线程
3、主线程收到一个请求的时候,新的工作线程会被建立起来,处理客户端请求
4、客户端断开时,工作线程将终止线程划分为用户线程和后台(daemon)进程,setDaemon将线程设置为后台进程
创建 TCP 时间戳服务器
编写一个 TCP 服务器程序,主要要求如下:
1、服务器监听在 0.0.0.0 的 21567 端口上
2、收到客户端数据后,将其加上时间戳后回送给客户端
3、如果客户端发过来的字符全是空白字符,则终止与客户端的连接
方案
创建 TCP 服务器的主要步骤如下:
1、创建服务器套接字:s = socket.socket()
2、绑定地址到套接字:s.bind()
3、启动监听:s.listen()
4、接受客户连接:s.accept()
5、与客户端通信:recv()/send()
6、关闭套接字:s.close()
从客户端收到数据后,使用 time.ctime()加上时间戳再将其发回给客户端。服务器需要不断的响应新客户端,当客户端断开连接后,服务器程序不应退出,需要将主程序写到循环中。
实现此案例需要按照如下步骤进行
步骤一:编写脚本
#!/usr/bin/env python
#coding:utf-8
import socket
import time
HOST = ‘‘
PORT = 21567
ADDR = (HOST, PORT)
tcpSer = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #创建 TCP 套接字
tcpSer.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #设置端口可立即重用
tcpSer.bind(ADDR)
tcpSer.listen(5)
print ‘server started, waiting client.‘
while True:
try:
tcpCliSock, addr = tcpSer.accept()
except KeyboardInterrupt:
break
print ‘Welcome:‘, addr
while True:
data = tcpCliSock.recv(2048).strip()
if len(data) == 0:
tcpCliSock.close()
break
tcpCliSock.send(‘[%s] %s\\n‘ % (time.ctime(), data))
tcpSer.close()
骤二:验证执行效果
[[email protected] bin]# ./tsTserv.py #启动服务器端 server started, waiting client.
客户端测试:
[[email protected] bin]# telnet localhost 21567 #客户端连接服务器 21567 端口 Trying ::1... telnet: connect to address ::1: Connection refused Trying 127.0.0.1... Connected to localhost. Escape character is ‘^]‘. hello [Sun Jul 5 11:34:54 2015] hello hello world! [Sun Jul 5 11:35:01 2015] hello world! #此处直接回车 Connection closed by foreign host.
创建 TCP 时间戳客户端
编写 TCP 时间戳客户端脚本,主要要求如下:
1、连接服务器的 21567
2、接收用户从键盘上的输入
3、发送接收到的字符串给服务器
4、如果用户按 ctrl + c 则退出程序
方案
创建 TCP 客户端的步骤主要如下:
1、 创建客户端套接字:cs = socket.socket()
2、尝试连接服务器:cs.connect()
3、与服务器通信:cs.send()/cs.recv()
4、关闭客户端套接字:cs.close()
客户端同样需要创建套接字,只是客户并不需要绑定端口。客户端的端口号是系统随机分配的大于 1024 的随机值。
客户端连接服务器后,通过循环不断的与服务器通信,满足程序员定义好的特定条件才退出。
实现此案例需要按照如下步骤进行。
步骤一:编写脚本
#!/usr/bin/env python
#coding:utf-8
import socket
HOST = ‘localhost‘ #定义要连接服务器的地址
PORT = 21567 #定义要连接的服务器的端口号
BUFSIZ = 1024 #指定接收数据缓冲区大小
ADDR = (HOST, PORT)
tcpCliSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcpCliSock.connect(ADDR) #连接服务器,如果服务器未运行,将有异常产生
while True:
data = raw_input(‘> ‘)
if not data: #如果用户输入为空则退出循环
break
tcpCliSock.send(data)
data = tcpCliSock.recv(BUFSIZ)
if not data: #如果未收到数据则退出循环
break
print data,
tcpCliSock.close()
步骤二:验证执行结果
[[email protected] bin]# ./tsTserv.py #启动上一实验的服务器程序 server started, waiting client.
执行客户端脚本
[[email protected] bin]# ./tsTcli.py #启动客户端程序 > hello world! [Sun Jul 5 11:51:15 2015] hello world! > get data [Sun Jul 5 11:51:23 2015] get data > #此处直接回车
创建 UDP 时间戳服务器
编写一个 UDP 服务器程序,主要要求如下:
1、服务器监听在 0.0.0.0 的 21567 端口上
2、收到客户端数据后,将其加上时间戳后回送给客户端
方案
创建 UDP 服务器的主要步骤如下:
1、创建服务器套接字:s = socket.socket()
2、绑定服务器套接字:s.bind()
3、接收、发送数据:s.recvfrom()/ss.sendto()
4、关闭套接字:s.close()
UDP 是非面向连接的协议,它只是在那里等待客户端发送过来的数据,所以它不需要“Listen” 。其他写法与 TCP 服务器程序类似
实现此案例需要按照如下步骤进行。
步骤一:编写脚本
#!/usr/bin/env python
#coding:utf-8
import socket
import time
HOST = ‘‘ #定义服务器监听在所有的地址上
PORT = 21567 #定义服务器监听的端口号
BUFSIZ = 1024 #定义接收数据缓冲区大小
ADDR = (HOST, PORT)
udpSerSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #创建 UDP 套接字
udpSerSock.bind(ADDR)
while True:
print ‘waiting for message...‘
data, addr = udpSerSock.recvfrom(BUFSIZ)
udpSerSock.sendto(‘[%s] %s‘ % (time.ctime(), data), addr)
print ‘...received from and returned to:‘, addr
udpSerSock.close()
创建 UDP 时间戳客户端
编写 UDP 时间戳客户端脚本,主要要求如下:
1、连接服务器的 21567
2、接收用户从键盘上的输入
3、发送接收到的字符串给服务器
4、如果用户按 ctrl + c 则退出程序
方案
UDP 客户端的编程与 TCP 客户端编程类似。由于 UDP 协议是非面向连接的协议,也就
不需要使用 connet()进行连接,想要发送数据时,直接调用 sendto()即可
实现此案例需要按照如下步骤进行。
步骤一:编写脚本
#!/usr/bin/env python
#coding:utf-8
import socket
HOST = ‘localhost‘ #定义要连接的服务器
PORT = 21567 #定义连接服务器的端口
BUFSIZ = 1024 #定义数据缓冲区大小
ADDR = (HOST, PORT)
udpCliSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #创建 UDP 套接字
while True:
data = raw_input(‘> ‘)
if not data: #如果用户输入为空,则中断循环
break
udpCliSock.sendto(data, ADDR)
data, addr = udpCliSock.recvfrom(BUFSIZ)
if not data: #如果未接收到数据则中断循环
break
print data
udpCliSock.close()
步骤二:验证执行结果
[[email protected] bin]# ./tsUserv.py #启动实验 4 的服务器 waiting for message...
执行客户端脚本
[[email protected] bin]# ./tsUcli.py #运行客户端程序 > hello world! [Sun Jul 5 15:56:48 2015] hello world! > #此处直接输入回车,结束程序
配置 xinetd 服务
编写 xinetd 服务配置文件,主要要求如下:
1、服务器监听在 0.0.0.0 的 51423 端口上
2、服务器采用 TCP 协议进行通信
3、服务器以 root 身份运行
4、服务器运行文件是/root/bin/exmaple.py
方案
前面所编写的服务器程序,都只能接受一个客户端的连接,如果超过一个客户端,后续的请求不得不排队等候。
使用超级守护进程 xinetd 是解决多客户端连接的一种方式。它能够绑定、侦听、和接受来对自服务器每个端口的请求,有客户端访问时,调用相应的服务器程序响应。
还能够同时响应多个客户端的连接请求。
基于 xinetd 超级守护进程的服务,需要在/etc/xinetd.d/目录下创建相关的服务器配置文件
步骤
实现此案例需要按照如下步骤进行。
步骤一:安装并配置 xinetd
RHEL6 操作系统默认没有安装 xinetd,首先安装
[[email protected] bin]# yum install -y xinetd [[email protected] bin]# service xinetd start [[email protected] bin]# chkconfig xinetd on
步骤二:创建服务器配置文件
[[email protected] bin]# vim /etc/xinetd.d/pyserver service pyserver #起始语句块 { flags = NAMEINARGS #使参数和 inetd 一样地传递 type = UNLISTED #如果服务不在/etc/services,指定为 UNLISTED port = 51423 #指定服务端口号 socket_type = stream #使用 TCP 协议,则为 stream protocol = tcp wait = no user = root server = /root/bin/example.py #指定服务器程序位置 server_args = /root/bin/example.py }
2. 使用标准输入输出搭建 TCP 服务器
编写 exmaple.py 服务器端程序,主要要求如下:
1、服务器采用 xinetd 的方式运行
2、服务器监听在 0.0.0.0 的 51423 端口上
3、收到客户端数据后,将其加上时间戳后回送给客户端
4、如果客户端发过来的字符全是空白字符,则终止与客户端的连接
5、采用标准输入输出的方式进行编写
方案
当使用 xinetd 的时候,它通过两个方法传递 socket:如文件描述符 0 和 1。相应的,调用 sys.stdin.readline()接收数据。
因为标准输出 sys.stdout 默认是被缓冲的,不会及时把信息显示在屏幕上,所以为了实时性,需要调用 sys.stdou.flush()。
实现此案例需要按照如下步骤进行。
步骤一:编写脚本
[[email protected] bin]# vim example.py
#!/usr/bin/env python
#coding:utf-8
import sys
import time
sys.stdout.write(‘Please enter a string:\\n> ‘)
sys.stdout.flush()
while True:
line = sys.stdin.readline().strip()
if not line:
break
sys.stdout.write(‘[%s] %s\\n> ‘ % (time.ctime(), line))
sys.stdout.flush()
步骤二:验证执行效果
[[email protected] bin]# telnet 127.0.0.1 51423 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is ‘^]‘. Please enter a string: > hello world! [Mon Jul 6 18:49:54 2015] hello world! > #此处直接回车,退出程序 Connection closed by foreign host. [[email protected] bin]#
3. 使用 socket 对象搭建 TCP 服务器
编写 exmaple.py 服务器端程序,主要要求如下:
1、服务器采用 xinetd 的方式运行
2、服务器监听在 0.0.0.0 的 51423 端口上
3、收到客户端数据后,将其加上时间戳后回送给客户端
4、如果客户端发过来的字符全是空白字符,则终止与客户端的连接
5、采用 socket 对象的方式进行编写
方案
使用 socket“更像”网络服务器程序。因为客户端的请求首先要发送到超级守护进程xinetd,所以需要根据 xinetd 传递过来的文件描述符建立 socket 对象。
通过调用socket.fromfd()可以建立 socket 对象
实现此案例需要按照如下步骤进行。
步骤一:编写脚本
[[email protected] bin]# mv example.py example1.py #将实验 2 的服务器程序改名 [email protected] bin]# vim example.py #建立新的服务器程序
#!/usr/bin/env python
#coding:utf-8
import sys
import time
import socket
s = socket.fromfd(sys.stdin.fileno(), socket.AF_INET, socket.SOCK_STREAM)
s.sendall(‘Welcome!\\n> ‘)
while True:
data = s.recv(4096).strip()
if not data:
break
s.sendall(‘[%s] %s\\n> ‘ % (time.ctime(), data))
s.close()
步骤二:验证执行结果
[[email protected] bin]# telnet 127.0.0.1 51423 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is ‘^]‘. Welcome! > hello world! [Mon Jul 6 19:03:02 2015] hello world! > #此处直接输入回车,退出连接 Connection closed by foreign host.
4. forking 基础应用
写 bfork.py 脚本,主要要求如下:
1、在父进程中打印“In parent”然后睡眠 10 秒
2、在子进程中编写循环,循环 5 次,输出当系统时间,每次循环结束后睡眠 1秒
3、父子进程结束后,分别打印“parent exit”和“child exit”
方案
os.fork()函数实现 forking 功能。 对 fork()的调用, 针对父进程返回子进程的 PID;对于子进程,返回 PID 0。
通过条件判断,返回值非零则为父进程,否则为子进程
实现此案例需要按照如下步骤进行。
步骤一:编写脚本
#!/usr/bin/env python
#coding:utf-8
import os
import time
pid = os.fork()
if pid: #父进程中执行的代码
print ‘In parent‘
time.sleep(10)
print ‘parent exit‘
else: #子进程中执行的代码
for i in range(5):
print time.ctime()
print ‘child exit‘
步骤二:验证执行结果
[[email protected]192-168-3-6 test]# python bfork.py In parent Wed Aug 2 10:06:38 2017 Wed Aug 2 10:06:38 2017 Wed Aug 2 10:06:38 2017 Wed Aug 2 10:06:38 2017 Wed Aug 2 10:06:38 2017 child exit parent exit [[email protected]192-168-3-6 test]#
5、利用 forking 创建 TCP 时间戳服务器
编写 forktsServer.py 服务器脚本,主要要求如下:
1、服务器监听在 0.0.0.0 的 21567 端口上
2、收到客户端数据后,将其加上时间戳后回送给客户端
3、如果客户端发过来的字符全是空白字符,则终止与客户端的连接
4、服务器能够同时处理多个客户端的请求
5、程序通过 forking 来实现
方案
如果服务器需要同时响应多个客户端,那么 forking 是解决问题最常用的方法之一。
父进程负责接受客户端的连接请求,子进程负责处理客户端的请求。
因为执行 fork 调用后,将会把父进程所有的资源都拷贝一份。所以要注意父进程需要断开与客户端的连接,继续等待新的请求;子进程需要关闭 socket,避免造成端口冲突
实现此案例需要按照如下步骤进行。
步骤一:编写脚本
#!/usr/bin/env python
#coding:utf-8
import socket
import time
import os
import sys
HOST = ‘‘
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)
def reap(): #定义清除僵尸进程函数
while True:
try:
result = os.waitpid(-1, os.WNOHANG)
if not result[0]:
break
except:
break
print ‘Reaped child process %d‘ % result[0]
tcpSerSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcpSerSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
tcpSerSock.bind(ADDR)
tcpSerSock.listen(5)
print ‘Parent at %d listening for connections‘ % os.getpid()
while True:
print ‘waiting for connection...‘
tcpCliSock, addr = tcpSerSock.accept()
print ‘...connected from:‘, addr
reap()
pid = os.fork()
if pid: #父进程关闭客户端连接,继续监听新的请求
tcpCliSock.close()
continue
else: #子进程关闭 socket,避免冲突
tcpSerSock.close()
while True:
data = tcpCliSock.recv(BUFSIZ)
if not data.strip():
break
tcpCliSock.send(‘[%s] %s‘ % (time.ctime(), data))
tcpCliSock.close()
sys.exit(0)
tcpSerSock.close()
步骤二:验证执行结果
[[email protected] bin]# ./forktsServer.py #启动服务器
[[email protected] bin]# telnet 127.0.0.1 21567 #可以打开多个终端,同时连接 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is ‘^]‘. hello world! [Mon Jul 6 19:25:42 2015] hello world! #此处输入回车,退出连接 Connection closed by foreign host
6. 创建多线程时间戳服务器
编写 mttsServer.py 服务器脚本,主要要求如下:
1、服务器监听在 0.0.0.0 的 12345 端口上
2、收到客户端数据后,将其加上时间戳后回送给客户端
3、如果客户端发过来的字符全是空白字符,则终止与客户端的连接
4、要求能够同时处理多个客户端的请求
5、要求使用多线程的方式进行编写
方案
多线程可以并行处理相互独立的子任务,大幅度地提升整个任务的效率。多数的线程服务器有同样的结构,
主线程是负责侦听请求的线程,主线程收到一个请求的时候,新的工作线程会被建立起来,处理客户端请求,客户端断开时,工作线程将终止。
实现此案例需要按照如下步骤进行。
步骤一:编写脚本
#!/usr/bin/env python
#coding:utf-8
import socket
import os
import sys
import threading
import time
host = ‘‘
port = 12345
addr = (host, port)
def handleChild(clientsock): #定义处理客户端连接请求的函数
print ‘New child‘, threading.currentThread().getName()
print ‘Got connection from:‘, clientsock.getpeername()
while True:
data = clientsock.recv(4096).strip()
if not len(data):
break
clientsock.sendall(‘[%s] %s\\n‘ % (time.ctime(), data))
clientsock.close()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(5)
while True:
clientsock, clientaddr = s.accept()
t = threading.Thread(target = handleChild, args = [clientsock])
t.setDaemon(1)
t.start()
步骤二:验证执行结果
[[email protected] bin]# ./mttsServer.py #启动服务器
测试客户端:
[[email protected] bin]# telnet 127.0.0.1 12345 #可以打开多个终端,同时连接 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is ‘^]‘. hello world! [Mon Jul 6 19:39:27 2015] hello world! #此处输入回车,退出连接 Connection closed by foreign host
tcpserv1.py内容:
#!/usr/bin/env python
#coding:utf8
import socket
host = ‘‘
port = 12345
addr = (host,port)
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
s.bind(addr)
s.listen(1)
cli_sock,cli_addr = s.accept()
print "Got connection from : " ,cli_addr
data = cli_sock.recv(4096)
print data
cli_sock.send("I C U!\\n")
cli_sock.close()
s.close()
测试:
[[email protected]192-168-3-6 ~]# python tcpserv.py 另起一个标签测试: [[email protected]192-168-3-6 ~]# netstat -tlnp| grep 12345 tcp 0 0 0.0.0.0:12345 0.0.0.0:* LISTEN 23493/python [[email protected]192-168-3-6 ~]# yum -y install telnet [[email protected]192-168-3-6 ~]# telnet 127.0.0.1 12345 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is ‘^]‘. I 4 c u I C U! [[email protected]192-168-3-6 ~]# python tcpserv.py Got connection from : (‘127.0.0.1‘, 50911) I 4 c u
tcpserv2.py内容:
#!/usr/bin/env python
#coding:utf8
import socket
host = ‘‘
port = 12345
addr = (host,port)
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
s.bind(addr)
s.listen(1)
while True:
cli_sock,cli_addr = s.accept()
print "Got connection from : " ,cli_addr
data = cli_sock.recv(4096)
print data
cli_sock.send("I C U!\\n")
cli_sock.close()
s.close()
测试:
[[email protected]192-168-3-6 ~]# telnet 127.0.0.1 12345 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is ‘^]‘. hello [Sun Jun 18 22:49:30 2017] hello
tcpserv3.py内容:
#!/usr/bin/env python
#coding:utf8
import socket
import time
host = ‘‘
port = 12345
addr = (host,port)
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
s.bind(addr)
s.listen(1)
while True:
cli_sock,cli_addr = s.accept()
print "Got connection from : " ,cli_addr
while True:
data = cli_sock.recv(4096)
if not data.strip():
break
cli_sock.send("[%s] %s " % (time.ctime(),data))
cli_sock.close()
s.close()
测试:
[[email protected]192-168-3-6 ~]# telnet 127.0.0.1 12345 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is ‘^]‘. hello [Sun Jun 18 22:49:30 2017] hello
tcpcli.py内容:
#!/usr/bin/env python
#coding:utf8
import socket
host = ‘127.0.0.1‘
port = 12345
addr = (host,port)
c = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
c.connect(addr)
while True:
data = raw_input(‘>‘)
if not data:
break
c.send(data)
print c.recv(4096)
c.close()
测试:
[[email protected]192-168-3-6 ~]# python tcpcli.py >hello [Sun Jun 18 23:03:44 2017] hello >world [Sun Jun 18 23:03:47 2017] world > [[email protected]192-168-3-6 ~]#
web.py内容:
#!/usr/bin/env python
#coding:utf8
import socket
host = ‘‘
port = 80
addr = (host,port)
with open(‘/home/index.html‘) as fobj:
contents = fobj.read()
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
s.bind(addr)
s.listen(1)
while True:
cli_sock,cli_addr = s.accept()
cli_sock.send(contents)
cli_sock.close()
s.close()
测试:
[[email protected]192-168-3-6 ~]# cd /home/ [[email protected]192-168-3-6 home]# vim index.html hello world [[email protected]192-168-3-6 ~]# systemctl stop httpd [[email protected]192-168-3-6 ~]# systemctl stop httpd [[email protected]192-168-3-6 ~]# python web.py
udpserv.py内容:
#!/usr/bin/env python
#coding:utf8
import socket
import time
host = ‘‘
port = 12345
addr = (host,port)
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
while True:
data,cli_addr = s.recvfrom(4096)
s.sendto("[%s] %s" % (time.ctime(),data),cli_addr)
s.close()
测试:
[[email protected]192-168-3-6 ~]# python udpserv.py [[email protected]192-168-3-6 ~]# netstat -ulnp | grep 12345 udp 0 0 0.0.0.0:12345 0.0.0.0:* 2914/python [[email protected]192-168-3-6 ~]#
udpcli.py内容:
#!/usr/bin/env python
#coding:utf8
import socket
import sys
host = sys.argv[1]
port = int(sys.argv[2])
addr = (host,port)
c = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while True:
data = raw_input(">")
if not data:
break
c.sendto(data,addr)
print c.recvfrom(4096)[0]
c.close()
step1:
yum -y install xinetd
yum -y install tftp-server
step2:
[[email protected]192-168-3-6 day10]# vim /etc/xinetd.d/pyserv service pyserv { flags = NAMEINARGS type = UNLISTED port = 12345 socket_type = stream protocol = TCP wait = no user = root server = /root/day10/example.py server_args = /root/day10/example.py }
step3:
/root/day10/example.py内容:
#!/usr/bin/env python
#coding:utf8
import sys
print "Welcome ."
print "Enter Line."
sys.stdout.flush()
line = sys.stdin.readline().strip()
sys.stdout.write("You entered %s chars" % len(line))
测试:
[[email protected]192-168-3-6 ~]# chmod +x /root/day10/example.py [email protected]192-168-3-6 ~]# cd /root/day10/ [[email protected]192-168-3-6 day10]# python example.py Welcome . Enter Line. hello world! You entered 12 chars [[email protected]192-168-3-6 day10]# [[email protected]192-168-3-6 day10]# systemctl restart xinetd [[email protected]192-168-3-6 day10]# netstat -tulnp| grep 12345 tcp6 0 0 :::12345 :::* LISTEN 3738/xinetd [[email protected]192-168-3-6 day10]# [[email protected]-192-168-3-6 day10]# telnet 127.0.0.1 12345 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is ‘^]‘. Welcome. Enter Line. agfasfasdfa You entered 11 chars Connection closed by foreign host. [[email protected]192-168-3-6 day10]#
修改/root/day10/example.py内容
#!/usr/bin/env python
#coding:utf8
import sys
import time
sys.stdout.write(‘>‘)
sys.stdout.flush()
while True:
data = sys.stdin.readline()
if not data.strip():
break
sys.stdout.write("[%s] %s>" %(time.ctime(),data))
sys.stdout.flush()
测试:
[[email protected]192-168-3-6 day10]# ./example.py >hello world [Mon Jun 19 10:19:20 2017] hello world >
修改/root/day10/example.py内容:
#!/usr/bin/env python
#coding:utf8
import sys
import time
import socket
s = socket.fromfd(sys.stdin.fileno(),socket.AF_INET,socket.SOCK_STREAM)
s.sendall("Welcome!\\n")
s.send("you are connected from %s\\n" % str(s.getpeername()))
s.send("Now: %s\\n" % time.ctime())
测试:
[[email protected]192-168-3-6 day10]# telnet 127.0.0.1 12345 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is ‘^]‘. Welcome! you are connected from (‘127.0.0.1‘, 51738) Now: Mon Jun 19 10:51:18 2017 Connection closed by foreign host.
修改/root/day10/example.py内容:
#!/usr/bin/env python
#coding:utf8
import sys
import time
import socket
s = socket.fromfd(sys.stdin.fileno(),socket.AF_INET,socket.SOCK_STREAM)
while True:
data = s.recv(4096)
if not data.strip():
break
s.send("[%s] %s " % (time.ctime(),data))
s.close()
测试:
[[email protected]192-168-3-6 day10]# telnet 127.0.0.1 12345 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is ‘^]‘. hello [Mon Jun 19 10:54:12 2017] hello world [Mon Jun 19 10:54:16 2017] world
############################################
myfork1.py内容:
#!/usr/bin/env python
#coding:utf8
import os
print "starting..."
os.fork()
print "hello world"
myfork2.py内容:
#!/usr/bin/env python
#coding:utf8
import os
print "starting..."
pid = os.fork()
if pid:
print "hello from parent."
else:
print "hello from child"
print "hello from both.."
fork_say.py内容:
#!/usr/bin/env python
#coding:utf8
import os
import sys
for i in range(6):
print "hello"
for i in range(6):
pid = os.fork()
if pid == ‘0‘:
print ‘hello‘
sys.exit() #测试时可以把这个注释掉看看有什么不同
fork_ping.py内容:
#!/usr/bin/env python
#coding:utf8
import sys
import os
ip_list = ("192.168.3.%s" % i for i in range(1,255))
for ip in ip_list:
pid = os.fork()
if pid == 0:
result = os.system("ping -c2 %s &> /dev/null" % ip)
if result == 0:
print "%s:up" % ip
else:
print "%s:down" % ip
sys.exit()
zb.py内容:
#!/usr/bin/env python
#coding:utf8
import os
import time
pid = os.fork()
if pid:
print "in parent.sleeping..."
time.sleep(20)
print "parent done"
else:
print "in child .sleeping..."
time.sleep(20)
print ‘child done‘
zb1.py内容:
#!/usr/bin/env python
#coding:utf8
import os
import time
pid = os.fork()
if pid:
print "in parent.sleeping..."
print os.waitpid(-1, 0)
time.sleep(5)
print "parent done"
else:
print "in child .sleeping..."
time.sleep(10)
print ‘child done‘
zb2.py内容:
#!/usr/bin/env python
#coding:utf8
import os
import time
pid = os.fork()
if pid:
print "in parent.sleeping..."
print os.waitpid(-1, os.WNOHANG)
time.sleep(5)
print "parent done"
else:
print "in child .sleeping..."
time.sleep(10)
print ‘child done‘
forktcpserv1.py内容:
#!/usr/bin/env python
#coding:utf8
import os
import time
import socket
import sys
host = ‘‘
port = 21345
addr = (host,port)
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(1)
while True:
cli_sock,cli_addr = s.accept()
pid = os.fork()
if pid:
cli_sock.close()
else:
s.close()
while True:
data = cli_sock.recv(4096)
if not data.strip():
cli_sock.close()
sys.exit()
cli_sock.send("[%s] %s" % (time.ctime(),data))
s.close()
测试:
[[email protected]192-168-3-6 ~]# telnet 127.0.0.1 21345 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is ‘^]‘. hello [Mon Jun 19 14:49:55 2017] hello world [Mon Jun 19 14:49:57 2017] world
forktcpserv2.py内容:
#!/usr/bin/env python
#coding:utf8
import os
import time
import socket
import sys
host = ‘‘
port = 21345
addr = (host,port)
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(1)
while True:
try:
while True:
result = os.waitpid(-1, os.WNOHANG)
if result[0] == 0:
break
except OSError:
pass
cli_sock,cli_addr = s.accept()
pid = os.fork()
if pid:
cli_sock.close()
else:
s.close()
while True:
data = cli_sock.recv(4096)
if not data.strip():
cli_sock.close()
sys.exit()
cli_sock.send("[%s] %s" % (time.ctime(),data))
s.close()
########################
variable.py内容:
#!/usr/bin/env python
#coding:utf8
import os
a = 10
pid = os.fork()
if pid:
a = a+ 10
print "in parent,a =" ,a
else:
a = "hello "
print "in child,a=",a
print a
mtping1.py内容:
#!/usr/bin/env python
#coding:utf8
import os
import threading
def ping(ip):
result = os.system("ping -c2 %s &> /dev/null " % ip)
if result == 0:
print "%s:up" % ip
else:
print "%s:down" % ip
if __name__ == "__main__":
ip_list = ("192.168.3.%s" % i for i in range(1,255))
for ip in ip_list:
ping(ip)
mtping2.py内容:
#!/usr/bin/env python
#coding:utf8
import os
import threading
def ping(ip):
result = os.system("ping -c2 %s &> /dev/null " % ip)
if result == 0:
print "%s:up" % ip
else:
print "%s:down" % ip
if __name__ == "__main__":
ip_list = ("192.168.3.%s" % i for i in range(1,255))
for ip in ip_list:
t = threading.Thread(target=ping,args=[ip])
t.start()
mt1.py内容:
#!/usr/bin/env python
#coding:utf8
import threading
import time
def say_hi():
time.sleep(5)
print "hello"
if __name__ == "__main__":
t = threading.Thread(target=say_hi)
t.start()
mt2.py内容:
#!/usr/bin/env python
#coding:utf8
import threading
import time
def say_hi():
time.sleep(5)
print "hello"
if __name__ == "__main__":
t = threading.Thread(target=say_hi)
t.setDaemon(1)
t.start()
mttcpserv.py内容:
#!/usr/bin/env python
#coding:utf8
import socket
import time
import threading
def handle_client(cli_sock):
while True:
data = cli_sock.recv(4096)
if not data.strip():
break
cli_sock.send("[%s] %s" % (time.ctime(),data))
cli_sock.close()
if __name__ =="__main__":
host = ‘‘
port = 21345
addr = (host,port)
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(1)
while True:
cli_sock,cli_addr = s.accept()
t = threading.Thread(target=handle_client,args=[cli_sock])
t.setDaemon(1)
t.start()
mtping3.py内容:
#!/usr/bin/env python
#coding:utf8
import os
import threading
class Ping(object):
def __init__(self,ip):
self.ip = ip
def __call__(self):
result = os.system("ping -c2 %s &> /dev/null " % self.ip)
if result == 0:
print "%s:up" % self.ip
else:
print "%s:down" % self.ip
if __name__ == "__main__":
ip_list = ("192.168.3.%s" % i for i in range(1,255))
for ip in ip_list:
t = threading.Thread(target=Ping(ip))
t.start()
以上是关于第十一篇:网络编程的主要内容,如果未能解决你的问题,请参考以下文章