总体概述一下:就是网络通信本质上还是进程间通信只不过该通信需要跨网络和跨主机,因此我们就要明确要将数据发给谁,收到数据的进程要知道是谁给他发的数据,确保能够及时做出回应。这样就引出了以下的概念:
上面说到端口号用来标识唯一一个进程,而进程ID也是用来标识唯一一个进程,那这两者之间是怎样的关系呢??
属于传输层协议,无连接,不可靠传输,面向数据报。
我们知道了IP和端口号就可以将数据正确传输给目标进程吗??
当然不是,我们知道内存中的多字节数据想对于内存地址有大小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大小端之分,同样网络数据流也有大小端之分。我们需要了解他是怎样的!!!
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行 ,可以调用以下库函数做网络网络字节序和主机字节序之间的转换。
#include
uint32_t htonl(uint32_t hostlong);//主机字节序到网络字节序
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);// 网络字节序到主机字节序
uint16_t ntohs(uint16_t netshort);
#include
int socket(int domain, int type, int protocol);
功能:创建套接字
返回值:成功返回一个文件描述符,出错返回-1。
参数如下表:
#include
#include
int bind(int sockfd, const struct sockaddr *address,socklen_t address_len);
功能:绑定本机IP和端口号。
返回值:成功返回0,失败-1。
参数如下:
虽然socket api的接口是sockaddr,但是由于在struct_sockaddr结构体中初始化IP和端口号不方便,便衍生出了Internet协议地址结构struct sockaddr_in,其形式如下:
但是,IP地址通常是字符串形式或点分十进制,例如:192.168.1.100。由于struct in_addr的成员s_addr 是无符号长整型,因此要进行变量转换,相关函数如下:
#include
#include
#include
(1) inet_addr函数
in_addr_t inet_addr(const char *cp);
功能:将点分十进制的IP地址转换为无符号长整形。
参数:cp 是以NULL结尾的点分IPV4字符串。
返回值:成功返回无符号长整形的IP地址,出错返回-1。
例如:addr=inet_addr("192.168.1.100")。
(2)inet_ntoa函数
char* inet_ntoa(struct in_addr in);
功能:将长整型IP地址转换为点分十进制。
参数:in是IPv4地址结构。
返回值:成功返回一指向包含点分IP地址的字符指针,出错返回NULL。
#include
ssize_t recvfrom(int socket, void *restrict buffer, size_t length, int flags,struct sockaddr *restrict address, socklen_t *restrict address_len);
参数说明:
socket :socket描述符
buffer : UDP数据报缓存地址
length :期望接收多长
falg :一般为0
restrict address:从哪接收的UDP数据报(不关心为NULL)
restrict address_len:接收的长度(不关心为NULL)
返回值:成功返回实际接收的字符长度,失败-1,错误原因存于errno 中
//#################################################################
ssize_t sendto(int socket, const void *message, size_t length,int flags, const struct sockaddr *dest_addr,socklen_t dest_len);
参数说明:
socket :socket描述符
message: 要发送的消息
length:期望发送消息长度
falgs:一般为0
dest_addr:往哪发即目的地址
dest_len:目标的地址长度
返回值:成功返回实际发送出去的字符长度,失败-1,错误原因存于errno 中
// u_server.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
class u_server{
private:
std::string ip;
int port;
int sock;
public:
u_server(std::string _ip="127.0.0.1",int _port=8080)
:ip(_ip),port(_port)
{}
void init_server()
{
sock=socket(AF_INET,SOCK_DGRAM,0);
std::cout<<"sock:"<<sock<<std::endl;
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=inet_addr(ip.c_str());
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){
std::cerr<<"bind error!\n"<<std::endl;
exit(1);
}
}
//echo server
void start()
{
char msg[64];
while(true)
{
msg[0]='\0';
struct sockaddr_in end_point;
socklen_t len=(sizeof(end_point));
ssize_t s=recvfrom(sock,msg,sizeof(msg)-1,0,(struct sockaddr*)&end_point,&len);
if(s>0)
{
msg[s]='\0';
std::cout<<"client# "<<msg<<std::endl;
std::string echo_string=msg;
echo_string+="[server echo!]";
sendto(sock,echo_string.c_str(),echo_string.size(),
0,(struct sockaddr*)&end_point,len);
}
}
}
~u_server()
{
close(sock);
}
};
// u_server.cc
#include"u_server.hpp"
int main()
{
u_server *up=new u_server();
up->init_server();
up->start();
delete up;
return 0;
}
//##############################################
//u_client.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
class u_client{
private:
std::string ip;
int port;
int sock;
public:
u_client(std::string _ip="127.0.0.1",int _port=8080)
:ip(_ip),port(_port)
{}
void init_client()
{
sock=socket(AF_INET,SOCK_DGRAM,0);
std::cout<<"sock:"<<sock<<std::endl;
}
//echo client
void start()
{
std::string msg;
struct sockaddr_in peer;
peer.sin_family=AF_INET;
peer.sin_port=htons(port);
peer.sin_addr.s_addr=inet_addr(ip.c_str());
while(true)
{
std::cout<<"Please Enter# ";
std::cin>>msg;
if(msg=="quit"){
break;
}
sendto(sock,msg.c_str(),msg.size(),0,(struct sockaddr*)&peer,sizeof(peer));
char echo[128];
ssize_t s=recvfrom(sock,echo,sizeof(echo)-1,0,nullptr,nullptr);
if(s>0)
{
echo[s]='\0';
std::cout<<"server# "<<echo<<std::endl;
}
}
}
~u_client()
{
close(sock);
}
};
// u_client.cc
#include"u_client.hpp"
int main()
{
u_client *uc=new u_client();
uc->init_client();
uc->start();
delete uc;
return 0;
}
当应用程序(客户或服务器)需要使用网络进行通信时,必须首先发出 socket 系统调用, 请求操作系统为其创建一 个 “套接字” 。这个调用的实际效果是请求操作系统把网络通信所需要的一些系统资源(存储器空间、CPU时间、网络带宽等)分配给相应的进程。操作系统把这些资源的总和用一个叫作套接字描述符(socket descriptor)的号码(本质就是文件描述符)来表示,然后把这个套接字描述符返回给应用进程。此后,应用进程所进行的网络操作(建立连接、收发数据、调整网络通信参数等)都必须使用这个套接字描述符(即socket函数返回值)。所以,几乎所有的网络系统都把这个套接字描述符作为套接字的许多参数中的第一个参数。在处理系统调用的时候,通过套接字描述符,操作系统就可以识别出应该使用哪些资源来完成应用进程所请求的服务。通信完毕后,应用进程通过一个关闭套接字的close 系统调用通知操作系统回收该套接字描述符相关的所有资源。由此可见,套接字是应用进程为了获得网络通信服务而与操作系统进行交互使用的一种机制。