1.Socket基本概念介绍

澳门新葡萄京官网注册,python之网络编程

一、概述

Socket是套接字的英文名称,主要用于网络通信编程。前几天看网络通信,发现有许多概念,诸如同步/异步,阻塞/非阻塞等,感觉迷惑不清,因此就先介绍一下这些概念。

本地的进程间通信有很多种方式,但可以总结为下面4类:

TCP(传输控制协议)和UDP(用户数据报协议是网络体系结构TCP/IP模型中传输层一层中的两个不同的通信协议。

同步方式是指发送方不等接收方响应,便接着发送下个数据包的通信方式;

  • 消息传递(管道、FIFO、消息队列)
  • 同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
  • 共享内存
  • 远程过程调用(Solaris门和Sun RPC)

TCP:传输控制协议,一种面向连接的协议,给用户进程提供可靠的全双工的字节流,TCP套接口是字节流套接口(stream
socket)的一种。

异步方式是指发送方发出数据后,等到接收方发回响应才发下个数据包的通信方式。

但这些都不是本文的主题!我们要讨论的是网络中进程之间如何通信?首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。

UDP:用户数据报协议。UDP是一种无连接协议。UDP套接口是数据报套接口(datagram
socket)的一种。

阻塞套接字是指执行此套接字的网络调用时,直到成功才返回,否则一直阻塞在此网络调用上,比如调用Receive()函数读取网络缓冲区中的数据,如果没有数据到达,将一直挂在Receive()这个函数调用上,直到读到一些数据,此函数调用才返回。

使用TCP/IP协议的应用程序通常采用应用编程接口:UNIX BSD的套接字和UNIX
System
V的TLI,来实现网络进程之间的通信。就目前而言,几乎所有的应用程序都是采用socket,而现在又是网络时代,网络中进程通信是无处不在,这就是我为什么说“一切皆socket”。

二、TCP和UDP介绍

非阻塞套接字是指执行此套接字的网络调用,不管是否执行成功,都立即返回,比如调用Receive()函数读取网络缓冲区中数据,不管是否读到数据都立即返回,而不会一直挂在此函数调用上。在实际windows网络通信软件开发中,异步非阻塞套接字使用最多,例如平常所说的C/S(客户端/服务器)结构的软件就是异步非阻塞模式的。

澳门新葡萄京官网注册 1

1)基本TCP客户—服务器程序设计基本框架

2.Socket编程原理

网络编程对所有开发语言都是一样的,Python也不例外。用Python进行网络编程,就是在Python程序本身这个进程内,连接别的服务器进程的通信端口进行通信。

澳门新葡萄京官网注册 2

Socket编程中最常使用的两种协议,即面向连接的TCP协议和无连接的UDP协议。下面结合图示分别来说明(图1是面向连接的,图2是无连接的)

IP、TCP和UDP
当您编写socket应用程序的时候,您可以在使用TCP还是使用UDP之间做出选择。它们都有各自的优点和缺点。
TCP是流协议,而UDP是数据报协议。换句话说,TCP在客户机和服务器之间建立持续的开放连接,在该连接的生命期内,字节可以通过该连接写出。然而,通过
TCP
写出的字节没有内置的结构,所以需要高层协议在被传输的字节流内部分隔数据记录和字段。
另一方面,UDP不需要在客户机和服务器之间建立连接,它只是在地址之间传输报文。UDP的一个很好特性在于它的包是自分隔的(self-delimiting),也就是一个数据报都准确地指出它的开始和结束位置。然而,UDP的一个可能的缺点在于,它不保证包将会按顺序到达,甚至根本就不保证。当然,建立在UDP之上的高层协议可能会提供握手和确认功能。
对于理解TCP和UDP之间的区别来说,一个有用的类比就是电话呼叫和邮寄信件之间的区别。在呼叫者用铃声通知接收者,并且接收者拿起听筒之前,电话呼叫不是活动的。只要没有一方挂断,该电话信道就保持活动,但是在通话期间,他们可以自由地想说多少就说多少。来自任何一方的谈话都按临时的顺序发生。另一方面,当你发一封信的时候,邮局在投递时既不对接收方是否存在作任何保证,也不对信件投递将花多长时间做出有力保证。接收方可能按与信件的发送顺序不同的顺序接收不同的信件,并且发送方也可能在他们发送信件是交替地接收邮件。与邮政服务不同,无法送达的信件总是被送到死信办公室处理,而不再返回给发送。

说明:(三路握手)
       
1.客户端发送一个SYN段(同步序号)指明客户打算连接的服务器端口,以及初始化序号(ISN)

       
2.服务器发回包含服务器的初始序号的SYN报文段作为应答。同时,将确认序号(ACK)设置为客户的ISN加1以对客户的SYN
报文段进行确认。一个SYN将占用一个序号。
       
3.客户必须将确认序号设置为服务器的ISN加1以对服务器的SYN报文段进行确认。

澳门新葡萄京官网注册 3

对等方、端口、名称和地址
除了TCP和UDP协议以外,通信一方还需要知道的关于与之通信的对方机器的两件事情:IP地址或者端口。IP地址是一个32位的数据值,为了人们好记,一般用圆点分开的4组数字的形式来表示,比如:64.41.64.172。端口是一个16位的数据值,通常被简单地表示为一个小于65536的数字。大多数情况下,该值介于10到100的范围内。一个IP地址获取送到某台机器的一个数据包,而一个端口让机器决定将该数据包交给哪个进程/服务。这种解释略显简单,但基本思路是正确的。
上面的描述几乎都是正确的,但它也遗漏了一些东西。大多数时候,当人们考虑Internet主机时,我们都不会记忆诸如64.41.64.172这样的数字,而是记忆诸如gnosis.cx这样的名称。为了找到与某个特定主机名称相关联的IP地址,一般都使用域名服务器,但是有时会首先使用本地查找(经常是通过/etc/hosts的内容)。对于本教程,我们将一般地假设有一个IP地址可用,不过下面讨论编写名称查找代码。

澳门新葡萄京官网注册 4

                          图1面向连接的TCP时序图

主机名称解析
命令行实用程序nslookup可以被用来根据符号名称查找主机IP地址。实际上,许多常见的实用程序,比如ping或者网络配置工具,也会顺便做同样的事情。但是以编程方式做这样的事情很简单。

2)
基本UDP客户—服务器程序设计基本框架流程图

对于TCP,步骤如下:

======================TCP/IP======================
应用层: 它只负责产生相应格式的数据 ssh ftp
nfs cifs dns http smtp pop3
-----------------------------------
传输层: 定义数据传输的两种模式:
TCP(传输控制协议:面向连接,可靠的,效率相对不高)
UDP(用户数据报协议:非面向连接,不可靠的,但效率高)
-----------------------------------
网络层: 连接不同的网络如以太网、令牌环网
IP 、ICMP、
IGMP
ARP ( 地址解析协议,作用是将IP解析成MAC )
-----------------------------------
数据链路层: 以太网传输
-----------------------------------
物理层:
主要任务是规定各种传输介质和接口与传输信号相关的一些特性
-----------------------------------

澳门新葡萄京官网注册 5

(1)服务器首先启动,然后在某一时刻启动客户机与服务器建立连接。服务器与客户机首先调用Socket()建立一个套接字Socket

澳门新葡萄京官网注册 6

 

(2)服务器调用Bind()将套接字与一个本机指定端口绑定在一起,再调用Listen()使套接字处于一种被动的准备接收状态,这时客户机建立套接字便可以通过调用Connect()和服务器建立连接。

TCP/IP(Transmission Control Protocol/Internet
Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网设计的。

3) UDP和TCP的对比:

(3)服务器可以调用Accept()方法来接受客户机连接,然后继续侦听端口,并发出阻塞,直到下一个请求出现,从而实现多个客户机连接。

TCP socket
由于在通向前需要建立连接,所以其模式较 UDP socket 负责些。

从上面的流程图比较我们可以很明显的看出UDP没有三次握手过程。

(4)连接建立之后,客户机和服务器之间就可以通过连接发送和接收数据。

澳门新葡萄京官网注册 7

简单点说。UDP处理的细节比TCP少。UDP不能保证消息被传送到(它也报告消息没有传送到)目的地。UDP也不保证数据包的传送顺序。UDP把数据发出去后只能希望它能够抵达目的地。

(5)等待数据传输结束,双方调用Close()关闭套接字。

UDP(User
Data
Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。如图:

TCP优缺点:

澳门新葡萄京官网注册 8

澳门新葡萄京官网注册 9

优点:
        1.TCP提供以认可的方式显式地创建和终止连接。
       
2.TCP保证可靠的、顺序的(数据包以发送的顺序接收)以及不会重复的数据传输。
        3.TCP处理流控制。
        4.允许数据优先
        5.如果数据没有传送到,则TCP套接口返回一个出错状态条件。
       
6.TCP通过保持连续并将数据块分成更小的分片来处理大数据块。—无需程序员知道

                               图2无连接的UDP时序图

UDP
Socket图:

缺点:
TCP在转移数据时必须创建(并保持)一个连接。这个连接给通信进程增加了开销,让它比UDP速度要慢。

对于UDP,客户机并不与服务器建立连接,而仅仅调用函数SendTo()给服务器发送数据报。相似地,服务器也不从客户端接收一个连接,只是调用函数ReceiveFrom(),等待客户端来的数据。依照ReceiveFrom()得到的协议地址以及数据报,服务器就可以给客户发送一个应答。

澳门新葡萄京官网注册 10

UDP优缺点:
        1.UDP不要求保持一个连接
       
2.UDP没有因接收方认可收到数据包(或者当数据包没有正确抵达而自动重传)而带来的开销。
        3.设计UDP的目的是用于短应用和控制消息
       
4.在一个数据包连接一个数据包的基础上,UDP要求的网络带宽比TDP更小。

UDP socket
server 端代码在进行bind后,无需调用listen方法。

三、Socket编程

TCP/IP协议族包括运输层、网络层、链路层,
而socket所在位置如图,Socket是应用层与TCP/IP协议族通信的中间软件抽象层。

Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序。要学Internet上的TCP/IP网络编程,必须理解Socket接口。

澳门新葡萄京官网注册 11

Socket接口设计者最先是将接口放在Unix操作系统里面的。如果了解Unix系统的输入和输出的话,就很容易了解Socket了。网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。常用的Socket类型有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。

Socket是什么

socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open
–> 读写write/read –>
关闭close”模式来操作。Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭).
说白了Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

注意:其实socket也没有层的概念,它只是一个facade设计模式的应用,让编程变的更简单。是一个软件抽象层。在网络编程中,我们大量用的都是通过socket实现的。

Socket是网络编程的一个抽象概念。通常我们用一个Socket表示“打开了一个网络链接”,而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可。

澳门新葡萄京官网注册 12

1、socket调用库函数主要有:

TCP编程

Socket是网络编程的一个抽象概念。通常我们用一个Socket表示“打开了一个网络链接”,而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可。TCP连接简图:
三次握手,数据传输,四次挥手澳门新葡萄京官网注册 13

创建套接字 
        Socket(af,type,protocol)

socket中TCP的三次握手建立连接详解

我们知道tcp建立连接要进行“三次握手”,即交换三个分组。大致流程如下:

  • 客户端向服务器发送一个SYN J
  • 服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
  • 客户端再想服务器发一个确认ACK K+1

只有就完了三次握手,但是这个三次握手发生在socket的那几个函数中呢?请看下图:

澳门新葡萄京官网注册 14

图1、socket中发送的TCP三次握手

从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN
J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN
J包,调用accept函数接收请求向客户端发送SYN
K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK
J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK
K+1时,accept返回,至此三次握手完毕,连接建立。

总结:客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。

建立地址和套接字的联系 
        bind(sockid, local addr, addrlen)

5、socket中TCP的四次握手释放连接详解

上面介绍了socket中TCP的三次握手建立过程,及其涉及的socket函数。现在我们介绍socket中的四次握手释放连接的过程,请看下图:

澳门新葡萄京官网注册 15

图2、socket中发送的TCP四次握手

图示过程如下:

  • 某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN
    M;
  • 另一端接收到FIN
    M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
  • 一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN
    N;
  • 接收到这个FIN的源发送端TCP对它进行确认。

这样每个方向上都有一个FIN和ACK。

服务器端侦听客户端的请求 
        listen( Sockid ,quenlen)

Python3 网络编程

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

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

建立服务器/客户端的连接
(面向连接TCP) 
        客户端请求连接 
        Connect(sockid, destaddr, addrlen) 
        服务器端等待从编号为Sockid的Socket上接收客户连接请求 
        newsockid=accept(Sockid,Clientaddr, paddrlen)

什么是 Socket?

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

socket和file的区别:

  • file模块是针对某个指定文件进行
  • socket模块是针对 服务器端 和 客户端Socket 进行

澳门新葡萄京官网注册 16

服务器端先初始化Socket,然后与端口绑定,对端口进行监听,调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器,如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

发送/接收数据 
        面向连接:send(sockid, buff, bufflen) 
        recv( ) 
        面向无连接:sendto(sockid,buff,…,addrlen) 
        recvfrom( )

socket()函数

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

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

释放套接字 
        close(sockid)

参数

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

2、TCP/IP应用编程接口(API)

简单实例

服务器的工作流程:首先调用socket函数创建一个Socket,然后调用bind函数将其与本机地址以及一个本地端口号绑定,然后调用listen在相应的socket上监听,当accpet接收到一个连接服务请求时,将生成一个新的socket。服务器显示该客户机的IP地址,并通过新的socket向客户端发送字符串”
hi,I am server!”。最后关闭该socket。

服务端

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

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

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

完整代码如下:

12345678910111213141516171819202122232425262728293031 #!/usr/bin/python3# 文件名:server.py# 导入 socket、sys 模块import socketimport 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" % strmsg='欢迎访问python教程!'+ "rn"clientsocket.send(msg.encode('utf-8'))clientsocket.close()
main()//未测试
{ 
    int sock_fd,client_fd; /*sock_fd:监听socket;client_fd:数据传输socket */ 
    struct sockaddr_in ser_addr; /* 本机地址信息 */ 
    struct sockaddr_in cli_addr; /* 客户端地址信息 */ 
    char msg[MAX_MSG_SIZE];/* 缓冲区*/
    ser_sockfd=socket(AF_INET,SOCK_STREAM,0);/*创建连接的SOCKET */
    if(ser_sockfd<0)
    {/*创建失败 */
        fprintf(stderr,"socker Error:%sn",strerror(errno));
        exit(1);
    } 
    /* 初始化服务器地址*/
    addrlen=sizeof(struct sockaddr_in);
    bzero(&ser_addr,addrlen);
    ser_addr.sin_family=AF_INET;
    ser_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    ser_addr.sin_port=htons(SERVER_PORT);
    if(bind(ser_sockfd,(struct sockaddr*)&ser_addr,sizeof(struct sockaddr_in))<0)
    { /*绑定失败 */
        fprintf(stderr,"Bind Error:%sn",strerror(errno));
        exit(1);
    } 
    /*侦听客户端请求*/
    if(listen(ser_sockfd,BACKLOG)<0)
    {
        fprintf(stderr,"Listen Error:%sn",strerror(errno));
        close(ser_sockfd);
        exit(1);
    }
    while(1)
    {/* 等待接收客户连接请求*/
        cli_sockfd=accept(ser_sockfd,(struct sockaddr*) & cli_addr,&addrlen);
        if(cli_sockfd<=0)
        {
            fprintf(stderr,"Accept Error:%sn",strerror(errno));
        }
        else
        {/*开始服务*/
            recv(cli_addr,msg,MAX_MSG_SIZE,0); /* 接受数据*/
            printf("received a connection from %sn", inet_ntoa(cli_addr.sin_addr));
            printf("%sn",msg);/*在屏幕上打印出来 */ 
            strcpy(msg,"hi,I am server!");
            send(cli_addr,msg,sizeof(msg),0); /*发送的数据*/
            close(cli_addr); 
        }
    }
    close(ser_sockfd);
}

客户端

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

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

完整代码如下:

12345678910111213141516171819202122232425 #!/usr/bin/python3# 文件名:client.py# 导入 socket、sys 模块import socketimport 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端,然后打开client端就能看到结果

 

客户端

大多数连接都是可靠的TCP连接。创建TCP连接时,主动发起连接的叫客户端,被动响应连接的叫服务器。

举个例子,当我们在浏览器中访问新浪时,我们自己的计算机就是客户端,浏览器会主动向新浪的服务器发起连接。如果一切顺利,新浪的服务器接受了我们的连接,一个TCP连接就建立起来的,后面的通信就是发送网页内容了。

所以,我们要创建一个基于TCP连接的Socket,可以这样做:

1234567 # 导入socket库:import socket# 创建一个socket:s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 建立连接:s.connect(('www.sina.com.cn', 80))

创建Socket时,AF_INET指定使用IPv4协议,如果要用更先进的IPv6,就指定为AF_INET6SOCK_STREAM指定使用面向流的TCP协议,这样,一个Socket对象就创建成功,但是还没有建立连接。

客户端要主动发起TCP连接,必须知道服务器的IP地址和端口号。新浪网站的IP地址可以用域名www.sina.com.cn自动转换到IP地址,但是怎么知道新浪服务器的端口号呢?

答案是作为服务器,提供什么样的服务,端口号就必须固定下来。由于我们想要访问网页,因此新浪提供网页服务的服务器必须把端口号固定在80端口,因为80端口是Web服务的标准端口。其他服务都有对应的标准端口号,例如SMTP服务是25端口,FTP服务是21端口,等等。端口号小于1024的是Internet标准服务的端口,端口号大于1024的,可以任意使用。

因此,我们连接新浪服务器的代码如下:

1 s.connect(('www.sina.com.cn', 80))

注意参数是一个tuple,包含地址和端口号。

建立TCP连接后,我们就可以向新浪服务器发送请求,要求返回首页的内容:

12 # 发送数据:s.send(b'GET / HTTP/1.1rnHost: www.sina.com.cnrnConnection: closernrn')

TCP连接创建的是双向通道,双方都可以同时给对方发数据。但是谁先发谁后发,怎么协调,要根据具体的协议来决定。例如,HTTP协议规定客户端必须先发请求给服务器,服务器收到后才发数据给客户端。

发送的文本格式必须符合HTTP标准,如果格式没问题,接下来就可以接收新浪服务器返回的数据了:

接收数据时,调用recv方法,一次最多接收指定的字节数,因此,在一个while循环中反复接收,直到recv()返回空数据,表示接收完毕,退出循环。

当我们接收完数据后,调用close()方法关闭Socket,这样,一次完整的网络通信就结束了:

12 # 关闭连接:s.close()

接收到的数据包括HTTP头和网页本身,我们只需要把HTTP头和网页分离一下,把HTTP头打印出来,网页内容保存到文件:

12345 header, html = data.split(b'rnrn', 1)print(header.decode('utf-8'))# 把接收的数据写入文件:with open('sina.html', 'wb') as f:f.write

现在,只需要在浏览器中打开这个sina.html文件,就可以看到新浪的首页了。

//TCP server 测试通过

#pragma comment(lib, "ws2_32.lib")
#include <Winsock2.h>
#include <stdio.h>
void main()
{
    //版本协商
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    wVersionRequested = MAKEWORD(1, 1); //0x0101
    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0)
    {
        return;
    }
    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)  //wsaData.wVersion!=0x0101
    {
        WSACleanup();
        return;
    }
    //创建Socket
    SOCKET sockSvr = socket(AF_INET, SOCK_STREAM, 0);
    //创建IP地址和端口
    SOCKADDR_IN addrSvr;
    addrSvr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    addrSvr.sin_family = AF_INET;
    addrSvr.sin_port = htons(6000);
    //绑定端口监听
    bind(sockSvr, (SOCKADDR*)&addrSvr, sizeof(SOCKADDR));
    listen(sockSvr, 5);
    sockaddr_in addrClient;
    int len = sizeof(sockaddr);
    while (true)
    {
        //阻塞方法,获得一个客户Socket连接
        SOCKET sockConn = accept(sockSvr, (sockaddr*)&addrClient, &len);
        char sendbuffer[128];
        sprintf(sendbuffer, "Welcom %s!", inet_ntoa(addrClient.sin_addr));
        //向客户Socket发送数据
        send(sockConn, sendbuffer, strlen(sendbuffer) + 1, 0);
        char recvbuffer[128];
        //从客户Soc接受数据
        recv(sockConn, recvbuffer, 128, 0);
        printf("%sn", recvbuffer);
        //关闭Socket
        closesocket(sockConn);
    }
    closesocket(sockSvr);
    //释放Winsock资源
    WSACleanup();
}

服务器

和客户端编程相比,服务器编程就要复杂一些。

服务器进程首先要绑定一个端口并监听来自其他客户端的连接。如果某个客户端连接过来了,服务器就与该客户端建立Socket连接,随后的通信就靠这个Socket连接了。

所以,服务器会打开固定端口监听,每来一个客户端连接,就创建该Socket连接。由于服务器会有大量来自客户端的连接,所以,服务器要能够区分一个Socket连接是和哪个客户端绑定的。一个Socket依赖4项:服务器地址、服务器端口、客户端地址、客户端端口来唯一确定一个Socket。

但是服务器还需要同时响应多个客户端的请求,所以,每个连接都需要一个新的进程或者新的线程来处理,否则,服务器一次就只能服务一个客户端了。

我们来编写一个简单的服务器程序,它接收客户端连接,把客户端发过来的字符串加上Hello再发回去。

首先,创建一个基于IPv4和TCP协议的Socket:

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

然后,我们要绑定监听的地址和端口。服务器可能有多块网卡,可以绑定到某一块网卡的IP地址上,也可以用0.0.0.0绑定到所有的网络地址,还可以用127.0.0.1绑定到本机地址。127.0.0.1是一个特殊的IP地址,表示本机地址,如果绑定到这个地址,客户端必须同时在本机运行才能连接,也就是说,外部的计算机无法连接进来。

端口号需要预先指定。因为我们写的这个服务不是标准服务,所以用9999这个端口号。请注意,小于1024的端口号必须要有管理员权限才能绑定:

12 # 监听端口:s.bind(('127.0.0.1', 9999))

紧接着,调用listen()方法开始监听端口,传入的参数指定等待连接的最大数量:

12 s.listen(5)print('Waiting for connection...')

接下来,服务器程序通过一个永久循环来接受来自客户端的连接,accept()会等待并返回一个客户端的连接:

123456 while True:# 接受一个新连接:sock, addr = s.accept()# 创建新线程来处理TCP连接:t = threading.Thread(target=tcplink, args=(sock, addr))t.start()

每个连接都必须创建新线程来处理,否则,单线程在处理连接的过程中,无法接受其他客户端的连接:

1234567891011 def tcplink(sock, addr):print('Accept new connection from %s:%s...' % addr)sock.send(b'Welcome!')while True:data = sock.recv(1024)time.sleep(1)if not data or data.decode('utf-8') == 'exit':breaksock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8'))sock.close()print('Connection from %s:%s closed.' % addr)

连接建立后,服务器首先发一条欢迎消息,然后等待客户端数据,并加上Hello再发送给客户端。如果客户端发送了exit字符串,就直接关闭连接。

要测试这个服务器程序,我们还需要编写一个客户端程序:

1234567891011 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 建立连接:s.connect(('127.0.0.1', 9999))# 接收欢迎消息:print(s.recv(1024).decode('utf-8'))for data in [b'Michael', b'Tracy', b'Sarah']:# 发送数据:s.sendprint(s.recv(1024).decode('utf-8'))s.send(b'exit')s.close()

我们需要打开两个命令行窗口,一个运行服务器程序,另一个运行客户端程序,就可以看到效果了:

 

UDP编程

TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据。相对TCP,UDP则是面向无连接的协议。

使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。

虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。

我们来看看如何通过UDP协议传输数据。和TCP类似,使用UDP的通信双方也分为客户端和服务器。服务器首先需要绑定端口:

123 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# 绑定端口:s.bind(('127.0.0.1', 9999))

创建Socket时,SOCK_DGRAM指定了这个Socket的类型是UDP。绑定端口和TCP一样,但是不需要调用listen()方法,而是直接接收来自任何客户端的数据:

123456 print 'Bind UDP on 9999...'while True:# 接收数据:data, addr = s.recvfrom(1024)print 'Received from %s:%s.' % addrs.sendto('Hello, %s!' % data, addr)

recvfrom()方法返回数据和客户端的地址与端口,这样,服务器收到数据后,直接调用sendto()就可以把数据用UDP发给客户端。

注意这里省掉了多线程,因为这个例子很简单。

客户端使用UDP时,首先仍然创建基于UDP的Socket,然后,不需要调用connect(),直接通过sendto()给服务器发数据:

1234567 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)for data in ['Michael', 'Tracy', 'Sarah']:# 发送数据:s.sendto(data, ('127.0.0.1', 9999))# 接收数据:print s.recv(1024)s.close()

从服务器接收数据仍然调用recv()方法。

客户端的工作流程:首先调用socket函数创建一个Socket,然后调用bind函数将其与本机地址以及一个本地端口号绑定,请求连接服务器,通过新的socket向客户端发送字符串”
hi,I am client!”。最后关闭该socket。

小结

UDP的使用与TCP类似,但是不需要建立连接。此外,服务器绑定UDP端口和TCP端口互不冲突,也就是说,UDP的9999端口与TCP的9999端口可以各自绑定。

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

  • 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets
    API,可以访问底层操作系统Socket接口的全部方法。
  • 高级别的网络服务模块 SocketServer,
    它提供了服务器中心类,可以简化网络服务器的开发。
main()//未测试
{
    int cli_sockfd;/*客户端SOCKET */
    int addrlen;
    char seraddr[14];
    struct sockaddr_in ser_addr,/* 服务器的地址*/
        cli_addr;/* 客户端的地址*/
    char msg[MAX_MSG_SIZE];/* 缓冲区*/
    GetServerAddr(seraddr);
    cli_sockfd=socket(AF_INET,SOCK_STREAM,0);/*创建连接的SOCKET */
    if(ser_sockfd<0)
    {/*创建失败 */
        fprintf(stderr,"socker Error:%sn",strerror(errno));
        exit(1);
    }
    /* 初始化客户端地址*/
    addrlen=sizeof(struct sockaddr_in);
    bzero(&ser_addr,addrlen);
    cli_addr.sin_family=AF_INET;
    cli_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    cli_addr.sin_port=0;
    if(bind(cli_sockfd,(struct sockaddr*)&cli_addr,addrlen)<0)
    { 
        /*棒定失败 */
        fprintf(stderr,"Bind Error:%sn",strerror(errno));
        exit(1);
    }
    /* 初始化服务器地址*/
    addrlen=sizeof(struct sockaddr_in);
    bzero(&ser_addr,addrlen);
    ser_addr.sin_family=AF_INET;
    ser_addr.sin_addr.s_addr=inet_addr(seraddr);
    ser_addr.sin_port=htons(SERVER_PORT);
    if(connect(cli_sockfd,(struct sockaddr*)&ser_addr,&addrlen)!=0)/*请求连接*/
    {
        /*连接失败 */
        fprintf(stderr,"Connect Error:%sn",strerror(errno));
        close(cli_sockfd);
        exit(1);
    }
    strcpy(msg,"hi,I am client!");
    send(sockfd,msg,sizeof(msg),0);/*发送数据*/
    recv(sockfd,msg,MAX_MSG_SIZE,0); /* 接受数据*/
    printf("%sn",msg);/*在屏幕上打印出来 */
    close(cli_sockfd);
}

 

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是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect

s.gettimeout()

返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。

s.fileno()

返回套接字的文件描述符。

s.setblocking

如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。

s.makefile()

创建一个与该套接字相关连的文件

server端:

12345678910111213141516171819202122 #!/usr/bin/env python# -*- coding:utf-8 -*-import socketip_port = ('127.0.0.1',9999)sk = socket.socket()sk.bindsk.listen(5)while True:print 'server waiting...'conn,addr = sk.accept()client_data = conn.recv(1024)print client_dataconn.sendall('不要回答,不要回答,不要回答')conn.close()socket server

client:

12345678910111213141516 #!/usr/bin/env python# -*- coding:utf-8 -*-import socketip_port = ('127.0.0.1',9999)sk = socket.socket()sk.connectsk.sendall('请求占领地球')server_reply = sk.recv(1024)print server_replysk.close()socket client

WEB服务应用:

123456789101112131415161718192021 #!/usr/bin/env python#coding:utf-8import socketdef handle_request:buf = client.recv(1024)client.send("HTTP/1.1 200 OKrnrn")client.send("Hello, World")def main():sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)sock.bind(('localhost',8080))sock.listen(5)while True:connection, address = sock.accept()handle_request(connection)connection.close()if __name__ == '__main__':main()
//TCP  client测试通过

#pragma comment (lib,"ws2_32.lib")
#include <Winsock2.h>
#include <stdio.h>
void main()
{
    //版本协商
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    wVersionRequested = MAKEWORD(1, 1); //0x0101
    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0)
    {
        return;
    }
    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)     //wsaData.wVersion!=0x0101
    {
        WSACleanup();
        return;
    }
    //创建连向服务器的套接字
    SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
    //创建地址信息
    SOCKADDR_IN hostAddr;
    hostAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    hostAddr.sin_family = AF_INET;
    hostAddr.sin_port = htons(6000);
    //连接服务器
    connect(sock, (sockaddr*)&hostAddr, sizeof(sockaddr));
    char revBuf[128];
    //从服务器获得数据
    recv(sock, revBuf, 128, 0);
    printf("%sn", revBuf);
    //向服务器发送数据
    send(sock, "Hello Host!", 12, 0);
    closesocket(sock);
}

 //错误 2
error LNK1104:
无法打开文件“C:UsersranjiDesktopConsoleApplication2Debugclient.exe”
C:UsersranjiDesktopConsoleApplication2ConsoleApplication2LINK   

     
先一直有这个错误,结果是自己要把生成文件,栓除,重新生成编译,即可。

3、UDP/IP应用编程接口(API)

服务器的工作流程:首先调用socket函数创建一个Socket,然后调用bind函数将其与本机地址以及一个本地端口号绑定,接收到一个客户端时,服务器显示该客户端的IP地址,并将字串返回给客户端。 
 

#pragma comment (lib,"ws2_32.lib")
#include <Winsock2.h>
#include <stdio.h>


////Server
void main()
{
    //版本协商
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    wVersionRequested = MAKEWORD(1, 1);
    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) {
        /* Tell the user that we could not find a usable */
        /* WinSock DLL.                                  */
        return;
    }
    /* Confirm that the WinSock DLL supports 2.2.*/
    /* Note that if the DLL supports versions greater    */
    /* than 2.2 in addition to 2.2, it will still return */
    /* 2.2 in wVersion since that is the version we      */
    /* requested.                                        */
    if (LOBYTE(wsaData.wVersion) != 1 ||
        HIBYTE(wsaData.wVersion) != 1) {
        /* Tell the user that we could not find a usable */
        /* WinSock DLL.                                  */
        WSACleanup();
        return;
    }
    /* The WinSock DLL is acceptable. Proceed. */
    //创建数据报套接字
    SOCKET svr = socket(AF_INET, SOCK_DGRAM, 0);
    //创建本地地址信息
    SOCKADDR_IN addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(6000);
    addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    int len = sizeof(sockaddr);
    bind(svr, (sockaddr*)&addr, len);
    //创建客户端地址对象
    SOCKADDR_IN addrClient;
    char recvBuf[128];
    char sendBuf[128];
    char tempBuf[256];
    while (true)
    {
        //接收数据
        printf("wait receive client message :n");
        recvfrom(svr, recvBuf, 128, 0, (sockaddr*)&addrClient, &len);
        char* ipClient = inet_ntoa(addrClient.sin_addr);
        sprintf(tempBuf, "%s said: %sn", ipClient, recvBuf);
        printf("%s", tempBuf);
        gets(sendBuf);
        //发送数据
        sendto(svr, sendBuf, strlen(sendBuf) + 1, 0, (sockaddr*)&addrClient, len);
    }
    closesocket(svr);
    WSACleanup();
}

 

 客户端的工作流程:首先调用socket函数创建一个Socket,填写服务器地址及端口号,从标准输入设备中取得字符串,将字符串传送给服务器端,并接收服务器端返回的字符串。最后关闭该socket。

#pragma comment (lib,"ws2_32.lib")
#include <Winsock2.h>
#include <stdio.h>

//Client
void main()
{
    //版本协商
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    wVersionRequested = MAKEWORD(1, 1);
    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) {
        /* Tell the user that we could not find a usable */
        /* WinSock DLL.                                  */
        return;
    }
    /* Confirm that the WinSock DLL supports 2.2.*/
    /* Note that if the DLL supports versions greater    */
    /* than 2.2 in addition to 2.2, it will still return */
    /* 2.2 in wVersion since that is the version we      */
    /* requested.                                        */
    if (LOBYTE(wsaData.wVersion) != 1 ||
        HIBYTE(wsaData.wVersion) != 1) {
        /* Tell the user that we could not find a usable */
        /* WinSock DLL.                                  */
        WSACleanup();
        return;
    }
    /* The WinSock DLL is acceptable. Proceed. */
    //创建服务器套接字
    SOCKET Svr = socket(AF_INET, SOCK_DGRAM, 0);
    //创建地址
    SOCKADDR_IN addrSvr;
    addrSvr.sin_family = AF_INET;
    addrSvr.sin_port = htons(6000);
    addrSvr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    char recvBuf[128];
    char sendBuf[128];
    int len = sizeof(sockaddr);
    while (true)
    {
        printf("client frist send message:n");
        gets(sendBuf);
        //发送数据

        sendto(Svr, sendBuf, strlen(sendBuf) + 1, 0, (sockaddr*)&addrSvr, len);   //sendBuf调试在内存中显示的ASCII码。
        //接收数据
        recvfrom(Svr, recvBuf, 128, 0, (sockaddr*)&addrSvr, &len);
        char* ipSvr = inet_ntoa(addrSvr.sin_addr);
        printf("%s said: %sn", ipSvr, recvBuf);
    }
    closesocket(Svr);
    WSACleanup();
}

 

四、调试

 TCP测试结果:client没有循环执行,在run下工作,Server在debug模式下。

澳门新葡萄京官网注册 17

 UDP测试结果:

澳门新葡萄京官网注册 18

 

参考: