21世纪是一个以网络为核心的信息时代,要实现信息化,就必须依靠完善的网络。而随着计算机技术和通讯技术的迅猛发展,计算机网络已经渗透到各个应用领域,其中最突出的是以TCP/IP协议为核心的Internet网络发展最为迅速。因此,计算机应用程序的开发也由传统单机处理模式,转向为多机通信为主的网络应用开发。
综合应用计算机网络理论知识、程序设计语言和网络服务器平台,对目标系统进行分析、设计、实现,最后完成必要的测试。在深入理解计算机网络基本原理的基础上,将书本上抽象的概念与具体的实现技术相结合,体会网络协议的设计与实现的过程,以及专业技术人员所使用的基本方法与技巧。基于TCP协议实现一简单的聊天程序实现网上聊天,包括服务器和客户端。要求:
(1)支持多人聊天
(2)客户端具有图形化用户界面
服务器端运行稳定,客户端具有图形化用户界面,且使用简便。服务器端和客户端可以运行在多个系统平台,具有良好的兼容性能。服务器端和客户端功能独立,可以运行在同一台计算机上或不同的计算机上,具有良好的灵活性。
具体功能:
(1)用户注册,用户的密码采用MD5算法就行加密;
(2)用户登录,在密码框中,用户输入的密码显示为“*”;
(3)显示在线用户,用户登录或退出成功,都会刷新在线用户列表;
(4)支持多人聊天,聊天发送的消息:用户名称、发送日期时间和内容。
TCP/IP协议的核心部分是传输层协议(TCP、UDP),网络层协议(IP)和物理 接口层,这三层通常是在操作系统内核中设计。因此用户一般不涉及。TCP是面 向连接的,通信双方保持一条通路,好比目前的电话线,使用telnet登陆 BBS, 用的就是 TCP协议;UDP是无连接的,通信双方都不保持对方的状态,浏览器访问Internet时使用的HTT协议就是基于UDP协议的。编程时,编程界面有两种形式:一、由内核心直接提供的系统调用;二、使用以库函数方式提供的各种函数。前者为核内设计,后者为核外设计。用户服务要通过核外的应用程序才能设计,所以要使用套接字(socket)来设计。
C/S结构(Client/Server结构)是大家熟知的客户端和服务器端结构。它是软件系统体系结构,通过它可以充分利用两端硬件环境的优势,将任务合理分配到Client端和Server端来实现,降低了系统的通讯开销。目前大多数应用软件系统都是Client/Server形式的两层结构,由于现在的软件应用系统正在向分布式的 Web应用发展,Web和 Client/Server应用都可以进行同样的业务处理,应用不同的模块共享逻辑组件。因此,内部的和外部的用户都可以访问新的和现有的应用系统,通过现有应用系统中的逻辑可以扩展出新的应用系统。这也就是目前应用系统的发展方向。
Socket是建立在传输层协议(主要是TCP和UDP)上的一种套接字规范,最初是由美国加州Berkley大学提出,它定义两台计算机间进行通信的规范(也是一种编程规范,如果说两台计算机是利用一个“通道”进行通信,那么这个“通道”的两端就是两个套接字。套接字屏蔽了底层通信软件和具体操作系统的差异,使得任何两台安装了TCP协议软件和实现了套接字规范的计算机之间的通信成为可能。
Socket是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。
Socket 系统调用包括创建 Socket、将创建的 Socket 与本地端口绑定、 建立Socket 连接服务器、监听是否有连接、请求数据的可控缓冲发送和可控缓冲接收,到最后关闭 Socket。
服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
图2.3-1 Socket介绍
PyCharm
PyCharm是一种Python IDE,带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具,比如调试、语法高亮、Project管理、代码跳转、智能提示、自动完成、单元测试、版本控制。此外,该IDE提供了一些高级功能,以用于支持Django框架下的专业Web开发。
在通信工具中,通常都采用客户端/服务器(C/S)体系结构,即客户机和服务器结构。它是软件系统体系结构,通过它可以充分利用两端硬件环境的优势,将任务合理分配到Client端和Server端来实现,降低了系统的通讯开销。目前大多数应用软件系统都是Client/Server形式的两层结构,由于现在的软件应用系统正在向分布式的Web应用发展,Web和Client/Server应用都可以进行同样的业务处理,应用不同的模块共享逻辑组件;因此,内部和外部的用户都可以访问新的和现有的应用系统,通过现有应用系统中的逻辑可以扩展出新的应用系统。这也就是目前应用系统的发展方向。
Client/Server模型最终可归结为一种“请求/应答”关系。一个请求总是首先被客户发出,然后服务器总是被动地接收请求,返回客户需要的结果。在客户发出一个请求之前,服务进程一直处于休眠状态。一个客户提出请求后,服务进程被“唤醒”并且为客户提供服务,对客户的请求做出所需要的应答。如下图所示:
图3.1-1 体系结构设计
服务器先创建socket(socket),然后绑定socket和端口号(bind),对端口号进行监听(listen),调用accept阻塞,接受来自客户端的连接请求(accept)。这时,如果客户端创建socket(socket),连接到指定计算机的端口(connect),如果连接成功,这时客户端与服务器端的连接就建立了。这时客户端向socket中写入信息(send),服务器端从socket中读取字符并处理(read),然后把回应数据发送给客户端,客户端读取数据,最后关闭连接(close),一次交互结束,这就是服务器和客户端一次完整的网络通信。
图3.2-1 网络通信设计
3.3.1服务端
服务端启动成功后,开始创建socket,接着绑定服务端所在的IP地址和端口号,接着开始监听所绑定的端口号,然后等待客户端的连接请求。当有客户端连接时,根据客户端的不同请求,服务端做出回应。当客户端登录时,服务端开始处理登录请求,根据已经存储的用户名和密码来检查客户端输入的用户名和密码是否正确,如果验证成功,则客户端可以前往聊天主界面,否则给出相应的提示;当客户端注册时,服务端开始处理注册请求,根据客户端输入的用户名与已有的用户名比较,若已存在,则提示“该用户已被注册!”,若不存在,在输入正确的请况下,则客户端注册成功,在user.txt存储客户端注册的用户名和密码,接着关闭注册界面,打开登录界面,其他请况则提示“反生未知错误!”;当客户端要发送消息时,服务端开始处理发送消息请求,首先服务端从聊天主界面的输入框获取发送消息的客户端所要发的消息,然后服务端先发一个字符串告诉所有客户端接下来是消息,最后客户端把发送消息的客户端所要发的消息全部发给所有客户端,实现了群聊;当客户端登录成功进入聊天主界面后,服务端开始处理刷新用户列表请求,服务端先发送用户列表给登录成功的客户端,接着发送列表人数,最后发送存储socket连接和用户对应关系的字典。若一个客户端下线,则服务端断开与该客户端的连接,用户列表移除该用户,存储socket连接和用户对应关系的字典也删除该用户,最后服务端处理刷新用户列表,其他客户端的用户列表刷新,没有了下线的该用户。
3.3.2客户端
初始化TCP客户端后,开始创建socket,接着连接服务端的IP地址和端口号,连接成功后,客户端可以发送不同的请求。当客户端还没有用户名和密码时,客户端可以从登录界面跳转到注册界面进行注册,当客户端什么都没有输入时,则提示“不能为空!”,当客户端输入的密码和确认密码不一致时,则提示“密码和确认密码不一致!”,当客户端输入的注册内容没问题后,向服务端发送注册请求,其中客户端注册的密码采用MD5加密,接着服务端开始处理注册请求,根据客户端输入的用户名与已有的用户名比较,若已存在,则提示“该用户已被注册”,若不存在,则在user.txt存储客户端注册的用户名和密码,客户端注册成功,接着关闭注册界面,打开登录界面,其他请况则提示“反生未知错误”;当客户端登录时,当客户端什么都没有输入时,则提示“用户名和密码不能为空!”,当客户端输入用户名和密码后,向服务端发送登录请求,根据已经存储的用户名和密码来检查客户端输入的用户名和密码是否正确,如果验证成功,则客户端登录成功,接着关闭登录界面,前往聊天主界面,并在聊天主界面刷新用户列表,否则给出提示“用户名或密码错误!”;当客户端要发送消息时,若输入框的内容为空,则提示“空消息不能发送!”,当用户想清空输入框可以点击清空按钮,当客户端输入好要发送的消息时,向服务端发送发送消息的请求,服务端给出相应回应。若客户端下线,则客户端断开socket连接,销毁聊天主界面,最后服务端处理刷新用户列表,其他客户端的用户列表刷新,没有了该客户端。
Tkinter是Python的标准GUI库,可以进行窗口视窗设计,Tkinter模块 ("Tk 接口")是Python的标准Tk GUI工具包的接口,Python使用Tkinter可以快速的创建GUI应用程序。由于 Tkinter 是内置到 python 的安装包中,只要安装好 Python 之后就能 import Tkinter 库,而且 IDLE 也是用 Tkinter 编写而成,Tkinter 可以实现很多简单直观的图形界面。
首先导入tkinter和time两个包。
from tkinter import * # GUI库
import time
接着设计客户端图形界面的聊天程序主界面类。
class ChatInterface:
def __init__(self, username, send_func, close_callback):
print("初始化聊天程序主界面")
self.username = username
self.friend_list = None
self.message_text = None
self.send_text = None
self.send_func = send_func
self.close_callback = close_callback
self.main_frame = None
def show(self):
global main_frame
main_frame = Tk()
main_frame.title("基于TCP协议网上聊天程序")
main_frame.configure(background="white")
# 设置窗口关闭按钮回调,用于退出时关闭socket连接
main_frame.protocol("WM_DELETE_WINDOW", self.close_callback)
width = 800
height = 600
screen_width = main_frame.winfo_screenwidth()
screen_height = main_frame.winfo_screenheight()
gm_str = "%dx%d+%d+%d" % (width, height, (screen_width - width) / 2,
(screen_height - 1.2 * height) / 2)
main_frame.geometry(gm_str)
# 设置最小尺寸
main_frame.minsize(width, height)
Label(main_frame, text=" 用户列表 欢迎您:" + self.username + " ", font=("楷体", 15), bg="#51c29f",
fg="white").grid(row=0, column=0, ipady=10, padx=10, columnspan=2, sticky=W)
friend_list_var = StringVar()
self.friend_list = Listbox(main_frame, selectmode=NO, listvariable=friend_list_var,
bg="lightgray", fg="black", font=("楷体", 15), highlightcolor="white")
self.friend_list.grid(row=1, column=0, rowspan=3, sticky=N + S, padx=10, pady=(0, 5))
main_frame.rowconfigure(1, weight=1)
main_frame.columnconfigure(1, weight=1)
sc_bar = Scrollbar(main_frame)
sc_bar.grid(row=1, column=0, sticky=N + S + E, rowspan=3, pady=(0, 5))
sc_bar['command'] = self.friend_list.yview
self.friend_list['yscrollcommand'] = sc_bar.set
msg_sc_bar = Scrollbar(main_frame)
msg_sc_bar.grid(row=1, column=1, sticky=E + N + S, padx=(0, 10))
self.message_text = Text(main_frame, bg="white", height=1,
highlightcolor="white", highlightthickness=1)
# 显示消息的文本框不可编辑,当需要修改内容时再修改版为可以编辑模式 NORMAL
self.message_text.config(state=DISABLED)
self.message_text.tag_configure('greencolor', foreground='green')
self.message_text.tag_configure('bluecolor', foreground='blue')
self.message_text.grid(row=1, column=1, sticky=W + E + N + S, padx=(10, 30))
msg_sc_bar["command"] = self.message_text.yview
self.message_text["yscrollcommand"] = msg_sc_bar.set
send_sc_bar = Scrollbar(main_frame)
send_sc_bar.grid(row=2, column=1, sticky=E + N + S, padx=(0, 10), pady=10)
self.send_text = Text(main_frame, bg="white", height=11, highlightcolor="white",
highlightbackground="#444444", highlightthickness=3)
self.send_text.see(END)
self.send_text.grid(row=2, column=1, sticky=W + E + N + S, padx=(10, 30), pady=10)
send_sc_bar["command"] = self.send_text.yview
self.send_text["yscrollcommand"] = send_sc_bar.set
Button(main_frame, text="发送", bg="lightblue", font=("楷体", 15), fg="black", command=self.send_func) \
.grid(row=3, column=1, pady=5, padx=10, sticky=W, ipady=3, ipadx=10)
Button(main_frame, text="清空", bg="lightgray", font=("楷体", 15), fg="black", command=self.clear_send_text) \
.grid(row=3, column=1, pady=5, sticky=W, padx=(110, 0), ipady=3, ipadx=10)
self.main_frame = main_frame
main_frame.mainloop()
# 刷新在线用户列表
def refresh_friends(self, names):
self.friend_list.delete(0, END)
for name in names:
self.friend_list.insert(0, name)
# 接受到消息,在文本框中显示,自己的消息用绿色,别人的消息用蓝色
def recv_message(self, user, content):
self.message_text.config(state=NORMAL)
title = "用户名:" + user + " " + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + "\n"
if user == self.username:
self.message_text.insert(END, title, 'bluecolor')
else:
self.message_text.insert(END, title, 'greencolor')
self.message_text.insert(END, content + "\n")
self.message_text.config(state=DISABLED)
# 滚动到最底部
self.message_text.see(END)
# 获取消息输入框内容
def get_send_text(self):
return self.send_text.get('0.0', END)
# 清空消息输入框
def clear_send_text(self):
self.send_text.delete('0.0', END)
服务端和客户端之间的网络通信是通过socket来实现的。服务端先创建socket(socket),然后绑定socket和端口号(bind),对端口号进行监听(listen),调用accept阻塞,接受来自客户端的连接请求(accept)。
if __name__ == "__main__":
try:
sk = socket.socket()
sk.bind(('127.0.0.1', 12323))
# 最大挂起数
sk.listen(10)
print("服务器启动成功,开始监听...")
while True:
conn, addr = sk.accept()
Thread(target=handle, args=(conn, addr)).start()
except Exception as e:
print("服务器出错: " + str(e))
这时,如果客户端创建socket(socket),连接到指定计算机的端口(connect),如果连接成功,这时客户端与服务器端的连接就建立了。这时客户端向socket中写入信息(send),服务器端从socket中读取字符并处理(read),然后把回应数据发送给客户端,客户端读取数据,最后关闭连接(close),一次交互结束,这就是服务端和客户端一次完整的网络通信。
def __init__(self):
print("初始化TCP客户端")
self.sk = socket.socket()
self.sk.connect(('127.0.0.1', 12323))
# 断开socket连接
def close_sk():
print("客户端断开socket连接")
client.sk.close()
多人聊天的过程为当一个客户端要发送群聊消息时,先请求服务端发送消息,服务端开始处理客户端的发送消息请求时,该客户端把群聊消息发送给服务端,服务端接受来自客户端的消息,最后服务端把该客户端的群聊消息发送给所有的在线客户端,多人聊天的功能就实现了。
客户端:
# 发送消息
def send_message():
content = main_frame.get_send_text()
if content == "" or content == "\n":
messagebox.showerror(title="提示", message="空消息不能发送!")
return
# 清空输入框
main_frame.clear_send_text()
client.send_message(content)
def send_message(self, message):
self.sk.sendall(bytes("3", "utf-8"))
self.send_string_with_length(message)
服务端:
# 获取请求类型
_type = str(_conn.recv(1), "utf-8")
If _type == "3": # 发送消息
print("开始处理发送消息请求")
_goon = handle_message(_conn, addr)
# 处理消息发送请求
def handle_message(_conn, addr):
content = recv_all_string(_conn)
# 发送给所有在线客户端
for c in online_conn:
# 先发一个字符串告诉所有客户端接下来是消息
send_string_with_length(c, "Message")
send_string_with_length(c, conn2user[_conn])
send_string_with_length(c, content)
return True
先运行服务端的程序,启动服务器,开始监听。
图5.1-1 启动服务端户
接着运行客户端的程序,进入登录界面。
图5.1-2 启动客户端
由登录界面的注册按钮,进入注册界面,进行注册。
图5.1-3 注册
查看保存用户名和密码的user.txt,检查刚才的注册是否成功,其中密码采用MD5算法加密。
图5.1-4 查看注册是否成功
用户注册成功后,关闭提示“注册成功”,会自动从注册界面跳转到登录,用注册的用户名和密码进行登录。
图5.2-1 登录
若登录成功,则用户可以从登录界面跳转到聊天程序主界面,同时刷新用户列表,用户列表中多了刚登录的用户名。
图5.2-2 登录成功
为了多人聊天,再登录两个用户“123”和“789”。
图5.3-1 多人登录
发送消息测试基于TCP协议网上聊天程序是否能支持多人聊天,其中用户自己发送的消息为蓝色,接受到其他用户的消息为绿色,发送的聊天消息包括:用户名称、发送日期时间和内容。
图5.3-2 多人聊天
通过系统测试,设计的基于TCP协议网上聊天程序,服务器端运行稳定,客户端具有图形化用户界面,且使用简便。服务器端和客户端可以运行在多个系统平台,具有良好的兼容性能。服务器端和客户端功能独立,可以运行在同一台计算机上或不同的计算机上,具有良好的灵活性。达到了老师的两个要求:支持多人聊天和客户端具有图形化用户界面。
具体实现了以下功能:
(1)用户注册,用户的密码采用MD5算法就行加密;
(2)用户登录,在密码框中,用户输入的密码显示为“*”;
(3)显示在线用户,用户登录或退出成功,都会刷新在线用户列表;
(4)支持多人聊天,聊天发送的消息:用户名称、发送日期时间和内容。
通过这次计算机网络课程设计,让我对计算机网络这门课有了更加深刻的理解,我复习了计算机网络中TCP协议相关知识,学习并利用socket进行TCP通信,本来开始我是选用编程语言C++来设计基于TCP协议网上聊天程序,但经过一周的折腾,我没有成功用C++完成该设计,随后向折老师提出换成Python语言来完成,感谢折老师同意我换编程语言,折老师是真的好,再一次真诚的感谢。
作为信息安全专业的学生,课程设计要体现安全性方面地考虑,在这次的计算机网络课程设计中,我在密码方面进行了安全性地考虑,采用MD5算法对用户输入的密码进行加密保存,经过MD5算法加密后,我设计的系统安全性得到了加强。还有一个就是TCP提供IP环境下的数据可靠传输,它提供的服务包括数据流传送、可靠性、有效流控、全双工操作和多路复用,通过面向连接、端到端和可靠的数据包发送。基于这个考虑我在发送TCP的函数中的send()和sendall()选用了sendall()函数。因为socket.send(string[, flags])发送TCP数据,返回发送的字节大小,这个字节长度可能少于实际要发送的数据的长度,换句话说,这个函数执行一次,并不一定能发送完给定的数据,可能需要重复多次才能发送完成。而socket.sendall(string[, flags])函数发送完整的TCP数据,成功返回None,失败抛出异常。所以选用sendall()函数会更好。
这次计算机网络课程设计不仅让我对计算机网络的相关知识有了更加深刻的理解,同时也加深我独立思考解决实际问题的能力,这宝贵的经验会给我日后走向社会参加工作带来极大的帮助,同时也让自己知道自己学到的专业知识还是非常匮乏的,所以在接下来的一年多时间里,我不仅要加强理论知识的深入学习,也要继续提高我独立思考解决实际问题和实际操作能力。不管做什么事都要脚踏实地,并且为之努力奋斗,有了扎实的专业知识,才是我以后工作立于不败之地的法宝。计算机网络课程设计,让我获益匪浅。