44网络编程_socket_TCP

 

成都创新互联是一家专注于成都做网站、成都网站制作与策划设计,唐县网站建设哪家好?成都创新互联做网站,专注于网站建设十年,网设计领域的专业建站公司;建站业务涵盖:唐县等地区。唐县做网站价格咨询:13518219792

 

 

目录

socket,套接字:...1

TCP编程:...2

TCP服务器端编程步骤:...3

群聊程序,TCP实现:...5

makefile:...7

TCP客户端编程步骤:...10

 

 

 

socket,套接字:

py中提供socket.py标准库,非常底层的接口库;

socket是一种通用的网络编程接口;

44网络编程_socket_TCP

 

协议族:

AF,address family,用于sock=socket.socket()第一个参数;

AF_NET,ipv4;

AF_NET6,ipv6;

AF_UNIX,unix domain socket,win没有这个;

 

socket类型:

socket.SOCK_STREAM,面向连接的流套接字,默认值TCP协议,如sock=socket.socket(type=socket.SOCK_STREAM或sock=socket.socket())均取默认;

socket.SOCK_DGRAM,无连接的数据报文套接字,UDP协议,如sock=socket.socket(type=socket.SOCK_DGRAM);

 

 

 

TCP编程:

socket编程,需要两端,一般需要一个服务端server,一个客户端client;

 

C/S模型,C/S编程;

B/S,B/S编程,本质上是C/S,是种特殊的C/S,要支持http、html(h6)、css、js、声音、视频等;只做B即web前端开发;

 

TCP服务器端编程步骤:

创建socket对象,socket(family=AF_INET, type=socket.SOCK_STREAM, proto=0, fileno=None);

绑定ip和port,bind()方法;ipv4地址为一个二元组('ip',port);

开始监听,将在指定的ip和port上监听,listen()方法;

获取用于传送数据的socket对象:

         accept(),阻塞等待客户端建立连接,返回一个新的socket对象和客户端地址(ipv4为二元组地址,远程客户端的地址),如accept() -> (socket object, address info);

         recv(bufsize[,flags]),接收数据,使用缓冲区接收数据;

         send(bytes),发送数据;

 

生产中编程时不会用这么底层的模块,当前使用是用于理解socket;

 

sock=socket.socket()

ip='127.0.0.1'

port=9999

addr=(ip,port)

sock.bind(addr)

sock.lienten()

conn,addrinfo=sock.accept()

 

conn.recv(bufsize[,flag]),获取数据,默认是阻塞方式,TCP接收数据;

conn.recvfrom(bufsize[,flag]),获取数据,返回一个二元组(bytes,address),UDP接收数据;

conn.recv_into(buffer[,nbytes][,flag]]),获取到nbytes的数据后,存储到buffer中;如果nbytes没有指定或0,将buffer大小的数据存入buffer中;返回接收的字节数;

conn.recvfrom_into(buffer[,nbytes[,flags]]),获取数据,返回一个二元组(bytes,address)到buffer中;

conn.send(bytes[,flags]),TCP发送数据;

conn.sendall(bytes[,flags]),TCP发送全部数据,成功返回None,本质上调的是send();

conn.sendto(string[,flag],address),UDP发送数据;

conn.sendfile(file,offset=0,count=None),发送一个文件直至EOF,使用高性能的os的sendfile机制,返回发送的字节数;win不支持sendfile,不是普通文件时,用send()发送文件;offset指起始位置;3.5版开始;

 

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

conn.getsockname(),返回套接字自己的地址,通常是元组(ipaddr,port);

conn.setblocking(flag),默认值True阻塞;False或0非阻塞,通常将套接字设为非阻塞,非阻塞模式下,如果调用recv()没有发现任何数据,或调用send()无法立即发送数据,将引起socket.error异常;Set the socket to blocking (flag is true) or non-blocking (false). setblocking(True) is equivalent to settimeout(None);setblocking(False) is equivalent to settimeout(0.0).;

conn.settimeout(value),设置套接字操作的超时期,timeout是一个浮点数,单位s,值为None表示没有超时期;一般,超时期应在刚创建套接字时设置,因为它们可能用于连接的操作,如connect();

conn.setsockopt(level,optname,value),设置套接字选项的值,如缓冲区大小等;具体看help有很多选项,不同OS不同version都不尽相同;

 

例:

sock = socket.socket()   #步骤1,均取默认,family=AF_NET,type=SOCK_STREAM

 

ip = '192.168.7.144'   #点分四段表示

port = 9999

addr = (ip, port)

sock.bind(addr)   #步骤2

 

sock.listen()   #步骤3

 

# s1 = socket.socket()

# s1.bind(addr)   #OSError: [WinError 10048]通常每个套接字地址(协议/网络地址/端口)只允许使用一次

# s1.listen()

 

time.sleep(3)

logging.info(sock)

 

conn, addrinfo = sock.accept()   #步骤4

# while True:   #不能这样写,接收进1个client请求后,之后的连接请求接收不到,解决:多线程

#     conn, addrinfo = sock.accept()

 

logging.info('{} {}'.format(conn, addrinfo))

 

for i in range(3):

    data = conn.recv(1024)

    logging.info(data)   #字节码

    logging.info(data.decode())   #字符串

    msg = 'ask {}'.format(data.decode())   #要发送字节码,若用data则抛TypeError异常

    conn.send(msg.encode())

 

conn.close()

sock.close()

输出:

2018-08-08-11:28:03       Thread info: 13528 MainThread

2018-08-08-11:28:04       Thread info: 13528 MainThread ('192.168.7.144', 7536)

2018-08-08-11:28:47       Thread info: 13528 MainThread b'fuck'

2018-08-08-11:28:47       Thread info: 13528 MainThread fuck

2018-08-08-11:28:52       Thread info: 13528 MainThread b'fuck again'

2018-08-08-11:28:52       Thread info: 13528 MainThread fuck again

2018-08-08-11:28:58       Thread info: 13528 MainThread b'fuck again again'

2018-08-08-11:28:58       Thread info: 13528 MainThread fuck again again

44网络编程_socket_TCP

44网络编程_socket_TCP

 

 

群聊程序,TCP实现:

例:

群聊程序:

需求分析:聊天工具CS程序;

服务器应具有的功能:

启动服务,包括绑定地址端口、监听;

建立连接,能和多个client建立连接;

接收不同用户信息;

分发,将接收的某个用户的信息转发到已连接的所有client;

停止服务;

记录连接的client;

 

class ChatServer:

    def __init__(self, ip='127.0.0.1', port=9999):   #生产中用ini文件配置

        self.sock = socket.socket()

        self.addr = (ip, port)

        self.clients = {}

        self.event = threading.Event()

 

    def start(self):

        self.sock.bind(self.addr)

        self.sock.listen()

        threading.Thread(target=self._accept, name='accept').start()

 

    def stop(self):

        for conn in self.clients.values():

            conn.close()

        self.sock.close()

        self.event.wait(3)   #TCP资源回收要些时间;UDP很快

        self.event.set()

 

    def _accept(self):

        # while True:

        while not self.event.is_set():

            conn, client = self.sock.accept()

            self.clients[client] = conn

            threading.Thread(target=self._recv, args=(conn, client), name='recv').start()

 

    def _recv(self, conn, client):

        # while True:

        while not self.event.is_set():

            data = conn.recv(1024)   #data为bytecode,此句可能有异常,建议放在try...except中

            data = data.strip().decode()   #data为string

            logging.info(data)

            if data == 'quit':

                logging.info('...quit')

                self.clients.pop(client)

                conn.close()

                break

            msg = 'ack {}'.format(data)   #msg为string

            # conn.send(msg.encode())

            for c in self.clients.values():   #注意此处c不能写为conn,否则会将上面的conn覆盖;一般循环次数用一个字符i,有意义的变量用多个字符

                c.send(msg.encode())   #send的数据要为bytecode

 

cs = ChatServer()

cs.start()

 

e = threading.Event()

def showthread():

    while not e.wait(5):

        logging.info(threading.enumerate())

# showthread()

# cs.stop()

threading.Thread(target=showthread, daemon=True).start()   #用于自己监控看,可随时关闭

 

while True:

    cmd = input('>>> ').strip()

    if cmd == 'quit':

        cs.stop()

        break

 

注:

仍有问题:各种异常处理;client主动退出后server不知道;

 

 

makefile:

conn,clientinfo=sock.accept(1024)

f=conn.makefile(mode='r',buffering=None,*,encoding=None,errors=None,newline=None)   #创建一个与该套接字相关联的文件对象;

 

高级接口用的是makefile;

 

例:

sock = socket.socket()

ip = '127.0.0.1'

port = 9999

addr = (ip,port)

sock.bind(addr)

sock.listen()

 

conn, addrinfo = sock.accept()

f = conn.makefile(mode='rw')

line = f.read(10)   #等价conn.recv(1024),两者差不多,makefile在处理字符串上容易些(不用decode()或encode())

print(line)

f.write('return your msg:{}'.format(line))  #同conn.send(msg)

f.flush()

 

 

例,使用makefile循环接收消息:

sock = socket.socket()

ip = '127.0.0.1'

port = 9999

addr = (ip,port)

sock.bind(addr)

sock.listen()

 

event = threading.Event()

 

def accept(sock, event:threading.Event):

    conn, addrinfo = sock.accept()

    f = conn.makefile(mode='rw')

    while True:

        line = f.readline()   #注意此处按行读取,遇到才接收;类似conn.recv(1024)

        print(line)

        if line.strip() == 'quit':

            break

        f.write('return your msg:{}'.format(line))   #conn.send(msg)

        f.flush()

    f.close()

    sock.close()

    event.wait(3)

    print('end')

    event.set()

 

threading.Thread(target=accept, args=(sock, event)).start()

while not event.wait(5):

    print(sock)

 

例,将ChatServer改为makefile;

class ChatServer:

    def __init__(self, ip='127.0.0.1', port=9999):

        self.sock = socket.socket()

        self.addr = (ip, port)

        self.clients = {}

        self.event = threading.Event()

 

    def start(self):

        self.sock.bind(self.addr)

        self.sock.listen()

        threading.Thread(target=self._accept, name='accept').start()

 

    def stop(self):

        for f in self.clients.values():

            f.close()

        self.sock.close()

        self.event.wait(3)

        self.event.set()

 

    def _accept(self):

        while not self.event.is_set():

            conn, client = self.sock.accept()

            f = conn.makefile(mode='rw')

            self.clients[client] = f

            threading.Thread(target=self._recv, args=(f, client), name='recv').start()

 

    def _recv(self, f, client):

        while not self.event.is_set():

            try:

                data = f.readline()

            except Exception as e:

                logging.info(e)

                data = 'quit'

            data = data.strip()

            logging.info(data)

 

            if data == 'quit':

                logging.info('{}: ...quit'.format(client))

                self.clients.pop(client)

                f.close()

                break

 

            msg = 'ack: {}'.format(data)

            for f in self.clients.values():

                f.writelines(msg)

                f.flush()

 

cs = ChatServer()

cs.start()

 

myutils.show_threads()

 

e = threading.Event()

while not e.wait(5):

    cmd = input('>>> ').strip()

    if cmd == 'quit':

        cs.stop()

        break

 

 

TCP客户端编程步骤:

创建socket对象;

连接到远端服务器的ip和port,connect()方法;

传输数据,使用send()、recv()方法;

关闭连接、释放资源;

 

TCP、UDP的客户端是随机的port,而服务器端是绑定死的(提供服务的场所要固定,如银行);

 

 

例:

sock = socket.socket()   #step 1

 

ip = '127.0.0.1'

port = 9999

addr = (ip, port)

sock.connect(addr)   #step 2

 

sock.send(b'hello\n')   #step3

data = sock.recv(1024)

print(data)

 

sock.close()   #step4,好习惯,fd资源有限

 

例,ChatClient:

class ChatClient():

    def __init__(self, ip='127.0.0.1', port=9999):

        self.sock = socket.socket()

        self.addr = (ip, port)

        self.event = threading.Event()

 

    def start(self):

        self.sock.connect(self.addr)

        threading.Thread(target=self._recv, name='recv').start()

 

    def stop(self):

        self.sock.close()

        self.event.wait(3)

        self.event.set()

 

    def _recv(self):

        while not self.event.is_set():

            try:

                data = self.sock.recv(1024)

            except Exception as e:

                logging.info(e)

                break

            logging.info(data.decode())

 

    def send(self):

        self.sock.send(data.encode())

 

def main():

    cc = ChatClient()

    cc.start()

 

    myutils.show_threads()

 

    while True:

        cmd = input('>>> ').strip()

        if cmd == 'quit':

            cc.send(cmd)

            cc.stop()

            break

        cc.send(cmd)

 

if __name__ == '__main__':

    main()

 


本文名称:44网络编程_socket_TCP
网页链接:http://scyanting.com/article/jjhooj.html