本篇文章主要介绍下UDP广播、组播以及简单Socket的实现(顺带在说下CGI)
UDP广播和UDP组播(组播可实现跨网段通讯)
UDP广播只能在同一网段(严格意义上的网段)内进行广播,不可跨网段广播,而UDP组播可以实现跨网段广播,组播地址只能作为目的地址(组播地址也就是组播组),另外组播也被称为多播
组播架构:点对点两台主机,或者多台主机连接配置了支持组播的路由器(路由一般默认关闭组播功能)。这些主机IP地址都可不在同一网段
IGMP协议:
IGMP是IP组播的基础,在IP协议出现以后为了加入对组播的支持,IGMP产生了。IGMP所做的时间上就是告诉路由,在这个路由所在的子网内有人对发送到某一个组播组(组播IP)的数据感兴趣,这样当这个组播组的数据到达后,路由就不会抛弃它,而是把它转送给所有感兴趣的客户。假如不同子网内的A和B要进行组播通讯,那么位于AB直接的所有路由器必须都支持IGMP协议,否则AB之间不能进行通讯
组播的原理:
组播首先有一个用户申请一个组播组,这个组播组被维护在路由中,其他用户申请加入,这样当一个用户向组内发送消息时,路由器将消息转发给组内的所有成员。如果申请加入的组不在本级路由中,且路由和交换机允许组播协议通过,路由则将申请加入的操作箱上级路由提交。广域网通讯要经过多级路由器和交换机,几乎所有的网络设备都默认阻止组播协议通过(只允许本网段内的广播,不向上级提交),这使得广域网组播实现有一定局限
UDP组播基本步骤:
1、建立socket
2、socket和端口绑定(最初的Receive方需要bind 0.0.0.0端口)
3、加入一个组播组
4、通过sendto/recvfrom进行数据收发
5、关闭socket
所有终端必须都要加入相同的主播地址
Python组播实现:
Terminal_One:
#-*- coding:utf-8 -*-
import time
import struct
from socket import *
SENDERIP = '192.168.8.11' # 本地ip
SENDERPORT = 1501 # 本地接口
MYPORT = 1234 # 发送数据到该端口
MYGROUP = '225.0.0.77' # 组播组
MYTTL = 255 # 发送数据的TTL
def sender():
s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
s.bind((SENDERIP, SENDERPORT))
# Set Time-to-live (optional)
ttl_bin = struct.pack('@i', MYTTL)
s.setsockopt(IPPROTO_IP, IP_MULTICAST_TTL, ttl_bin)
status = s.setsockopt(IPPROTO_IP,
IP_ADD_MEMBERSHIP,
inet_aton(MYGROUP) + inet_aton(SENDERIP)) # 加入到组播组
i = 0
while True:
data = 'cisco'
s.sendto(data + '\0', (MYGROUP, MYPORT))
i = i + 1
print "%d send data ok !" % i
time.sleep(10)
if __name__ == "__main__":
sender()
Terminal_Two:
# -*- coding:utf-8 -*-
import time
import socket
SENDERIP = '169.254.51.246'
MYPORT = 1234 #监听组播组端口
MYGROUP = '225.0.0.77'
def receiver():
# create a UDP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
# allow multiple sockets to use the same PORT number
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Bind to the port that we know will receive multicast data
#bind一般用于服务器绑定、监听本地IP和端口,只可以可以绑定本机所具有的IP和端口
#但在组播中bind应该设置为监听组播IP和其端口,但bind无法设置为绑定、监听非本机的IP(保留IP也不可以被设置为监听对象)
#所以在组播中必须bind所有ip,和对应组播的端口号
sock.bind(('0.0.0.0', MYPORT))#留空也可以
# tell the kernel that we are a multicast socket
# sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255)
# Tell the kernel that we want to add ourselves to a multicast group
# The address for the multicast group is the third param
#加入组播组
status = sock.setsockopt(socket.IPPROTO_IP,
socket.IP_ADD_MEMBERSHIP,
socket.inet_aton(MYGROUP) + socket.inet_aton(SENDERIP));
sock.setblocking(0)
# ts = time.time()
while 1:
try:
data, addr = sock.recvfrom(1024)
except socket.error, e:
pass
else:
print "Receive data!"
print "TIME:", time.time()
print "FROM: ", addr
print "DATA: ", data
if __name__ == "__main__":
receiver()
CGI程序:
CGI程序一般指运行于HTTP服务器上的后台交互程序
Web后台架构可分为:
1、服务端(HTTP协议解析)
2、中间件(WSGI才有)
3、应用端(CGI/WSGI程序)
一个HTTP请求到达一台服务器的80端口后,需要有一个程序来响应该请求。所谓HTTP Response,其实只是运行一个程序,它的输入是HTTP Request Header,它的返回是HTTP Response。HTTP Request Header传递方式就分为CGI、WSGI以及其他各种GI的区别
如果是CGI,通常来说是一个Web Server(例如Apache、Nginx)接收到请求后,将请求中的HTTP Request Header按照一定规则设置成环境变量,然后启动一个程序,将stdout的输出(其中HTTP Response Header)封装成HTTP Response返回给客户的
例如:Django跑在uWSGI、unicorn之类的容器里,那么程序是一个常驻进程,Web Server和Python进程用WSGI协议传递HTTP Request Header信息,然后返回给用户。如果是Django的dev server,它使用Python自带的wsgiref模块实现了一个简单的HTTP Server响应HTTP请求。从以上可以知道服务端Server对利用Apache等软件对HTTP协议解析后还会将其解析的信息加入另一协议中,此协议根据服务器的后台逻辑框架而定(Django用WSGI协议),利用此进行服务端HTTP请求和后台逻辑框架的交互
Socket
Socket是应用层与TCP/IP协议族通信的中间软件抽象层。注意,UDP则不具备这一软件抽象层
Socket在网络通信中所处的位置:
Socket网络编程常用结构体(struct sockaddr, struct in_addr, struct sockaddr_in)定义:
struct sockaddr
{
unsigned short sa_family;//协议族(定义使用哪种底层网络协议),AF_INET(TCP/IPv4),AF_INET6(TCP/IPv6),AF_LOCAL或AF_UNIX(本地通信,用于本地进程间的socket通讯)
char sa_data[14];//14字节的协议地址
}
//str 2 in_addr变量赋值需使用inet_addr函数:in_addr addr = inet_addr("192.168.0.2");
//in_addr变量 2 str需使用inet_ntoa函数:char *str = inet_ntoa(addr);
typedef struct in_addr
{
union{
struct{unsigned char s_b1, s_b2, s_b3, s_b4;}S_un_b;
struct{unsigned short s_w1, s_w2;}S_un_w;
unsigned long S_addr;
}S_un;
}IN_ADDR;
struct sockaddr_in
{
short int sin_family;//协议族,在网络编程中一般就是用AF_INET
unsigned short int sin_port;//存放端口号(需转为大端模式再存放)
struct in_addr sin_addr;//存储IP地址
unsigned char sin_zero[8];//留空字节,保证sockaddr和sockaddr_in可以相互转换
}
Socket网络通讯编程流程:
Server端:
1、创建套接字(Socket)
2、将套接字绑定到一个本地地址和端口上(Bind)
3、将套接字设置为监听模式,准备接收客户端请求(Listen)
4、等待客户端请求到来,当请求到来后接受连接请求,返回一个新的对应于此次连接请求的套接字(accept)
5、用返回的套接字和客户端进行通信(Send/Recv)
6、返回,等待新请求
7、关闭套接字
一个简单的Server端Socket实现(C语言):
/*************************************************************************
> File Name: simplesocket.c
> Author:lizhong
> Mail:
> Created Time: Thu 09 Mar 2017 11:02:12 PM PST
************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>//路径:/usr/include/
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define DPORT 33333
#define SIZE 1000
int Select(int confd)
{
fd_set fd;
struct timeval time;
int ts = 0;
time.tv_sec = 0;//0秒
time.tv_usec = 500;//500毫秒
FD_ZERO(&fd);
FD_SET(confd, &fd);
/*最后一个time参数设置为空意味着阻塞,直到有收到东西*/
ts = select(confd + 1, &fd, NULL, NULL, NULL);
printf("Select Return:%d\n", ts);
if(ts)
return 1;
else
return 0;
}
int main()
{
int sfd,confd;
struct sockaddr_in serveradd;
char *rebuff;
char *sebuff;//= "I received the message!";
int relen = 0,j = 0,k = 0, sel = 0;
/*初始化Socket,返回socket的文件描述符*/
sfd = socket(AF_INET, SOCK_STREAM, 0);
if( sfd == -1 )
{
printf("Created Socket Error:%s\n", strerror(errno));
exit(0);
}
/*配置本服务器地址参数*/
memset(&serveradd, 0, sizeof(struct sockaddr_in));
serveradd.sin_family = AF_INET;
/*系统自动获取本机IP*/
serveradd.sin_addr.s_addr = htonl(INADDR_ANY);
/*设置监听端口为DPORT*/
serveradd.sin_port = htons(DPORT);
/*Socket和端口绑定*/
j = bind(sfd, (struct sockaddr*)&serveradd, sizeof(struct sockaddr_in));
if(j == -1)
{
printf("Bind Error :%s\n", strerror(errno));
exit(0);
}
j = 0;
/*开启监听客户端请求,(开闸)*/
j = listen(sfd, 10);
if(j == -1)
{
printf("Listen Error:%s\n", strerror(errno));
exit(0);
}
j= 0;
printf("***********************Wait Request**********************\n");
/*接受客户端连接,此条语句有阻塞效果*/
confd = accept(sfd, (struct sockaddr*)NULL, NULL);
if(confd == -1)
{
printf("Accept Error:%s\n", strerror(errno));
/*出错则结束*/
return 0;
}
while(1)
{
/*接收客户端传来的数据*/
rebuff = malloc(SIZE);
memset(rebuff, 0, SIZE);
/*注意传入confd,而不是sfd。阻塞,直到有收到东西,收到东西之后用recv函数接收下数据*/
/*将select函数放到recv函数后将一直返回0,因为recv后缓冲区没有数据了*/
sel = Select(confd);
/*flags设置为0值则是阻塞的(默认阻塞),并且接受完一次数据后接收缓冲区的数据会被清除*/
/*因为先前socket设置了socket stream(使用面向连接的TCP协议)所以没有数据被丢失,具体表现为:*/
/*SIZE过小会触发多次接收,每次relen的值最大为SIZE*/
relen = recv(confd, rebuff, SIZE, 0);
if(relen != -1 || relen != 0)
{
/*可以接收数据,但是数据长度却为0说明客户端断开了TCP连接*/
if(sel == 1 && relen == 0)
{
printf("Socket is close!\n");
free(rebuff);
break;
}
else
{
printf("Receive Data is :\n********%d:%s****relen:%d********\n", ++k, rebuff, relen);
sebuff = malloc(strlen("I received the message:") + relen);
strcpy(sebuff, "I received the message:");//'\0'也会被copy(MSDN上有说明)
strcat(sebuff, rebuff);
send(confd, sebuff, strlen(sebuff), 0);
free(rebuff);
free(sebuff);
}
}
else
{
printf("Receive Error is :%s\n", strerror(errno));
free(rebuff);
break;
}
}
return 0;
}
Client端:
1、创建套接字(Socket)
2、向服务端发出连接请求(Connect)
3、和服务端进行通信(Send/Recv)
4、关闭套接字
Client与Server流程图: