python----网络编程之server与client实现聊天功能

Socket套接字方法

family(socket家族)

socket.AF_UNIX: # 用于本机进程间通讯,为了保证程序安全,两个独立的程序(进程)间是不能互相访问彼此的内存的,但为了实现进程间的通讯,可以通过创建一个本地的socket来完成
socket.AF_INET: # (还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)

socket type类型

socket.SOCK_STREAM  # for tcp
socket.SOCK_DGRAM  # for udp
socket.SOCK_RAW  # 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
socket.SOCK_RDM  # 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用

服务端套接字函数

s.bind()  # 绑定(主机,端口号)到套接字
s.listen()  # 开始TCP监听
s.accept()  # 被动接受TCP客户的连接,(阻塞式)等待连接的到来

客户端套接字函数

s.connect()  # 主动初始化TCP服务器连接
s.connect_ex() connect() # 函数的扩展版本,出错时返回出错码,而不是抛出异常

公共用途的套接字函数

s.recv()  # 接收数据
s.send()  # 发送数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完,可后面通过实例解释)
s.sendall()  # 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom()  # 从套接字接收数据. 返回值是一对(字节,地址)
s.getpeername()  # 连接到当前套接字的远端的地址
s.close()  # 关闭套接字
socket.setblocking(flag)  # True or False,设置socket为非阻塞模式,以后讲io异步时会用
socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0) # 返回远程主机的地址信息,例子 socket.getaddrinfo('luffycity.com',80)
socket.getfqdn()  # 拿到本机的主机名
socket.gethostbyname()  # 通过域名解析ip地址

基本的套接字例子

服务端:

和客户端编程相比,服务器编程就要复杂一些。

服务器进程首先要绑定一个端口并监听来自其他客户端的连接。如果某个客户端连接过来了,服务器就与该客户端建立Socket连接,随后的通信就靠这个Socket连接了。

所以,服务器会打开固定端口监听,每来一个客户端连接,就创建该Socket连接。由于服务器会有大量来自客户端的连接,所以,服务器要能够区分一个Socket连接是和哪个客户端绑定的。一个Socket依赖4项:服务器地址、服务器端口、客户端地址、客户端端口来唯一确定一个Socket。

首先,创建一个基于IPv4和TCP协议的Socket

然后,我们要绑定监听的地址和端口。服务器可能有多块网卡,可以绑定到某一块网卡的IP地址上,也可以用0.0.0.0绑定到所有的网络地址,还可以用127.0.0.1绑定到本机地址。127.0.0.1是一个特殊的IP地址,表示本机地址,如果绑定到这个地址,客户端必须同时在本机运行才能连接,也就是说,外部的计算机无法连接进来。

端口号需要预先指定。因为我们写的这个服务不是标准服务,所以用8083这个端口号。请注意,小于1024的端口号必须要有管理员权限才能绑定

紧接着,调用listen()方法开始监听端口,传入的参数指定等待连接的最大数量

接下来,服务器程序通过一个永久循环来接受来自客户端的连接,accept()会等待并返回一个客户端的连接

连接建立后,客户端发来"hello",服务端返回大写的"HELLO"

import socket

# phone和conn两个套接字
# 1.买手机  基于网络通信,基于TCP协议
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# print(phone)

# 2.绑定手机  真实情况下,服务端的地址
phone.bind(('127.0.0.1', 8083))  # 0-65535:0-1024个操作系统使用,1024以后随便用

# 3.开机  监听listen  最大挂起5个
phone.listen(5)

# 4.等电话链接
print('staring...')
conn, client_addr = phone.accept()  # 接收链接对象  等于客户端的connect,底层完成了套接字的三次握手

# 5.收 发消息
while True:  # 通信循环
    data = conn.recv(1024)  # 1.单位:bytes 2.1024代表最大接受1024个bytes
    print('客户端的数据', data)
    conn.send(data.upper())

# 6.挂电话
conn.close()

# 7.关机
phone.close()
View Code

客户端:

创建Socket时,AF_INET指定使用IPv4协议,如果要用更先进的IPv6,就指定为AF_INET6SOCK_STREAM指定使用面向流的TCP协议,这样,一个Socket对象就创建成功,但是还没有建立连接。

客户端要主动发起TCP连接,必须知道服务器的IP地址和端口号。

服务器,提供什么样的服务,端口号就必须固定下来。由于我们想要访问网页,因此新浪提供网页服务的服务器必须把端口号固定在80端口,因为80端口是Web服务的标准端口。其他服务都有对应的标准端口号,例如SMTP服务是25端口,FTP服务是21端口,等等。端口号小于1024的是Internet标准服务的端口,端口号大于1024的,可以任意使用。

注意phone.connect参数是一个tuple,包含地址和端口号。

建立TCP连接后,我们就可以向服务器发送请求。

接收数据时,调用recv(max)方法,一次最多接收指定的字节数,因此,在一个while循环中反复接收,直到recv()返回空数据,表示接收完毕,退出循环。

当我们接收完数据后,调用close()方法关闭Socket,这样,一次完整的网络通信就结束了.

import socket


# 1.买手机  基于网络通信,基于TCP协议
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# print(phone)

# 2.拨号
phone.connect(('127.0.0.1', 8083))


# 3.发 收消息
phone.send('hello'.encode('utf-8'))
data = phone.recv(1024)
print(data)

# 4.关闭
phone.close()
View Code

Server与Client循环收发数据

服务端:

import socket


# phone和conn两个套接字
# 1.买手机  基于网络通信,基于TCP协议
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# print(phone)

# 2.绑定手机  真实情况下,服务端的地址
phone.bind(('127.0.0.1', 8083))  # 0-65535:0-1024个操作系统使用,1024以后随便用

# 3.开机  监听listen  最大挂起5个
phone.listen(5)

# 4.等电话链接
print('staring...')
conn, client_addr = phone.accept()  # 接收链接对象
print(client_addr)

# 5.收 发消息
while True:
    data = conn.recv(1024)  # 1.单位:bytes 2.1024代表最大接受1024个bytes
    print('客户端的数据', data)
    conn.send(data.upper())

# 6.挂电话
conn.close()

# 7.关机
phone.close()
View Code

客户端:

import socket


# 1.买手机  基于网络通信,基于TCP协议
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# print(phone)

# 2.拨号
phone.connect(('127.0.0.1', 8083))


# 3.发 收消息
while True:
    msg = input('>>>:').strip()
    phone.send(msg.encode('utf-8'))
    data = phone.recv(1024)
    print(data)

# 4.关闭
phone.close()
View Code

Server与Client实现简单的聊天功能

服务端:

import socket

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

phone.bind(('127.0.0.1', 8083))

phone.listen(5)

conn, client_addr = phone.accept()

while True:
    try:
        data = conn.recv(1024)
        print('Client recv:', data)
        if not data:
            break
        response = input('>>>:').strip()
        conn.send(response.encode())
        print('Server send:', response)
    except ConnectionResetError:
        print('客户端强制关闭')
        break

conn.close()

phone.close()
View Code

客户端:

import socket

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

phone.connect(('127.0.0.1', 8083))

while True:
    msg = input('>>>:').strip()
    if not msg:continue
    phone.send(msg.encode('utf-8'))
    data = phone.recv(1024)
    print(data)

phone.close()
View Code

简单的的聊天功能存在以下问题:

1.多个客户端与服务端交互的时候,处于一个派对的状态,只有第一个客户端与服务端断开后,下一个客户端与服务端才可以交互.

2.实际上断开第一个客户端,服务端也跟着断了,原因在于服务端收不到数据,直接就断开了

下面是优化后的聊天功能代码:

服务端:

import socket

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

phone.bind(('127.0.0.1', 8083))

phone.listen(5)
while True:

    conn, client_addr = phone.accept()

    while True:
        try:
            data = conn.recv(1024)
            print('Client recv:', data)
            if not data:  # 服务端收不到数据,就直接break了
                break
            response = input('>>>:').strip()
            conn.send(response.encode())
            print('Server send:', response)
        except ConnectionResetError:
            print('客户端强制关闭')
            break

    conn.close()

phone.close()
View Code

客户端:

import socket

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

phone.connect(('127.0.0.1', 8083))

while True:
    msg = input('>>>:').strip()
    if not msg:continue
    phone.send(msg.encode('utf-8'))
    data = phone.recv(1024)
    print(data)

phone.close()
View Code

重启服务端时可能会遇到端口被占用的问题:

解决方法:

sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)  # 一行代码搞定,写在bind之前
sock_server.bind((HOST, PORT))

或者服务端和客户端修改端口.

posted on 2019-04-17 22:29  cn_ike  阅读(996)  评论(0编辑  收藏  举报

导航