【计算机网络】4 Socket网络编程

目录

写在前面的话

概览

环境

URL请求程序:

2. 系统时间查询

服务端

T_TCPServer.py代码

客户端

T_TCPClient.py代码

运行效果

3. 网络文件传输

服务端

TF_TCPServer.py代码

运行效果(后面加了远程功能,效果图暂时还在本地)

4. 网络聊天室

服务端

UDPServer.py代码

客户端

UDPClient.py代码

运行效果

开启服务器,jennie客户端和john客户端陆续加入群聊

聊天(4栏:服务器,mike,  john,  jennie)

mike回复消息同样被转发给全部用户

用户退出

问题及解决:

URL 请求程序

系统时间查询

网络文件传输

网络聊天室

实验心得体会

总结


写在前面的话

这个危,开学初老师曾觉得这个编程实验可能会劝退我这个小白。emmm熬过来了。上学期有老师说得先自学下Java搞这个,emmm寒假整c++一些个小系统,Java纯纯学点点写几个基本小系统罢了觉得不太可。幸好我的班用python。前年学了点毛皮捡捡还能用,1周速成python,再1周速成个多人聊天室,爬过来就可。

概览

▪ UDP与TCP套接字的区别

▪ UDP和TCP套接字编程方法

▪ 简单网络应用的编程思路

▪ 网络编程相关的一些库

1. URL 请求程序

2. 系统时间查询

3. 网络文件传输

4. 网络聊天室

环境

▪ 具有Internet连接的主机, VScode(直接在其终端运行),PowerShell

▪ 编程语言: python

  1. URL请求程序:

请求一个网页,并存储为html文件,计算所请求网页的大小。打印所请求网页的URL、存储文件名、文件大小等信息。(图1 2)

Figure 1 url请求程序

调用requests库请求对应url并保存。将url切片获得文件名,通过os库获得文件大小。

Figure 2 保存下来HTML网页文件

2. 系统时间查询

实现一个基于客户/服务器的系统时间查询程序。

传输层使用TCP

交互过程

1) 客户端向服务器端发送字符串Time

2) 服务器端收到该字符串后,返回当前系统时间。

3) 客户端向服务器端发送字符串Exit

4) 服务器端返回Bye,然后结束TCP连接。

  • 服务端

服务端创建TCP协议的socket套接字进程,端口号为12000,并保持监听状态。一旦收到客户端的连接就从socket套接字中获取客户端发送的消息。 如果收到无效信息回复提示。直到Exit才停止收发并断开连接。并可以与另外的客户端通信。

T_TCPServer.py代码

from socket import *
import time#获得系统时间

serverPort = 12000
serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(('', serverPort))
serverSocket.listen(1)

print('----------------------------------')
print('服务器已经准备好连接了')
print('服务器地址为:',serverSocket.getsockname())

while True:
    connectionSocket, addr = serverSocket.accept()
    print('----------------------------------')
    print('接收到一个新连接')
    print('连接地址为:',serverSocket.getsockname())
    print('客户端地址为:',addr)

    while True:
        request = connectionSocket.recv(1024).decode()
        if request == '':
            continue
        print('收到请求: ', request)
        
        if request == 'Time':
            # 格式化成2016-03-20 11:45:39形式
            now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 
            print('发送响应:',now)
            connectionSocket.send(('收到当前服务器上系统时间为:'+now).encode())
        elif request == 'Exit':
            print('发送响应:Bye')
            connectionSocket.send('收到回复:Bye'.encode())
            connectionSocket.close()#结束TCP连接
            break#跳出循环
        else:
            print('发送响应:输入有误,请重新输入')
            connectionSocket.send('输入有误,请重新输入'.encode())

  • 客户端

考虑远程实现,需要手动输入服务端IP及端口号12000。通过ipconfig查询,本机IP:192.168.0.107。

T_TCPClient.py代码

from socket import *

clientSocket = socket(AF_INET, SOCK_STREAM)
print('1个客户端正在运行')

serverName = input('请输入要连接的服务端IP:')
serverPort = int(input('请输入要连接的服务端的端口号:'))

clientSocket.connect((serverName, serverPort))
print('客户端地址:',clientSocket.getsockname())
print('连接到',clientSocket.getpeername())

while True:
    request = input('发送一条请求:')
    clientSocket.send(request.encode())#请求发送
    modifiedSentence = clientSocket.recv(1024)#回复报文
    print(modifiedSentence.decode())#打印回复
    
    if request == 'Exit':
        clientSocket.close()
        break

  • 运行效果

先启动服务端,再启动客户端A连接并交互,发送“Time”时服务端回复系统时间,发送“Exit”时回复“Bye”并断开与客户端的连接。发送命令时无效,并回复提示信息。断开与客户端A的连接后服务端还可以继续与其他客户端通信。(图3 

Figure 3 系统时间查询

3. 网络文件传输

实现一个基于客户/服务器的网络文件传输程序。

传输层使用TCP

交互过程

1) 客户端从用户输入获得待请求的文件名。

2) 客户端向服务器端发送文件名。

3) 服务器端收到文件名后,传输文件。

4) 客户端接收文件,重命名并存储在硬盘。

  • 服务端

同样服务端需要先创建TCP协议下的与固定端口号绑定的进程套接字socket,并保持监听状态等待与客户端连接。每次发送文件分组64字节,直至发送完毕后关闭连接。引入os库来查看文件是否存在,并返回提示。

TF_TCPServer.py代码

from socket import *

clientSocket = socket(AF_INET, SOCK_STREAM)
print('-----------------------------')
print('客户端正在运行')

serverName = input('请输入要连接的服务端IP:')
serverPort = int(input('请输入要连接的服务端的端口号:'))

clientSocket.connect((serverName, serverPort))
print('客户端地址:',clientSocket.getsockname())
print('连接到',clientSocket.getpeername())

filename = input('请输入所请求的文件名:')
clientSocket.send(filename.encode())#请求发送
print('已发送文件名 【', filename, '】 至服务器')

modifiedSentence = clientSocket.recv(1024).decode()#回复报文
print(modifiedSentence)#打印回复报文

if modifiedSentence == 'ok':
    file_size = clientSocket.recv(1024).decode()#获得文件大小
    print('文件大小为:', file_size, ' 字节')#打印回复报文

    print('-----------------------------')
    f = open(filename, 'wb')#新建待写入文件

    while True:
        r = clientSocket.recv(64)#每次接收64字节报文

        if len(r)==0:
            print('-----------------------------')
            print('1个名为 【', filename, '】、大小为 ', file_size, ' 字节的文件已被保存')
            print('接收完毕!')
            print('-----------------------------')
            break#发完退出

        print('收到 ', len(r), ' 字节的数据')
        f.write(r)#写入文件

clientSocket.close()

  • 运行效果(后面加了远程功能,效果图暂时还在本地)

先启动好服务器并保持监听状态等待连接,在启动客户端与服务器建立连接(TCP),发送所请求传输的文件名。服务器收到文件名后利用os库查询是否存在文件。文件不存在返回提示信息并断开连接。文件存在则开始传输每次64字节的文件分组,直至检测到待传输文件分组为0字节时停止传输,并提示传输完毕信息,断开连接。客户端在每次收到文件分组的时候将其存于客户端本地的同名文件。直至所收到分组为0字节时确定接收完毕,打印提示信息并关闭连接。

4. 网络聊天室

实现一个基于客户/服务器的网络聊天程序。

要求实现多个用户的群聊。要求客户端打印聊天消息,服务器打印系统信息。

传输层使用UDP

不要求实现GUI界面。

  • 服务端

服务器程序维护一个聊天室用户字典列表userList来了解聊天室里有哪些用户(客户端),映射关系为IP:name。新用户到达时加进来,旧用户离开时删除。每次用户发来消息,通过知道聊天室里有哪些用户(客户端),确定该用户是否是新用户。

需要实现的群聊功能是一个用户发消息,所有用户都能收到。相应地,一个客户端把聊天消息发给服务器,服务器再将收到的消息转发给所有客户端。所以在服务端需要有一个SendUser() 函数来实现每次收到信息转发给用户表内全部用户相同信息的功能。

服务端创建与固定端口号绑定的socket并开启等待消息,消息有3类:新用户加入的昵称消息,‘quit’用户退出群聊消息,一般聊天消息。

第一类为新用户加入的昵称消息。如果所收到客户端的IP是用户字典里不存在的,则可判断为新用户且发送过来的消息为他的昵称。回复欢迎并将新用户加入消息转发给全部用户(用户字典里的)。

第二类“quit”消息。当用户发送“quit”时,用户退出群聊,服务端断开与其的连接并将该消息转发给全部用户。

第三类一般聊天消息。如果所接受消息并非上面2类,就可判断其为一般聊天消息,服务器需要将其加上用户名转发给用户字典里的全部用户。用户名可通过消息的IP地址在用户字典里查询到。

UDPServer.py代码

from socket import *

userList = {} #创建userList(map ip:name)

def SendUsers(userList, data):#将数据发送给全部用户
    for user in userList:
        serverSocket.sendto(data.encode(), user)
        print('发送给 ', user, ' ---> ',data)

serverPort = 12000
serverSocket = socket(AF_INET, SOCK_DGRAM)
serverSocket.bind(('', serverPort))
print('服务器已经准备好接收了')

while True:
    message, clientAddress = serverSocket.recvfrom(2048)
    m = message.decode()#解码报文
    print('收到来自 ', clientAddress, ' 的信息 ---> ', m)

    #如果所接收用户地址不在,则为新用户,加入列表
    if not clientAddress in userList:
        userList[clientAddress] = m
        SendUsers(userList, m + ' 已经加入群聊!')
    elif m == 'quit':#
        SendUsers(userList, userList[clientAddress]+' 已经离开群聊!')
        del userList[clientAddress]
    else:#聊天
        SendUsers(userList, userList[clientAddress]+':'+m )

  • 客户端

客户端为了保证收发同时,需要两个线程实现的。一个线程Receive() 负责接收并显示消息,另一个线程Send() 负责获取输入和发送消息。Python里threading库可以实现多线程编程。

UDPClient.py代码

from socket import *
import threading#多线程
import time

#发送函数线程
def Send(clientSocket, serverName, serverPort):
    while True:
        m = input()
        clientSocket.sendto(m.encode(), (serverName, serverPort))
        if m == 'quit':
            time.sleep(1)#延时1秒再关闭连接,避免收发线程冲突
            clientSocket.close()#发送退出则断开连接并跳出循环
            break

#接收函数线程
def Receive(clientSocket):
    while True:
        try:    # 利用 close 作为退出标志
            modifiedMessage, serverAddress = clientSocket.recvfrom(2048)
            print(modifiedMessage.decode())
        except:
            break

serverName = 'localhost'
serverPort = 12000

clientSocket = socket(AF_INET, SOCK_DGRAM)#创建客户端进程套接字

#发送昵称给服务器
name = input('请输入昵称:') 
clientSocket.sendto(name.encode(), (serverName, serverPort))

print('欢迎', name, ',输入 <quit> 退出群聊!')

#创建接收进程并启动
th_Send = threading.Thread(target=Send, args=(clientSocket, serverName, serverPort))
th_Receive = threading.Thread(target=Receive, args=(clientSocket,))
th_Send.start()
th_Receive.start()

  • 运行效果

总体效果

  • 开启服务器,jennie客户端和john客户端陆续加入群聊

服务器先开启,jennie客户端先发送昵称加入消息,服务器收到消息并将jennie加入用户字典并向jennie回复欢迎信息,并将新用户加入消息转发给全部用户(当前用户字典仅有jennie)。接下来john加入,同样的过程,这时全部用户(jennie,john)同时收到转发消息,后面还有Mike加入下图未展示(左:服务器,中:john客户端2,右:jennie客户端1)

  • 聊天(4栏:服务器mikejohn,  jennie

    jennie先发送消息“你们知道那时候可以会深大吗”,服务器收到消息后通过查询用户字典知道jennie为老用户,发送的是一般聊天信息,则会加上jennie昵称把该消息转发给全部用户(用户字典里:jennie, john, mike),全部用户客户端可以收到该条消息。

  • mike回复消息同样被转发给全部用户

  • 用户退出

    用户在发送‘quit’给服务器后,服务器会将信息该用户退出信息转发给全部用户,而该用户也会等待1s后(避免与receive线程冲突)在关闭客户端连接。如图中mike退出。

    mike退出后其他用户继续聊天,服务器也继续接收。

问题及解决:

  • URL 请求程序

    • 在创建文件名名时可以直接通过接受的url字符串切片获得。
    • 可通过os库的相关功能获得文件大小

  • 系统时间查询

  • 在“系统时间查询”实验部分中,我发现要实现远程就必须添加服务器地址的输入。也必须获得本机IP。通过ipconfig查询,本机IP:192.168.0.107
  • 通过socket库的getsockname() 函数可获得本地进程IP地址和端口号,getpeername() 获得所连接的远程服务端的地址和端口号。
  • 利用time库获得格式化系统时间表达。

  • 网络文件传输

    • 通过文件分组的大小是否为0判断是否文件发送完毕。
    • 利用os库判断文件是否存在

  • 网络聊天室

    • 显示套接字不可以作为线程函数参数,经查阅资料发现线程参数接受的是元组,需要在参数参数后面加‘,’代表这是个元组。
    • 某一个用户退出时,服务器显示某个远程主机强制断开连接报错,而代码里我在用户发送‘quit’后就断开该客户端,可能会导致后续客户端继续收服务器信息的receive线程出现问题,所以为了避免冲突我在用户发送‘quit’后等待1s再关闭客户端连接,避免客户端receive线程发生冲突,问题也解决了。

实验心得体会

通过本次实验,学会通过python的requests库来完成url请求并获得网页文件和相关信息。同时可利用os库获取文件具体信息。

我加深关于TCP/UDP套接字的认识,了解了他们的区别,比如TCP套接字连接后服务端就可以保持监听状态,创建连接后的短期内通信与同一客户端无需再次连接。而UDP并无严格意义的连接过程,服务端也没有监听的概念。

另外我通过该次试验,初步学习了socket编程方法,通过套接字的创建实现服务端与客户端基于TCP/UDP协议的进程通信,实现文件传输。

学会了基于此再利用多线程编程的知识即可实现多人聊天功能,比如在这里通过利用python的threading库实现客户端的收发双线程并发通信。注意的是当客户端准备关闭连接的时候,需等待一段时间避免接收线程由于管道的提前关闭而导致出错。 

总结

我们可以通过python的requests库来完成url请求并获得网页文件和相关信息。

通过套接字的创建实现服务端与客户端基于TCP/UDP协议的进程通信,实现文件传输。

基于此再利用多线程编程的知识即可实现多人聊天功能,比如在这里通过利用python的threading库实现客户端的收发双线程并发通信。注意的是当客户端准备关闭连接的时候,需等待一段时间避免接收线程由于管道的提前关闭而导致出错。

  • 6
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
JAVA SOCKET 编程的经典之书,(中文版)里面的代码可直接复制使用! 目录: 第1章简介..........3 1.1 计算机网络,分组报文和协议..........3 1.2 关于地址..........6 1.3 关于名字..........8 1.4 客户端和服务器..........8 1.5 什么是套接字..........9 1.6 练习..........10 第2章基本套接字..........10 2.1 套接字地址..........10 2.2 TCP套接字..........17 2.2.1 TCP客户端..........17 2.2.2 TCP服务器端..........22 2.2.3 输入输出流..........26 2.3 UDP套接字..........28 2.3.1 DatagramPacket类..........28 2.3.2 UDP客户端..........30 2.3.3 UDP服务器端..........36 2.3.4 使用UDP套接字发送和接收信息..........38 2.4 练习..........40 第3章发送和接收数据..........41 3.1 信息编码..........42 3.1.1 基本整型..........42 3.1.2 字符串和文本..........48 3.1.3 位操作:布尔值编码..........50 3.2 组合输入输出流..........51 3.3 成帧与解析..........52 3.4 Java特定编码..........58 3.5 构建和解析协议消息..........59 3.5.1 基于文本的表示方法..........62 3.5.2 二进制表示方法..........65 3.5.3 发送和接收..........67 3.6 结束..........76 3.7 练习..........76 第4章进阶..........77 4.1 多任务处理..........77 4.1.1 Java 多线程..........78 4.1.2 服务器协议..........80 4.1.3 一客户一线程..........84 4.1.4 线程池..........86 4.1.5 系统管理调度:Executor接口..........89 4.2 阻塞和超时..........91 4.2.1 accept(),read()和receive()..........91 4.2.2 连接和写数据..........92 4.2.3 限制每个客户端的时间..........92 4.3 多接收者..........94 4.3.1 广播..........94 4.3.2 多播..........95 4.4 控制默认行为..........100 4.4.1 Keep-Alive..........100 4.4.2 发送和接收缓存区的大小..........101 4.4.3 超时..........101 4.4.4 地址重用..........102 4.4.5 消除缓冲延迟..........102 4.4.6 紧急数据..........103 4.4.7 关闭后停留..........103 4.4.8 广播许可..........103 4.4.9 通信等级..........104 4.4.10 基于性能的协议选择..........104 4.5 关闭连接..........104 4.6 Applets..........111 4.7 结束..........112 4.8 练习..........112 第5章 NIO..........112 5.1 为什么需要NIO?..........113 5.2 与Buffer一起使用Channel..........115 5.3 Selector..........118 5.4 Buffer详解..........125 5.4.1 Buffer索引..........125 5.4.2 创建Buffer..........126 5.4.3 存储和接收数据..........128 5.4.4 准备Buffer:clear(),flip(),和rewind()..........130 5.4.5 压缩Buffer中的数据..........132 5.4.6 Buffer透视:duplicate(),slice()等..........134 5.4.7 字符编码..........136 5.5 流(TCP)信道详解..........136 5.6 Selector详解..........139 5.6.1 在信道中注册..........139 5.6.2 选取和识别准备就绪的信道..........141 5.6.3 信道附件..........143 5.6.4 Selector小结..........144 5.7 数据报(UDP)信道..........144 5.8 练习..........149 1. 使用定长的写缓冲区改写TCPEchoClientNonblocking.java。..........149 2.使用Buffer和DatagramChannel编写一个回显客户端。..........149 第6章深入剖析..........149 6.1 缓冲和TCP..........152 6.2 死锁风险..........155 6.3 性能相关..........158 6.4 TCP套接字的生存周期..........158 6.4.1 连接..........158 6.4.2 关闭TCP连接..........164 6.5 解调多路复用揭秘..........167 6.6 练习..........169
计算机网络Socket 编程是一种通过网络进行通信的编程方法。它允许应用程序在不同计算机之间传输数据,实现客户端和服务器之间的交互。 在 Socket 编程中,客户端和服务器通过套接字(Socket)建立连接并进行数据传输。套接字是一种抽象的通信接口,可用于在网络中进行数据的发送和接收。 在编写 Socket 程序时,需要使用特定的编程语言和网络库,如 C/C++ 中的 socket.h 头文件,或是 Python 中的 socket 模块。以下是一个简单的示例,展示了如何使用 Python 进行基本的 Socket 编程: ```python import socket # 创建客户端套接字 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 连接服务器 server_address = ('服务器IP地址', 端口号) client_socket.connect(server_address) # 发送数据到服务器 data = 'Hello, server!' client_socket.sendall(data.encode()) # 从服务器接收数据 received_data = client_socket.recv(1024).decode() print('Received:', received_data) # 关闭套接字 client_socket.close() ``` 在这个示例中,我们创建了一个客户端套接字,并使用 `connect()` 方法连接到指定的服务器地址和端口号。然后,我们使用 `sendall()` 方法将数据发送到服务器,并使用 `recv()` 方法接收服务器返回的数据。最后,我们关闭了套接字连接。 类似地,服务器端的 Socket 编程也是通过创建套接字、绑定地址和端口,监听连接请求,并处理客户端的数据发送和接收。不过服务器端的代码会更复杂一些。 Socket 编程是计算机网络中非常重要的一部分,它为应用程序提供了一种可靠、灵活和高效的通信方式。在实际应用中,我们可以使用 Socket 编程实现各种网络应用,如 web 服务器、聊天系统、文件传输等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jennie佳妮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值