基于网络的日志系统

1. 原型三问

如何进行网络开发

Socket 分为阻塞性Socket非阻塞性Socket。客户端的Socket就像是对话中的一个终端,服务端的Socket就像是电话接线板的操作员。而且,Socket是目前位置最受欢迎的IPC(进程间通信)形式之一,相遇对其他方案,Socket实现更为容易,也具有跨平台的优势。

客户端Socket的生命过程:创建socket,连接服务器,交换数据,销毁。

#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))

服务端Socket生命过程:创建socket,绑定ip和端口,监听。

#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)

注意:

  • 使用hostname方式可以让服务端的socket对外可访问
  • 使用localhost或者127.0.0.1仅限于本机监听
  • 使用''则表示在本机任意ip上监听(存在于多网卡的场景)
  • 端口号尽量使用四位数字以上
  • listen()方法中的5的意思是,最多支持5个客户端连接,超过这个上限的客户端连接请求会被拒绝

创建一个mainloop,将服务端作为一个简易的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()

Server Socket的作用:不发送任何数据,也不接收任何数据,只创建客户端Socket,用于跟对端的客户端Socket通信。服务端处理客户端Socket的方式有三种,client_thread(clientsocket)只是伪代码:分配一个线程来处理clientsocket,创建一个子进程来处理clientsocket,使用非阻塞性socket。

客户端的clientsocket服务端的clientsocket建立连接之后,我们需要两个动词来描述它们之间的通信:sendrecv,而其对应的操作也是socket编程的难点部分。我们的职责就是通过sendrecv来操作网络缓存,使用它们来完成消息处理过程。

recv返回0个字节时,表示对端关闭了连接,你再也收不到任何数据。HTTP协议中使用socket作为一次性传递方式:客户端发起一个请求,然后读取回应。

对于一个socket而言,没有EOT(End of Transfer)这种东西。就是说,socket不会跟你说接下来没啥还要读取的了。所以,当一个socket的sendrecv返回0字节之后,这个socket已经broken。如果没有,你就会永远卡在recv这一步。

所以,使用socket传递消息的时候,要么消息长度固定,要么存在分隔符,要么隐含消息有多长,或者关闭连接来作为结束。

socket在使用结束之后需要调用close来显式关闭,虽然Python存在自动回收机制,但依赖这个机制并不是个好习惯。如果对端没有调用close来关闭socket,如果使用多线程来处理clientsocket,那么这个线程本质上来说已经僵死,并且也没啥能采取的措施。不要试图杀死一个线程,否则整个进程会被玩坏。

非阻塞性Socket

Python中创建非阻塞性socket,只需在socket创建之后只用之前,调用socket.setblocking(0)。但这样的话就需要用sendrecvconnectaccept,除了各种各样的返回码可以把你逼疯之外,还可以让你app的代码变得庞大,具备各种bug以及搞死CPU的能力。

一次性把事情做对的方法是:用select。

ready_to_read, ready_to_write, in_error = \
               select.select(
                  potential_readers,
                  potential_writers,
                  potential_errs,
                  timeout)

三个返回值和前三个入参都是list,如果有一个服务端socket,把它放到potential_readers中,如果在ready_to_read中发现了它,说明可以调用acceptrecv数据了;反之,客户端socket放到potential_writers中,并在ready_to_wirte中检查。timeout则作为超时时间,避免阻塞。

以上内容全部演绎自官方文档 Socket Programming HOWTO

2. 什么是 UDP 协议

UDP 协议是一种简单的不可靠信息传送服务,只管发,不操心对端是否成功接收。缺点是不提供数据包分组、组装和不能对数据包进行排序,但是相对于 TCP 协议,传送过程简单快速(不需要三次握手、错误重传等)。

3. 基于 Python 的 UDP 服务端/客户端

服务端代码:

# -*- coding: utf-8 -*-

import socket
import select

def main():
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(('', 9527))
    while True:
        reads, wirtes, errs = select.select([sock,],[],[],3)
        if len(reads) != 0:
            data, (host, port) = sock.recvfrom(8192)
            print "host %s:%s, said: %s" % (host, port, data)
        print 'no data.'

    sock.close()

if __name__ == '__main__':
    main()

客户端代码:

# -*- coding: utf-8 -*-

import socket

def main():
    clientSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    while True:
        input = raw_input(">>> ")
        if input in ['q', 'quit', 'exit']:
            break
        clientSock.sendto(input, ('localhost', 9527))

    clientSock.close()

if __name__ == '__main__':
    main()

测试结果: Simple-UDP-Server-Client.png

results matching ""

    No results matching ""