Linux 网络编程套接字

Posted qnbk

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux 网络编程套接字相关的知识,希望对你有一定的参考价值。

网络编程套接字

源IP地址和目的IP地址

在IP数据报头部,有两个IP地址,分别叫做源IP(从哪来)和目的IP地址(到哪去)

端口号

端口号(port)是传输协议的内容

  • 端口号是一个2字节16位的整数
  • 端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理
  • IP地址+端口号能够标识网络上的某一台主机的某一个进程
  • 一个端口号只能被一个进程占用

socket通信,本质是进程间通信,跨网络的进程间通信

任何的网络服务与网络客户端,如果要进行正常的数据通信,必须要有端口号,来唯一标识自身
-》 在同一个OS内,一个进程可以与一个端口号进行绑定,该端口号就在网络层面唯一标识一台主机上唯一一个进程

公网ip:唯一标识全网内唯一的一台主机
端口号port:标识一台主机上的唯一一个进程
-》ip+port:标识全网内唯一的一个进程(socket通信)

进程的pid与port

port本质是是网络级的概念
进程本质是系统的概念
一台机器上,可能存在大量的进程,但不是所有的进程都要对外进行网络数据请求。

源端口号和目的端口号

传输层协议(TCP/UDP)的数据段有两个端口号,分别叫做源端口号(数据是谁发的)和目的端口号(要发给谁)

TCP协议

传输层协议:

  • 有连接
  • 可靠传输
  • 面向字节流

UDP协议

传输层协议:

  • 无连接
  • 不可靠传输
  • 面向数据报

网络字节序

内存中的多字节数据相对于内存地址有大小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大小端之分,网络数据流同样有大小端之分。
那么该如何帝国一网络数据流的地址?

  • 发送主机通常将发送缓冲区数据的数据按内存地址从低到高的顺序发出
  • 接受主机把从网络上接到字节一次保存在接受缓存去中,也是按内存地址从高到低的顺序保存
  • 网络数据流的地址应:先发出的数据是低地址,后发出的地址是高地址
  • TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节
  • 无论此主机是大端机还是小端机,都会按照TCP/IP规定的网络字节序来发送/接受数据
  • 如果当前是小端,需将数据转成大端

网络字节序和主机字节序转换的函数

#include <arpa/inet.h>
 uint32_t htonl(uint32_t hostlong);
 uint16_t htons(uint16_t hostshort);
 uint32_t ntohl(uint32_t netlong);
 uint16_t ntohs(uint16_t netshort);

  • 如果主机是小端字节序,这些函数将参数做成相应的大小端转换然后返回
  • 如果主机是大端字节序,函数不做转换,将参数原封不动的返回

socket编程接口

常见API

//创建socket文件描述符(TCP/UDP,客户端+服务器)
int socket(int domain, int type, int protocol);
//绑定端口号(TCP/UDP,服务器)
int bind(int socket,const struct sockaddr *address
		,socklen_t address_len);
//开始监听socket(TCP,服务器)
int listen(int sockfd, int backlog);
//接受请求(TCP,服务器)
int accept(int sockfd, struct sockaddr *addr
		  ,socklen_t *addrlen);
//建立连接(TCP,客户端)
int connect(int sockfd, const struct sockaddr *addr
           ,socklen_t addrlen);

sockaddr

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,eg:IPV4,IPV6以及UNIX Domain Socket,然而,各种网络协议的地址格式并不相同

  • ipv4和ipv6的地址格式定义在netinet/in.h中 ,ipv4地址用sockaddr_in结构体表示,包括16位地址类型,16位端口号和32位ip地址
  • ipv4,ipv6地址类型分别定义为常数AF_INET,AF_INET6。这样只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockadd结构体,就可以根据地址类型字段确定结构体中的内容
  • socket API可以都用struct sockaddr* 类型来表示,在使用的时候需要强制转化成sockaddr_in;这样的好处是程序的通用性,可以接受IPV4,IPV6以及UNIX Domain Socket各种类型的sockaddr结构体指针作为参数

sockaddr结构

struct sockaddr

	_SOCKADDR_COMMON(sa_);
	//Common data:address family and length
	char sa_data[14];//address data

sockaddr_in 结构

struct sockaddr_in

	_SOCKADDR_COMMON(sin_);
	in_port_t sin_port;//port number
	struct in_addr sin_addr;//Internet address
	unsigned char sin_zero[sizeof(struct sockaddr) - 
	 _SOCKADDR_COMMON_SIZE - sizeof(in_port_t) - 
	 sizeof(struct in_addr)];

该结构里主要有三部分信息:地址类型,端口号,ip地址

in_addr结构
typedef uint32_t in_addr_t;
struct in_addr

	in_addr_t s_addr;

简单的UDP网络程序

地址转化函数

字符串转in_addr函数

#include <arpa/inet.h>
int inet_aton(const char* strptr,struct in_addr* addrptr);
in_addr_t inet_addr(const char* strptr);
int inet_pton(int family,const char* strptr,void *addrptr);

in_addr转字符串函数

char* inet_ntoa(struct in_addr inaddr);
const char* inet_ntop(int family,const void* addrptr,char* strptr);

其中inet_pton和inet_ntop不仅可以转换ipv4的in_addr,还可以转换ipv6的in6_addr,因此函数接口是void* addrptr;

inet_ntoa

inet_ntoa函数返回了一个char*,是该函式在内部申请了一块内存来保存ip,是把返回结果放到静态存储区,不需要我们手动释放。
因为inet_ntoa把结果放到自己内部的一个静态存储区,这样第二次调用时的结果会覆盖掉上一次的结果

netstat

netstat -nltp/-nlup
  • netstat :用来查看当前的网络状态
  • n: 能显示成数字就显示成数字
  • l:list
  • tp:tcp
  • up:udp

udp_server.hpp

#pragma once 
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define DEFAULT 8081
class UdpServer

  private: 
    int port;
    int sockfd;
    std::string ip;
  public:
    UdpServer(std::string _ip,int _port = DEFAULT):port(_port),sockfd(-1),ip(_ip)
    
    bool  InitUdpserver()
    
      sockfd = socket(AF_INET,SOCK_DGRAM,0);
      if(sockfd < 0)
      
        std::cerr<<"socket error"<<std::endl;
        return false;
      
      std::cout<<"socket create success,sockfd:"<<sockfd<<std::endl;
      struct sockaddr_in local;
      memset(&local,'\\0',sizeof(local));
      local.sin_family = AF_INET;//协议家族
      local.sin_port = htons(port);//port需要发送到网络中,需要设置为网络序列
      local.sin_addr.s_addr =inet_addr(ip.c_str()) ;//把字符串ip转化成整数ip
      if(bind(sockfd,(struct sockaddr*)&local,sizeof(local)) < 0)
        //绑定套接字
         
        std::cerr <<"bind error" <<std::endl;
        return false;
      
      std::cout<<"bind success"<<std::endl;
      return true;
    
    void Start()
    
#define SIZE 128
      char buffer[SIZE];//定义缓冲区
      for(;;)
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        ssize_t size = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);//读取数据
        if(size > 0)//读取成功
        
          buffer[size] = 0;
          int _port = ntohs(peer.sin_port);//把网络序列转换成主机序列
          std::string _ip = inet_ntoa(peer.sin_addr);
          std::cout<<_ip<<":"<<_port<<"#"<<buffer<<std::endl;

        
        else
          std::cerr<<"recvfrom error"<<std::endl;
        
      
    
    ~UdpServer()
;

udp_server.cc

#include "udp_server.hpp"

//udp_server  port

int main(int argc,char *argv[])

  if(argc !=2)
    std::cerr<<"Usage: "<<argv[0]<<"port"<<std::endl;
    return 1;
  
  std::string ip = "127.0.0.1";//127.0.0.1=localhost:表示本地主机-》本地环回
  int port = atoi(argv[1]);
  
  UdpServer *srv = new UdpServer(ip,port);
  srv->InitUdpserver();
  srv->Start();
  return 0;


udp_client.hpp

#pragma once 

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
class UdpClient

  private:
    int sockfd;
    std::string server_ip;
    int server_port;
  public:
    UdpClient(std::string _ip,int _port)
      :server_ip(_ip),server_port(_port)
    
    bool InitUdpclient()
    
      sockfd = socket(AF_INET,SOCK_DGRAM,0);//创建套接字
      if(sockfd < 0)
      
        std::cerr<<"sockfd create error"<<std::endl;
        return false;
      

      //客户端不需要绑定吗?
      //客户端不需要port吗?
      
      return true;
    
    void Start()
    
       struct sockaddr_in peer;
       memset(&peer,0,sizeof(peer));
       peer.sin_family = AF_INET;
       peer.sin_port = htons(server_port);
       peer.sin_addr.s_addr = inet_addr(server_ip.c_str());
       std::string msg;
      for(;;)
      
        std::cout<<"please Enter# ";
        std::cin >> msg;
        sendto(sockfd,msg.c_str(),msg.size(),0,(struct sockaddr*)&peer,sizeof(peer));//发送数据


      
    
    ~UdpClient()
;

udp_client.cc

#include "udp_client.hpp"
// ./udp_client server_ip server_port

int main(int argc,char* argv[])

  if(argc !=3)
  
    std::cerr<<"Usage"<<argv[0]<<"server_ip server_port"<<std::endl;
 return 1;
  
  std::string ip = argv[1];
  int port = atoi(argv[2]);

  UdpClient *ucli = new UdpClient(ip,port);
  ucli->InitUdpclient();
  ucli->Start();
  
  return 0;



服务器对外网访问

云服务器的ip是由对应的云厂商提供的,这个ip不能直接绑定,如果需要bind,需要让外网放访问-》bind 0(意味着服务器可以接受来自任何客户端的请求)
如果想直接绑定:虚拟机或自定义安装的Linux

//INADDR_ANY ->0 

udp_server.hpp

#pragma once 
#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define DEFAULT 8081
class UdpServer

  private: 
    int port;
    int sockfd;
    //std::string ip;
  public:
    //UdpServer(std::string _ip,int _port = DEFAULT):port(_port),sockfd(-1),ip(_ip)
    
    UdpServer(int _port = DEFAULT):port(_port),sockfd(-1)
    
    bool  InitUdpserver()
    
      sockfd = socket(AF_INET,SOCK_DGRAM,0);
      if(sockfd < 0)
      
        std::cerr<<"socket error"<<std::endl;
        return false;
      
      std::cout<<"socket create success,sockfd:"<<sockfd<<std::endl;
      struct sockaddr_in local;
      memset(&local,'\\0',sizeof(local));
      local.sin_family = AF_INET;//协议家族
      local.sin_port = htons(port);//port需要发送到网络中,需要设置为网络序列
      //local.sin_addr.s_addr =inet_addr(ip.c_str()) ;//把字符串ip转化成整数ip
      
      local.sin_addr.s_addr = INADDR_ANY;
      if(bind(sockfd,(struct sockaddr*)&local,sizeof(local)) < 0)
        //绑定套接字
         
        std::cerr <<"bind error" <<std::endl;
        return false;
      
      std::cout<<"bind success"<<std::endl;
      return true;
    
    void Start()
    
#define SIZE 128
      char buffer[SIZE];//定义缓冲区
      for(;;)
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        ssize_t size = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);//读取数据
        if(size > 0)//读取成功
        
          buffer[size] = 0;
          int _port = ntohs(peer.sin_port);//把网络序列转换成主机序列
          std::string _ip = inet_ntoa(peer.sin_addr);
          std::cout<<_ip<<":"<<_port<<"#"<<buffer<<std::endl;
          
          // 服务器发送信息
          std::string echo_msg = "sever get->";
          echo_msg += buffer;
          sendto(sockfd,echo_msg.c_str(),echo_msg.size(),0,(struct sockaddr*)&peer,len);
        
        else
          std::cerr<<"recvfrom error"<<std::endl;
        
      
    
    ~UdpServer()
    
      if(sockfd >= 0)
      
        close(sockfd);
      
    
;

udp_server.cc

#include "udp_server.hpp"

//udp_server  port

int main(int argc,char *argv[])

  if(argc !=2以上是关于Linux 网络编程套接字的主要内容,如果未能解决你的问题,请参考以下文章

Linux网络编程和套接字

2018-11-06 Visual Studio Code插件-英汉词典初版发布

linux网络编程——套接字(socket)入门

Linux/UNIX网络编程的目录

Linux Socket 原始套接字编程

linux 网络编程 (套接字)