1. 网络传输方式
(1) UDP传输方式
UDP (User Datagram Protocol )用户数据报协议,是一种面向无连接的传输方式,不提供复杂的控制机制, 如果传输过程中出现丢包, UDP 也不负责重发. 甚至当出现包到达顺序乱掉时候也没有纠正的功能. UDP最大的特点就是可靠传输:
1) 发送数据不需要建立连接(面向无连接)
2) 不能保证数据的有效性和准确性
3) 只能发送少量数据,每个数据包最大64K
4) 适合做广播,主要用于广播和视频直播等
5) 比TCP节省资源
(2) TCP传输方式
TCP (Transmission Control Protocol)传输控制协议,是一种面向有连接的通信服务, 只有在确认通信对端存在时才会收发数据, 从而可以控制通信流量的浪费. TCP最大的特点就是可靠传输:
1)发送数据需要建立连接(面向有连接)
2)可以发送大量数据
3)可以保证数据的有效性和准确性
4)不适合做广播,传输速度没有UDP快
5)使用资源较多
2. UDP通信过程
(1)套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议根进行交互的接口 。
一个socket由一个IP地址和一个端口号确定。
UDP套接字不会保持连接状态,每次传输数据都要添加目标地址信息,这相当于在邮寄包裹前填写收件人地址。
创建套接字的函数是socket(),函数原型为:
int socket(int domain, int type, int protocol);
其中 “int domain”参数表示套接字要使用的协议簇,常用的协议簇:
- AF_UNIX(本机通信)
- AF_INET(TCP/IP – IPv4)
- AF_INET6(TCP/IP – IPv6)
其中 “type”参数指的是套接字类型,常用的类型有:
- SOCK_STREAM(TCP流)
- SOCK_DGRAM(UDP数据报)
- SOCK_RAW(原始套接字)
最后一个 “protocol”一般设置为“0”
socket是一个函数,那么它也有返回值,当套接字创建成功时,返回套接字,失败返回“-1”,错误代码则写入“errno”中。
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/socket.h>
int sock_fd_tcp;
int sock_fd_udp;
sock_fd_tcp = socket(AF_INET, SOCK_STREAM, 0);
sock_fd_udp = socket(AF_INET, SOCK_DGRAM, 0);
if(sock_fd_tcp < 0){
perror("TCP SOCKET ERROR!\n");
exit(-1);
}
if(sock_fd_udp < 0){
perror("UDP SOCKET ERROR!\n");
exit(-1);
}
套接字地址这个数据结构里面包含了:地址类型、端口号、IP地址、填充字节这4种数据。而它的数据结构原型为:
struct sockaddr_in{
unsigned short sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
其中:
- sin_family表示地址类型,对于基于TCP/IP传输协议的通信,该值只能是AF_INET;
- sin_prot表示端口号,例如:21 或者 80 或者 27015,总之在0 ~ 65535之间;
- sin_addr表示32位的IP地址,例如:192.168.1.5 或 202.96.134.133;
- sin_zero表示填充字节,一般情况下该值为0;
Socket数据的赋值实例:
struct sockaddr_in Lewis;
Lewis.sin_family = AF_INET;
Lewis.sin_port = htons(80);
Lewis.sin_addr.s_addr = inet_addr("202.96.134.133");
memset(Lewis.sin_zero,0,sizeof(Lewis.sin_zero));
(2) UDP通信过程
服务器端的步骤如下:
(1) socket: 建立一个socket
(2) bind: 将这个socket绑定在某个端口上(AF_INET)
(3) recvfrom: 如果没有客户端发起请求,则会阻塞在这个函数里
(4) close: 通信完成后关闭socket
客户端的步骤如下:
(1) socket: 建立一个socket
(2) sendto: 向服务器的某个端口发起请求(AF_INET)
(3) close: 通信完成后关闭socket
基于UDP的接收和发送函数
recvfrom用于接收数据,sendto用于发送数据
int recvfrom(int sockfd, void * buf, size_t len, int flags, struct sockaddr * src_addr, socklen_t * addrlen);
recvfrom:
sockfd:用于接收UDP数据的套接字;
buf:保存接收数据的缓冲区地址;
len:可接收的最大字节数(不能超过buf缓冲区的大小);
flags:可选项参数,若没有可传递0;
src_addr:存有发送端地址信息的sockaddr结构体变量的地址;
addrlen:保存参数 src_addr的结构体变量长度的变量地址值。
int sendto(int sockfd, const void * buf, size_t len, int flags, const struct sockaddr * dest_addr, socklen_t addrlen);
sendto:
sockfd:用于传输UDP数据的套接字;
buf:保存待传输数据的缓冲区地址;
len:带传输数据的长度(以字节计);
flags:可选项参数,若没有可传递0;
dest_addr:存有目标地址信息的 sockaddr 结构体变量的地址;
addrlen:传递给参数 dest_addr的地址值结构体变量的长度。
3. UDP通信实例
(1) 在两台PC上分别建立client和server文件, 在client中设置server所在PC的IP, 保证两台PC在同一局域网中.
server.cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#define SERV_PORT 8000
int main()
{
/* sock_fd --- socket文件描述符 创建udp套接字*/
int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(sock_fd < 0)
{
perror("socket");
exit(1);
}
/* 将套接字和IP、端口绑定 */
struct sockaddr_in addr_serv;
int len;
memset(&addr_serv, 0, sizeof(struct sockaddr_in)); //每个字节都用0填充
addr_serv.sin_family = AF_INET;
addr_serv.sin_addr.s_addr = htonl(INADDR_ANY);
addr_serv.sin_port = htons(SERV_PORT);
//addr_serv.sin_family = AF_INET; //使用IPV4地址
//addr_serv.sin_addr.s_addr = htonl(INADDR_ANY); //自动获取IP地址
//addr_serv.sin_port = htons(SERV_PORT); //端口
/* INADDR_ANY表示不管是哪个网卡接收到数据,只要目的端口是SERV_PORT,就会被该应用程序接收到 */
len = sizeof(addr_serv);
/* 绑定socket */
if(bind(sock_fd, (struct sockaddr *)&addr_serv, sizeof(addr_serv)) < 0)
{
perror("bind error:");
exit(1);
}
int recv_num;
int send_num;
char send_buf[20] = "i am server!";
char recv_buf[20];
struct sockaddr_in addr_client;
while(1)
{
printf("server wait:\n");
recv_num = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&addr_client, (socklen_t *)&len);
if(recv_num < 0)
{
perror("recvfrom error:");
exit(1);
}
recv_buf[recv_num] = '\0';
printf("server receive %d bytes: %s\n", recv_num, recv_buf);
send_num = sendto(sock_fd, send_buf, recv_num, 0, (struct sockaddr *)&addr_client, len);
if(send_num < 0)
{
perror("sendto error:");
exit(1);
}
}
close(sock_fd);
return 0;
}
client.cpp
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define DEST_PORT 8000 //端口号
#define DSET_IP_ADDRESS "192.168.0.172 " // server文件所在PC的IP
int main()
{
/* socket文件描述符 */
int sock_fd;
/* 建立udp socket */
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(sock_fd < 0)
{
perror("socket");
exit(1);
}
/* 设置address */
struct sockaddr_in addr_serv;
int len;
memset(&addr_serv, 0, sizeof(addr_serv));
addr_serv.sin_family = AF_INET;
addr_serv.sin_addr.s_addr = inet_addr(DSET_IP_ADDRESS);
addr_serv.sin_port = htons(DEST_PORT);
len = sizeof(addr_serv);
int send_num;
int recv_num;
char send_buf[20] = "hey, who are you?";
char recv_buf[20];
printf("client send: %s\n", send_buf);
send_num = sendto(sock_fd, send_buf, strlen(send_buf), 0, (struct sockaddr *)&addr_serv, len);
if(send_num < 0)
{
perror("sendto error:");
exit(1);
}
recv_num = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&addr_serv, (socklen_t *)&len);
if(recv_num < 0)
{
perror("recvfrom error:");
exit(1);
}
recv_buf[recv_num] = '\0';
printf("client receive %d bytes: %s\n", recv_num, recv_buf);
close(sock_fd);
return 0;
}
实验结果:
client向服务器发送了“hey, who are you?”的字符串,server返回"i am server!"的字符串。
(2) 在同一PC传递数组
server2.cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#define SERV_PORT 8000
int main()
{
/* sock_fd --- socket文件描述符 创建udp套接字*/
int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(sock_fd < 0)
{
perror("socket");
exit(1);
}
/* 将套接字和IP、端口绑定 */
struct sockaddr_in addr_serv;
int len;
memset(&addr_serv, 0, sizeof(struct sockaddr_in)); //每个字节都用0填充
addr_serv.sin_family = AF_INET;
addr_serv.sin_addr.s_addr = htonl(INADDR_ANY);
addr_serv.sin_port = htons(SERV_PORT);
//addr_serv.sin_family = AF_INET; //使用IPV4地址
//addr_serv.sin_addr.s_addr = htonl(INADDR_ANY); //自动获取IP地址
//addr_serv.sin_port = htons(SERV_PORT); //端口
/* INADDR_ANY表示不管是哪个网卡接收到数据,只要目的端口是SERV_PORT,就会被该应用程序接收到 */
len = sizeof(addr_serv);
/* 绑定socket */
if(bind(sock_fd, (struct sockaddr *)&addr_serv, sizeof(addr_serv)) < 0)
{
perror("bind error:");
exit(1);
}
int recv_num;
int send_num;
int send_buf[20];
int recv_buf[20];
struct sockaddr_in addr_client;
send_buf[0]=00;
while(1)
{
printf("server wait:\n");
recv_num = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&addr_client, (socklen_t *)&len);
if(recv_num < 0)
{
perror("recvfrom error:");
exit(1);
}
recv_buf[recv_num] = '\0';
printf("server receive %d bytes: %d%d\n", recv_num, recv_buf[0],recv_buf[2]);
send_num = sendto(sock_fd, send_buf, recv_num, 0, (struct sockaddr *)&addr_client, len);
if(send_num < 0)
{
perror("sendto error:");
exit(1);
}
}
close(sock_fd);
return 0;
}
client.cpp
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define DEST_PORT 8000
#define DSET_IP_ADDRESS "127.0.0.1 "
int main()
{
/* socket文件描述符 */
int sock_fd;
/* 建立udp socket */
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(sock_fd < 0)
{
perror("socket");
exit(1);
}
/* 设置address */
struct sockaddr_in addr_serv;
int len;
memset(&addr_serv, 0, sizeof(addr_serv));
addr_serv.sin_family = AF_INET;
addr_serv.sin_addr.s_addr = inet_addr(DSET_IP_ADDRESS);
addr_serv.sin_port = htons(DEST_PORT);
len = sizeof(addr_serv);
int send_num;
int recv_num;
int send_buf[20];
int recv_buf[20];
send_buf[0]=1;
send_buf[2]=2;
printf("client send: %d%d\n", send_buf[0],send_buf[2]);
send_num = sendto(sock_fd, send_buf, sizeof(send_buf), 0, (struct sockaddr *)&addr_serv, len);
if(send_num < 0)
{
perror("sendto error:");
exit(1);
}
recv_num = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&addr_serv, (socklen_t *)&len);
if(recv_num < 0)
{
perror("recvfrom error:");
exit(1);
}
recv_buf[recv_num] = '\0';
printf("client receive %d bytes: %d\n", recv_num, recv_buf[0]);
close(sock_fd);
return 0;
}
实验结果: