成品1688网站,广元网络推广,模板建站费用,蓬莱网站建设哪家好目录1. 端口号2. 套接字socket3. 网络通信3.1 sockaddr与sockaddr_in3.2 接口服务端3.2.1 创建套接字#xff0c;打开网络文件3.2.2 给该服务器绑定端口和ip#xff08;特殊处理#xff09;3.2.3 初始化相关服务器3.2.4 提供服务客户端3.2.5 绑定3.2.6 使用服务4. makefile实…
目录1. 端口号2. 套接字socket3. 网络通信3.1 sockaddr与sockaddr_in3.2 接口服务端3.2.1 创建套接字打开网络文件3.2.2 给该服务器绑定端口和ip特殊处理3.2.3 初始化相关服务器3.2.4 提供服务客户端3.2.5 绑定3.2.6 使用服务4. makefile实现5. 整体代码1. 端口号
IP能够唯一地标识互联网中的一台主机而端口号能够唯一地标识一台机器上的唯一一个进程。
那么这两个因素加起来就构成了能够标识互联网上的唯一一个进程。这就是端口号与IP的联系。
2. 套接字socket
整个网络看作是一个大的OS所有网络上网行为都是在这里面实现进程间通信的。
IP地址port端口号 socket
端口号之于进程PID就相当于身份证号之于学生学号。 为什么有了PID在网络里还需要端口号来进行标识
这就好比学校不用身份证来对学生信息进行管理、录入。因为学校内部会有更合适的管理系统采用自己一套的管理方式能够使管理更加得心应手也不惧怕外部环境的改变实现与外部解耦。PID和端口号也是这个原理。
3. 网络通信
3.1 sockaddr与sockaddr_in
要实现网络通信首先是要找到目标主机其次是找到该主机上的目标进程。
而进程具有独立性要实现网络通信需要让两个进程看到同一份资源这份资源就是网络。
应用层的下一层就是传输层传输层有TCP/UDP协议。 统一接口
对于将要介绍的sock接口要先说明一个概念就是sockarr结构由于各种网络协议的实现方式不同导致接口需要对应设计所以干脆就设计出来了一个通用的接口该参数统一为sockaddr。
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及UNIX DomainSocket。 然而, 各种网络协议的地址格式并不相同。
目前我们采用的是struct sockaddr这是一个通用接口也即是说可以兼容后两个接口类型的参数。
通用接口与其他两个不同协议的接口 虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址。
sockaddr与sockaddr_in其实都是结构体类型
sockaddr
它把目标地址和端口信息实现在一起
struct sockaddr{sa_family_t sin_family;//16位地址类型家族char sa_data[14];//14位长度地址数据,包含套接字中的目标地址和端口信息
}sockaddr_in
sockaddr_in的头文件包含在
#include netinet/in.h
#include arpa/inet.h该结构体解决了sockaddr的缺陷把port和addr 分开储存在两个变量中如下
typedef uint16_t in_port_t;//十六位数字struct sockaddr_in{sa_family_t sin_family;//16位地址类型家族in_port_t sin_port;//16位端口号struct in_addr sin_addr;//32位ip地址unsigned char sin_zero;//未使用
}in_addr其实就是32位的IPv4地址
typedef uint32_t in_addr_t;struct in_addr{In_addr_t s_addr; //32位IPv4地址
};sockaddr_un
struct sockaddr_un{//16位地址类型//108字节路径名
}IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。
socket API可以都用struct sockaddr * 类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数。
3.2 接口
服务端
3.2.1 创建套接字打开网络文件
// 创建 socket 文件描述符 (TCP/UDP, 客户端 服务器)
int socket(int domain, int type, int protocol);参数
domain就是用来区分传入的是哪个协议种类。
type代表协议特性是套接字类型。比如流式套结、原始套结等。
proyocol在tcp中只需要全部设置为0。
返回值创建成功后新的套接字会返回一个文件描述符。
#include iostream
#include cerrno
int main()
{int sock socket(AF_INET,SOCK_DGRAM,0);if(sock 0){std::coutsocket create error!errnostd::endl;return 1;}std::coutsock:sockstd::endl;return 0;
}运行结果 3.2.2 给该服务器绑定端口和ip特殊处理
作为一个服务器对应的服务器地址IPport是必须要被客户人、软件、浏览器所知晓的并且不能轻易地被改变。
首先要绑定端口号
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int sockfd, const struct sockaddr *addr,
socklen_t address_len);参数
sockfd代表创建好的套接字的文件描述符addr代表需要用户指定服务器的相关socket信息address_len代表传入的结构体大小。
返回值
绑定成功返回0失败返回-1也可以是错误码。
3.2.3 初始化相关服务器
这里的参数addr是相关服务器的信息所以要bind就要先初始化好addr的信息
首先定义一个sockaddr_in类型的结构体因为我们平时传的addr其实是sockaddr_in类型的只需要在绑定的时候做一下强转即可。
那么接下来就是将它里面的对应字段全部初始化
const uint16_t port 8080;
struct sockaddr_in local;
local.sin_family AF_INET; //IPV4
local.sin_port htons(port); //该端口号是主机序列要改成网络序列
local.sin_addr.s_addr INADDR_ANY;其中port是主机序列需要转为网络序列使用的是htons函数
h是host代表主机n代表network网络 剩下最后一个字段初始化为INADDR_ANY的意思是由该服务器发送的到任意主机IP上的数据都能被你的网络进程接收。
如果不这样初始化而是绑定了确定的IP例如
//inet_addr函数将地址从数点法转化为整数IP
//网络需要的是整数表示IP而不是数点法表示的地址
local.sin_addr.s_addr inet_addr(xx.xxx.xx.xxx);这样的话只有发送到该IP的数据才能被你进程接收所以一般不这么写。
并且云服务器不直接绑定公网IP。
然后就可以进行绑定
if(bind(sock,(struct sockaddr*)lockal,sizeof(local)) 0){std::cout bind error : errno std::endl;return 2;
}3.2.4 提供服务
绑定完成以后等待他人给自己的服务器发送信息是接收的过程。 recvfrom
udp的数据读取不是用文件的接口而是有专门的接口recvfrom 参数分别是自身套接字fd、读完的数据放到buf、读len大小、读的方式flag默认0剩下最后两个代表的是和你的服务器通信的客户端的信息是一个输出型参数作用是如需要返回数据给它就可以拿到其地址返回。 sendto
给对端发信息用的是sendto
其他参数都一样最后两个参数代表的是要给谁发 由此可以提供服务
// 3. 提供服务
#define NUM 1024
char buffer[NUM];
bool quit false;
while(!quit){struct sockaddr_in send;socklen_t len sizeof(send);//开始读recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)send,len);std::cout client buffer std::endl;//收到以后回复hellostd::string echo hello;sendto(sock,echo.c_str(),echo.size(),0,(struct sockaddr*)send,len);}客户端
3.2.5 绑定
客户端不需要显示绑定。
对于客户端来说也必须要有套接字但是它不需要显示绑定。因为一旦绑定了它就要和某一个端口关联了该端口不一定会存在或者被占用那么该客户端将无法使用。
对于服务端来说端口必须要明确并且不能变更而对于客户端只要有端口就行因为是客户端访问他人。所以客户端一般都是由OS在发送数据的时候自动绑定采用的是随机端口。
// 1. 创建
int sockfd socket(AF_INET,SOCK_DGRAM,0);
if(sockfd 0){std::coutsocket create error!errnostd::endl;return 1;
}
std::coutsock:sockfdstd::endl;3.2.6 使用服务
实现基本和服务端一样但是由于自定义遵守./udp_client server_ip server_port的格式所以要用到命令行参数来规范行为不规范的话会打印使用手册。
void Usage(std::string proc)
{std::cout Usage: \n\t proc server_ip server_port std::endl;
}
int main(int argc,char* argv[])
{if(argc ! 3){Usage(argv[0]);return 0;}// 1. 创建int sockfd socket(AF_INET,SOCK_DGRAM,0);if(sockfd 0){std::coutsocket create error!errnostd::endl;return 1;}std::coutclient:sock:sockfdstd::endl;//客户端也必须要有套接字//不需要显示bind// 2. 使用服务// ./udp_client server_ip server_port的格式struct sockaddr_in server;server.sin_family AF_INET;server.sin_port htons(atoi(argv[2]));server.sin_addr.s_addr inet_addr(argv[1]);while(1){// 1. 数据来源 std::string s;std::cout输入# ;std::cins;// 2. 发给谁sendto(sockfd,s.c_str(),s.size(),0,(struct sockaddr*)server,sizeof(server));//此处tmp就是一个”占位符“struct sockaddr_in tmp;socklen_t len sizeof(tmp);char buffer[1024];recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)tmp, len);std::cout server echo# buffer std::endl;}return 0;
}由于云服务器需要手动在后台开通窗口才可通信所以IP使用127.0.0.1来测试
下列运行结果
客户端
服务端 4. makefile实现
.PHONY:all
all:udp_server udp_clientudp_server:udp_server.ccg -o $ $^ -stdc11udp_client:udp_client.ccg -o $ $^ -stdc11.PHONY:clean
clean:rm -f udp_server udp_client5. 整体代码
服务端
#include netinet/in.h
#include arpa/inet.h
#include iostream
#include cerrnoconst uint16_t port 8080;
int main()
{// 1. 创建套接字打开网络文件int sock socket(AF_INET,SOCK_DGRAM,0);if(sock 0){std::coutsocket create error!errnostd::endl;return 1;}std::coutserver:sock:sockstd::endl;// 2. 给该服务器绑定端口和ipstruct sockaddr_in local;local.sin_family AF_INET; //IPV4local.sin_port htons(port); //该端口号是主机序列要改成网络序列local.sin_addr.s_addr INADDR_ANY;if(bind(sock,(struct sockaddr*)local,sizeof(local)) 0){std::cout bind error : errno std::endl;return 2;}// 3. 提供服务#define NUM 1024char buffer[NUM];bool quit false;while(!quit){struct sockaddr_in send;socklen_t len sizeof(send);recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)send,len);std::cout client say# buffer std::endl;//收到以后回复hellostd::string echo hello;sendto(sock,echo.c_str(),echo.size(),0,(struct sockaddr*)send,len);}return 0;
}客户端
#include iostream
#include string
#include cerrno
#include sys/socket.h
#include sys/types.h
#include netinet/in.h
#include arpa/inet.h//正确用法
// ./udp_client server_ip server_port的格式
void Usage(std::string proc)
{std::cout Usage: \n\t proc server_ip server_port std::endl;
}
int main(int argc,char* argv[])
{if(argc ! 3){Usage(argv[0]);return 0;}// 1. 创建int sockfd socket(AF_INET,SOCK_DGRAM,0);if(sockfd 0){std::coutsocket create error!errnostd::endl;return 1;}std::coutclient:sock:sockfdstd::endl;//客户端也必须要有套接字//不需要显示bind// 2. 使用服务// ./udp_client server_ip server_port的格式struct sockaddr_in server;server.sin_family AF_INET;server.sin_port htons(atoi(argv[2]));server.sin_addr.s_addr inet_addr(argv[1]);while(1){// 1. 数据来源 std::string s;std::cout输入# ;std::cins;// 2. 发给谁sendto(sockfd,s.c_str(),s.size(),0,(struct sockaddr*)server,sizeof(server));//此处tmp就是一个”占位符“struct sockaddr_in tmp;socklen_t len sizeof(tmp);char buffer[1024];recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)tmp, len);std::cout server echo# buffer std::endl;}return 0;
}