1 网络套接字
1.1 port端口号
本质:端口号是一个2字节16位的整数,范围[0,65535];
作用:端口号用来表示一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理。一个端口号只能被一个进程所占用,一个进程可以占用多个端口号(前提是在同一台机器中)。
知名端口:1~1023(轻易不要绑定)
oracle:1521;mysql:3306;
http协议:使用的是80端口;https协议:使用的是443端口。
1.2 网络数据的五元组信息(源IP、源端口、目的IP、目的端口、协议)
源IP地址:标识网络数据从哪台主机发出
源端口:标识网络数据从“源IP”对应这台主机的哪个进程产生
目的IP:标识网络数据要去往哪一台主机
目的端口:当通过目的IP,找到目的主机之后,通过目的端口找到对应进程
协议:双方传输数据的时候,使用什么协议(一般指UDP/TCP)
在网络当中传输的数据,都是包含五元信息组的!
1.3 网络字节库
小端字节库:低位放在低地址;
大端字节库:低位放在高地址。
网络字节序:规定网络传输数据的时候采用大端字节序进行传输;
主机字节序:指的是机器本身的字节序,如果是大端,则主机字节序为大端,反之则为小端。
1.4 主机字节序和网络字节序互相转换
主机字节序转化成为网络字节序
ip:uint32_t
uint32_t htonl(uint32_t hostlong); //hostlong:ip地址
port:uint16_t
uint16_t htons(uint16_t hostshort); //hostshort:端口
网络字节序转化为主机字节序
ip:uint32_t
uint32_t ntohl(uint32_t netlong); //netlong:ip地址
port:uint16_t
uint16_t ntohs(uint16_t netshort); //netshort:端口
1.5 TCP和UDP协议特性和区别
UDP:
无连接:UDP双方在发送数据之前,是不需要进行沟通的,只需要知道对方的IP和端口就好(可能对方进程还没准备好),就可以发送。
不可靠:不保证UDP的数据是可靠,有序的到达对方。
面向数据报:UDP和应用层/网络层递交数据的时候,都是整条数据进行交付的。
TCP:
面向链接:TCP双方在发送数据之前会先建立连接。(1、确保对方能正常通信;2、沟通双方发送后续数据的细节。)
可靠传输:TCP保证传输的数据时可靠的、有序的到达对端的。
面向字节流:1、对于传输的数据没有明显的边界;2、对于接收方而言,可以按照任意的字节进行接收。
2 udp-socket编程
2.1 编程流程
客户端 - 服务端
创建套接字的含义:
将进程和网卡进行绑定,进程可以从网络协议栈当中接受或者发送数据。
绑定地址信息的含义:
给进程绑定IP,绑定端口,为了在网络当中标识一台主机和一个进程。
2.2 编程接口
2.2.1 创建套接字的接口
#include<sys/socket.h>
int socket(int domain,int yype,int protocol);
//参数:
//domain:地质域--选择一个具体协议组进行沟通,对于我们而言,udp/tcp可以认为在指定网络层使用什么协议。
//AF_UNIX:本地域套接字(在同一台机器使用文件进行通信,不用跨机器)
//AF_INET:ipv4版本的ip协议
//AF_INET6:ipv6版本的ip协议
//type:套接字的类型
//SOCK_DGRAM:用户数据报套接字--对应UDP
//SOCK_STREAM:流式套接字--对应TCP
//protocol:协议
//0:标识按照套接字类型选择默认协议
//IPPROTO_TCP(6):代表TCP协议
//IPPROTO_UDP(17):代表UDP协议
//返回值:返回套接字操作句柄,本质上就是一个文件描述符
//大于等于0:创建成功
//小于0:创建失败
2.2.2 绑定接口
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
//参数:
//sockfd:套接字描述符
//addr:绑定的地址信息
//adderlen:绑定的地址信息长度
2.2.3 发送接口
ssize_t sendto(int sockfd,const void *buf,size_t len,int flags,const struct sockaddr *dest_addr,socklen_t addrlen);
//参数:
//sockfd:套接字描述符
//buf:要发送的数据
//len:发送的数据的长度
//flags:0(阻塞发送)
//dest_addr:对端地址信息结构,包含对端主机的IP,端口
//adderlen:对端地址信息结构长度
//返回值:
//真正发送的数据长度,单位字节
2.2.4 接受接口
ssize_t recvfrom(int sockfd,void *buf,size_t len,int flags,struct sockaddr *src_addr,socklen_t *addrlen);
//参数:
//sockfd:套接字描述符
//buf:接收到的数据保存到buf指向的空间当中,buf指向的空间需要提前开辟
//len:最多可以接受多少数据
//flags:0(阻塞接收)
//src_addr:接收到的数据从哪里来,对端的地址信息结构
//客户端调用,接收到的就是服务端的地址信息结构
//服务端调用,接收到的就是客户端的地址信息结构
//addrlen:绑定的地址信息长度
//返回值:
//真正接收到的数据长度,单位字节
2.2.5 关闭套接字接口
int close(int fd);
代码模拟
服务端
//这三个头文件是套接字编程需要包含的头文件
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <iostream>
int main(){
/*
* 1.创建套接字
* 2.绑定地址信息
* 3.收发数据
* 4.关闭套接字
* */
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sockfd < 0){
return 0;
}
//addr : 服务端绑定的地址信息结构, 保存着服务端需要绑定的ip地址和端口
struct sockaddr_in addr;
// 填充地址类型, 后续bind函数解析了地址类型之后, 就知道传入的是哪一个数据结构了
addr.sin_family = AF_INET;
// 填充端口, 一定要注意将主机字节序转换为网络字节序
addr.sin_port = htons(8080);
// 填充ip地址
// addr.sin_addr.s_addr : 接收的是无符号32位的整数(ip地址)
// in_addr_t inet_addr(const char *cp);
// 参数:字符指针, 接收一个点分十进制的字符串
// 功能:将点分十进制的ip地址转换为无符号32位的整数
// 将无符号32位的整数从主机字节序转换为网络字节序
// "0.0.0.0" : 绑定本机的任意网卡
// 容易犯错的地方: 绑定了云服务器的公网ip, 这个是万万不行的
// 绑定云服务器的私网ip地址, 还可以
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
int ret = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0){
return 0;
}
//能接收, 但是没有给客户端进行响应
while(1){
char buf[1024] = {0};
struct sockaddr_in peer_addr;
socklen_t peer_addrlen = sizeof(peer_addr);
recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&peer_addr, &peer_addrlen);
printf("[buf is ] %s from %s, %d\n", buf, inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port));
/*
* ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
* */
memset(buf, '\0', sizeof(buf));
printf("please enter msg: ");
fflush(stdout);
std::cin >> buf;
//sprintf(buf, "hello, %s:%d, i am server", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port));
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&peer_addr, sizeof(peer_addr));
}
close(sockfd);
return 0;
}
接收端
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
int main(){
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sockfd < 0){
perror("socket");
return 0;
}
//struct sockaddr_in b_addr;
//b_addr.sin_family = AF_INET;
//b_addr.sin_port = htons(10010);
//b_addr.sin_addr.s_addr = inet_addr("0.0.0.0");
//int ret = bind(sockfd, (struct sockaddr*)&b_addr, sizeof(b_addr));
//if(ret < 0){
// perror("bind");
// return 0;
//}
/*
* ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
*/
const char* str = "i am client...";
struct sockaddr_in addr;
addr.sin_family = AF_INET;
// 这里填充的是服务端的端口, 因为数据是发送到服务端的
addr.sin_port = htons(8080);
// 这里填充的是服务端的ip
// 本地回环地址
addr.sin_addr.s_addr = inet_addr("120.78.126.148");
//addr.sin_addr.s_addr = inet_addr("127.0.0.1");
while(1){
char buf[1024] = {0};
printf("please enter msg: ");
fflush(stdout);
std::cin >> buf;
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&addr, sizeof(addr));
/*
* ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
* */
//相当于不要recvfrom函数返回服务端的地址信息,以及地址信息的长度, 因为
//客户端是清楚服务端的地址信息的
memset(buf, '\0', sizeof(buf));
recvfrom(sockfd, buf, sizeof(buf) - 1, 0, NULL, NULL);
printf("[buf is] %s\n", buf);
sleep(1);
}
close(sockfd);
return 0;
}