socket多线程图形化界面聊天室实例


效果图

效果图

一、项目地址

socket-chat-room

二、socket简介

socket 的原意是“插座”,在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。

socket “插座”
socket 的典型应用就是 Web 服务器和浏览器:浏览器获取用户输入的 URL,向服务器发起请求,服务器分析接收到的 URL,将对应的网页内容返回给浏览器,浏览器再经过解析和渲染,就将文字、图片、视频等元素呈现给用户。

三、socket类型

根据数据的传输方式,可以将 Internet 套接字分成两种类型。通过 socket() 函数创建连接时,必须告诉它使用哪种数据传输方式。

1、流格式套接字(SOCK_STREAM)

流格式套接字(Stream Sockets)也叫“面向连接的套接字”,在代码中使用SOCK_STREAM 表示。
特点:

  • 用TCP协议;
  • 数据在传输过程中不会消失;
  • 数据是按照顺序传输的;
  • 数据的发送和接收不是同步的。

可以将 SOCK_STREAM 比喻成一条传送带,只要传送带本身没有问题(不会断网),就能保证数据不丢失;同时,较晚传送的数据不会先到达,较早传送的数据不会晚到达,这就保证了数据是按照顺序传递的。

2、数据报格式套接字(SOCK_DGRAM)

数据报格式套接字(Datagram Sockets也叫“无连接的套接字”,代码中使用SOCK_DGRAM 表示。
特点

  • 用UDP协议;
  • 强调快速传输而非传输顺序;
  • 传输的数据可能丢失也可能损毁;
  • 限制每次传输的数据大小;
  • 数据的发送和接收是同步的。

众所周知,速度是快递行业的生命。用摩托车发往同一地点的两件包裹无需保证顺序,只要以最快的速度交给客户就行。这种方式存在损坏或丢失的风险,而且包裹大小有一定限制。因此,想要传递大量包裹,就得分配发送。

QQ 视频聊天和语音聊天就使用 SOCK_DGRAM 来传输数据,因为首先要保证通信的效率,尽量减小延迟,而数据的正确性是次要的,即使丢失很小的一部分数据,视频和音频也可以正常解析,最多出现噪点或杂音,不会对通信质量有实质的影响。
注意:SOCK_DGRAM 没有想象中的糟糕,不会频繁的丢失数据,数据错误只是小概率事件。

四、socket通信逻辑

socket是基于C/S架构的,也就是说进行socket网络编程,通常需要编写两个文件,一个服务端,一个客户端。
其通信逻辑如下(图源网络):

socket通信原理

socket通信原理

五、socket创建

此处以Python语言为例,说明如何创建套接字。
在Python中,import socket后,用socket.socket()方法来创建套接字,语法格式如下:
sk = socket.socket([family[, type[, proto]]])
参数说明:
family: 套接字家族,可以使AF_UNIX或者AF_INET。
type: 套接字类型,根据是面向连接的还是非连接分为SOCK_STREAM或SOCK_DGRAM,也就是TCP和UDP的区别。
protocol: 一般不填默认为0。
下面是具体的参数定义:

socket类型描述
socket.AF_UNIX只能够用于单一的Unix系统进程间通信
socket.AF_INETIPv4
socket.AF_INET6IPv6
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只能够用于单一的Unix系统进程间通信
创建TCP Socket:s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
创建UDP Socket:s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

通过sk = socket.socket()方法,我们可以获得一个socket对象sk,也就是通常说的获取了一个“套接字”,该对象具有以下方法:

服务器端方法描述
s.bind()绑定地址(host,port)到套接字,在AF_INET下,以元组(host,port)的形式表示地址
s.listen(backlog)开始监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。
s.accept()被动接受客户端连接,(阻塞式)等待连接的到来,并返回(conn,address)二元元组,其中conn是一个通信对象,可以用来接收和发送数据。address是连接客户端的地址。
客户端方法描述
s.connect(address)客户端向服务端发起连接。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
s.connect_ex()connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共方法描述
s.recv(bufsize)接收数据,数据以bytes类型返回,bufsize指定要接收的最大数据量。
s.sendall()完整发送数据。将数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
s.recvform()接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收的数据,address是发送数据的套接字地址。
s.close()关闭套接字,必须执行。

注意事项
Python3以后,socket传递的都是bytes类型的数据,字符串需要先转换一下,string.encode()即可;另一端接收到的bytes数据想转换成字符串,只要bytes.decode()一下就可以。
在正常通信时,accept()和recv()方法都是阻塞的。所谓的阻塞,指的是程序会暂停在那,一直等到有数据过来。

六、socket编程完整步骤(TCP)

1、服务端
  1. 创建套接字,绑定套接字到本地IP与端口:
    socket.socket(socket.AF_INET,socket.SOCK_STREAM),

  2. 设置socket选项(可选),s.bind() 开始监听连接:s.listen()

  3. 进入循环,不断接受客户端的连接请求:s.accept() 接收传来的数据,或者发送数据给对方:s.recv()

  4. s.sendall() 传输完毕后,关闭套接字:s.close()

2、客户端
  1. 创建套接字,连接服务器地址:socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.connect()
  2. 连接后发送数据和接收数据:s.sendall(), s.recv()
  3. 传输完毕后,关闭套接字:s.close()

七、socket(TCP)多线程图形化界面聊天室实例

1、需求分析

通过以上对socket编程的一些讲解,可以发现单纯的在命令行实现socket通信并不算太难,如果给它加上图形化界面,会更直观。所以我们就一起来实现一个socket(TCP)多线程图形化界面聊天室吧。

根据需求分析,此聊天室项目主要包含两个方面:

  • socket网络编程
  • 图形化界面

socket网络编程我们上面已经讲解过一些基础知识,接下来只要用Python去实现即可,。至于图形化界面,我们这里用的是python自带的GUI库tkinter,其跨平台性能良好,并且本身就是为快速GUI开发而生,已经比较成熟。除了tkinter,读者也可以使用其他的GUI框架,比如PyQt,此处就不具体实现了。

2、具体实现

我们结合代码具体讲解,此处主要包含两个py文件,一个服务器端server.py,一个客户端client.py。因为服务器端主要是接受客户端请求并返回消息,所以此处我们将图形化界面的编写融合到客户端的代码文件中,更加“直观”的感受客户端的请求发送。

需要注意的是,此处我们使用了Python内置的threading模块进行多线程处理,因为Python的socket模块,默认情况下创建的是单进程单线程,同时只能处理一个连接请求。

(1)服务器端

服务端主要包含用户字典库,以及服务端接受及处理服务端请求。
此处为类的定义(完整代码见:socket-chat-room):

import socket 
import threading
from settings import Local_Host,Local_Port
#User database for authentication
db = {
    '北落':'Love',
    '师门':'You',
    '北落师门':'Forever',
}   
class ChatServer:
    clients_list = []     #客户端列表
    last_received_message = ""  #接收的最新请求
    def __init__(self):
        self.server_socket = None
        self.create_listening_ser

运行服务器端server.py,可以发现服务正在监听等待请求消息:
服务器监听

(2)客户端

客户端主要为图形化界面的实现,以及请求的发送与接收。
此处为类的定义(完整代码见:socket-chat-room)

from tkinter import Tk, Frame, Scrollbar, Label, END, Entry, Text, VERTICAL, Button, messagebox 
import socket 
import threading 

class GUI:
    client_socket = None
    last_received_message = None  
    def __init__(self, master):
        self.root = master
        self.chat_transcript_area = None
        self.name_widget = None
        self.password_widget = None
        self.enter_text_widget = None
        self.join_button = None
        self.initialize_socket()              #初始化socket
        self.initialize_gui()                 #图形化界面
        self.listen_for_incoming_messages_in_a_thread()    #监听消息
   

运行客户端代码client.py),可以看到服务端显示有客户端请求:

客户端请求
此时客户端部分,桌面会弹出一个新的页面,如下所示:
图形界面
可以看到,图形化界面主要包含三个部分:

  • 聊天区:用于展示(多个)客户端发送的请求消息
  • 用户信息区:用户的昵称、密码,点击加入群聊,服务端进行身份验证,通过后,可以开始聊天。
  • 消息编写发送区:键入需要发送的消息,回车发送。

键入昵称与密码后,服务端会进行身份验证,主要是通过将昵称与密码结合成字符串,发送至服务端,服务端将其与自身保存的用户字典中的用户进行匹配。若在字典中,则验证通过,服务器端显示成功连接:
建立连接

此时客户端的昵称、密码输入框以及加入群聊按钮都被禁用,即不允许再修改身份:
不可修改身份
若身份验证失败,则服务端显示身份验证失败,断开连接:
验证失败则无法建立连接

此时客户端弹出身份验证失败提示框:
验证失败

当新的客户端加入聊天室时,聊天区会展示该用户已加入群聊(has joined)。在两个客户端分别键入消息并发送,其结果如下:
聊天界面

注意:

  1. 通过tkinter的事件绑定,昵称、密码输入后,点击“加入群聊”按钮,输入框状态修改为"disabled",即无法再修改身份信息。
    聊天区也绑定了事件,即聊天区消息无法通过键盘输入或修改。

  2. 聊天区无法修改历史聊天消息。
    无法修改历史消息

  3. 当未输入昵称或密码,直接点击“加入群聊按钮”,或者尝试在消息区编写发送消息,系统会弹出提示框,提醒用户先进行登录。

输入提示

八、远程服务器测试

上面的聊天室实现是基于本机的,服务端与客户端绑定的都为本机ip:127.0.0.1,端口port:10319。那么如何进行远程服务器测试呢?
重要的一点是要将用于socket通信的远程服务器的端口打开,注意是出方向入方向同时打开(笔者此处使用4004端口)。

服务器端口设置
端口设置

并且需要将远程服务器ip绑定为"0.0.0.0",即允许监听所有端口。
ip绑定

设置后,客户端与服务端即可正常通信,亲测可用!

Reference
[1] https://www.liujiangblog.com/course/python/76
[2]http://c.biancheng.net/cpp/socket/

  • 6
    点赞
  • 85
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值