• c++ 实时通信系统(c++socket篇)


      在上一篇简单的介绍了TCP/IP协议,在这一篇我们主要介绍socket的具体实现的函数

    第一步首先我们套添加上头文件:(#pragma comment(lib, "WS2_32")这是静态的加入库文件,这里面有API函数的内容)

    #include <winsock2.h>  
    #include<ws2tcpip.h>//定义socklen_t
    
    using namespace std;
    
    #pragma comment(lib, "WS2_32")  // 链接到WS2_32.lib
    

    第二步初始化套接字环境,(为了在应用程序当中调用任何一个Winsock API函数,首先第一件事情就是必须通过WSAStartup函数完成对Winsock服务的初始化,因此需要调用WSAStartup函数。使用Socket的程序在使用Socket之前必须调用WSAStartup函数。该函数的第一个参数指明程序请求使用的Socket版本,)

    	int   Ret;
    	WSADATA   wsaData;                        // 用于初始化套接字环境
    	// 初始化WinSock环境
    	if ((Ret = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0)
    		{
    		   printf("WSAStartup()   failed   with   error   %d
    ", Ret);
    		   WSACleanup();
    
    		}

    第三步接下来就是socket的API函数的按顺序调用就是上一篇中的socket架构,接下来介绍一下API函数

     1.socket

      int socket(int domain, int type, int protocol);

    SOCKET s = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
        if (s == INVALID_SOCKET)
        {
            int er = WSAGetLastError();
            return 0;
        }
    

      

      socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样.

      创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:

    • domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(其实就是IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
    • type:指定socket类型。常用的socket类型有,SOCK_STREAM(流套接字,使用TCP协议)、SOCK_DGRAM(数据报套接字,使用UDP协议)、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的类型有哪些?)。
    • protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议(这个协议我将会单独开篇讨论!)。

    注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。

    当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。我们创建的套接字都是主动的,阻塞的套接字。

      如果不出错,socket函数将返回socket的描述符(句柄,一般都是大于零的),否则,将返回INVALID_SOCKET。

      2.bind

      int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    sockaddr_in service;
      service.sin_family = AF_INET;
      service.sin_addr.s_addr = inet_addr("127.0.0.1");
      service.sin_port = htons(27015);
    
      //----------------------
      // Bind the socket.
      if (bind( ListenSocket, (SOCKADDR*) &service,sizeof(service)) == SOCKET_ERROR) {
        closesocket(ListenSocket);
        return;
      }
    

      

      正如上面所说bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

      函数的三个参数分别为:

    • sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
    • addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4对应的是: 
      struct sockaddr_in {
          sa_family_t     sin_family; /* address family: AF_INET */
          in_port_t       sin_port;   /* port in network byte order */
          struct in_addr  sin_addr;   /* internet address */
      };
      
      /* Internet address. */
      struct in_addr {
          uint32_t       s_addr;     /* address in network byte order */
      };
      //ipv6对应的是: 
      struct sockaddr_in6 { 
          sa_family_t     sin6_family;   /* AF_INET6 */ 
          in_port_t       sin6_port;     /* port number */ 
          uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
          struct in6_addr sin6_addr;     /* IPv6 address */ 
          uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
      };
      
      struct in6_addr { 
          unsigned char   s6_addr[16];   /* IPv6 address */ 
      };
      //Unix域对应的是: 
      #define UNIX_PATH_MAX    108
      
      struct sockaddr_un { 
          sa_family_t sun_family;               /* AF_UNIX */ 
          char        sun_path[UNIX_PATH_MAX];  /* pathname */ 
      };
    • addrlen:对应的是地址的长度。

    通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

      无错误返回0,又错误返回SOCKET_ERROR(-1),我们可以调用WSAGetLastError来看具体的错误。

    htons(host to unsigned short)和htonl(host to unsigned long)

    各个机器cpu对数据存储和表示的方法不通,intel机器用littele-endian存数据,而IBM机器用big-endian存数据。网络协议为取消这种差异,一致采用big-endian方式。htons用于将unsigned short的数值从littele-endian转换为big-endian,由于short只有2字节,常用语port数值的转换。htons用于将unsigned long的数值从littele-endian转换为big-endian,long有4字节,常用于ipv4地址的转换。

    1 u_short htons( __in     u_short hostshort);
    2 u_long htonl(__in      u_long hostlong);

    inet_addr和inet_ntoa

    inet_addr用于将ipv4格式的字符串转换为unsigned long的数值。inet_ntoa用于将struct in_addr的地址转换为ipv4格式的字符串。

    1 unsigned long inet_addr(  __in    const char* cp);
    2 char* FAR inet_ntoa(  __in     struct   in_addr in);

      3.listen

      int listen(int sockfd, int backlog);

    if (listen(s,SOMAXCONN ) == SOCKET_ERROR)
       {
              int er = WSAGetLastError();
              closesocket(s);
          }

      listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。

      没有错误发生将返回0,否则返回SOCKET_ERROR .

          4.connect

      int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    sockaddr_in server;
            server.sin_family = AF_INET;
            server.sin_port = htons(8828);
            server.sin_addr.s_addr = inet_addr("127.0.0.1");
            if (connect(cnetsocket,(sockaddr*)&server,sizeof(server)) == SOCKET_ERROR)
            {
                break;
            }

      connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。

      0表示正确,否则,将返回SOCKET_ERROR。如果是阻塞式的socket连接,返回值代表了连接正常与失败。

      5.accept

      int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    SOCKET ac =accept(listener, (struct sockaddr*)&client_address, &client_addrLength);
        if (ac == INVALID_SOCKET)
        {
            int er = WSAGetLastError();
            closesocket(s);
        }
    

      

    accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。

    如果不发生错误,accept将返回一个新的SOCKET描述符,即新建连接的socket句柄。否则,将返回INVALID_SOCKET。传进去的addrlen应该是参数addr的长度,返回的addrlen是实际长度

    注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。

      6.send,recv

      ssize_t send(int sockfd, const void *buf, size_t len, int flags);

      ssize_t recv(int sockfd, void *buf, size_t len, int flags);

      send的返回值标识已发送数据的长度,这个值可能比参数len小,这也意味着数据缓冲区没有全部发出去,要进行后续处理。返回SOCKET_ERROR标识send出错。

      recv的返回值标识已接收数据的长度。如果连接已关闭,返回值将是0。返回SOCKET_ERROR标识recv出错。

      9.closesocket

      closesocket( __in SOCKET );//关闭socket

      

      10 :getsockname  获取本地IP和PORT

      Description:The getsockname function retrieves the local name for a socket.

    1 int getsockname(
    2   __in          SOCKET s,
    3   __out         struct sockaddr* name,
    4   __in_out      int* namelen
    5 );

      Parameters

      s

    Descriptor identifying a socket.

      name

    Pointer to a SOCKADDR structure that receives the address (name) of the socket.

      namelen

    Size of the name buffer, in bytes.

      Return Value

      If no error occurs, getsockname returns zero. Otherwise, a value of SOCKET_ERROR is returned, and a specific error code can be retrieved by calling WSAGetLastError.

      11 :getpeername  获取对端IP和PORT

    1 int getpeername(
    2   __in          SOCKET s,
    3   __out         struct sockaddr* name,
    4   __in_out      int* namelen
    5 );

      Getpeername和getsockname参数一样。

      12.实现socket的非阻塞(在下一篇有他的用法)

      nt ioctlsocket (SOCKET s,         long cmd,         u_long FAR* argp  );

    unsigned long flag=1; 
    if (ioctlsocket(sock,FIONBIO,&flag)!=0) 
    { 
        closesocket(sock); 
        return false; 
    }
    
    以下是对ioctlsocket函数的相关解释: 
    int PASCAL FAR ioctlsocket( SOCKET s, long cmd, u_long FAR* argp); 
    s:一个标识套接口的描述字。 
    cmd:对套接口s的操作命令。 
    argp:指向cmd命令所带参数的指针。
    

      具体的操作可以看https://blog.csdn.net/susubuhui/article/details/7568431

       

  • 相关阅读:
    RHEL双网卡绑定
    图解机房收费系统报表制作的全过程
    linux内存管理机制
    hdu4432 Sum of divisors(数论)
    树和而叉查找树的实现
    49. 面向对象的LotusScript(十五)之Log4Dom下
    HDU 4009 不定根最小树形图
    模拟+二分 poj-1019-Number Sequence
    SQL Server 事务日志传输
    百度开放云java+tomcat部署web项目-小皇帝詹姆斯
  • 原文地址:https://www.cnblogs.com/yskn/p/9346570.html
Copyright © 2020-2023  润新知