1.ip地址 2.port 3.网络字节序 4.浅层次tcp和udp区别 5.udp协议实现网络通信 6.tcp协议实现网络通信
1.ip地址
a.作用:在网络中唯一标识一台主机
b.本质:uint32_t 无符号4个字节
c.源ip和目的ip地址
5元组 = 源ip+源端口+目的ip+目的端口+协议
ipv6:a.ipv4和ipv6制的不同版本的ip协议
b.ipv6并不兼容ipv4,两者的ip协议的报头格式不同
c.ipv6 16个字节
2.port
a.端口在一台主机中表示一个进程
b.本质:uint16_t 范围65535
c.知名端口:0-1023 mysql--3306 oracle--1521 ssh--20 http--80
d.网络通信的时候双方都需要端口
3.网络字节序
字节序:CPU对内存当中的数据进行存取的顺序
大端字节序:低地址存放高位
小端字节序:低地址存放低位
网络字节序:大端字节序进行传输;网络标准
小端字节序转换成网络字节序:
uint32_t htonl(uint32_t hostlong)
uint32_t ntohl(uint32_t netlong)
uint16_t htons(uint16_t hostshort)
uint16_t ntohs(uint16_t netshort)
#include <iostream>
#include <stdio.h>
int main()
{
union Data
{
int a;
char b;
}data;
//0x00000001
data.a = 1;
if(data.b == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
4.浅层次tcp和udp区别
TCP
面向连接:tcp的通信双方在发送数据之前,需要先建立连接,才可以发送数据
可靠传输:保证TCP数据包可靠到达对端
面向字节流:对于TCP数据可以任意存取
UDP
无连接:UDP通信双方在发送数据之前,不需要建立连接,只需要知道对方的ip和port就可以直接发送数据
不可靠:如果UDP数据在网络当中传输的时候,丢失掉了,则不会保证udp数据一定到达对端
面向数据包:UDP在发送数据的时候,是整条发送整条接收的
5.udp协议实现网络通信
UDP套接字流程接口
服务端:
1.创建套接字:将进程和网卡建立连接
2.绑定地址信息:将端口和进程联系起来;绑定IP地址+Port
3.接收数据
4.发送数据
5.关闭套接字
客户端( client):
1.创建套接字
2绑定地址信息(不推荐在客户端绑定地址信息, 1.操作系统会帮我们绑定地址信息2.如果绑定地址信息,也就是意味着固定客户端的端口了,由于端口不能同时被多个进程所占用。当前机器就只能启动一个客户端程序了; )
3.发送数据
4.接收数据
5.关闭套接字
接口:
1.创建套接字的接口
int socket(int domain, int type, int protocol)
domain:地址域,传入协议的版本
网络层: AF_ _INT-->ipv4版本的ip协议
AF_ INET6-->ipv6版本的ip协议
type:套接字的类型
传输层: tcp/udp ;
tcp : SOCK STREAM:流式套接字;默认的协议就是tcp ,不支持udp的
udp: SOCK DGRAM:数据报套接字;默认读的协议就是udp ,不支持tcp的protocol:协议
0 :采用套接字默认协议
tcp: IPPROTO _TCP(6)
udp:IPPROTO_ UDP(17)
返回值:
返回套接字的操作句柄,其实就是-一个文件描述符; - -般称之为套接字描述符
2.绑定地址信息
int bind(int sockfd, const struct sockaddr* addr, socklen. _t addrlen)
sockfd:套接字操作句柄。
addr:
struct sockaddr
{
sa_ family. _t sa. _family;//用来填充地址域的(AF_ INET),占用两个字节的;
char sa_ data[14]; 填充地址信息; IP+ PORT
}
为了很好的兼容不同协议的地址信息;
ipv4 : struct sockaddr. _in
addrlen:地址信息长度
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:发送的数据长度
falgs: .
0:阻塞发送
dest_ _addr:目标主机地址信息
addrlen:地址信息长度
4.接收接口
ssize_ t recvfrom(int sockfd, void* buf, size. _t len, int flags, struct sockaddr* src_ addr, socklen_ t* addrlen);
sockfd:套接字描述符
buf:从接收缓冲区当中拿到的数据放到哪一个buffer当中
len:buffer的最大长度,意味着最大可以接收多少数据,预留“\0” 的位置
flags:
0 :阻塞接收
src_ addr:源主机的地址信息(标识这条数据从哪一个主机上面哪一个进程来 )
addrlen:地址信息长度,同时是一个输入输出型参数;
5.关闭套接字的接口
close(int sockfd);
//udpsvr.hpp
#pragma once
#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class UdpSvr
{
public:
UdpSvr()
{
sock_ = -1;
}
~UdpSvr()
{
}
//1.Socket
bool CreateSocket()
{
sock_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sock_ < 0)
{
perror("socket");
return false;
}
return true;
}
//2.Bind
bool Bind(std::string& ip, uint16_t port)
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
//inet_addr:1.将点分十进制的ip转换成uint32 2.将主机字节序转换为网络字节序
addr.sin_addr.s_addr = inet_addr(ip.c_str());
int ret = bind(sock_, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind");
return false;
}
return true;
}
//3.Send
bool Send(std::string& data, struct sockaddr_in* destaddr)
{
int sendsize = sendto(sock_, data.c_str(), data.size(), 0, (struct sockaddr*)destaddr, sizeof(struct sockaddr_in));
if(sendsize < 0)
{
perror("sendto");
return false;
}
return true;
}
//4.Recv
//buf : 出参, 返回给调用者接收到了什么数据
//srcaddr : 这条数据从哪里来的,地址信息是数据源端主机的地址信息
bool Recv(std::string* buf, struct sockaddr_in* srcaddr)
{
char tmp[1024];
memset(tmp, '\0', sizeof(tmp));
socklen_t socklen = sizeof(struct sockaddr_in);
//如果接收缓冲区当中没有数据,且flags为0,则recvfrom会阻塞等待
int recvsize = recvfrom(sock_, tmp, sizeof(tmp) - 1, 0, (struct sockaddr*)srcaddr, &socklen);
if(recvsize < 0)
{
perror("recvfrom");
return false;
}
(*buf).assign(tmp, recvsize);
return true;
}
//5.Close
void Close()
{
close(sock_);
sock_ = -1;
}
private:
int sock_;
};
//udpsvr.cpp
#include "udpsvr.hpp"
int main(int argc, char* argv[])
{
// ./svr [ip] [port]
if(argc != 3)
{
printf("Use this process, please \"./svr [ip] [port]\"");
return 0;
}
std::string ip = argv[1];
uint16_t port = atoi(argv[2]);
UdpSvr us;
if(!us.CreateSocket())
{
return 0;
}
if(!us.Bind(ip, port))
{
return 0;
}
while(1)
{
//接收数据
std::string buf;
//peeraddr 对端地址信息
struct sockaddr_in peeraddr;
us.Recv(&buf, &peeraddr);
printf("cli say: %s\n", buf.c_str());
//发送数据
printf("server say: ");
fflush(stdout);
std::cin >> buf;
us.Send(buf, &peeraddr);
}
us.Close();
return 0;
}
//udpcli.cpp
#include "udpsvr.hpp"
//对于客户端程序而言,命令行参数当中的ip和port需要指定为服务端的ip和端口
int main(int argc, char* argv[])
{
if(argc != 3)
{
printf("./cli [svr_ip] [svr_port]\n");
return 0;
}
std::string svr_ip = argv[1];
uint16_t svr_port = atoi(argv[2]);
UdpSvr us;
if(!us.CreateSocket())
{
return 0;
}
//组织服务端的地址信息,以便sendto使用
struct sockaddr_in destaddr;
destaddr.sin_family = AF_INET;
destaddr.sin_port = htons(svr_port);
destaddr.sin_addr.s_addr = inet_addr(svr_ip.c_str());
while(1)
{
//发送数据
printf("Client Say: ");
fflush(stdout);
std::string buf;
std::cin >> buf;
us.Send(buf, &destaddr);
//接收数据
struct sockaddr_in peeraddr;
us.Recv(&buf, &peeraddr);
printf("svr say: %s\n", buf.c_str());
}
us.Close();
return 0;
}
6.tcp协议实现网络通信
TCP编程接口:
1.创建套接字
2.bind
3.监听
int listen(int sockfd, int backlog)
sockfd:套接字描述符
baclog:已完成连接的队列大小
backlog可以指定已完成连接的队列大小,如果当已完成连接队列被打满的,处理方式就是丢弃新来的连接;使用接收新连接,从已完成连接队列当中获取创建完成的新连接;
会创建新的socket和客户端通信:如果只有一个socket无法区分那个是客户端发送的数据,如果有一个socket,则无法接收一个TCP连接之后,通信起来,在接受新的连接。
接收连接
int accept(int sockfd, struct sockaddr* addr, socklen_ t* addrlen);
sockfd:侦听sockfd ;从侦听sockfd接收新的连接请求
addr:客户端地址信息
addrlen:客户端地址信息长度
返回值:
返回新创建出来的socket ,通过新创建出来的socket和客户端进行数据收发;
注意:因为该接口是从已完成连接队列当中获取新的连接,所以,当已完成连接队列当中没有已经完成的新的连接的时候, 该接口如果调用就会阻塞;直到获取一个新的已完成连接到来;
发起连接
int connect(int sockfd, const struct sockaddr *addr, socklen_ t addrlen);sockfd:套接字描述符
addr:服务端地址信息,需要自己在代码当中填充好,传递给connectaddrlen:地址信息长度
关闭套接字:
int close(int sockfd);
发送数据
ssize_ _t send(int sockfd, const void *buf, size_ t len, int flags);
sockfd:套接字描述符,accept函数的返回值;
buf:要发送的数据
len:发送数据的长度
flags:
0 :阻塞发送
接收数据
ssize_ _t recv(int sockfd, void* buf, size_ t len, int flags);
sockfd:套接字描述符, accept函数的返回值;
buf :接收到的数据放到哪里去
len :最大接收长度
flags:
0 :阻塞发送
MSG_ PEEK :探测接收的含义:从接收缓冲区当中复制数据当应用层,但是并不删除接收缓冲区当中的数据
相比较之前的阻塞接收而言,阻塞接收也会从接收缓冲区复制数据到应用层,但是会删除接收缓冲区当中的数据
返回值:
大于0 :接受了多少字节的数据
等于0:表示对端关闭了连接,含义就是对端调用了close
小于0 :接收失败
//tcpsvr.hpp
#pragma once
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#include <string>
class TcpSvr
{
public:
TcpSvr()
{
sockfd_ = -1;
}
~TcpSvr()
{
}
//创建套接字
bool CreateSocket()
{
sockfd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sockfd_ < 0)
{
perror("socket");
return false;
}
return true;
}
//绑定地址信息
bool Bind(std::string& ip, uint16_t port)
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
int ret = bind(sockfd_, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind");
return false;
}
return true;
}
//侦听
bool Listen(int backlog = 5)
{
int ret = listen(sockfd_, backlog);
if(ret < 0)
{
perror("listen");
return false;
}
return true;
}
//获取连接
//bool Accept(struct sockaddr_in* peeraddr, int* sockfd)
//peeraddr:出参,返回客户端的地址信息
//ts:出参,返回一个TcpSvr类的实例化指针,在这个类的实例化指针当中保存新创建出来的套接字描述符,上层调用者可以使用返回的类的实例化指针和客户端进行通信
bool Accept(struct sockaddr_in* peeraddr, TcpSvr* ts)
{
socklen_t addrlen = sizeof(struct sockaddr_in);
int serverfd = accept(sockfd_, (struct sockaddr*)peeraddr, &addrlen);
if(serverfd < 0)
{
perror("accept");
return false;
}
ts->sockfd_ = serverfd;
return true;
}
//发起连接(client)
bool Connect(std::string& ip, uint16_t port)
{
struct sockaddr_in destaddr;
destaddr.sin_family = AF_INET;
destaddr.sin_port = htons(port);
destaddr.sin_addr.s_addr = inet_addr(ip.c_str());
int ret = connect(sockfd_, (struct sockaddr*)&destaddr, sizeof(destaddr));
if(ret < 0)
{
perror("connect");
return false;
}
return true;
}
//发送数据
bool Send(std::string& data)
{
int sendsize = send(sockfd_, data.c_str(), data.size(), 0);
if(sendsize < 0)
{
perror("send");
return false;
}
return true;
}
//接收数据
//data:是一个出参,将接收到的数据返回给调用者
bool Recv(std::string* data)
{
char buf[1024] = {0};
int recvsize = recv(sockfd_, buf, sizeof(buf) - 1, 0);
if(recvsize < 0)
{
perror("recv");
return false;
}
else if(recvsize == 0)
{
printf("peer shutdown connect\n");
return false;
}
(*data).assign(buf, recvsize);
return true;
}
//关闭套接字
void Close()
{
close(sockfd_);
sockfd_ = -1;
}
private:
int sockfd_;
};
//svr.cpp
#include "tcpsvr.hpp"
int main(int argc, char* argv[])
{
if(argc != 3)
{
printf("Start Server: ./svr [ip] [port]\n");
return 0;
}
std::string ip = argv[1];
uint16_t port = atoi(argv[2]);
TcpSvr ts;
if(!ts.CreateSocket())
{
return 0;
}
if(!ts.Bind(ip, port))
{
return 0;
}
if(!ts.Listen())
{
return 0;
}
TcpSvr ser_ts;
while(1)
{
struct sockaddr_in clientaddr;
if(!ts.Accept(&clientaddr, &ser_ts))
{
return 0;
}
std::string buf;
ser_ts.Recv(&buf);
printf("client say: %s\n", buf.c_str());
printf("please return to client msg: ");
fflush(stdout);
std::cin >> buf;
ser_ts.Send(buf);
}
ser_ts.Close();//关闭新创建出来的套接字
ts.Close();//关闭掉了侦听套接字
return 0;
}
//cli.cpp
#include "tcpsvr.hpp"
//从命令行当中获取连接服务端的ip和port
int main(int argc, char* argv[])
{
if(argc != 3)
{
printf("Start Client: ./cli [server ip] [server port]\n");
return 0;
}
std::string ip = argv[1];
uint16_t port = atoi(argv[2]);
TcpSvr ts;
if(!ts.CreateSocket())
{
return 0;
}
if(!ts.Connect(ip, port))
{
return 0;
}
while(1)
{
printf("please enter msg to server: ");
fflush(stdout);
std::string buf;
std::cin >> buf;
ts.Send(buf);
//服务端没有返回数据,意味着客户端TCPsocket套接字当中用的接收缓冲区当中并没有数据,没有数据可以接收,recv的flags为0的情况下,则recv接口阻塞等待
ts.Recv(&buf);
printf("Server say: %s\n", buf.c_str());
}
ts.Close();
return 0;
}