zoukankan      html  css  js  c++  java
  • TCP协议下Socket的基础编程类型

    套接字的基本操作有:

      创建(socket)、命名(bind)、侦听(listen)、连接(accept)、关闭(shutdown)、发送(send)、接受(recv)。

    下面逐个分析:

      一、创建(socket):

        函数原型:int socket(int domain, int type, int protocol);

        参数:

          domain:指定发送通信的域

            可取值:AF_UNIX:本地主机通信,与IPC类似

                AF_INET:Internet地址IPV4协议

          type:指定通信类型

            可取值:SOCK_STREAM(流套接字)、SOCK_DGRAM(数据报套接字)、SOCK_RAW(原始套接字)

          protocol:指定该套接字描述符上的一个特殊的协议,如TCP,UDP等,一般设为0

        返回值:

          成功:返回创建的套接字描述符

          失败:-1

        补充:SOCK_STREAM(流套接字)应用TCP协议,提供顺序的,可靠的,基于字节流的双向链接

             SOCK_DGRAM(数据报套接字)应用UDP协议,无链接,不可靠,不固定

           SOCK_RAW(原始套接字)提供访问互联网协议和Internal Network Interfaces的权限,只有超级用户才可使用。

      二、命名(bind)

        函数原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

        参数:

          sockfd:套接字描述符

          addr:指向通用套接字的协议地址结构,包括协议、地址和端口等信息

          addrlen:协议地址结构的长度,一般为sizeof(sockaddr_in)  

        但是,一般情况addr这个参数并不采用struct sockaddr *类型,而是struct sockaddr_in,使用时要注意强制类型转换。看看struct sockaddr_in的成员:

    struct sockaddr_in {
      short             sin_family;  //16位地址协议族
      u_short           sin_port;    //16位端口地址
      struct in_addr    sin_addr;    //32位IP地址
    
      
      unsigned char   sin_zero[8]   //使结构sockaddr_in与sockaddr长度相同     
    };
    struct in_addr {
        u_long    s_addr;
    };

        该结构中描述IP的是一个32位整型变量,而我们平时所用的是由”.“隔开的字符串。二者之间相互转换参照这几个函数,具体使用方法参照man命令

          

    unsigned long inet_addr(const char *cp);
    
    int inet_aton(const char *cp, struct in_addr *inp);
    
    char *inet_ntoa(struct in_addr in);

        网络通信中数据存储采用网络字节序,因此要进行主机字节序与网络字节序之间的相互转化,参照以下函数

    uint32_t htonl(uint32_t hostlong);
    
    uint16_t htons(uint16_t hostshort);
    
    uint32_t ntohl(uint32_t netlong);
    
    uint16_t ntohs(uint16_t netshort);

        返回值:

          成功:0

          失败:-1

      三、侦听(listen)

        函数原型:int listen(int sockfd, int backlog);

        参数:

          sockfd:用socket创建的套接字描述符

          backlog:sockfd接收连接的最大数目

        返回值:

          成功:0

          失败:-1

    TCP通信模型中,服务器端要完成创建、命名和侦听后才能调用accept接收客户端请求,为了提高代码重用度,这里将以上三步进行封装,代码如下:

    /**************************************
    函数名:CreateSock
    参数:
        pSock:回传创建的侦听套接字描述符
        nPort:指定套接字侦听端口
        nMax:该套接字最大连接数
    函数功能:封装套接字的创建、命名和侦听
    返回值:0
    **************************************/
    int
    CreateSock(int *pSock, int nPort , int nMax) { struct sockaddr_in addrin; struct sockaddr *paddr = (struct sockaddr*)&addrin; assert(pSock != NULL && nPort > 0 && nMax > 0); /*清空addrin*/ memset(&addrin, 0,sizeof(addrin)); addrin.sin_family = AF_INET; addrin.sin_addr.s_addr = htonl(INADDR_ANY); addrin.sin_port = htons(nPort); /*创建TCP套接字描述符*/ *pSock = socket(AF_INET, SOCK_STREAM, 0); /*命名套接字*/ bind(*pSock, paddr, sizeof(addrin)); /*进入侦听状态*/ listen(*pSock, nMax); return 0; }

      四、连接(accept)

        函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

        参数:

          sockfd:用socket创建的套接字描述符

          addr:指向通用套接字的协议地址结构,包括协议、地址和端口等信息      

          addrlen:协议地址结构的长度,一般为sizeof(sockaddr_in)

        返回值:

          成功:创造返回一个新的socket与客户进程通信,原sockfd仍用于套接字侦听。

    这里再封装一个函数,将accept也加入其中

    /**************************************
    函数名:AcceptSock
    参数:
        pSock:创建的新的套接字描述符与客户
            进程通信
        nSock:accept成功后依然用于套接字侦听
    函数功能:接受客户端的套接字连接申请
    返回值:0
    **************************************/
    int
    AcceptSock(int *pSock, int nSock) { struct sockaddr_in addrin; int lSize; assert(pSock != NULL && nSock > 0); while(1) { lSize = sizeof(addrin); memset(&addrin, 0, sizeof(addrin)); if((*pSock = accept(nSock, (struct sockaddr*)&addrin, &lSize)) > 0) return 0; else assert(0); } }

      五、接收(recv)

        函数原型:ssize_t recv(int sockfd, void *buf, size_t len, int flags);

        参数:

          sockfd:与远程通信连接的套接字描述符

          buf:接收数据的缓冲区地址

          len:缓冲区长度

          flags:接收标志

            取值:MSG_OOB、MSG_PEEK或MSG_WAITALL

    有了以上知识,我们就可以用socket进行简易通讯了,本处设计一个服务器端程序的例子,创建socket,与客户端建立连接并打印收到的数据。代码如下:

      头文件:socket.h

    #include <sys/types.h>         
    #include <sys/socket.h>
    #include <unistd.h>
    #include <netinet/in.h>
    #include <assert.h>
    #include <string.h>
    #include <arpa/inet.h>
    
    /**************************************
    函数名:CreateSock
    参数:
        pSock:回传创建的侦听套接字描述符
        nPort:指定套接字侦听端口
        nMax:该套接字最大连接数
    函数功能:封装套接字的创建、命名和侦听
    返回值:0
    **************************************/
    int CreateSock(int *pSock, int nPort , int nMax)
    {
        struct sockaddr_in addrin;
        struct sockaddr *paddr = (struct sockaddr*)&addrin;
    
        assert(pSock != NULL && nPort > 0 && nMax > 0);
        memset(&addrin, 0,sizeof(addrin));
    
        addrin.sin_family = AF_INET;
        addrin.sin_addr.s_addr = htonl(INADDR_ANY);
        addrin.sin_port = htons(nPort);
    
        /*创建TCP套接字描述符*/
        *pSock = socket(AF_INET, SOCK_STREAM, 0);
    
        /*命名套接字*/
        bind(*pSock, paddr, sizeof(addrin));
    
        /*进入侦听状态*/
        listen(*pSock, nMax);
    
        return 0;
    }
    
    /**************************************
    函数名:AcceptSock
    参数:
        pSock:创建的新的套接字描述符与客户
            进程通信
        nSock:accept成功后依然用于套接字侦听
    函数功能:接受客户端的套接字连接申请
    返回值:0
    **************************************/
    
    int AcceptSock(int *pSock, int nSock)
    {
        struct sockaddr_in addrin;
        int lSize;
        assert(pSock != NULL && nSock > 0);
        while(1)
        {
            lSize = sizeof(addrin);
            memset(&addrin, 0, sizeof(addrin));
            if((*pSock = accept(nSock, (struct sockaddr*)&addrin, &lSize)) > 0)
                return 0;
    
            else
                assert(0);
        }
    }

      主程序:

    #include <stdio.h>
    #include "socket.h"
    
    int main()
    {
        int nSock,pSock;
        char buf[2048];
        
        CreateSock(&nSock, 9001 , 9);
        
        AcceptSock(&pSock, nSock);
        
        memset(buf, 0, sizeof(buf));    //初始化缓冲区
        
        recv(pSock, buf, sizeof(buf), 0);
    
        fprintf(stderr, buf);      //打印接收到的数据
    
        close(pSock);
    
        close(nSock);
    
        return 0;
    }

    由于接收函数recv默认以阻塞方式读取数据,所以未读到数据进程会进入阻塞状态。

      1、编译好可执行程序后,执行

      2、另开一个终端,查看套接字连接情况

        命令:netstat -an|grep 9001

      3、打开浏览器,地址栏输入xxx.xxx.xxx.xxx:9001,xxx为UNIX系统的IP地址,要确保浏览器与UNIX能正常通信

      4、进程收到数据后会打印出来

      

      

      这次先记到这里,其他的基本操作以后用到了再做记录。

      如果有疑问或错误,欢迎指出

  • 相关阅读:
    Sql 中取小数点后面两位小数
    常用SQL时间格式SQLServer中文版的默认的日期字段datetime格式是yyyy-mm-d
    sql server 2008 R2连接失败 错误:18456
    SQl server 2008 附加数据库失败,错误:5120
    sql server 2008 R2无法连接127.0.0.1报错 Server error:40(错误:53)
    SQL Server 2008的MSSQLSERVER 请求失败或服务未及时响应
    查看系统事件日志
    ssh-keygen公钥进行免登
    docker命令
    maven将依赖的jar包复制到指定位置
  • 原文地址:https://www.cnblogs.com/51qianrushi/p/4363927.html
Copyright © 2011-2022 走看看