准备工作
网络字节序
由于每一台主机的大小端都有可能是不一样的,我们在主机内部进行通信的时候,不会有大小端不同的问题,而在进行跨主机进行通信的时候,就会出现两台主机的字节序不同,为了统一标准,在网络中通信的数据,统一采用大端的存储方式。如果机器是大端,在发送的时候,不做任何修改,如果主机是小端的字节序,则在发送的时候,需要先将数据转化为大端,然后再发送。
为了便于我们直接通过调用一些已经写好的接口直接能完成网络字节序的装换,系统为我们提供了以下的一些接口
uint32_t htonl(uint32_t hostlong); //主机转网络
uint16_t htons(uint16_t hostshort); //主机转网络
uint32_t ntohl(uint32_t hostlong); //网络转主机
uint16_t ntohs(uint16_t hostshort); //网络转主机
以上的,就是网络字节序的转化
IP + PORT
在网络通信的过程中,IP唯一标识网络中的一台主机,而PORT,也就是端口号,唯一标识一台主机上的唯一的一个进行网络通信的进程。通过IP寻找主机,再通过端口号,寻找该主机上的一个进程,就保证了传输的准确性。
复杂的网络环境,其实可以看做是一台超级巨大的主机,只不过这台主机进行进程间通信的时候,是通过网络环境进行的。
socket的一些接口以及这些接口中数据类型的详细信息
常见的API接口
int socket(int domain,int type,int protocol);
//这个接口的作用是创建一个socket文件描述符,然后将其返回
int bind(int socket,const struct sockaddr* address,socklen_t address_len);
//这个接口的作用是 将你先前创建的socket文件描述符与你后来创建的sockaddr_in类型的变量进行绑定
//(先将端口号和ip在sockaddr_in类型的变量中填充,再进行绑定),创建好网络通信的环境。
// 该接口的作用就是,利用本地的一个socket类型的文件描述符,和一个sockaddr_in类型(里面含有ip和端口号)的变量,
//实现对文件描述符的读写操作,等同于网络间的收发信息。
UDP传输协议
UDP是什么
UDP协议是网络中两个主机进行通信的时候,所采用的的一种非连接的通信协议,核心数非连接。(非连接的意思是:接受方与发送方并不建立连接,接收方不看给他发送信息的发送者是谁,只是一味地接收,发送方也不看接收方是谁,只是一味地发送) 。面向的是数据报。不可靠的一种传输方式。
如何进行搭建
1.文件描述符创建
刚才说了,网络间通信可以看做是进程间进行通信的一种特殊形式,既然是进程间通信,就必须有一个公共区域,也就是一个文件,对这个文件进行操作的话,就必须有文件描述符。我们第一步的操作就是,创建文件描述符,并检验他的正确性。
int sockfd = socket(AF_INET,SOCK_DGRAM,0);\
// 用socket调用,创建用于网络间通信的文件描述符。
//socket函数的第一个参数是 通信协议类型 AF_INET是ipv4协议家族。
//第二个参数是通信方式,因为这里是UDP协议的通信方式,所以是SOCK_DGRAM,无连接的通信方式。
//最后一个参数是接收方通信协议地选择,我们这里用0,表示自动选择。
if(sockfd < 0)
{
exit(2);
}
2.将文件描述符进行绑定
我们进行网络间通信的最重要的是先要找到对方,而我们找到对方的方式就是通过IP+PORT进行寻找的,我们已经创建好了文件描述符,接下来就是要对文件描述符与IP+PORT进行绑定的过程
sockaddr_in 类型和sockaddr类型,在物理存储上是一样的,只是表示方式不同,我们在使用的时候,需要将sockaddr_in类型强制类型转换为sockaddr类型的。
bind函数如果成功,返回值就是0,如果不成功,就返回错误码。
string ip;
short port;
struct sockaddr_in local;
bzeor(&local,sizeof(local));
//对结构体进行清空
local.sin_family = AF_INET;
//填充协议家族
local.sin_addr.s_addr = inet_addr(ip);
//填充IP地址,需要将字符串类型转化为十进制,
//用inet_addr接口,可以直接完成转换
local.sin_port = htons(port);
//填充端口号,也是需要转换,将short类型主机转网络,
//也就是开始说的网络字节序的转换。
if( bind(sockfd, (struct sockaddr*)&local, sizeof(local) ) == 0)
{
// true
// ...
}
else
{
// false
exit(3);
}
3.开始网络间通信
网络间通信,至少有两个角色,一个是服务器,一个是客户端。既然如此,就需要服务器和客户端完成各自的任务。
服务器:服务器需要接收客户端发来的请求,并作出回应。
客户端:想服务器提出请求,并接收服务器的返回
服务器框架(这里只完成了服务器的接收操作,并没有进行服务器的发送操作,服务器的发送,和客户端的发送是一样的,调用的是同一个接口,所以不做多的解释)
char buf[1024];
struct sockaddr_in peer;
for(;;)
{
socklen_t len = sizeof(peer);
ssize_t size = recvfrom(sockfd,buf,sizeof(buf)-1,0,(struct sockaddr*)&peer,&len);
//recvfrom函数的参数,从左到右,依次是:接收的文件描述符,
//将接收到的数据放进哪个缓冲区,缓冲区最大能接受的大小(这里留了一个自己,用来存贮 '\0' ),
//用来存储发送方的网络套接字,存储发送方的网络套机字的长度。
if(size > 0)
{
buf[size] = 0;
std::string client_ip = inet_ntoa(peer.sin_addr);
// 将接收来的网络套接字的IP分离出来 ,
//将十进制装换为点分十进制的字符串
int client_port = ntohs(peer.sin_port);
// 将接收来的网络套接字的PORT分离出来,网络转主机,
//类型是short类型
std::cout << "client ip #" << client_ip << " client port # " << client_port << std::endl;
std::cout << "client msg # :" << buf << std::endl;
}
else{
std::cout << "recvfrom error" << std::endl;
exit(4);
}
}
客户端框架(客户端也是只有发送操作,接收操作参考服务器的接收操作)
std::string msg;
struct sockaddr_in server;
server.sin_addr.s_addr = inet_addr(peer_ip.c_str());
server.sin_port = htons(peer_port);
while(1)
{
socklen_t len = sizeof(server);
std::cout << "请输入信息 #" ;
std::cin >> msg;
ssize_t s = sendto(sockfd,msg.c_str(),msg.size(),0,(struct sockaddr*)&server,len);
//发送接口,参数依次是: 发送到哪个文件,发送什么,期望发送的大小,
//以什么样的方式进行发送(这里0,表示阻塞式发送),接收方的套接字,
//接收方的套接字长度
}
总结代码
以上,就是基本的接口的框架,如何将这些接口进行组合,并实现真正的网络通信环境呢?我准备了如下的一些代码,分为四个部分,两个服务器代码文件,两个客户端代码文件
这里的代码,在使用的时候,是通过命令函参数进行IP和PORT的传入的
给server的是服务器启动时,服务器的IP和PORT,用于服务器的初始化。client 启动时,给的是服务器的IP和PORT,用于客户端的通信目标。
传输方法
server.cpp
#pragma once
#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<strings.h>
#include<string>
class server
{
private:
short port;
std::string ip;
int sockfd;
public:
server(std::string _ip = "127.0.0.1",short _port = 8080) : port(_port),ip(_ip)
{
}
void InitServer()
{
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
std::cerr<< "socket error" << std::endl;
exit(2);
}
struct sockaddr_in local;
bzero(&local,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = inet_addr(ip.c_str());
if(bind(sockfd,(struct sockaddr*)&local,sizeof(local)) == 0)
{
std::cout << "run fun" << std::endl;
}
else{
std::cerr << "bind error" << std::endl;
exit(3);
}
}
void run()
{
char buf[1024] = {0};
struct sockaddr_in peer;
for(;;)
{
socklen_t len = sizeof(peer);
ssize_t size = recvfrom(sockfd,buf,sizeof(buf)-1,0,(struct sockaddr*)&peer,&len);
if(size > 0)
{
buf[size] = 0;
std::string client_ip = inet_ntoa(peer.sin_addr);
int client_port = ntohs(peer.sin_port);
std::cout << "client ip #" << client_ip << " client port # " << client_port << std::endl;
std::cout << "client msg # :" << buf << std::endl;
}
else{
std::cout << "recvfrom error" << std::endl;
exit(4);
}
}
}
~server()
{
if(sockfd >= 0)
close(sockfd);
}
};
server.cc
#include"server.hpp"
void usage()
{
std::cout << "请输入ip + port" << std::endl;
}
int main(int argc,char *argv[])
{
if(argc != 3)
{
usage();
exit(2);
}
server *sp = new server(argv[1],atoi(argv[2]));
sp->InitServer();
sp->run();
delete sp;
return 0;
}
client.cpp
#pragma once
#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<strings.h>
#include<string>
class client{
private:
short peer_port;
std::string peer_ip;
int sockfd;
public:
client(std::string _ip = "127.0.0.1",short _port = 8080):peer_ip(_ip) , peer_port(_port)
{}
void InitClient()
{
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
std::cout << "socket error" << std::endl;
exit(2);
}
}
void run()
{
std::string msg;
struct sockaddr_in server;
server.sin_addr.s_addr = inet_addr(peer_ip.c_str());
server.sin_port = htons(peer_port);
while(1)
{
socklen_t len = sizeof(server);
std::cout << "请输入信息 #" ;
std::cin >> msg;
ssize_t s = sendto(sockfd,msg.c_str(),msg.size(),0,(struct sockaddr*)&server,len);
}
}
~client()
{
if(sockfd > 0)
{
close(sockfd);
}
}
};
client.cc
#include"client.hpp"
void usage()
{
std::cout << "请输入正确的发送对象,格式为 ip + port";
}
int main(int argc,char *argv[])
{
if(argc != 3)
{
usage();
exit(3);
}
client* sp = new client(argv[1],atoi(argv[2]));
sp->InitClient();
sp->run();
delete sp;
return 0;
}