目录
一、IP地址和端口号
1、源IP地址和目的IP地址
IP协议是工作在网络层的协议,负责地址管理和路由选择的。当数据传输到网络层时,会封装IP首部形成IP数据报。IP数据报首部通常包含两个地址:源IP地址和目的IP地址。
- IP地址:用来标识网络中唯一一台主机的地址,(公网IP)具有全网唯一性,能够通过IP地址找到唯一一台主机。
- 源IP地址:标识消息发送放主机的IP地址。
- 目的IP地址:消息接收方的主机地址。
2、端口号
1)什么是端口号?
数据通过网络传输不仅要知道发送给那个主机还要直到发送到该主机上的具体某一个进程,也就是说真正通信的实体是在主机中的进程,是这台主机中的一个进程和另一台主机中的一个进程进行通信。因此,严格来说两台主机进行通信就是两台主机之间的应用进程进行通信。IP地址可以帮我们找到具体的某一台主机,而端口号就是标识唯一一个进程的,它可以帮我们找到主机上的具体一个进程,它属于传输层协议的内容。
- 端口号用来标识一个进程,告诉操作系统当前数据要交给那个进程来处理。
- 端口号是一个2个字节(16bit)的整数。
- 通过IP地址+端口号就可以找到具体的某一台主机上的具体一个进程。IP+端口号就称为套接字(socket)
- 一个端口号只能被一个进程占用(具有唯一性,唯一标识一个进程)。
2)源端口号和目的端口号
- 源端口号:消息发送方的端口号(客户端)
- 目的端口号:消息接收方的端口号(服务器端)
3)端口号和进程ID的关系
- 联系:都是用来唯一标识一个进程的
- 区别:每个进程都由进程ID,但是只有网络中的进程才会有端口号。
3、示例理解IP地址、进程ID、端口号
示例1:10086
当我们拨打10086人工客服时,我们要找的实际上是某一个客服,但是我们拨打的是10086。这里的10086就相当于IP地址,直到IP地址我们还需要具体某一个客服给我们服务(端口号)。
示例2:字节跳动
假如说字节跳动的每一个员工都会有一个员工编号,同时每一个员工肯定也有身份证号。每个人都会有身份证号(进程ID),但是只有字节跳动的员工才会有字节跳动员工编号(端口号)。
二、认识TCP、UDP
TCP和UDP两个协议都是工作在传输层的重要协议,传输层协议主要工作就是负责数据的传输,其中包括错误检测等。
1、TCP
- 有连接:发送数据时需要建立连接
- 可靠传输:它会进行各种检测,例如数据传输过程中数据出现了错误等
- 面向字节流:将应用程序交付下来的数据块看成一连串的无结构的字节流进行传输,TCP并不知道传输的字节流的含义。
2、UDP
- 无连接的:发送数据时不需要建立连接
- 不可靠传输:简单理解,只负责传输不管传输过程中是否会出现问题。
- 面向数据报:发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付IP层。
三、网络字节序
1、什么是网络字节序?
现实中,我们的电脑有大端机和小端机之分。大端机是指,多字节的数据在内存中按照“高位存储子在低地址,低位存储在高地址”的方式存储的;小端机是指,多字节数据在内存中按照“高位存储在高地址,低位存储在低地址中”。网络中的数据流也存在大小端之分,那么如何定义网络中数据流地址呢(小端机传输的数据和大端机传输过来的数据不一样,如何知道传输过来的数据是小端存储还是大端存储的)?
- 发送端主机通常时按照从低地址到高地址的顺序发送的。
- 接收端主机按照从低地址到高地址的顺序将接收到的数据保存在缓冲区中。
- 在TCP/IP协议中,规定网络中的数据都是大端的(发送端先发送的是高字节的数据)。
- 如果发送端主机是小端机,则需要将数据转换成大端在发送到网络中。否则,直接发送。
2、网络字节序和主机字节序转换相关接口
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);//将主机字节序转换成long(一般为4个字节)类型的网络字节序
uint16_t htons(uint16_t hostlong);//将主机字节序转换成short(一般为2个字节)类型的网络字节序
uint32_t ntohl(uint32_t netlong);//将网络字节序转换成long类型的主机字节序
uint16_t ntohs(uint16_t netlong);//将网络字节序转换成short类型的主机字节序
四、socket编程
1、socket常见接口
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
参数:
domain:使用的协议家族中的某一种协议(AF_INET表示IPV4)
type:套接字类型(SOCK_STREAM表示面向字节流,例如TCP;SOCK_DGRAM表示面向报文段,例如UDP)
protocol:该协议指定要与套接字一起使用的特定协议(一般情况下为0)
返回值:文件描述符,创建的socket所在的文件描述符
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数:
sockfd:套接字文件描述符
addr:协议地址
addrlen:协议地址长度
返回值:成功返回0,失败返回-1
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
2、sockaddr结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议(IPV4、IPV6等),然而各种网络协议的地址格式并不相同。
- IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址.
- IPv4、 IPv6地址类型分别定义为常数AF_INET、 AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.
- socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数;
1)sockaddr结构
2)sockaddr_in结构
虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址
3)in_addr结构
in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数;
3、实现简单的UDP网络
1)分析
在任何一个通信网络中一定存在至少一个服务器端和一个客户端,服务器端用来接收客户端发送的请求并做出响应。
- 该网络协议的功能:客户端向服务器端发送一个数据,服务器端接收到收据后进行打印并对数据进行封装返回给客户端,客户端打印经过服务器端处理后的数据。
- 两种角色:客户端、服务器端
- 客户端需求分析
客户端要做的任务是接收用户输入的数据,发送给服务器端同时接收服务器端处理过的数据进行打印。具体如下:
- 创建套接字:创建客户端套接字文件描述符
- 接收用户输入的数据
- 将数据转换为大端:客户端主机存在大端机和小端机之分,网络字节序规定都是大端的,因此需要将要发送给服务器的数据转为大端。
- 将数据发送给服务器端
- 接收服务器端处理后的数据并打印
- 服务器端需求分析
服务器端要做的是接收客户端发送的数据并显示,在对数据进行规定的格式的封装在发送给客户端。
- 创建套接字:创建客户端套接字文件描述符
- 绑定端口号
- 转换网络字节序
- 接收客户端的消息
- 转发客户端的消息给客户端:规定转发格式为:将接收到的消息中的字符小写转大写
2)相关接口
- 创建套接字:int socket(int domain, int type, int protocol);
- 绑定端口号:int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
- 接收消息:ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
- 发送消息:size_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
3)代码实现
#udpServer.hpp
-
#pragma once
-
-
#include<iostream>
-
#include<string>
-
#include<unistd.h>
-
#include<sys/types.h>
-
#include<sys/socket.h>
-
#include<arpa/inet.h>
-
#include<string.h>
-
#define MAX_SIZE 128
-
-
class Server
-
{
-
private:
-
std::
string ip;
//服务器端IP地址
-
int port;
//端口号
-
int sock;
//套接字
-
public:
-
Server(
std::
string _ip =
"127.0.0.1",
int _port =
8080)
-
:ip(_ip),port(_port)
-
{}
-
-
//初始化
-
void init()
-
{
-
//创建套接字文件描述符
-
sock = socket(AF_INET,SOCK_DGRAM,
0);
-
//绑定
-
struct sockaddr_in local;
//服务器端网络协议地址
-
local.sin_family = AF_INET;
-
local.sin_port = htons(port);
//将端口号转为网络字节序格式
-
local.sin_addr.s_addr = inet_addr(ip.c_str());
//将ip地址从字符串转换为指定格式
-
-
if(bind(sock, (struct sockaddr*)&local,
sizeof(local)) ==
-1)
-
{
-
//绑定失败
-
std::
cerr<<
"服务器出错"<<
std::
endl;
-
exit(
-1);
-
}
-
}
-
-
void start()
-
{
-
//接收客户端发送的数据
-
char msg[MAX_SIZE];
-
while(
1)
-
{
-
struct sockaddr_in addr_end;
//对端地址(发送端)
-
socklen_t len =
sizeof(addr_end);
-
//接收客户端发送的数据
-
ssize_t size = recvfrom(sock, msg,
sizeof(msg)
-1,
0,(struct sockaddr*)&addr_end, &len) ;
-
if(size >
0)
-
{
-
//打印接收到的消息
-
std::
cout<<
"client# "<<msg<<
std::
endl;
-
//对数据进行封装,在发送给客户端
-
//对数据进行处理
-
std::
string tmp = msg;
-
for(
int i =
0;i < tmp.size();i++)
-
{
-
if(tmp[i] >=
'a' && tmp[i] <=
'z')
-
tmp[i] -=
32;
-
}
-
sendto(sock,tmp.c_str(),tmp.size(),
0,(struct sockaddr*)&addr_end,len);
-
}
-
else
-
{
-
std::
cout<<
"没有消息"<<
std::
endl;
-
}
-
}
-
}
-
-
~Server()
-
{
-
close(sock);
-
}
-
};
-
#udpClient.hpp
-
#pragma once
-
-
#include<iostream>
-
#include<string>
-
#include<unistd.h>
-
#include<sys/types.h>
-
#include<sys/socket.h>
-
#include<arpa/inet.h>
-
#include<string.h>
-
#define MAX_SIZE 128
-
-
class Client
-
{
-
private:
-
std::
string ip;
//服务器端IP地址
-
int port;
//端口号
-
int sock;
//套接字
-
public:
-
Client(
std::
string _ip =
"127.0.0.1",
int _port =
8080)
-
:ip(_ip),port(_port)
-
{}
-
-
//初始化
-
void init()
-
{
-
//创建套接字文件描述符
-
sock = socket(AF_INET,SOCK_DGRAM,
0);
-
}
-
-
void start()
-
{
-
std::
string msg;
-
struct sockaddr_in addr_end;
//对端地址(发送端)
-
socklen_t len =
sizeof(addr_end);
-
addr_end.sin_family = AF_INET;
-
addr_end.sin_port = htons(port);
-
addr_end.sin_addr.s_addr = inet_addr(ip.c_str());
-
while(
1)
-
{
-
std::
cout<<
"client # ";
-
std::
cin>>msg;
-
//向服务器发送数据
-
sendto(sock,msg.c_str(),msg.size(),
0,(struct sockaddr*)&addr_end,len);
-
//接收服务器发送的数据并打印
-
char tmp [MAX_SIZE];
-
ssize_t size = recvfrom(sock, tmp,
sizeof(tmp)
-1,
0,
nullptr,
nullptr) ;
-
if(size >
0)
-
std::
cout<<
"Server #"<<msg<<
std::
endl;
-
}
-
}
-
-
~Client()
-
{
-
close(sock);
-
}
-
};
#udpServer.cc
-
#include"udpServer.hpp"
-
-
int main()
-
{
-
Server* s =
new Server();
-
s->init();
-
s->start();
-
delete s;
-
return
0;
-
}
#udpClient.cc
-
#include"udpClient.hpp"
-
-
int main()
-
{
-
Client* c =
new Client();
-
c->init();
-
c->start();
-
delete c;
-
return
0;
-
}
注意:服务器是一直在网络中找属于自己的数据的,它是被动运行的,当没有客户端发送数据时他就处于阻塞状态。因此,需要将服务器和网络进行绑定,客户端可以随时在网络中找到。
转载:https://blog.csdn.net/qq_47406941/article/details/116673665