【socket】简单的网络数据收发程序

一、实验目的

通过本实验,学习采用 Socket(套接字)设计简单的网络数据收发程序,理解应用数据 包是如何通过传输层进行传送的。

二、实验内容

Socket(套接字)是一种抽象层,应用程序通过它来发送和接收数据,就像应用程序打开一个文件句柄,将数据读写到稳定的存储器上一样。一个 socket 允许应用程序添加到网络中,并与处于同一个网络中的其他应用程序进行通信。一台计算机上的应用程序向 socket写入的信息能够被另一台计算机上的另一个应用程序读取,反之亦然。

不同类型的 socket 与不同类型的底层协议族以及同一协议族中的不同协议栈相关联。现 在TCP/IP 协议族中的主要 socket 类型为流套接字(sockets sockets)和数据报套接字(datagram sockets)。流套接字将 TCP 作为其端对端协议(底层使用 IP 协议),提供了一个可信赖的字节流服务。一个 TCP/IP 流套接字代表了 TCP 连接的一端。数据报套接字使用 UDP 协议(底层同样使用 IP 协议),提供了一个"尽力而为"(best-effort)的数据报服务,应用程序可以通过它发送最长 65500 字节的个人信息。一个 TCP/IP 套接字由一个互联网地址,一个端对端 协议(TCP 或 UDP 协议)以及一个端口号唯一确定。

三、实验步骤

0.环境介绍

python3.7;socket;pycharm

1.采用TCP进行数据发送的简单程序

1.1客户端

1.1.1建立socket对象

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

协议簇:AF_INET(TCP/IP – IPv4);类型:SOCK_STREAM(TCP)

1.1.2连接socket

连接socket需要提供一个tuple,包括host(主机名或者IP)和port(远程端口)

s.connect(('127.0.0.1', 1234))

127.0.0.1是回送地址,指本地机

1.1.3从socket获取信息

print(s.recv(1024))

从 socket 中接收数据,最多 1024 个字符

1.1.4 发送与接收数据

while True:
    data = input('Please input data:')
    s.send(data.encode('UTF-8'))
    receiveBytes = s.recv(1024)
    print('Server replied:\'{0}\' '.format(receiveBytes.decode('UTF-8')))
    if data == 'exit':
        print('Bye~')
        break

利用send发送数据,recv接收数据。输入exit表示退出。

1.2服务器

1.2.1建立socket对象

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

协议簇:AF_INET(TCP/IP – IPv4);类型:SOCK_STREAM(TCP)

1.2.2设置socket选项

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

将SO_REUSEADDR标记为TRUE,操作系统会在服务器socket被关闭或服务器进程终止后马上释放该服务器的端口
,否则操作系统会保留几分钟该端口。防止socket server重启后端口被占用。

1.2.3绑定到一个端口

s.bind(('127.0.0.1', 1234))

127.0.0.1是回送地址,指本地机。端口选择和客户端连接socket的端口一致

1.2.4侦听连接

利用listen()函数进行侦听连接。该函数只有一个参数,其指明了在服务器实际处理连接的时候,允许有多少个未决(等待)的连接在队列中等待。

s.listen(5)

1.2.5处理连接

conn, addr = s.accept()
while True:
    data = conn.recv(1024)
    print('{0} client send data is \'{1}\''.format(addr,data.decode()))
    if data.decode('UTF-8') == 'exit':
        print('{0} connection close'.format(addr))
        conn.send(bytes('Connection closed!', 'UTF-8'))
        break
    conn.send(bytes('Yes, I have received: \'{0}\''.format(data.decode()), "UTF-8"))

accept函数接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。接收TCP 客户的连接(阻塞式)等待连接的到来。

连接后在while循环里利用recv接收数据,若为exit则关闭连接;利用send发送数据。

1.3 完整代码

SimpleSocketC.py:

import socket

try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('127.0.0.1', 1234))
except socket.error as msg:
    print(msg)
    exit(1)

while True:
    data = input('Please input data:')
    s.send(data.encode('UTF-8'))
    receiveBytes = s.recv(1024)
    print('Server replied:\'{0}\' '.format(receiveBytes.decode('UTF-8')))
    if data == 'exit':
        print('Bye~')
        break

s.close()

SimpleSocketS.py:

import socket

try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    #s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(('127.0.0.1', 1234))
    s.listen(5)
except socket.error as msg:
    print(msg)
    exit(1)
print('Waiting connection...')

conn, addr = s.accept()
while True:
    data = conn.recv(1024)
    print('{0} client send data is \'{1}\''.format(addr,data.decode()))
    if data.decode('UTF-8') == 'exit':
        print('{0} connection close'.format(addr))
        conn.send(bytes('Connection closed!', 'UTF-8'))
        break
    conn.send(bytes('Yes, I have received: \'{0}\''.format(data.decode()), "UTF-8"))

conn.close()

1.4 运行结果

1.4.1 首先运行服务器(SimpleSocketS.py)

此时服务器准备接收TCP 客户的连接(阻塞式),等待连接的到来

此时打开cmd,查看端口的占用,键入

Netstat -ano|findstr "127.0.0.1:1234"

再去看一下现在运行的python程序是不是能够对应到这个PID,键入

tasklist | findstr python

可以发现,PID为13076,就是刚刚运行的那个服务器程序

1.4.2 然后运行客户端(SimpleSocketC.py)

此时可以向服务器发送数据了

在发送数据之前,同样的先查看一下端口占用

可以看到这两个端口(1234与58441)即为服务端的进程与客户端的python的程序,且前者的源/目的地址,即为后者的目的/源地址。

1.4.3 发送数据与接收数据

1.4.4 退出通信并关闭连接

2.采用UDP进行数据发送的简单程序

2.1 客户端

使用UDP时,当socket被建立,程序调用的是SOCK_DGRAM,而不是SOCK_STREAM。

使用UDP时,也可不调用connect函数。发送数据用sendto函数,返回值是发送的字节数。接收数据用recvfrom函数,返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。

2.2 服务器

服务器端使用UDP时,可以像使用TCP那样建立一个socket,设置选项,并调用bind函数。然而,不必使用listen和accept函数,仅仅使用recvfrom函数就可以了。这个函数会返回两个信息:收到的数据,以及发送这些数据的程序地址和端口号。因为UDP是无连接的协议,所以仅需要能发送一个答复,不需要像TCP那样有一个专门的socket和客户端相连

2.3 完整代码

SimpleUDPC.py:

import socket

try:
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
except socket.error as msg:
    print(msg)
    exit(1)

while True:
    data = input('Please input data:')
    s.sendto(data.encode('UTF-8'),('127.0.0.1',1234))
    if data == 'exit':
        print('Bye~')
        break

s.close()

SimpleUDPS.py:

import socket

try:
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    #s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(('127.0.0.1', 1234))
except socket.error as msg:
    print(msg)
    exit(1)
print('Waiting connection...')

while True:
    data, addr = s.recvfrom(1024)
    print('{0} client send data is \'{1}\''.format(addr,data.decode()))
    if data.decode('UTF-8') == 'exit':
        print('{0} connection close'.format(addr))
        break

s.close()

2.4 运行结果

3.多线程\线程池对比

当一个客户端向一个已经被其他客户端占用的服务器发送连接请求时,虽然其在连接建 立后即可向服务器端发送数据,服务器端在处理完已有客户端的请求前,却不会对新的客户 端作出响应。 并行服务器:可以单独处理没一个连接,且不会产生干扰。并行服务器分为两种:一客 户一线程和线程池。 每个新线程都会消耗系统资源:创建一个线程将占用 CPU 周期,而且每个线程都自己 的数据结构(如,栈)也要消耗系统内存。另外,当一个线程阻塞(block)时,JVM 将保 存其状态,选择另外一个线程运行,并在上下文转换(context switch)时恢复阻塞线程的状 态。随着线程数的增加,线程将消耗越来越多的系统资源。这将最终导致系统花费更多的时 间来处理上下文转换和线程管理,更少的时间来对连接进行服务。那种情况下,加入一个额 外的线程实际上可能增加客户端总服务时间。 我们可以通过限制总线程数并重复使用线程来避免这个问题。与为每个连接创建一个新 的线程不同,服务器在启动时创建一个由固定数量线程组成的线程池(thread pool)。当一个 新的客户端连接请求传入服务器,它将交给线程池中的一个线程处理。当该线程处理完这个 客户端后,又返回线程池,并为下一次请求处理做好准备。如果连接请求到达服务器时,线 程池中的所有线程都已经被占用,它们则在一个队列中等待,直到有空闲的线程可用。

3.1 多线程

3.1.1 TCPServerThread.py

利用threading实现多线程

import socket
import time
import threading
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(('127.0.0.1', 1234))
    s.listen(5)
except socket.error as msg:
    print(msg)
    exit(1)

def deal_data(conn,addr):
    print('Accept new connection from {0}'.format(addr))
    conn.send(('Hi, Welcome to the server!').encode())
    while True:
        data = conn.recv(1024)
        print('{0} client send data is \'{1}\''.format(addr, data.decode()))
        time.sleep(1)
        if data.decode('UTF-8') == 'exit':
            print('{0} connection close'.format(addr))
            conn.send(bytes('Connection closed!', 'UTF-8'))
            break
        conn.send(bytes('Yes, I have received: \'{0}\''.format(data.decode()), "UTF-8"))
    conn.close()

print('Waiting connection...')
while True:
    conn, addr = s.accept()
    t = threading.Thread(target=deal_data,args=(conn,addr))
    t.start()

3.1.2 TCPClientThread.py

客户端代码无须太大改动。为了模拟多用户,以下代码可复制多份,命名为TCPClient2Thread.py、TCPClient3Thread.py等等

import socket

try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('127.0.0.1', 1234))
except socket.error as msg:
    print(msg)
    exit(1)

print('\'{0}\' '.format(s.recv(1024).decode('UTF-8')))
while True:
    data = input('Please input data:')
    s.send(data.encode('UTF-8'))
    receiveBytes = s.recv(1024)
    print('Server replied:\'{0}\' '.format(receiveBytes.decode('UTF-8')))
    if data == 'exit':
        print('Bye~')
        break

s.close()

3.1.3运行结果

首先运行TCPServerThread.py,然后运行TCPClientThread.py和TCPClient2Thread.py,输入数据

3.2 线程池

3.2.1 TCPServerPool.py

利用concurrent.futures中的ThreadPoolExecutor来实现线程池

import socket
import time
from concurrent.futures import ThreadPoolExecutor

try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(('127.0.0.1', 1234))
    s.listen(5)
except socket.error as msg:
    print(msg)
    exit(1)

def deal_data(conn,addr):
    print('Accept new connection from {0}'.format(addr))
    conn.send(('Hi, Welcome to the server!').encode())
    while True:
        data = conn.recv(1024)
        print('{0} client send data is \'{1}\''.format(addr, data.decode()))
        time.sleep(1)
        if data.decode('UTF-8') == 'exit':
            print('{0} connection close'.format(addr))
            conn.send(bytes('Connection closed!', 'UTF-8'))
            break
        conn.send(bytes('Yes, I have received: \'{0}\''.format(data.decode()), "UTF-8"))
    conn.close()

print('Waiting connection...')
with ThreadPoolExecutor(max_workers=5) as executor:
    while True:
        conn, addr = s.accept()
        executor.submit(deal_data, conn, addr)

3.2.2 TCPClientPool.py

客户端代码无须太大改动。和TCPClientThread.py代码一致。代码可复制多份,命名为TCPClient2Pool.py、TCPClient3Pool.py等等

3.2.3运行结果

4.写一个简单的 chat 程序,并能互传文件,编程语言不限

https://www.jianshu.com/p/8c83da946a1a

5.主要参考连接:

python官方文档-socket库
python之socket编程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容