网络编程套接字---------主要讲关于网络通信的实现;
本文主要讲述:IP地址,端口号,网络字节序等网络编程中的基本概念;socket api的基本用法;能够实现一个简单的udp客户端/服务器;
1.IP地址的功能:在网络上唯一标识一台主机;
2.IP地址的常见类型和发展:
(1)IPV4-----uint32_t类型
(2)IPV6-----uint64_t类型 (不兼容IPV4的网络程序,为了考虑经济性没有被推广起来);
(3)DHCP-----动态地址分配(功能:谁上网就给谁分配IP地址);
(4)NAT-----地址替换(当一个人用的流量不多时会造成地址浪费,因此通过路由器给多个主机分配一个IP地址,当有需要客户端向服务端发出请求时,主机将信息发给路器,路由器此时用自己的地址替换用户的地址,然后将信息发送到服务器;服务器回复信息是也是将信息先回复到路由器上,然后路由器将信息发送给用户,实现多人同时上网;)
因为数字不好记,所以使用点分十进制的形式展示IP地址;(四个数字组成,如:192.168.122.132(每个数字只有一个字节,所以表示的范围是0~255);
3.源IP地址和目的IP地址:
每条数据中都会包含source ip(源IP)和 destination ip(目的IP);标识了这条数据从哪来到哪去;
1.功能:在一台主机上标识一个进程;(uint16_t类型的数字来标识一个进程;范围是0~65535,其中0 ~1024是不推荐使用的)
面试题:在主机上标识一个进程为什么不用进程ID呢????
因为进程ID的pid会改变;因为网络通信中有客户端(主动发出请求)和服务端(被动接收请求)两个端口;在发送信息时,客户端向服务端发起请求或数据,这时就要找服务端的地址入口,此时服务端的地址必须是一个固定的地址,客户端发出的请求才能被服务端接收到;实现数据的交互和信息的传递;
一个端口只能被一个进程占用,因为端口是用来标识进程的;但是一个进程可以使用多个端口;(一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定; )
每条数据都会包含:src port 和 dest port(源端口和目的端口,标识了这条数据从哪一个进程到另一个进程);
一条数据中包含了:sip,sport ,dip ,dport ,proto(五元组–>标识一条通信);
1.字节序的概念:CPU在内存中对数据进行存取的顺序;
2.大小端的概念:
大端:低地址存高位,高地址存低位;
小端:低地址存低位,高地址存高位;
判断大小端问题:
#include <stdio.h>
int Islittend(){
int a=0x11223344;
char *b=(char*)&a;
if(*b==0x11){
return 1;
}
else{
return 0;
}
}
int main(){
printf("%d\n",Islittlend());
return 0;
}
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏 移地址也有大端小端之分, 网络数据流同样有大端小端之分;那么如何定义网络数据流的地址呢?
(1)发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址;
(2)TCP/IP协议规定:网络数据流应采用大端字节序,即低地址高字节.;不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;;如果当前发送主机是小端, 就需要先将数据转成大端;;否则就忽略, 直接发送即可;
3.什么是主机字节序:
主机字节序:当前计算机的字节序-------大小端取决于CPU架构;
X86_64架构—小端
MIPS----大端
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换 :
#include
uint32_t htonl(unit32_t hostlong);
unit16_t htons(uint16_t hostshort);
unit32_t ntohl(uint32_t netlong);
unit16_t ntohs(unit16_t netshort);
面试题:大小端字节序对网络通信有什么影响????
----------如果通信两端主机字节序不同,就会造成数据二义性(存储大于一个字节的数据);
如何解决:订立标准-----网络字节序(在网络通信的时候都是用网络字节序)—大端字节序;
4.为了保证一个程序的可移植性,通信双方必须使用网络字节序进行通信;在网络通信中,数据的字节序转换主要是针对数据存储大于一个字节类型的数据;
1.关于TCP和UDP的概念及特性:
TCP:传输控制协议;
特性:面向连接,可靠传输,提供字节流服务;
(1)面向连接:通信之前先建立连接,确保双方在线;
(2)可靠传输:在网络正常的情况下,数据不会丢失;
(3)面向字节流:传输灵活,但是存在TCP粘包问题(没有明显的数据格式约定)
UDP:用户数据报协议;
特性:无连接,不可靠,面向数据报;
(1)面向数据报:每条数据有长度标识,整条发,整条收的过程,传输不够灵活,但是不会存在粘包问题;
2.TCP和UDP应用该如何选择:(主要看其使用场景)
TCP使用场景:数据安全性要高,数据不能丢失;(传输文件保证数据安全)
UDP使用场景:不需要保证可靠传输,所以传输的速度非常快;因此对数据实时要求比较高;(如:传输视频----保证传输速度)
传输层基于UDP协议实现网络通信编程:只能一对一进行通信;
1.传输流程:(先起来的是服务端程序)
服务端:
(1)创建套接字:通过套接字使进程与网卡建立联系;—在内核中创建 struct socket结构体;
(2)为套接字绑定地址信息(IP +PORT端口);
(3)接收数据;
(4)发送数据;
(5)关闭套接字;
客户端:
(1)创建套接字;
(2)为套接字绑定地址信息,通常客户端不推荐用户手动绑定地址信息;
(3)发送数据(如果用户socket还没有绑定地址,这时操作系统会选择一个合适的地址端口进行绑定);
(4)接收数据;
(5)关闭套接字;
具体图形表现如下:
2.接口信息介绍:
(1)创建套接字:socket();
domain:地址域
AF_INET:IPV4网络协议地址域;
type:套接字类型
SOCK_STREAM 流式套接字,默认协议TCP,不支持UDP;
SOCK_STREAM 数据报套接字,默认协议UDP,不支持TCP;
protocol: 协议类型
0:使用套接字默认协议;
6/IPPROTO_TCP TCP协议;
17/IPPROTO_UDP UDP协议;
返回值:套接字操作句柄-文件描述符 失败:-1;
(2)为套接字绑定地址信息 bind();
int bind (int sockfd,const struct sockaddr*addr,socklen_t addrlen); 创建套接字返回描述符
sockfd:创建套接字返回的描述符;
addr:地址信息;
addrlen:地址信息长度;
返回值:成功--0;失败-- -1;
(3)接收数据:recvfrom();
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
sockfd: 操作句柄,套接字描述符;
buf: 用buf存储接收的数据;
len: 想要接收的数据长度;
flags: 0---默认阻塞接收(没数据就阻塞);
saddr: 发送端的地址信息;
addrlen: 地址信息长度(输入输出型参数);不但要指定想接收多长还要保存实际接收了多长;
返回值: 失败:-1;成功:实际接收的数据长度;
(4)发送数据:sendto();
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd: 套接字描述符;
buf: 要发送的数据;
len: 要发送的数据长度;
flags: 0--默认阻塞发送;
daddr: 目地段地址信息---标识数据要发送到哪里去;
addrlen: 地址信息长度;
返回值: 失败:-1;成功:实际接收的数据长度;
(5)关闭套接字:close();
int close(int fd);
3.使用C++封装一个UDP的socket类,实现UDP的一个客户端程序和服务端程序实现通信;
//vim udpsocket.hpp
//实现以UdpSocket类封装udp常用操作:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <string>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class UdpSocket{
pubilc:
//构造函数:
UdpSocket():_sock(-1){}
//析构函数:
~UdpSocket(){}
bool Socket(){ //创建套接字
//int socket(int domain, int type, int protocol);
_socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(_sock<0){
perror("socket error");
return false;
}
return true;
}
bool Bind(std::string &ip,uint16_t port){ //绑定地址信息
// int bind (int sockfd,const struct sockaddr*addr,socklen_t addrlen);
struct socketaddr_in addr;
addr.sin_family=AF_INET;
// uint16_t htons(uint16_t hostshort);
addr.sin_port=htons(port);
// in_addr_t inet_addr(const char *cp);
addr.sin_addr.s_addr=inet_addr(ip.c_str());
socklen_t len=sizeof(struct sockaddr_in);
int ret=bind(_sock,(struct sockaddr*)&addr,len);
if(ret<0){
perror("bind error");
return false;
}
return true;
}
bool Recv(std::string &buf,struct sockaddr_in *saddr){ //接收数据
// ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
char tmp[1500]={0};
socklen_t len=sizeof(struct sockaddr_in);
int ret=recvforom(_sock, tmp,1500,0,(struct sockaddr*)saddr,&len);
if(ret<0){
perror("recvfrom,error");
return false;
}
buf.assign(tmp,ret); //从tmp中拷贝指定长度的数据到buf中;
return true;
}
bool Send(std::string &buf,struct sockaddr_in *daddr){ //发送数据
// ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
socklen_t len=sizeof(struct sockaddr_in);
int ret=sendto(_sock,buf.c_str,buf.size(),0,(struct sockaddr*)daddr,len);
if(ret<0){
perror("sendto error");
return false;
}
return true;
}
bool Close(){ //关闭套接字
close(_sock);
_sock=-1;
}
private:
int _sock;
};
touch udp_srv.cpp 创建服务端程序:
touch udp_srv.cpp 服务端程序
实现服务端的程序:通过UdpSocket实现UDP服务端程序;
#include "udpsocket.hpp"
#define CHECK_RET(q) if((q)==false) {return -1;}
int main(int argc,char *argv[]){
if(argc!=3){
printf("./udp_srv ip port\n");
return -1;
}
std::string ip=argv[1];
uint16_t port=atoi(argv[2]);
UdpSocket sock;
CHECK_RET(sock.Socket()); //失败直接退出;
CHECK_RET(sock.Bind(ip,port)); //绑定;
while(1){
std::string buf;
struct sockaddr_in cli_addr;
CHECK_RET(sock.Recv(buf,&cli_addr));
std::cout<<"client say:"<<buf<<std::endl;
std::cout<<"server say:";
fflush(stdout);
std::cin>>buf;
CHECK_RET(sock.Send(buf,&cli_addr));
}
sock.Close(); //关闭;
}
touch udp_cli.cp 创建客户端程序:
touch udp_cli.cp 客户端程序
实现客户端程序:touch udp_cli.cp
#include "udpsocket.hpp"
#define CHECK_RET(q) if((q)==false) {return -1;}
int main(int argc,char *argv[]){
if(argc!=3){
printf("./udp_srv ip port\n");
return -1;
}
//这个地址是服务端地址,为了让客户端知道数据请求发送到哪里了;
std::string ip=argv[1];
uint16_t port=atoi(argv[2]);
UdpSocket sock;
CHECK_RET(sock.Socket()); //失败直接退出;
//客户端并不推荐手动绑定地址;一旦绑定想启用多个客户端时就无法实现;
// CHECK_RET(sock.Bind(ip,port)); //绑定;
struct sockaddr_in cli_addr; //客户端在发送数据时前提必须知道服务端的地址;
cli_addr.sin_family=AF_INET;
srv_addr.sin_port=htons(port);
srv_addr.sin_addr.s_addr=inet_addr(ip.c_str());
while(1){
std::string buf;
std::cout<<"server say:";
fflush(stdout);
std::cin>>buf;
//先发送后接收;
CHECK_RET(sock.Send(buf,&srv_addr));
CHECK_RET(sock.Recv(buf,&srv_addr));
std::cout<<"client say:"<<buf<<std::endl;
}
sock.Close(); //关闭;
}
在Linux系统下可实现客户端与服务端一对一进行信息交互和数据交流;
本文主要总结了关于网络编程套接字的基础概念IP,port接口,网络字节序,TCP和UDP的相关概念以及知识点,此外实现了初级的套接字编程实现用户端与服务段之间的一对一简单交互;
在网络套接字(二)中,将继续总结关于网络套接字的其他相关内容及知识点;