原理
是IP协议暴露在传输层上的接口。增加了端口概念。
端口(port)
是伴随着传输层
诞生的概念。IP协议进行的是IP地址到IP地址的传输,这意味者两台计算机之间的对话。每台计算机中需要有多个通信通道,并将多个通信通道分配给不同的进程
使用,一个端口
就代表了这样的一个通信通道
。它可以将网络层的IP通信分送到各个通信通道。
类别:UDP协议和TCP协议尽管在工作方式上有很大的不同,但它们都建立了从一个端口到另一个端口的通信。TCP协议复杂,但传输可靠。UDP协议简单,但传输不可靠。
UDP的数据包组成为头部(header)
和数据(payload)
两部分。UDP是传输层(transport layer)协议,这意味着UDP的数据包需要经过IP协议的封装(encapsulation),然后通过IP协议传输到目的电脑。随后UDP包在目的电脑拆封,并将信息送到相应端口
的缓存中。
头部
source port和destination port分别为UDP包的出发端口和目的地端口。
Length为整个UDP包的长度。
checksum校验IP、端口的正确性。
socket
随着我们进入传输层,我们也可以调用操作系统中的API,来构建socket。
Socket是操作系统提供的一个编程接口
,它用来代表某个网络通信。应用程序通过socket来调用系统内核中处理网络协议的模块
,而这些内核模块会负责具体的网络协议的实施。这样,我们可以让内核来接收网络协议的细节,而我们只需要提供所要传输的内容就可以了,内核会帮我们控制格式,并进一步向底层封装。
因此,在实际应用中,我们并不需要知道具体怎么构成一个UDP包,而只需要提供相关信息(比如IP地址,端口号,所要传输的信息),操作系统内核会在传输之前会根据我们提供的相关信息构成一个合格的UDP包(以及下层的包和帧)。
代码
1、htonl函数
将主机的unsigned long值转换成网络字节顺序(32位)(一般主机跟网络上传输的字节顺序是不通的,分大小端),函数返回一个网络字节顺序的数字。
n代表网络,h代表主机host,l代表long的长度,还有相对应的s代表16位的short
sockaddr_in
sin.sin_family = AF_INET;//ipv4固定值
sin.sin_port = htons(7001); //发送端使用的发送端口,可以根据需要更改,
//n代表网络,h代表主机host,l代表long的长度,还有相对应的s代表16位的short
sin.sin_addr.s_addr = htonl(INADDR_ANY);//默认为本机IP地址,
填充这个结构体我们通常只关心以上三个参数,特别是IP地址和端口号。IP地址的赋值有多种方式:
(1)SocketAddr.sin_addr.s_addr=INADDR_ANY;默认为本机IP地址
INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。 一般来说,在各个系统中均定义成为0值。
(2)SocketAddr.sin_addr.s_addr = inet_addr(inet_ntoa((struct in_addr )p->h_addr_list[0]));如果本机有几个IP地址,以获取到的第一个IP地址为服务器地址
(3)SocketAddr.sin_addr.s_addr = inet_addr(“192.168.2.183”);常用的方法,以字符串设置IP地址
WSAStartup
int WSAStartup ( WORD wVersionRequested, LPWSADATA lpWSAData );
⑴ wVersionRequested:一个WORD(双字节)型数值,在最高版本的Windows Sockets支持调用者使用,
高阶字节指定小版本(修订本)号,低位字节指定主版本号。
⑵lpWSAData 指向WSADATA数据结构的指针,用来接收Windows Sockets [1] 实现的细节。
bind()
将一本地地址与一套接口捆绑。本函数适用于未连接的数据报或流类套接口,在connect()或listen()调用前使用。当用socket()创建套接口后,它便存在于一个名字空间(地址族)中,但并未赋名。bind()函数通过给一个未命名套接口分配一个本地名字来为套接口建立本地捆绑(主机地址/端口号)。
int PASCAL FAR bind( SOCKET sockaddr, const struct sockaddr FAR* my_addr,int addrlen);
如无错误发生,则bind()返回0。否则的话,将返回-1
套接字类型
SOCKET sockListener=socket(AF_INET, SOCK_DGRAM, 0);
int socket(int domain, int type, int protocol);
domain参数:
PF_INET, AF_INET: Ipv4网络协议;
PF_INET6, AF_INET6: Ipv6网络协议。
type:
SOCK_DGRAM: 如果你传输的是视频音频等数据,丢几个包也无所谓的,可以采用UDP
SOCK_STREAM: 如果需要传输的数据是准确的,建议采用TCP,也就是SOCK_STREAM
参数protocol:
用来指定socket所使用的传输协议编号。这一参数通常不具体设置,一般设置为0即可。
RECEIVE
#include <stdio.h>
#include<winsock.h>
#include <conio.h>
//创建新的套接字之前需要调用一个引入Ws2_32.dll库的函数,否则服务器和客户端连接不上
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[])
{
WSADATA wsaData; //指向WinSocket信息结构的指针
SOCKET sockListener;
SOCKADDR_IN sin, saClient; //设置两个地址,sin用来绑定
//saClient用来从广播地址接收消息
char cRecvBuff[800]; //定义接收缓冲区
int nSize, nbSize;
int iAddrLen = sizeof(saClient);
if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) //进行WinSocket的初始化
{
printf("Can't initiates windows socket!Program stop.\n");//初始化失败返回-1
return -1;
}
sockListener = socket(AF_INET, SOCK_DGRAM, 0);
sin.sin_family = AF_INET;
sin.sin_port = htons(7001); //发送端使用的发送端口,可以根据需要更改
sin.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(sockListener, (SOCKADDR FAR *)&sin, sizeof(sin)) != 0)
{
printf("Can't bind socket to local port!Program stop.\n");//初始化失败返回-1
return -1;
}
while (1)
{
nSize = sizeof(SOCKADDR_IN);
if ((nbSize = recvfrom(sockListener, cRecvBuff, 800, 0, (SOCKADDR FAR *) &saClient, &nSize)) == SOCKET_ERROR) //若接收失败则提示错误
{
printf("Recive Error");
break;
}
cRecvBuff[nbSize] = '\0'; //字符串终止
printf("%s\n", cRecvBuff); //显示所接收到的字符串
}
return 0;
}
SEND
#include <stdio.h>
#include <string>
#include <iostream>
#include <winsock.h>
using namespace std;
//创建新的套接字之前需要调用一个引入Ws2_32.dll库的函数,否则服务器和客户端连接不上
#pragma comment(lib,"ws2_32.lib")
struct test {
string str;
};
struct UdpHeartPack {
char UDPData[16];
};
int main(int argc, char* argv[])
{
struct UdpHeartPack udpPack;
static int UDP_PORT = 7001;
udpPack.UDPData[0] = 'h';
udpPack.UDPData[1] = 'e';
udpPack.UDPData[2] = 'l';
udpPack.UDPData[3] = 'l';
udpPack.UDPData[4] = 'o';
udpPack.UDPData[5] = ' ';
udpPack.UDPData[6] = 'w';
udpPack.UDPData[7] = 'o';
udpPack.UDPData[8] = 'r';
udpPack.UDPData[9] = 'l';
udpPack.UDPData[10] = 'd';
udpPack.UDPData[11] = '\0';
char *pPack = (char *)&udpPack;
WSADATA wsaData; //指向WinSocket信息结构的指针
SOCKET sockListener; //创建套接字
SOCKADDR_IN saUdpServ; //指向通信对象的结构体指针
BOOL fBroadcast = TRUE; //用于setsockopt(),表示允许
char sendBuff[800]; //缓冲区存放发送的数据
int ncount = 0; //用于显示消息数目
//*************************** 第一步初始化Winsock *****************************//
if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) //进行WinSocket的初始化
{
printf("Can't initiates windows socket!Program stop.\n");//初始化失败返回-1
return -1;
}
//******************** 第二步建立一个数据报类型的UDP套接字 ******************//
sockListener = socket(PF_INET, SOCK_DGRAM, 0);
// setsockopt函数用于设置套接口选项
// 采用广播形式须将第三个参数设置为SO_BROADCAST
setsockopt(sockListener, SOL_SOCKET, SO_BROADCAST, (CHAR *)&fBroadcast, sizeof(BOOL));
// 参数设置,注意要将IP地址设为INADDR_BROADCAST,表示发送广播UDP数据报
saUdpServ.sin_family = AF_INET;
saUdpServ.sin_addr.s_addr = htonl(INADDR_BROADCAST);
saUdpServ.sin_port = htons(UDP_PORT); //发送用的端口,可以根据需要更改
while (1) //循环发送数据
{
Sleep(1000);
sprintf(sendBuff, "Message %d is: ok", ncount++); //将ncount的值放入字符串senBuff中
//********************** 第三步使用sendto函数进行通信 *************************//
sendto(sockListener,/*sendBuff*/pPack, lstrlen(sendBuff)/*sizeof(udpPack)*/, 0, (SOCKADDR *)&saUdpServ, sizeof(SOCKADDR_IN));
printf("%s\n", sendBuff); //将要广播的数据串输出
}
//********************* 第四步关闭socket ***************************************//
closesocket(sockListener); //关闭监听socket
WSACleanup();
return 0;
}
const static int BUFFER_LENGTH = 12;