linux:网络:socket套接字

套接字socket

在正式接触网络编程套接字之前,需要先认识几个基本概念。

1.源IP地址和目的IP地址
2.端口号port
3.“端口号” 和 "进程ID"
4.源端口号和目的端口号

相关概念

1.源IP地址和目的IP地址

在IP数据包头部中, 有两个IP地址;分别叫做源IP地址, 和目的IP地址。
源IP地址:发送请求的IP地址。目的IP地址:被请求的主机IP地址。

比如:A机的IP地址为192.168.0.1,B机的IP地址为192.168.0.2
那么一个数据包从A机发送到B机,此通信过程中的A机192.168.0.1就是"源IP地址";B机192.168.0.2就是"目的IP地址".

2.端口号 port

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

3.“端口号” 和 “进程ID”

端口号也是唯一表示一个进程。一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定;

PID是各进程的身份标识符,程序一运行系统就会自动分配给进程一个独一无二的PID。进程终止后,PID被系统回收,可能会被继续给新运行的程序。

4.源端口号和目的端口号

在一台主机上,一个进程对应一个端口。端口的作用就是用来唯一标识这个进程。源端口标识一台主机发起通信的那个进程,目的端口标识另一台主机接受通信的那个进程。有了端口号,接受到报文后才能够知道将报文发送到哪个进程。

传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 “数据是谁发的, 要发给谁”;

5.套接字的本质

套接字本质是进程间通信。
ip:标定全公网内唯一一台主机。
端口号(port):主机上唯一一个进程。
ip+port:全网唯一一个进程,即为套接字(socket)。

6.TCP协议

此处我们先对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识;

传输层协议
有连接
可靠传输
面向字节流

7.认识UDP协议

此处我们也是对UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识;

传输层协议
无连接
不可靠传输
面向数据报

8.网络字节序

TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。
不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;

如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;
在这里插入图片描述

调用以下库函数做网络字节序和主机字节序的转换。
在这里插入图片描述

h表示host,n表示network,l表示32位长整数,s表示16位短整数。
例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。

9.socket编程接口

// 创建 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 socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

10.sockaddr结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及UNIX Domain Socket. 然而,各种网络协议的地址格式并不相同。
在这里插入图片描述
IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址;

IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6;这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容;
在这里插入图片描述
socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数;
在这里插入图片描述
虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址;
在这里插入图片描述
in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数。

简单的UDP网络程序

1.服务器端(接收消息)

#pragma once
#include <iostream>
#include <string>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
using namespace std;\

class udpServer
{
  private:
    //源ip地址,端口号,套接字
    string ip;
    int port;
    int sock;
  public:
    //构造,初始化列表,给ip和port默认值
    udpServer(string _ip="127.0.0.1",int _port=8080)
      :ip(_ip)
      ,port(_port)
    {}
    //初始化服务器
    void initServer()
    {
      //创建套接字,IPv4协议,UDP套接字,0为默认方式
      sock=socket(AF_INET,SOCK_DGRAM,0);
      //sock默认值为3,
      cout<<"sock:"<<sock<<endl;
      struct sockaddr_in local;
      //sockaddr_in保存通信协议类型,端口号以及ip地址
      local.sin_family=AF_INET;
      local.sin_port=htons(port);
      //实际对in_addr中的s_addr进行赋值
      local.sin_addr.s_addr=inet_addr(ip.c_str());
      //绑定失败退出
      //bind(绑定函数)将套接字与主机信息绑定,成功返回0,失败返回-1;
      if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
      {
        //标准错误输出
        cerr<<"bind error!\n"<<endl;
        exit(1);
      }
    }
    //echoserver
    void start()
    {
      //要发送的信息
      char msg[64];
      for(;;)
      {
        msg[0]='\0';
        struct sockaddr_in end_point;
        socklen_t len=sizeof(end_point);
        //recvfrom(经过socket接收消息)
        //从socket读数据msg;读写方式为0(阻塞)
        //end_point代表“谁发的数据”
        //len是输入也是输出,输入时:传入结构体大小,输出时:读到的结构体大小
        ssize_t s=recvfrom(sock,msg,sizeof(msg)-1,\
            0,(struct sockaddr*)&end_point,&len);
        if(s>0)
        {
          msg[s]='\0';
          cout<<"client# "<<msg<<endl;
          string echo_string=msg;
          echo_string+="[server echo!]";
          //sendto:将从socket中读到的信息发送
          //从socket中读取,发什么,发的长度,发送方式,往哪发,发的长度
          sendto(sock,echo_string.c_str(),echo_string.size(),\
              0,(struct sockaddr*)&end_point,len);
        }
      }
    }
    ~udpServer()
    {
      close(sock);
    }
};

#include "udpServer.h"

int main()
{
  udpServer us;
  us.initServer();
  us.start();
  return 0;
}

2.用户端发送消息

#pragma once
#include <iostream>
#include <string>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
using namespace std;

class udpClient
{
  private:
    //源ip地址,端口号,
    string ip;
    int port;
    int sock;
  public:
    //传源IP地址以及port
    udpClient(string _ip="127.0.0.1",int _port=8080)
      :ip(_ip)
      ,port(_port)
    {}
    void initClient()
    {
      sock=socket(AF_INET,SOCK_DGRAM,0);
      cout<<"sock:"<<sock<<endl;
    }
    //echoserver
    void start()
    {
      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());
      for(;;)
      {
        cout<<"please enter# "<<endl;
        cin>>msg;
        if(msg=="quit")
        {
          break;
        }
        sendto(sock,msg.c_str(),msg.size(),0,(struct sockaddr*)&peer,\
            sizeof(peer));
        char echo[128];
        struct sockaddr_in temp;
        ssize_t s=recvfrom(sock,echo,sizeof(echo)-1,0,nullptr,nullptr);
        if(s>0)
        {
          cout<<"server# "<<echo<<endl;
        }
      }
    }
    ~udpClient()
    {
      close(sock);
    }
};

#include"udpClient.h"
int main()
{
  udpClient uc;
  uc.initClient();
  uc.start();
  return 0;
}

3.运行截图

用户端发送消息
在这里插入图片描述
服务器端接收消息
在这里插入图片描述

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值