跳至工具栏

Python3 网络编程

Python3 网络编程

Python 提供了两个级别访问的网络服务。:

  • 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口的全部方法。
  • 高级别的网络服务模块 SocketServer, 它提供了服务器中心类,可以简化网络服务器的开发。

什么是 Socket?

Socket又称”套接字”,应用程序通常通过”套接字”向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。


socket()函数

Python 中,我们用 socket()函数来创建套接字,语法格式如下:

socket.socket([family[, type[, proto]]]) 

参数

  • family: 套接字家族可以使AF_UNIX或者AF_INET
  • type: 套接字类型可以根据是面向连接的还是非连接分为SOCK_STREAMSOCK_DGRAM
  • protocol: 一般不填默认为0.

Socket 对象(内建)方法

函数 描述
服务器端套接字
s.bind() 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。
s.listen() 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。
s.accept() 被动接受TCP客户端连接,(阻塞式)等待连接的到来
客户端套接字
s.connect() 主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。
s.send() 发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
s.sendall() 完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
s.recvform() 接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
s.sendto() 发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
s.close() 关闭套接字
s.getpeername() 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
s.getsockname() 返回套接字自己的地址。通常是一个元组(ipaddr,port)
s.setsockopt(level,optname,value) 设置给定套接字选项的值。
s.getsockopt(level,optname[.buflen]) 返回套接字选项的值。
s.settimeout(timeout) 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
s.gettimeout() 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
s.fileno() 返回套接字的文件描述符。
s.setblocking(flag) 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
s.makefile() 创建一个与该套接字相关连的文件

简单实例

服务端

我们使用 socket 模块的 socket 函数来创建一个 socket 对象。socket 对象可以通过调用其他函数来设置一个 socket 服务。

现在我们可以通过调用 bind(hostname, port) 函数来指定服务的 port(端口)

接着,我们调用 socket 对象的 accept 方法。该方法等待客户端的连接,并返回 connection 对象,表示已连接到客户端。

完整代码如下:

#!/usr/bin/python3
# 文件名:server.py
 
# 导入 socket、sys 模块
import socket
import sys 
# 创建 socket 对象 serversocket = socket.socket(
 
   socket.AF_INET, socket.SOCK_STREAM)  
# 获取本地主机名 host = socket.gethostname()  port = 9999 
# 绑定端口 serversocket.bind((host, port)) 
# 设置最大连接数,超过后排队 serversocket.listen(5)  while True:
    
# 建立客户端连接
     clientsocket,addr = serversocket.accept()
 

print("连接地址: %s" % str(addr))
 
    msg='欢迎访问W3Cschool教程!'+ "rn"
     clientsocket.send(msg.encode('utf-8'))
     clientsocket.close() 

客户端

接下来我们写一个简单的客户端实例连接到以上创建的服务。端口号为 12345。

socket.connect(hosname, port ) 方法打开一个 TCP 连接到主机为 hostname 端口为 port 的服务商。连接后我们就可以从服务端后期数据,记住,操作完成后需要关闭连接。

完整代码如下:

#!/usr/bin/python3
# 文件名:client.py
 
# 导入 socket、sys 模块
import socket
import sys 
# 创建 socket 对象 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
# 获取本地主机名 host = socket.gethostname()  
# 设置端口好 port = 9999 
# 连接服务,指定主机和端口 s.connect((host, port)) 
# 接收小于 1024 字节的数据 msg = s.recv(1024)  s.close()  
print (msg.decode('utf-8')) 

现在我们打开连个终端,第一个终端执行 server.py
文件:

$ python3 server.py

第二个终端执行 client.py
文件:

$ python3 client.py
  欢迎访问W3Cschool教程!  

这是我们再打开第一个终端,就会看到有以下信息输出:

连接地址: ('192.168.0.118', 33397) 

Python Internet 模块

以下列出了 Python 网络编程的一些重要模块:

协议 功能用处 端口号 Python 模块
HTTP 网页访问 80 httplib, urllib, xmlrpclib
NNTP 阅读和张贴新闻文章,俗称为”帖子” 119 nntplib
FTP 文件传输 20 ftplib, urllib
SMTP 发送邮件 25 smtplib
POP3 接收邮件 110 poplib
IMAP4 获取邮件 143 imaplib
Telnet 命令行 23 telnetlib
Gopher 信息查找 70 gopherlib, urllib

更多内容可以参阅官网的 Python Socket Library and Modules。

Arduino 网络通信

德州仪器的CC3000 WiFi模块是一个小型银包,最终为你的Arduino项目带来了易用,经济实惠的WiFi功能。
它使用SPI进行通信(而不是UART),因此你可以根据需要尽可能快或尽可能慢地推送数据。它有一个合适的IRQ引脚中断系统,因此你可以有异步连接。它支持802.11b/g,open/WEP/WPA/WPA2安全,TKIP及AES。具有“BSD socket”接口的内置TCP/IP堆栈支持客户端和服务器模式下的TCP和UDP。

Arduino 网络通信

必需的组件

你将需要以下组件:

  • 1 × Arduino Uno
  • 1 × Adafruit CC3000分线板
  • 1 × 5V继电器
  • 1 × 整流二极管
  • 1 × LED
  • 1 × 220欧姆电阻
  • 1 × 面包板和一些跳线

对于这个项目,你只需要通常的Arduino IDE,Adafruit的CC3000库以及CC3000 MDNS库。我们也将使用aREST库通过WiFi向中继发送命令。

程序

按照电路图进行连接,如下图所示。

Arduino 网络通信

这个项目的硬件配置非常简单。

  • 将CC3000板的IRQ引脚连接到Arduino板的引脚3。
  • VBAT连接到引脚5,CS连接到引脚10。
  • 将SPI引脚连接到Arduino板:MOSI,MISO和CLK分别连接到引脚11,12和13。
  • Vin连接到Arduino 5V,GND连接到GND。

现在,让我们连接继电器。
将继电器放在面包板上后,你可以开始识别继电器上的两个重要部分:指示继电器的线圈部分和连接LED的开关部分。

  • 首先,将Arduino板的8号引脚连接到线圈的一个引脚。
  • 将另一个引脚连接到Arduino板的接地。

您还必须将整流二极管(阳极连接到接地引脚)放置在线圈的引脚上,以在继电器切换时保护电路。

  • 将Arduino板的+5V连接到继电器开关的公共引脚。
  • 最后,将开关的另一个引脚(通常是继电器断开时未连接的引脚)连接到与220欧姆电阻串联的LED,并将LED的另一端连接到Arduino的接地。

测试单个组件

你可以使用以下草图测试继电器:

const
int relay_pin = 8;

// Relay pin
 

void setup()
{
 
  Serial.begin(9600);

 
  pinMode(relay_pin,OUTPUT);


  }
 
void loop()
{
   // Activate relay
    
   digitalWrite(relay_pin, HIGH);

   // Wait for 1 second
   
  delay(1000);

   // Deactivate relay
    
   digitalWrite(relay_pin, LOW);

   // Wait for 1 second
   
  delay(1000);


  }

代码说明

代码是不言自明的。你只需将其上传到电路板,继电器将每秒切换状态,LED将相应地亮起和熄灭。

添加WiFi连接

现在让我们使用CC3000 WiFi芯片无线控制继电器。该项目的软件基于TCP协议。但是,对于这个项目,Arduino板将运行一个小的Web服务器,以便我们可以“监听”来自计算机的命令。我们先来看看Arduino草图,然后我们将看到如何编写服务器端代码并创建一个漂亮的界面。
首先,Arduino草图。这里的目标是连接到你的WiFi网络,创建Web服务器,检查是否有传入的TCP连接,然后相应地更改继电器的状态。

代码的重要部分

#include <Adafruit_CC3000.h>#include <SPI.h>#include <CC3000_MDNS.h>#include <Ethernet.h>#include <aREST.h>

你需要在代码中定义特定于你的配置的内容,即Wi-Fi名称和密码,以及TCP通信端口(我们在此使用了80)。

// WiFi network (change with your settings!)
   
#define WLAN_SSID "yourNetwork" // cannot be longer than 32 characters!
   
#define WLAN_PASS "yourPassword"
   
#define WLAN_SECURITY WLAN_SEC_WPA2 // This can be WLAN_SEC_UNSEC, WLAN_SEC_WEP,
 //WLAN_SEC_WPA or WLAN_SEC_WPA2  // The port to listen for incoming TCP connections
   
#define LISTEN_PORT 80

然后我们可以创建CC3000实例,服务器和aREST实例:

// Server instance
    Adafruit_CC3000_Server restServer(LISTEN_PORT);

// DNS responder instance
    MDNSResponder mdns;

// Create aREST instance
    aREST rest = aREST();

在草图的setup()部分,我们现在可以将CC3000芯片连接到网络:

cc3000.connectToAP(WLAN_SSID, WLAN_PASS, WLAN_SECURITY);

计算机将如何知道在哪里发送数据?一种方法是运行草图一次,然后获取CC3000板的IP地址,并再次修改服务器代码。但是,我们可以做得更好,这就是CC3000 MDNS库发挥作用的地方。我们将使用此库为我们的CC3000板分配一个固定名称,以便我们可以将此名称直接写入服务器代码。
这可以用下面的代码片段完成:

if (!mdns.begin("arduino", cc3000))
{
   while(1);


  }

我们还需要监听传入的连接。

restServer.begin();

接下来,我们要对将被连续执行的草图的loop()函数进行编码。我们首先要更新mDNS服务器。

mdns.update();

在Arduino板上运行的服务器将等待传入连接并处理请求。

Adafruit_CC3000_ClientRef client = restServer.available();

rest.handle(client);

现在通过WiFi测试项目非常容易。确保你使用自己的WiFi名称和密码更新草图,并将草图上传到Arduino板。打开你的Arduino IDE串口监视器,并查找电路板的IP地址。
我们假设其余的是192.168.1.103。
然后,只需进入你喜欢的网络浏览器,然后键入:
192.168.1.103/digital/8/1
你应该看到继电器自动打开。

构建继电器界面

 

我们现在将编写项目的界面。这里将有两个部分:包含界面的HTML文件和用于处理界面上点击的客户端Javascript文件。这里的界面基于aREST.js项目,这是为了方便从你的计算机控制WiFi设备。
让我们先看一下名为interface.html的HTML文件。第一部分包括导入所有界面需要的库:

<head>
<meta charset = utf-8 />
<title>Relay Control </title>
<link rel = "stylesheet" type = "text/css"
 
  href = "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
<link rel="stylesheet" type = "text/css" href = "style.css">
<script type = "text/javascript"
 
  src = "https://code.jquery.com/jquery-2.1.4.min.js">
</script>
<script type = "text/javascript"
 
  src = "https://cdn.rawgit.com/Foliotek/AjaxQ/master/ajaxq.js">
</script>
<script type = "text/javascript"
 
  src = "https://cdn.rawgit.com/marcoschwartz/aREST.js/master/aREST.js">
</script>
<script type = "text/javascript"
 
  src = "script.js">
</script>
</head>

然后,我们在界面中定义两个按钮,一个用于打开继电器,另一个用于再次关闭继电器。

<div class = 'container'>
<h1>Relay Control</h1>
<div class = 'row'>   <div class = "col-md-1">Relay</div>   <div class = "col-md-2">
 <button id = 'on' class = 'btn btn-block btn-success'>On</button>      </div>   <div class = "col-md-2">
 <button id = 'off' class = 'btn btn-block btn-danger'>On</button>      </div>   </div>
</div>

现在,我们还需要一个客户端Javascript文件来处理按钮上的点击。我们还将创建一个设备,我们将链接到Arduino设备的mDNS名称。如果你在Arduino代码中改变了这个,你也需要在这里修改它。

// Create device var device = new Device("arduino.local");

// Button
$('#on').click(function()
{
   device.
   digitalWrite(8, 1);


  });

$('#off').click(function()
{
   device.
   digitalWrite(8, 0);


  });

该项目的完整代码可以在 GitHub 存储库中找到。进入界面文件夹,只需用你喜欢的浏览器打开HTML文件。你应该会在浏览器中看到类似的内容:

Arduino 网络通信

 

尝试点击Web界面上的按钮;它应该立即改变继电器的状态。
如果你设法让它工作了,恭喜你,你刚刚构建了一个Wi-Fi控制的电灯开关。当然,通过这个项目你可以控制更多的电灯。只需确保你的继电器支持你想要控制的设备所需的电源,你就可以很好的实现了。

第十五回、PHP基础教程,PHP实现服务器端数据提交(传输)

使用HttpClient类库

有些时候,我们需要在一个页面中引用自己站点中的一个页面或外部网站上的一个页面,但这个页面又要求必须以POST方式向其发送参数。这一技术实现起来比较麻烦,大致使用Curl、socket、file_get_contents技术来实现,但最常用的是file_get_contents()函数,但,它默认是以GET方式提交数据,想改成POST方式,实现起来相当的麻烦,所以,我们就不得不想“歪门斜道”了——使用现在比较流行的一个类(HttpClient),这个类文件可以到http://scripts.incutio.com/httpclient/index.php下载最新版本。我已经下载好了这个类文件,我用的文件名称是HttpClient.class.php,把它引用到需要用PHP直接以POST方式提交数据的页面中,然后按照下面的实例操作即可。

例一:用HttpClient类POST数据到另一页面(注意,这里不使用表单)

$pageContents = HttpClient::quickPost(‘http://www.abc.com/news.php’, array(

‘add’ =>’河南省’,

‘name’ =>’张三’,

‘sex’=>’男’

));

//此时,$pageContents中存放的就是news.php页面中的内容。

说明:quickPost方法需要两个参数,第一个参数给出需要引用页面的URL地址。第二个参数是一个数组,用以设置POST数据时的参数,这里传递了三个参数add,name,sex。

例二:静态方法获取另一网页中的内容

$pageContents = HttpClient::quickGet(‘http://example.com/’);

例三:Get方法获取

$client = new HttpClient(‘example.com’);

if (!$client->get(‘/’))
{

die(‘An error occurred: ‘.$client->getError());

}

$pageContents = $client->getContent();

例四:带调试的Get方法获取

$client = new HttpClient(‘example.com’);

$client->setDebug(true);

if (!$client->get(‘/’))
{

die(‘An error occurred: ‘.$client->getError());

}

$pageContents = $client->getContent();

例五:带自动转向的Get方法

$client = new HttpClient(‘www.amazon.com’);

$client->setDebug(true);

if (!$client->get(‘/’))
{

die(‘An error occurred: ‘.$client->getError());

}

$pageContents = $client->getContent();

例六:检查页面是否存在

$client = new HttpClient(‘example.com’);

$client->setDebug(true);

if (!$client->get(‘/thispagedoesnotexist’))
{

die(‘An error occurred: ‘.$client->getError());

}

if ($client->getStatus() == ‘404’)
{

echo ‘Page does not exist!’;

}

$pageContents = $client->getContent();

二、PHP curl实现服务器端http、ftp等操作

curl 是一个利用url语法规定来传输文件和数据的工具,支持很多协议,如HTTP、FTP、TELNET等,PHP作为一种日益普通和流行使用的网站后台编写语言,也同样支持 curl库。

在PHP中使用curl功能,必须让PHP开启curl功能,即加载php_curl.dll模块,打开PHP的ini文件,打开文件,找到extension=php_curl.dll扩展,如果extension前面有英文的分号,把分号删除后保存,然后重启IIS或APACHE。如果没有分号,则说明你的PHP已经支持curl功能。

curl的使用步骤是:

1、初始化curl;

2、设置curl相关设置;

3、执行curl远程请求操作,并获取所需结果;

4、释放curl句柄。

使用DEMO:

<?php

$curl=curl_init();

//初始化curl,并把句柄给变量$curl

//下面开始设置curl功能的相关设置

curl_setopt($curl,CURLOPT_URL, “http://www.demo.com/content.php”);

//设置请求的目标地址

curl_setopt($curl, CURLOPT_RETURNTRANSFER,1);

//把获取到的内容以文件流的形式返回,而不是直接输出,值为0是直接输出。

curl_setopt($curl,CURLOPT_HEADER,0);

//只需要返回http头信息,并不把头信息输出

$result=curl_exec($curl);

//执行curl请求操作,并把请求的结果存入变量$result。如果请求失败,curl_exec()函数返回逻辑值false

if($result===false)//如果请求失败,给出错误信息,注意这里使用了三个=符号

{

echo”curl请求失败:”.curl_error($curl);

//输出错误信息

}

else

{

echo $result;

//将结果输出到客户端浏览器

}

curl_close($curl);

//关闭curl句柄

?>

上例只是一个非常简单的curl功能使用,其实curl还有很多非常实用的功能,比如模拟浏览请求,服务器端文件上传等。curl功能的一切玄机都在curl_setopt()这个参数配置参数上。比如,在编写网页内容采集程序时,有些网站的服务器设置为必须是浏览请求再给予正确的应答,此时就用到了curl功能的模拟浏览器功能。下面给出一个模拟浏览器的DEMO。

<?php

$curl=curl_init();

curl_setopt($curl,CURLOPT_URL, “http://www.demo.com/content.php”);

curl_setopt($curl,CURLOPT_SSL_VERIFYHOST,1);

//从证书中检查SSL加密算法是否存在

curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,false);

//是否开启对认证证书的来源进行检查

curl_setopt($curl,CURLOPT_POST,1);

//用POST方法向目标地址传递数据

curl_setopt($curl,CURLOPT_POSTFIELDS,array(“tid”=>1020, “page”=>1));

//需要向目标地址传递的参数及值,支持数组形式

curl_setopt($curl,CURLOPT_FOLLOWLOCATION,0);

//不开启自动跳转模拟

curl_setopt($curl,CURLOPT_REFERER, “referrer string”);

//http请求的referer请求字符串,根据需要设置,不需要传递referer字符串时,此设置可为空为不设置此项。

curl_setopt($curl,CURLOPT_USERAGENT,”Mozilla/5.0 (Windows NT 5.1;

rv:9.0.1) Gecko/20100101 Firefox/9.0.1″);

//模拟火狐浏览器

curl_setopt($curl,CURLOPT_RETURNTRANSFER,1);

//得到的结果不直接输出

curl_setopt($curl,CURLOPT_HEADER,false);

//不发送请求头信息

$result=curl_exec($curl);

//将请求结果放入变量$result

curl_close($curl);

//关闭curl句柄

?>

此DEMO模拟了火狐浏览器的数据请求。如果想要模拟客户端使用的浏览器,此,浏览器配置项可以使用curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER[‘HTTP_USER_AGENT’])。

curl_setopt()还有很多其它的配置选项,请参考PHP手册。

SOCKET编程进阶

socketserver

虽说用Python编写简单的网络程序很方便,但复杂一点的网络程序还是用现成的框架比较好。这样就可以专心事务逻辑,而不是套接字的各种细节。SocketServer模块简化了编写网络服务程序的任务。同时SocketServer模块也是Python标准库中很多服务器框架的基础。
socketserver模块可以简化网络服务器的编写,Python把网络服务抽象成两个主要的类,一个是Server类,用于处理连接相关的网络操作,另外一个则是RequestHandler类,用于处理数据相关的操作。并且提供两个MixIn 类,用于扩展 Server,实现多进程或多线程。

 

Server类

它包含了种五种server类,BaseServer(不直接对外服务)。TCPServer使用TCP协议,UDPServer使用UDP协议,还有两个不常使用的,即UnixStreamServer和UnixDatagramServer,这两个类仅仅在unix环境下有用(AF_unix)。
 

创建一个socketserver 至少分以下几步
1.首先,您必须创建一个请求处理类,继承BaseRequestHandlerclass类并且重 写父类的handle()方法,该方法将处理传入的请求。
2.其次,你必须实例化一个上面类型中的一个类(如TCPServer)传递服务器的地址和你上面创建的请求处理类 给这个TCPServer。
3.然后,调用handle_request()或者serve_forever()方法来处理一个或多个请求。

ser.handle_request()  
# 只处理一个请求,处理完就退出了
ser.serve_forever()   
# 处理多个请求,永远执行。

4.最后,调用server_close()关闭socket。
 
聊天并发实例

import socketserver
class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        print ("服务端启动...")
        while True:
            conn = self.request
            print (self.client_address)
            while True:
                client_data=conn.recv(1024)
                print (str(client_data,"utf8"))
                print ("waiting...")
                server_response=input("
>>>")
                conn.sendall(bytes(server_response,"utf8"))
                
# conn.sendall(client_data)
            conn.close()
            
# print self.request,self.client_address,self.server
if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8098),MyServer)
    server.serve_forever()
##########################################
import socket
ip_port = ('127.0.0.1',8098)
sk = socket.socket()
sk.connect(ip_port)
print ("客户端启动:")
while True:
    inp = input('
>>>')
    sk.sendall(bytes(inp,"utf8"))
    server_response=sk.recv(1024)
    print (str(server_response,"utf8"))
    if inp == 'exit':
        break
sk.close()

 

解决大数据传送和粘包问题
粘包:相邻两次发送的数据粘在了一起

import socketserver
import subprocess
class Myserver(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            conn=self.request
            conn.sendall(bytes("欢迎登录","utf8"))
            while True:
                client_bytes=conn.recv(1024)
                if not client_bytes:break
                client_str=str(client_bytes,"utf8")
                print(client_str)
                command=client_str
                result_str=subprocess.getoutput(command)
                result_bytes = bytes(result_str,encoding='utf8')
                info_str="info|%d"%len(result_bytes)
                conn.sendall(bytes(info_str,"utf8"))
                
# conn.recv(1024)
                conn.sendall(result_bytes)
            conn.close()
if __name__=="__main__":
    server=socketserver.ThreadingTCPServer(("127.0.0.1",9998),Myserver)
    server.serve_forever()
#####################################client
import socket
ip_port=("127.0.0.1",9998)
sk=socket.socket()
sk.connect(ip_port)
print("客户端启动...")
print(str(sk.recv(1024),"utf8"))
while True:
    inp=input("please input:").strip()
    sk.sendall(bytes(inp,"utf8"))
    basic_info_bytes=sk.recv(1024)
    print(str(basic_info_bytes,"utf8"))
    
# sk.send(bytes('ok','utf8'))
   result_length=int(str(basic_info_bytes,"utf8").split("|")[1])
    print(result_length)
    has_received=0
    content_bytes=bytes()
    while has_received<result_length:
        fetch_bytes=sk.recv(1024)
        has_received+=len(fetch_bytes)
        content_bytes+=fetch_bytes
    cmd_result=str(content_bytes,"utf8")
    print(cmd_result)
sk.close()

 

文件上传

import socket,os
ip_port=("127.0.0.1",8898)
sk=socket.socket()
sk.bind(ip_port)
sk.listen(5)
BASE_DIR=os.path.dirname(os.path.abspath(__file__))
while True:
    print("waiting connect")
    conn,addr=sk.accept()
    flag = True
    while flag:
            client_bytes=conn.recv(1024)
            client_str=str(client_bytes,"utf8")
            func,file_byte_size,filename=client_str.split("|",2)

path=os.path.join(BASE_DIR,‘yuan’,filename)
has_received=0
file_byte_size=int(file_byte_size)

f=open(path,“wb”)
while has_received<
file_byte_size:
data=conn.recv(1024)
f.write(data)
has_received+=len(data)
print(“ending”)
f.close()

#———————————————-client
#———————————————-
import socket
import re,os,sys
ip_port=(“127.0.0.1”,8898)
sk=socket.socket()
sk.connect(ip_port)
BASE_DIR=os.path.dirname(os.path.abspath(__file__))
print(“客户端启动….”)
while True:
inp=input(“please input:”)
if inp.startswith(“post”):
method,local_path=inp.split(“|”,1)
local_path=os.path.join(BASE_DIR,local_path)
file_byte_size=os.stat(local_path).st_size
file_name=os.path.basename(local_path)
post_info=“post|%s|%s”%(file_byte_size,file_name)
sk.sendall(bytes(post_info,“utf8”))
has_sent=0
file_obj=open(local_path,“rb”)
while has_sent<
file_byte_size:
data=file_obj.read(1024)
sk.sendall(data)
has_sent+=len(data)
file_obj.close()
print(“上传成功”)

 

注意:
1  纸条就是conn
2  一收一发(解决粘包的关键)
3   client_data=conn.recv(1024)
if  那边send一个空数据  这边recv为空,则recv继续阻塞,等待其他的数据。所以聊天的时候好好聊,别发空数据。
 


SOCKET编程

回顾
要想理解socket,就要先来回顾一些网络通信的知识

IP地址   (1) 用来标识网络上一台独立的主机

              (2) IP地址 = 网络地址 + 主机地址(网络号:用于识别主机所在的网络/网段。主机号:用于识别该网络中的主机)

              (3) 特殊的IP地址:127.0.0.1(本地回环地址、保留地址,点分十进制)可用于简单的测试网卡是否故障。表示本机。

端口号:  (1) 用于标识进程的逻辑地址。不同的进程都有不同的端口标识。

              (2) 端口:要将数据发送到对方指定的应用程序上,为了标识这些应用程序,所以给这些网络应用程序都用数字进行标识。为了方便称呼这些数字,则将这些数字称为端口。(此端口是一个逻辑端口)

传输协议:通讯的规则。例如:TCP、UDP协议(好比两个人得用同一种语言进行交流)

①、UDP:User Datagram Protocol用户数据报协议

特点:

  • 面向无连接:传输数据之前源端和目的端不需要建立连接。
  • 每个数据报的大小都限制在64K(8个字节)以内。
  • 面向报文的不可靠协议。(即:发送出去的数据不一定会接收得到)
  • 传输速率快,效率高。
  • 现实生活实例:邮局寄件、实时在线聊天、视频会议…等。

②、TCP:Transmission Control Protocol传输控制协议

特点:

  • 面向连接:传输数据之前需要建立连接。
  • 在连接过程中进行大量数据传输。
  • 通过“三次握手”的方式完成连接,是安全可靠协议。
  • 传输速度慢,效率低。

注意:在TCP/IP协议中,TCP协议通过三次握手建立一个可靠的连接

# “我能给你讲个关于tcp的笑话吗?”

#   “行,给我讲个tcp笑话.”

#   “好吧那我就给你讲个tcp笑话.”

TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,定义了主机如何连入因特网及数据如何再它们之间传输的标准,

从字面意思来看TCP/IP是TCP和IP协议的合称,但实际上TCP/IP协议是指因特网整个TCP/IP协议族。不同于ISO模型的七个分层,TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中

应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等

传输层:TCP,UDP

网络层:IP,ICMP,OSPF,EIGRP,IGMP

数据链路层:SLIP,CSLIP,PPP,MTU

每一抽象层建立在低一层提供的服务上,并且为高一层提供服务,看起来大概是这样子的

    我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,我们经常把socket翻译为套接字,socket是在应用层和传输层(TCP/IP协议族通信)之间的一个抽象层,是一组接口,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。

    应用程序两端通过“套接字”向网络发出请求或者应答网络请求。可以把socket理解为通信的把手(hand)

 

socket通信流程(python语言)

 
SOCKET编程
 

# 流程描述:
# 1 服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket
# 2 服务器为socket绑定ip地址和端口号
# 3 服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有
#  被打开
# 4 客户端创建socket
# 5 客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket
# 6 服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接

#  信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直等到客户端返回连接信息后才返回,

#  开始接收下一个客户端连接请求
# 7 客户端连接成功,向服务器发送连接状态信息
# 8 服务器accept方法返回,连接成功
# 9 客户端向socket写入信息(或服务端向socket写入信息)
# 10 服务器读取信息(客户端读取信息)
# 11 客户端关闭
# 12 服务器端关闭

 

相关方法及参数介绍

sk.bind(address)
  #s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。
   在AF_INET下,以元组(host,port)的形式表示地址。
sk.listen(backlog)
  #开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。
      #backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept
      进行处理的连接个数最大为5
      #这个值不能无限大,因为要在内核中维护连接队列
sk.setblocking(bool)
  #是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。
sk.accept()
  #接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和
   发送数据。address是连接客户端的地址。
  #接收TCP 客户的连接(阻塞式)等待连接的到来
sk.connect(address)
  #连接到address处的套接字。一般,address的格式为元组(hostname,port),
   如果连接出错,返回socket.error错误。
sk.connect_ex(address)
  #同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,
   例如:10061
sk.close()
  #关闭套接字
sk.recv(bufsize[,flag])
  #接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。
   flag提供有关消息的其他信息,通常可以忽略。
sk.recvfrom(bufsize[.flag])
  #与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,
   address是发送数据的套接字地址。
sk.send(string[,flag])
  #将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能
   小于string的字节大小。即:可能未将指定内容全部发送。
sk.sendall(string[,flag])
  #将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。
   成功返回None,失败则抛出异常。内部通过递归调用send,将所有内容发送出去。
sk.sendto(string[,flag],address)
  #将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。
   返回值是发送的字节数。该函数主要用于UDP协议。
sk.settimeout(timeout)
  #设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有
   超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作
   (如 client 连接最多等待5s )
sk.getpeername()
  #返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
sk.getsockname()
  #返回套接字自己的地址。通常是一个元组(ipaddr,port)
sk.fileno()
  #套接字的文件描述符

 

简单实例

SOCKET编程
背景:从前,有个屌丝小明,想追女神洛姬,闷骚型,心想:老子就给她一次机会,不把握就算了…

#################server
import socket
ip_port = ('127.0.0.1',8090)
sk = socket.socket()
sk.bind(ip_port)
sk.listen(5)
print ('server waiting...')
conn,addr = sk.accept()
client_data = conn.recv(1024)
print (str(client_data,"utf8"))
conn.sendall(bytes('滚蛋!',encoding="utf-8"))
sk.close()

################client
import socket
ip_port = (‘127.0.0.1’,8090)
sk = socket.socket()
sk.connect(ip_port)
sk.sendall(bytes(‘俺喜欢你’,encoding=“utf8”))
server_reply = sk.recv(1024)
print (str(server_reply,“utf8”))

 


第八十七讲 综合运用(2)


昨天我们我们了定义了一个网络库,但是只是给出了一些相关的信息,还没有真正的实现的,好吧,今天我们来说一下这个的类的实现,不过在

现在我们可以来实现的我们的相关class了,我们从简单的开始:
=======================================

//=====我们先从PER_IO_DATA着手吧=======

//构造函数,初始化相关对象

PER_IO_DATA::PER_IO_DATA(){

ReSet();

_socket = INVALID_SOCKET;

_op = ONIDEL_POSTED;

}


//析构函数,清楚断开socket

PER_IO_DATA::~PER_IO_DATA(){

if (_socket != INVALID_SOCKET){

closesocket(_socket);

_socket = INVALID_SOCKET;

}

}

//重置buffer,不过这个函数没用到,因为下面的ReSet更好一些


void PER_IO_DATA::ResetBuf(){

ZeroMemory(_buf, MAX_BUF);

}

//重置所有相关对象,除socket外


void PER_IO_DATA::ReSet(){

ZeroMemory(&

_ol, sizeof(WSAOVERLAPPED));

ZeroMemory(_buf, MAX_BUF);

_wsabuf.buf = _buf;

_wsabuf.len = MAX_BUF;

_op = ONIDEL_POSTED;

}

//其实这个也没用到,只是在完成例程中用到

MyNet* PER_IO_DATA::GetMyNet(){


return _pthis;

}

===================================

//现在我们再来看看PER_HANDLE_DATA的实现


PER_HANDLE_DATA::PER_HANDLE_DATA(){

_socket = INVALID_SOCKET;

memset(&

_addr, 0, sizeof(SOCKADDR_IN));

_Name = "

"

;

//保存连接客户的姓名

_Count = 0;

}


PER_HANDLE_DATA::~PER_HANDLE_DATA(){

if (_socket != INVALID_SOCKET){

closesocket(_socket);

_socket = INVALID_SOCKET;

}

}


//设置套接字


void PER_HANDLE_DATA::SetSocket(SOCKET _Sock){

_socket = _Sock;

}

//获取套接字

SOCKET&

PER_HANDLE_DATA::GetSocket(){


return _socket;

}


//设置客户地址


void PER_HANDLE_DATA::SetAddr(SOCKADDR_IN&

addr){

memcpy(&

_addr, &

addr, sizeof(SOCKADDR_IN));

}


//获取地址

SOCKADDR_IN&

PER_HANDLE_DATA::GetAddr(){


return _addr;

}



void PER_HANDLE_DATA::SetName(std::string name){

_Name = name;

}


//获取用户姓名

std::string&

PER_HANDLE_DATA::GetName(){


return _Name;

}


//原子操作将操作数+1


void PER_HANDLE_DATA::AddCount(){

InterlockedIncrement(&

_Count);

}


//原子操作将操作数-1


void PER_HANDLE_DATA::DecCount(){

InterlockedDecrement(&

_Count);

}


//获取目前的操作数

const unsigned long PER_HANDLE_DATA::GetCount() const{


return _Count;

}


//==================这章节就先说这里,余下的内容下面的章节再说

================================================

这两个class,开始的时候我直接打算就用PER_IO_DATA来操作的,但是后来发觉容易搞混,于是就重新封装一下,所以相信大家理解起来不会困难,好吧,下一讲我们开始来说说MyNet的实现。


================================

回复D&

d查看目录,回复相应的数字查看章节内容


原文始发于微信公众号(

C/C++的编程教室

):第八十七讲 综合运用(2)

|