文章目录
一、基本概念
接上一篇网络基础(一),我们继续了解网络中的一些基本概念。
1.1 端口(port)
1、端口与传输层息息相关,端口是传输层协议的内容;
2、端口是一个2字节16位的整数,范围是0~65535;
3、端口用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
4、IP地址 + 端口能够标识网络上的某一台主机的某一个进程;
5、一个端口只能被一个进程占用,一个进程可以占用多个端口。
1.2 五元组
所谓的五元组,就是包括源IP地址、目的IP地址、源端口、目的端口、协议。网络传输当中的数据里,一定是存在五元组信息的,如果缺少五元组信息,则一定不能传输。
示意图:
1.3 网络字节序
字节序:CPU对内存数据的读取顺序。
大小端字节序:
大端:低位保存在高地址;小端:低位保存在低地址
如何判断当前机器是大端机器还是小端机器呢?
参考之前的文章:判断大小端机器
网络字节序:不管机器是大端机器还是小端机器,在传输的时候都是按照网络字节序进行传输,网络字节序本质上就是大端字节序。
主机字节序:机器本身的字节序(大/小端)
网络传输当中,传输数据时,将主机字节序转换为网络字节序;接收数据时,将网络字节序转换为主机字节序。
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换:
uint32_t htonl(uint32_t hostlong);
//将主机字节序转化成为网络字节序,转化4字节
uint32_t ntohl(uint32_t netlong);
//将网络字节序转化成为主机字节序,转化4字节
uint16_t htons(uint18_t hostshort);
//将主机字节序转化成为网络字节序,转化2字节
uint16_t ntohs(uint16_t netshort);
//将网络字节序转化成为主机字节序,转化2字节
二、socket 编程接口
2.1 socket 常见API
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
domain:地址域,网络层使用什么协议。其中包含AF_INET:ipv4版本的ip协议;AF_INET6:ipv6版本的协议;AF_UNIX:域套接字
type:套接字的类型。其中包括SOCK_DGRAM:用户数据报套接字,默认协议是UDP协议;SOCK_STREAM:流式套接字,默认的协议是TCP协议
protocol:表示协议。IPPROTO_UDP(17)、IPPROTO_TCP(6)、也可以传递0,表示使用套接字的默认协议。
返回值:套接字描述符,本质上还是一个文件描述符。
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
sockfd:套接字描述符
addr:地址信息
addrlen:传入的结构体的真实字节数量
//UDP的socket发送接口
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:要将数据发送到哪里去,对端的地址信息
addrlen:地址信息的长度
//UDP的socket接收端口
ssize_t recvfrom(int sockfd,void* buf,size_t len,int flags,struct sockaddr* src_addr,socklen_t* addrlen);
sockfd:套接字描述符
buf:接收数据准备的缓冲区
len:缓冲区接收的最大能力
flags:0表示阻塞接收
src_addr:消息发送端的地址信息,接收回来的消息是从哪里来的
addrlen:输入输出型参数,返回的就是地址信息的真实长度
//TCP发送接口
ssize_t send(int sockfd,const void* buf,size_t len,int flags);
sockfd:套接字描述符,客户端是socket函数的返回值;服务端是为客户端新创建的套接字描述符
buf:待发送的数据
len:发送数据的长度
flags:0表示阻塞发送
//TCP接收端口
ssize_t recv(int sockfd,void* buf,size_t len,int flags);
sockfd:套接字描述符,客户端是socket函数的返回值;服务端是为客户端新创建的套接字描述符
buf:缓冲区,用来接收数据
len:接收的最大能力
flags:0表示阻塞接收
返回值大于0表示接收了多少字节的数据;等于0表示对端断开了连接,意味着对端调用了close;小于0表示接收失败
// 开始监听socket (TCP, 服务器)
// 当调用监听之后,意味着告诉操作系统,当前进程可以接收新的TCP连接了。换句话说,告诉操作系统,当前进程可以开始接收客户端的三次握手请求了
int listen(int sockfd, int backlog);
sockfd:套接字描述符
backlog:已完成连接队列的大小
//获取连接(TCP,服务器)
int accept(int sockfd,struct sockaddr* addr,socklen_t* addrlen);
sockfd:套接字描述符,传递的是侦听套接字
addr:对端地址信息
addrlen:对端地址信息长度
返回值:返回为新连接创建的套接字描述符
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockfd:套接字描述符
addr:服务端的地址信息
addrlen:地址信息长度
//关闭套接字
close(int sockfd);
sockfd:套接字描述符
2.2 sockaddr结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX DomainSocket. 然而, 各种网络协议的地址格式并不相同。
1、IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址;
2、IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容;
3、socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数。
三、UDP编程
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <iostream>
int main()
{
//1.创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sockfd < 0)
{
perror("socket");
return -1;
}
while(1)
{
char buf[1024] = {
0};
memset(buf, '\0', sizeof(buf));
std::cin >> buf;
struct sockaddr_in svr_addr;
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(19999