目录
一、IP地址和端口号
1、源IP地址和目的IP地址
2、端口号
1)什么是端口号?
2)源端口号和目的端口号
3)端口号和进程ID的关系
3、示例理解IP地址、进程ID、端口号
二、认识TCP、UDP
1、TCP
2、UDP
三、网络字节序
1、什么是网络字节序?
2、网络字节序和主机字节序转换相关接口
四、socket编程
1、socket常见接口
2、sockaddr结构
1)sockaddr结构
2)sockaddr_in结构
3)in_addr结构
3、实现简单的UDP网络
1)分析
2)相关接口
3)代码实现
IP协议是工作在网络层的协议,负责地址管理和路由选择的。当数据传输到网络层时,会封装IP首部形成IP数据报。IP数据报首部通常包含两个地址:源IP地址和目的IP地址。
数据通过网络传输不仅要知道发送给那个主机还要直到发送到该主机上的具体某一个进程,也就是说真正通信的实体是在主机中的进程,是这台主机中的一个进程和另一台主机中的一个进程进行通信。因此,严格来说两台主机进行通信就是两台主机之间的应用进程进行通信。IP地址可以帮我们找到具体的某一台主机,而端口号就是标识唯一一个进程的,它可以帮我们找到主机上的具体一个进程,它属于传输层协议的内容。
示例1:10086
当我们拨打10086人工客服时,我们要找的实际上是某一个客服,但是我们拨打的是10086。这里的10086就相当于IP地址,直到IP地址我们还需要具体某一个客服给我们服务(端口号)。
示例2:字节跳动
假如说字节跳动的每一个员工都会有一个员工编号,同时每一个员工肯定也有身份证号。每个人都会有身份证号(进程ID),但是只有字节跳动的员工才会有字节跳动员工编号(端口号)。
TCP和UDP两个协议都是工作在传输层的重要协议,传输层协议主要工作就是负责数据的传输,其中包括错误检测等。
现实中,我们的电脑有大端机和小端机之分。大端机是指,多字节的数据在内存中按照“高位存储子在低地址,低位存储在高地址”的方式存储的;小端机是指,多字节数据在内存中按照“高位存储在高地址,低位存储在低地址中”。网络中的数据流也存在大小端之分,那么如何定义网络中数据流地址呢(小端机传输的数据和大端机传输过来的数据不一样,如何知道传输过来的数据是小端存储还是大端存储的)?
#include
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 文件描述符 (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);
socket API是一层抽象的网络编程接口,适用于各种底层网络协议(IPV4、IPV6等),然而各种网络协议的地址格式并不相同。
虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址
in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数;
在任何一个通信网络中一定存在至少一个服务器端和一个客户端,服务器端用来接收客户端发送的请求并做出响应。
客户端要做的任务是接收用户输入的数据,发送给服务器端同时接收服务器端处理过的数据进行打印。具体如下:
服务器端要做的是接收客户端发送的数据并显示,在对数据进行规定的格式的封装在发送给客户端。
#udpServer.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#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<<"服务器出错"< 0)
{
//打印接收到的消息
std::cout<<"client# "<= 'a' && tmp[i] <= 'z')
tmp[i] -= 32;
}
sendto(sock,tmp.c_str(),tmp.size(), 0,(struct sockaddr*)&addr_end,len);
}
else
{
std::cout<<"没有消息"<
#udpClient.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#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 #"<
#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;
}
注意:服务器是一直在网络中找属于自己的数据的,它是被动运行的,当没有客户端发送数据时他就处于阻塞状态。
为什么服务器端需要将端口号绑定到网络而客户端不需要?
其实绑定端口,就是编程的人员预先给服务器设置一个确定的端口,而不是由系统随机分配一个端口。服务器绑定了某一个端口,客户端才能通过该端口向服务器发起连接请求·。如果不绑定端口,而是由系统随机分配给服务器一个端口,既然是随机的,那么客户端也不知道系统分配的是哪个端口。客户端不知道服务器的端口,还怎么与服务器通信呢?客户端就不需要绑定端口了,系统随机分配一个就可以了。当客户端第一次向服务器发送数据时,客户端的端口号随同数据一起就给了服务器。服务器有了客户端的端口自然就可以向客户端发送数据。当然除了绑定端口外还要绑定IP地址。