Linux操作系统 - 网络编程socket

Posted TangguTae

tags:

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

目录

socket基本概念

socket编程接口

1、创建套接字

2、绑定端口号和ip地址

3、recvfrom函数与sendto函数


socket基本概念

套接字(socket),站在Linux内核的角度来看,就是一个通信的端点。从Linux程序的角度来看,套接字就是一个有相应描述符的打开文件。

在通信的角度来看,大多数通信发生在不同的主机之间,其本质是进程间通信(所看到的公共资源就是网络)。ip地址可以标识全网内唯一的主机,而端口号port可以标识该主机上的唯一的一个进程(这里与进程的pid不一样)

进程pid是每个进程都拥有的,而端口号只有特定的进程(网络进程)才拥有。

pid标定系统内的唯一进程,端口号标识一堆网络进程中的某一个进程。

一个进程只能拥有一个pid,但是一个进程可以用有多个端口号,一个端口号不能被多个进程绑定。

所以可以说,套接字socket = ip + port;

由于套接字也是文件描述符,所以也有一些系统级IO是针对网路通信的

socket编程接口

1、创建套接字

第一个参数是指名通信域,也就是通信协议(协议家族sin_family中的成员)。

具体的有以下这些协议

常用的都是基于TCP/IP协议,所以选择AF_INET(IPV4) 或者AF_INET6(IPV6).

第二个参数是套接字的类型

其中有基于流式的SOCK_STREAM,基于数据报的SOCK_DGRAM。

TCP:面向字节流的可靠传输

UDP:面向数据报的不可靠传输

第三个参数默认设置为0。

返回值

创建成功返回一个文件描述符,失败返回-1 。

#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>

using namespace std;

int main()

  int sock = socket(AF_INET,SOCK_DGRAM,0);//基于IPV4,数据报(UDP)
  cout<<sock<<endl;                         
  return 0;

输出为3,前面的0,1,2对应stdin,stdout,stderr。

2、绑定端口号和ip地址

ip地址和端口号绑定在sockaddr_in结构体(针对IPV4)中

sin_family协议类型,上面已经说过,端口号port的大小是16位,ipv4的ip地址为 32位。针对这个结构有一个通用结构,struct sockaddr结构体

会发现接下来的函数bind,connect,accept都是用这个通用结构,因为不同的协议类型他的结构有所不同,只能用通用结构sockaddr,在传参是都需要强制类型转换为sockaddr*。在函数内部通过首部的地址类型来确定到底使用的是哪种结构。

在这里还需要注意一些问题

网络字节序:在网络中的数据都是以大端的方法存放的。

所以需要一批接口将主机序列转化为网络序列。

例如htons,“host to network (short)”,主机序列转化为网络序列,且是short类型的参数

bind函数

用来进行ip和端口号的绑定。绑定的目的是让内核将ip地址和端口号与socketfd进行联系起来(强相关),这个过程客户端和服务器都可以绑定,但是客户端没有必要与socketfd强相关,因为存在大量的客户端,而且客户端一般不会长时间连接服务器。而服务器正好相反,所以只针对服务器

服务器端的ip和端口号,尤其是端口号不能修改,必须是确定的,众所周知的

一旦服务器端端口号发生改变,客户端就找不到服务器,因为客户端是向服务器发出请求,必须明确服务器的端口号和ip地址,服务器接收到数据时会知道是谁发过来的,见recvfrom函数的参数。除此之外:

1、当客户端进行绑定时,很容易出现冲突,客户端就无法启动

2、客户端虽然也需要唯一性,但是不需要明确必须是哪个端口号,所以可以让操作系统自动去筛选分配

第一个参数就是创建好的套接字socket。

第二个参数就是sockaddr结构体指针。

第三个参数还需要传入结构体的大小。

绑定的过程:

#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;                                              

class udpServer

  private:
    string _ip;
    int _port;
  public:
    udpServer(string ip = "127.0.0.1",int port = 8080)
      :_ip(ip)
      ,_port(port)
    
    void initServer()
    
      int sock = socket(AF_INET,SOCK_DGRAM,0);
      if(sock < 0)
      
        cerr<<"sock error!"<<endl;
        exit(1);
      
        //进行绑定
      struct sockaddr_in local;
      local.sin_family = AF_INET;
      local.sin_port = htons(_port);
        //inet_addr把点分十进制ip地址字符串转化为32位网络序列
      local.sin_addr.s_addr = inet_addr(_ip.c_str());
      //需要强制类型转换把sockaddr_in*类型转化为sockaddr*类型
      if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)
      
        cerr<<"bind error!"<<endl;
        exit(1);
      
      else 
        cout<<"bind successfully"<<endl;
    
  ;
int main(int argc,char* argv[])//从命令行读取服务器的ip地址和端口号

  if(argc != 3)
  
    cerr<<"parameter error!"<<endl;
    exit(1);
                                                           
  udpServer *us = new udpServer(argv[1],atoi(argv[2]));
  us->initServer();
  return 0;

udp服务器初始化结束之后需要进行通信,由于socket本质上是文件描述符,所以还需要两组IO

3、recvfrom函数与sendto函数

recvfrom接收数据

在udp服务器里只讨论recvfrom函数,因为通过recvfrom函数还可以获得远端的地址数据。recv函数在tcp服务器里会经常出现。

recvfrom的第一个参数为socket,第二个参数为接收buffer,第三个为期望读到的长度,第四个参数是当没有数据时是阻塞等待还是非阻塞等待。后面两个参数是输出型参数,关心的是谁发给我的,也就是远端的地址数据。第六个参数一定要初始化,不然出bug。

sendto发送数据

和上面一样udp通信只关心sendto函数,前面几个参数是一样的道理,第五个参数是目的地址参数

写一个启动服务器start函数

void start()

  while(true)
  
    char msg[64]='\\0';
    struct sockaddr_in remote;//用来保存远端地址
    socklen_t len = sizeof(remote);
    ssize_t s = recvfrom(sock,msg,sizeof(msg)-1,0,(struct sockaddr*)&remote,&len);
    if(s > 0)
    
      msg[s] = '\\0';
      cout<<"get msg from Client: "<<msg<<endl;
      string echo_string = msg;
      echo_string+="[server echo]";
      //收到数据后并返回一个消息
      sendto(sock,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&remote,len);
    
  

用netstat查看当前操作系统的网络信息  -nlup中的n表示以数字的形式显示,u表示udp通信,p表示进程。发现当前网络多了一个ip地址为127.0.0.1,端口为8080的网络进程。

简易版的udp服务器大概就是这样,现在来看看客户端

客户端首先不需要进行bind操作,其他操作和服务器是一样的

#include<iostream>                                                                 
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;
//客户端
class udpClient

  private:
    string _ip;
    int _port;
    int sock;
  public:
    //需要服务器的ip地址和端口号
    udpClient(string ip = "127.0.0.1",int port = 8080)
      :_ip(ip)
      ,_port(port)
    
    void initClient()
    
      sock = socket(AF_INET,SOCK_DGRAM,0);
      if(sock < 0)
      
        cerr<<"sock error!"<<endl;
        exit(1);
      
    //不需要绑定
    
    void start()
    
      //服务器的ip地址和端口号填入sockaddr结构中
      struct sockaddr_in remote;
      remote.sin_family = AF_INET;
      remote.sin_port = htons(_port);
      remote.sin_addr.s_addr = inet_addr(_ip.c_str());
      while(true)
      
        string msg;
        cout<<"please enter msg# ";
        cin>>msg;
        sendto(sock,msg.c_str(),msg.size(),0,(struct sockaddr*)&remote,sizeof(remote));
        char echo[128] = '\\0';
        //最后两个参数置空,因为已经知道了服务器的ip地址和端口号
        ssize_t s = recvfrom(sock,echo,sizeof(echo)-1,0,nullptr,nullptr);
        if(s > 0)
        
          echo[s] = '\\0';
          cout<<"get msg from Server: "<<echo<<endl;
        
      
    
;
int main(int argc,char* argv[])

  if(argc != 3)
  
    cerr<<"parameter error!"<<endl;
    exit(1);
  
  udpClient *uc = new udpClient(argv[1],atoi(argv[2]));
  uc->initClient();
  uc->start();
  return 0;
                                                                                           

客户端需要知道服务器的ip地址和端口号才能知道我要向谁通信。

 

其实在服务器这边,不一定需要指定的ip地址,可以在s_addr里面填入INADDR_ANY这个选项,表示该主机上的任意ip地址来的数据都可以收到。

上面的小程序并没有连接,监听等操作,同样也说明udp是面向无连接的。

以上是关于Linux操作系统 - 网络编程socket的主要内容,如果未能解决你的问题,请参考以下文章

linux socket编程:简易客户端与服务端

socket网络编程

Linux系统编程—网络编程—socket编程步骤

socket编程--socket模块介绍

网络中进程通信-----socket

Python socket编程