linux进程间通信--套接字 tcp

2021-02-04

linux下tcp函数实现示例

包含ipv4与ipv6

双栈单独版本

server

#include <stdio.h>
#include <stdlib.h>

#include <sys/socket.h>
#include <netinet/in.h> //互联网地址族
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <time.h>

#include <ctype.h> //toupper (小写转化为大写)

int port = 8000;

/*服务端*/
int main(int argc, char **argv)
{

#ifdef TCP_IPV6
    struct sockaddr_in6 sin;
    struct sockaddr_in6 pin;
#else
    struct sockaddr_in6 sin; //struct sockaddr和struct sockaddr_in这两个结构体用来处理网络通信的地址。
    struct sockaddr_in6 pin;
#endif
    int sock_descriptor; //  套接口描述字
    int temp_sock_descriptor;
    int address_size;
    char buf[16384]; // 缓冲区大小

    int i, len;

    /*
     *int socket(int domain, int type, int protocol);
     * PF_INET, AF_INET: Ipv4网络协议
     * PF_INET6, AF_INET6: Ipv6网络协议。
     * type参数的作用是设置通信的协议类型,可能的取值如下所示:
        SOCK_STREAM: 提供面向连接的稳定数据传输,即TCP协议。
        OOB: 在所有数据传送前必须使用connect()来建立连接状态。
        SOCK_DGRAM: 使用不连续不可靠的数据包连接。
        SOCK_SEQPACKET: 提供连续可靠的数据包连接。
        SOCK_RAW: 提供原始网络协议存取。
        SOCK_RDM: 提供可靠的数据包连接。
        SOCK_PACKET: 与网络驱动程序直接通信。
     */
    //socket函数,向系统申请一个通信端口
#ifdef TCP_IPV6
    sock_descriptor = socket(AF_INET6, SOCK_STREAM, 0); 
#else
    sock_descriptor = socket(AF_INET, SOCK_STREAM, 0); //IPV4 TCP协议
#endif
    if (sock_descriptor == -1)                         //申请失败
    {
        perror("call to socket");
        exit(1);
    }

    bzero(&sin, sizeof(sin));         // 初始化 然后是设置套接字
#ifdef TCP_IPV6
    sin.sin6_family = AF_INET6;
    sin.sin6_addr = (struct in6_addr)IN6ADDR_ANY_INIT;
    sin.sin6_port = htons(port);
#else
    sin.sin_family = AF_INET;         //协议族,在socket编程中只能是AF_INET(TCP/IP协议族)
    sin.sin_addr.s_addr = INADDR_ANY; //sin_addr存储IP地址,使用in_addr这个数据结构
                                      //s_addr按照网络字节顺序存储IP地址
                                      //in_addr32位的IPv4地址
    sin.sin_port = htons(port);       //存储端口号
#endif
    //将套接字(sin) 跟端口(sock_descriptor)链接
    if (bind(sock_descriptor, (struct sockaddr *)&sin, sizeof(sin)) == -1)
    {
        perror("call to bind");
        exit(1);
    }
    /*int PASCAL FAR listen( SOCKET s, int backlog);
       S:用于标识一个已捆绑未连接套接口的描述字。
       backlog:等待连接队列的最大长度。
     * listen()仅适用于支持连接的套接口,如SOCK_STREAM类型的。
     */
    if (listen(sock_descriptor, 20) == -1) //在端口sock_descriptor监听
    {
        perror("call to listen");
        exit(1);
    }

    printf("accepting connections \n");

    while (1)
    { //用来监听的端口sock_descriptor
        temp_sock_descriptor = accept(sock_descriptor, (struct sockaddr *)&pin, &address_size);
        if (temp_sock_descriptor == -1)
        {
            perror("call to accept");
            exit(1);
        }

        /*int PASCAL FAR recv( SOCKET s, char FAR* buf, int len, int flags);
          s:一个标识已连接套接口的描述字。
          buf:用于接收数据的缓冲区。
          len:缓冲区长度。
          flags:指定调用方式。
         */

        printf("wait to recv message\n");

        struct timeval tv;

        // Set up the struct timeval for the timeout.
        tv.tv_sec = 30;
        tv.tv_usec = 0;
        
        // 添加socket延时
        // setsockopt(temp_sock_descriptor, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval));

        if (recv(temp_sock_descriptor, buf, 16384, 0) == -1)
        {
            perror("call to recv");
            exit(1);
        }

        printf("received from client:%s\n", buf);

        len = strlen(buf);
        for (i = 0; i < len; i++)
        {
            buf[i] = toupper(buf[i]); //将小写字母转化为大写字母
        }
        /*int PASCAL FAR send( SOCKET s, const char FAR* buf, int len, int flags);
          s:一个用于标识已连接套接口的描述字。
          buf:包含待发送数据的缓冲区。
          len:缓冲区中数据的长度。
          flags:调用执行方式。*/

        /*send()   基于链接的发送 TCP
         *sendto() 基于无链接到   UDP
         */

        if (send(temp_sock_descriptor, buf, len, 0) == -1)
        {
            perror("call to send");
            exit(1);
        }

        close(temp_sock_descriptor);
    }

    return (EXIT_SUCCESS);
}

client

#include <stdio.h>
#include <stdlib.h>

#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
/*客户端*/
#ifdef TCP_IPV6
char *host_name = "::1";
#else
char *host_name = "127.0.0.1"; //需要搜寻服务端IP地址
#endif
int port = 8000;

/*argc: 整数,用来统计你运行程序时送给main函数的命令行参数的个数
 * argv: 字符串数组,用来存放指向你的字符串参数的指针数组,每一个元素指向一个参数
  argv[0] 指向程序运行的全路径名
  argv[1] 指向在DOS命令行中执行程序名后的第一个字符串
  argv[2] 指向执行程序名后的第二个字符串
   */
int main(int argc, char **argv)
{
    char buf[8192];
    char message[256];
    int socket_descriptor;
#ifdef TCP_IPV6
    struct sockaddr_in6 pin;
#else
    struct sockaddr_in pin; //处理网络通信的地址
#endif
    /*
     * hostent记录主机的信息,包括主机名、别名、地址类型、地址长度和地址列表
     * struct hostent {
         char *h_name;地址的正式名称
         char **h_aliases;空字节-地址的预备名称的指针
         int h_addrtype;地址类型; 通常是AF_INET。
         int h_length;地址的比特长度。
         char **h_addr_list;零字节-主机网络地址指针。网络字节顺序。
      };
     #define h_addr h_addr_list[0] //h_addr_list中的第一地址
     */
    struct hostent *server_host_name;

    char *str = "A default test string";

    if (argc < 2) //运行程序时送给main函数到命令行参数个数
    {
        printf("Usage:test \"Any test string\"\n");
        printf("we will send a default test string. \n");
    }
    else
    {
        str = argv[1];
    }
    /*
     * gethostbyname()返回对应于给定主机名的包含主机名字和地址信息的
     * hostent结构指针。结构的声明与gethostaddr()中一致。*/
    if ((server_host_name = gethostbyname(host_name)) == 0)
    {
        perror("Error resolving local host \n");
        exit(1);
    }

    bzero(&pin, sizeof(pin));
#ifdef TCP_IPV6
    pin.sin6_family = AF_INET6;
    if (inet_pton(AF_INET6, host_name, &pin.sin6_addr) <= 0)
    {
        perror("pton");
        return 2;
    }
    pin.sin6_port = htons(port);
    if ((socket_descriptor = socket(AF_INET6, SOCK_STREAM, 0)) == -1)
    {
        perror("Error opening socket \n");
        exit(1);
    }
#else
    pin.sin_family = AF_INET;
    //htonl()将主机的无符号长整形数转换成网络字节顺序
    pin.sin_addr.s_addr = htonl(INADDR_ANY);                                      //s_addr按照网络字节顺序存储IP地址
                                                                                  //in_addr 32位的IPv4地址  h_addr_list中的第一地址
    pin.sin_addr.s_addr = ((struct in_addr *)(server_host_name->h_addr))->s_addr; // 跟书上不一样 必须是h_addr

    pin.sin_port = htons(port);
    /*申请一个通信端口*/
    if ((socket_descriptor = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("Error opening socket \n");
        exit(1);
    }
#endif
    //pin 定义跟服务端连接的 IP 端口
    if (connect(socket_descriptor, (void *)&pin, sizeof(pin)) == -1)
    {
        perror("Error connecting to socket \n"); ////
        exit(1);
    }
    printf("Sending message %s to server \n", str);
    int num1 = 0;
    while(num1++ < 60) {
        printf("num1:%d\n", num1);
        sleep(1);
    }

    if (send(socket_descriptor, str, strlen(str), 0) == -1)
    {
        perror("Error in send\n");
        exit(1);
    }

    printf("..sent message.. wait for response...\n");

    if (recv(socket_descriptor, buf, 8192, 0) == -1)
    {
        perror("Error in receiving response from server \n");
        exit(1);
    }

    printf("\n Response from server:\n\n%s\n", buf);

    close(socket_descriptor);

    return (EXIT_SUCCESS);
}

ipv4与ipv6优化版本

根据argv[1]输入的地址是ipv4 还是ipv6自行判断使用哪一个

server

#include <stdio.h>
#include <stdlib.h>

#include <sys/socket.h>
#include <netinet/in.h> //互联网地址族
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <time.h>

#include <ctype.h> //toupper (小写转化为大写)

// int port = 8000;
// char *server = "localhost";
char *server = "127.0.0.1";


void PrintSocketAddress(const struct sockaddr *address, FILE *stream) {
    // Test for address and stream
    if (address == NULL || stream == NULL)
        return;

    void *numericAddress; // Pointer to binary address
    // Buffer to contain result (IPv6 sufficient to hold IPv4)
    char addrBuffer[INET6_ADDRSTRLEN];
    in_port_t port; // Port to print
    // Set pointer to address based on address family
    switch (address->sa_family) {
        case AF_INET:
            numericAddress = &((struct sockaddr_in *) address)->sin_addr;
            port = ntohs(((struct sockaddr_in *) address)->sin_port);
            break;
        case AF_INET6:
            numericAddress = &((struct sockaddr_in6 *) address)->sin6_addr;
            port = ntohs(((struct sockaddr_in6 *) address)->sin6_port);
            break;
        default:
            fputs("[unknown type]", stream);    // Unhandled type
            return;
    }
    // Convert binary to printable address
    if (inet_ntop(address->sa_family, numericAddress, addrBuffer,
                sizeof(addrBuffer)) == NULL)
        fputs("[invalid address]", stream); // Unable to convert
    else {
        fprintf(stream, "%s", addrBuffer);
        if (port != 0)                // Zero not valid in any socket addr
            fprintf(stream, "-%u", port);
    }
}

int create_socket(char *servername, char *port)
{
    int sockfd = 0;

    int address_size;

    struct addrinfo hints;
    struct addrinfo *result;

    int i, len;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
    hints.ai_socktype = SOCK_STREAM; /* Datagram socket */
    hints.ai_flags = AI_PASSIVE;    /* For wildcard IP address */
    hints.ai_protocol = 0;          /* Any protocol */
    hints.ai_canonname = NULL;
    hints.ai_addr = NULL;
    hints.ai_next = NULL;

    int s = getaddrinfo(servername, port, &hints, &result);
    if (s != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
        exit(EXIT_FAILURE);
    }

    //socket函数,向系统申请一个通信端口
    sockfd = socket(result->ai_family, result->ai_socktype, 0); //IPV4 TCP协议
    if (sockfd == -1)                         //申请失败
    {
        perror("call to socket");
        exit(1);
    }

    //将套接字(sin) 跟端口(sockfd)链接
    if (bind(sockfd, result->ai_addr, result->ai_addrlen) == -1)
    {
        perror("call to bind");
        exit(1);
    }
    struct sockaddr_storage local_addr;
    socklen_t addr_size=sizeof(local_addr);
    if(getsockname(sockfd,(struct sockaddr *)&local_addr,&addr_size)<0)
    {
        // DieWithSystemMessage("getsockname() failed!");
    }
    fputs("Binding to ",stdout);
    PrintSocketAddress((struct sockaddr*)&local_addr,stdout);
    freeaddrinfo(result);

    /*int PASCAL FAR listen( SOCKET s, int backlog);
       S:用于标识一个已捆绑未连接套接口的描述字。
       backlog:等待连接队列的最大长度。
     * listen()仅适用于支持连接的套接口,如SOCK_STREAM类型的。
     */
    if (listen(sockfd, 20) == -1) //在端口sockfd监听
    {
        perror("call to listen");
        exit(1);
    }

    printf("accepting connections \n");
    return sockfd;
}

/*服务端*/
int main(int argc, char **argv)
{

    int sock_descriptor = create_socket(argv[1], "8000");
    int temp_sock_descriptor;
    int address_size;
    char buf[16384]; // 缓冲区大小
    struct sockaddr_storage pin;
    address_size = sizeof(pin);

    int i, len;

    while (1)
    { //用来监听的端口sock_descriptor
        temp_sock_descriptor = accept(sock_descriptor, (struct sockaddr *)&pin, &address_size);
        if (temp_sock_descriptor == -1)
        {
            perror("call to accept");
            exit(1);
        }

        /*int PASCAL FAR recv( SOCKET s, char FAR* buf, int len, int flags);
          s:一个标识已连接套接口的描述字。
          buf:用于接收数据的缓冲区。
          len:缓冲区长度。
          flags:指定调用方式。
         */

        printf("wait to recv message\n");

        struct timeval tv;

        // Set up the struct timeval for the timeout.
        tv.tv_sec = 30;
        tv.tv_usec = 0;
        
        // 添加socket延时
        // setsockopt(temp_sock_descriptor, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval));

        if (recv(temp_sock_descriptor, buf, 16384, 0) == -1)
        {
            perror("call to recv");
            exit(1);
        }

        printf("received from client:%s\n", buf);

        len = strlen(buf);
        for (i = 0; i < len; i++)
        {
            buf[i] = toupper(buf[i]); //将小写字母转化为大写字母
        }
        /*int PASCAL FAR send( SOCKET s, const char FAR* buf, int len, int flags);
          s:一个用于标识已连接套接口的描述字。
          buf:包含待发送数据的缓冲区。
          len:缓冲区中数据的长度。
          flags:调用执行方式。*/

        /*send()   基于链接的发送 TCP
         *sendto() 基于无链接到   UDP
         */

        if (send(temp_sock_descriptor, buf, len, 0) == -1)
        {
            perror("call to send");
            exit(1);
        }

        close(temp_sock_descriptor);
    }

    return (EXIT_SUCCESS);
}

client

#include <stdio.h>
#include <stdlib.h>

#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
/*客户端*/
// char *host_name = "fe80::20c:29ff:fe09:2d36"; //需要搜寻服务端IP地址
// char *host_name = "::1"; //需要搜寻服务端IP地址
int port = 8000;


int create_sock(char *servername, char *port)
{
    int sockfd;

    struct addrinfo hints;
    struct addrinfo *result;

    int i, len;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
    hints.ai_socktype = SOCK_STREAM; /* Datagram socket */
    hints.ai_flags = AI_PASSIVE;    /* For wildcard IP address */
    hints.ai_protocol = 0;          /* Any protocol */
    hints.ai_canonname = NULL;
    hints.ai_addr = NULL;
    hints.ai_next = NULL;

    int s = getaddrinfo(servername, port, &hints, &result);
    if (s != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
        exit(EXIT_FAILURE);
    }

    //socket函数,向系统申请一个通信端口
    sockfd = socket(result->ai_family, result->ai_socktype, 0); //IPV4 TCP协议
    if (sockfd == -1)                         //申请失败
    {
        perror("call to socket");
        exit(1);
    }

    //pin 定义跟服务端连接的 IP 端口
    if (connect(sockfd, result->ai_addr, result->ai_addrlen) == -1)
    {
        perror("Error connecting to socket \n"); ////
        exit(1);
    }
    freeaddrinfo(result);
    return sockfd;
}

/*argc: 整数,用来统计你运行程序时送给main函数的命令行参数的个数
 * argv: 字符串数组,用来存放指向你的字符串参数的指针数组,每一个元素指向一个参数
  argv[0] 指向程序运行的全路径名
  argv[1] 指向在DOS命令行中执行程序名后的第一个字符串
  argv[2] 指向执行程序名后的第二个字符串
   */
int main(int argc, char **argv)
{
    char buf[8192];
    char message[256];
    int socket_descriptor;
    /*
     * hostent记录主机的信息,包括主机名、别名、地址类型、地址长度和地址列表
     * struct hostent {
         char *h_name;地址的正式名称
         char **h_aliases;空字节-地址的预备名称的指针
         int h_addrtype;地址类型; 通常是AF_INET。
         int h_length;地址的比特长度。
         char **h_addr_list;零字节-主机网络地址指针。网络字节顺序。
      };
     #define h_addr h_addr_list[0] //h_addr_list中的第一地址
     */

    char *str = "A default test string";

    if (argc < 3) //运行程序时送给main函数到命令行参数个数
    {
        printf("Usage:test \"Any test string\"\n");
        printf("we will send a default test string. \n");
    }
    else
    {
        str = argv[2];
    }

    socket_descriptor = create_sock(argv[1], "8000");

    printf("Sending message %s to server \n", str);
    int num1 = 0;
    while(num1++ < 10) {
        printf("num1:%d\n", num1);
        sleep(1);
    }

    if (send(socket_descriptor, str, strlen(str), 0) == -1)
    {
        perror("Error in send\n");
        exit(1);
    }

    printf("..sent message.. wait for response...\n");

    if (recv(socket_descriptor, buf, 8192, 0) == -1)
    {
        perror("Error in receiving response from server \n");
        exit(1);
    }

    printf("\n Response from server:\n\n%s\n", buf);

    close(socket_descriptor);

    return (EXIT_SUCCESS);
}
words: 3535 tags: linux tcp c