Socket学习二

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/buhuiguowang/article/details/81590597

socket 长连接PK短连接、P2P,流协议

  • 最开始并不能完成服务器一对多的要求??
    • 虽然客户端与服务器建立了连接,但服务器是执行不到accept函数的,一直停在循环里,只是单进程。所以这是前面服务器不能支持客户端多并发的原因。
    • 这里客户端能连接的原因是因为listen函数,告诉linux内核中的TCP/IP协议族要维护一系列的客户端连接。因此客户端实际上是 linux内核中的TCP/IP为其建立的TCP三次握手,并没有拿到服务器的连接。
    • 解决的办法就是前面提过的调用fork()函数,建立多进程。
服务器

#include <stdio.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    /*
    man socket
    int socket(int domain, int type, int protocol);
    */
    int sockfd = 0;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd == -1)
    {
        perror("fun socket\n");
        exit(0);
    }
    /*
    man 7 ip
    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
    };

    struct sockaddr
    {
        sa_family_t sa_family;
        char        sa_data[14];
    };
    */
    struct sockaddr_in srvaddr;
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(8001);
    srvaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

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

    struct sockaddr
    {
        sa_family_t sa_family;
        char        sa_data[14];
    };
    */
    if(bind(sockfd, (struct sockaddr *)&srvaddr, sizeof(srvaddr)) < 0)
    {
        perror("fun socket\n");
        exit(0);
    }

    /*
     man listen
     int listen(int sockfd, int backlog);
    */
    //Once you call listen function,this socket will become passive,it means that can only recive and doesn't send actively
    if(listen(sockfd, SOMAXCONN) < 0)
    {
        perror("fun listen\n");
        exit(0);
    }

    //struct sockaddr peeraddr;  //Universal ip
    //socklen_t peerlen;
    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
    };
    //man 2 accept
    //int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    struct sockaddr_in peeraddr;  //tcpip address structure
    socklen_t peerlen = sizeof(peerlen);
    unsigned int conn = 0;

    while(1)
    {
        //Accept will return a new connection, it is 
    //If there is no client connection, it will block here
        conn = accept(sockfd, (struct sockaddr *)&peeraddr, &peerlen);
        if(conn == -1)
        {
            perror("fun conn\n");
            exit(0);
        }
        printf("peeraddr:%s\n peerport:%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));

        int pid = fork();
        if(pid == 0)  //this process is child process
        {
            close(sockfd);    // child process doesn't need to listen
            char rvbuf[1024] = {0};
            while(1)
            {
                /*
                man 2 read
                size_t read(int fd, void *buf, size_t count);
                */
                int ret = read(conn, rvbuf, sizeof(rvbuf));
                if(ret == 0)
                {
                    //If the other party is already closed during the reading process,tcpip will return a zero packet
                    printf("peer is already closed\n");
                    exit(0);
                }
                else if(ret < 0)
                {
                    printf("read data failure\n");
                    exit(0);
                }
                fputs(rvbuf, stdout);  // server receives the data and prints the screen.
                /*
                man 2 write
                size_t write(int fd, const void *buf, size_t count);
                */
                write(conn, rvbuf, sizeof(ret));  //server sends pocket back       
            }
        }

        else if(pid > 0)   //parent process
        {
            close(conn);   //parent doesn't need connection
        }

        else
        {
            printf("create fork failure");
            exit(0);
        }
    }

    return 0;

}
  • 那么这里的子进程又是什么时候结束呢??
    • 这就涉及到长链接和短链接的知识了。
    • 一般来说是客户机决定连接是长连接还是短连接
  • 长连接:

    • 客户端在建立了一个连接后,还没有断开,又建立了一个连接即客户端和服务器的连接不断开,就是长连接。比如说上面的子进程
  • 短连接

    • 就是说客户端作完一个业务就和服务器断开,然后在需要的时候就再和服务器连接。 比如说上面的父进程。

P2P服务程序开发

  • 原理图:
  • 代码实现:
服务器
#include <stdio.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    /*  
    man socket
    int socket(int domain, int type, int protocol);
    */
    int sockfd = 0;
    sockfd = socket(AF_INET, SOCK_STREAM, 0); 
    if(sockfd == -1) 
    {   
        perror("fun socket\n");
        exit(0);
    }   
    /*
    man 7 ip
    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
    };

    struct sockaddr
    {
        sa_family_t sa_family;
        char        sa_data[14];
    };
    */
    struct sockaddr_in srvaddr;
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(8002);
    srvaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

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

    struct sockaddr
    {
        sa_family_t sa_family;
        char        sa_data[14];
    };
    */
    if(bind(sockfd, (struct sockaddr *)&srvaddr, sizeof(srvaddr)) < 0)
    {
        perror("fun socket\n");
        exit(0);
    }

    /*
     man listen
     int listen(int sockfd, int backlog);
    */
   //Once you call listen function,this socket will become passive,it means that can only recive and doesn't send actively
    if(listen(sockfd, SOMAXCONN) < 0)
    {
        perror("fun listen\n");
        exit(0);
    }

    //struct sockaddr peeraddr;  //Universal ip
    //socklen_t peerlen;
    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
    };
    //man 2 accept
    //int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    struct sockaddr_in peeraddr;  //tcpip address structure
    socklen_t peerlen = sizeof(peerlen);
    unsigned int conn = 0;
    //Accept will return a new connection, it is 
    conn = accept(sockfd, (struct sockaddr *)&peeraddr, &peerlen);
    if(conn == -1)
    {
        perror("fun conn\n");
        exit(0);
    }
    printf("peeraddr:%s\n peerport:%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
    char revbuf[1024];
    char sendbuf[1024];
    int pid = 0;
    pid = fork();
    if(pid>0)
    {
    while(1)
        {
            memset(revbuf,0, sizeof(revbuf));
            /*
            man 2 read
            size_t read(int fd, void *buf, size_t count);
            */
            int ret = read(conn, revbuf, sizeof(revbuf));
            if(ret == 0)
            {
                //If the other party is already closed during the reading process,tcpip will return a zero packet
                printf("peer is already closed\n");
                exit(0);
            }
            else if(ret < 0)
            {
                printf("read data failure\n");
                exit(0);
            }
            fputs(revbuf, stdout);  // server receives the data and prints the screen
            memset(revbuf, 0, sizeof(revbuf));
        }
    }
    else if(pid == 0)   //parent process
    {
        memset(sendbuf, 0, sizeof(sendbuf));
        while(fgets(sendbuf,sizeof(sendbuf), stdin) != NULL )
        {
            printf("sendbuf is:%s", sendbuf);
            write(conn, sendbuf, strlen(sendbuf));
            memset(sendbuf, 0, sizeof(sendbuf));
        }
    }
    close(sockfd);
    close(conn);

    return 0;
客户端

#include <stdio.h>
#include <string.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    /*  
    man socket
    int socket(int domain, int type, int protocol);
    */
    int sockfd = 0;
    sockfd = socket(AF_INET, SOCK_STREAM, 0); 
    if(sockfd == -1) 
    {   
        perror("fun socket\n");
        exit(0);
    }   
    /*  
    man 7 ip
    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
    };

    struct sockaddr
    {
        sa_family_t sa_family;
        char        sa_data[14];
    };
    */
    struct sockaddr_in srvaddr;
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(8002);
    srvaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    if(connect(sockfd, (struct sockaddr *)(&srvaddr), sizeof(srvaddr)) < 0)
    {
        perror("fun socket\n");
        exit(0);
    }

    char revbuf[1024];
    char sendbuf[1024];
    int pid = 0;
    pid = fork();
    if(pid>0)
    {
        memset(sendbuf, 0, sizeof(sendbuf));
        while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
        {
            printf("sendbuf is :%s", sendbuf);
            write(sockfd, sendbuf, strlen(sendbuf)); //server send message back
            memset(sendbuf, 0, sizeof(sendbuf));
     }
     }
    else if(pid==0)
    {
        memset(revbuf, 0, sizeof(revbuf));
        while(1)
        {
            read(sockfd, revbuf, sizeof(revbuf));
            fputs(revbuf, stdout);
            memset(revbuf, 0, sizeof(revbuf));
        }
    }
    close(sockfd);
    return 0;

}
  • 这里的客户端是怎么退出的呢:
    • 客户端主动退出调用close函数,或者客户端挂掉,tcpip协议会偷偷的向服务器发送FIN数据段。FIN字段就相当于一个\0,当服务器读到FIN字段的时候,服务器就就知道客户端断开了。
  • 利用信号来实现客户端断开,服务器退出。
#include <stdio.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

void handle (int num)
{
    printf("recv num:%d\n", num);
    exit(0);
}

int main()
{
    //Installation signal
    signal(SIGUSR1, handle);
    /*
    man socket
    int socket(int domain, int type, int protocol);
    */
    int sockfd = 0;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd == -1)
    {
        perror("fun socket\n");
        exit(0);
    }
    /*
    man 7 ip
    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
    };

    struct sockaddr
    {
        sa_family_t sa_family;
        char        sa_data[14];
    };
    */
    struct sockaddr_in srvaddr;
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(8002);
    srvaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

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

    struct sockaddr
    {
        sa_family_t sa_family;
        char        sa_data[14];
    };
    */
    if(bind(sockfd, (struct sockaddr *)&srvaddr, sizeof(srvaddr)) < 0)
    {
        perror("fun socket\n");
        exit(0);
    }

    /*
     man listen
     int listen(int sockfd, int backlog);
    */
    //Once you call listen function,this socket will become passive,it means that can only recive and doesn't send actively
    if(listen(sockfd, SOMAXCONN) < 0)
    {
        perror("fun listen\n");
        exit(0);
    }

    //struct sockaddr peeraddr;  //Universal ip
    //socklen_t peerlen;
    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
    };
    //man 2 accept
    //int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    struct sockaddr_in peeraddr;  //tcpip address structure
    socklen_t peerlen = sizeof(peerlen);
    unsigned int conn = 0;
    //Accept will return a new connection, it is 
    conn = accept(sockfd, (struct sockaddr *)&peeraddr, &peerlen);
    if(conn == -1)
    {
        perror("fun conn\n");
        exit(0);
    }
    printf("peeraddr:%s\n peerport:%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
    char revbuf[1024];
    char sendbuf[1024];
    int pid = 0;
    pid = fork();
    if(pid>0)
    {
        while(1)
        {
            memset(revbuf,0, sizeof(revbuf));
            /*
            man 2 read
            size_t read(int fd, void *buf, size_t count);
            */
            int ret = read(conn, revbuf, sizeof(revbuf));
            if(ret == 0)
            {
                //If the other party is already closed during the reading process,tcpip will return a zero packet
                printf("peer is already closed\n");
                break;
            }
            else if(ret < 0)
            {
                printf("read data failure\n");
                exit(0);
            }
            fputs(revbuf, stdout);  // server receives the data and prints the screen
            memset(revbuf, 0, sizeof(revbuf));
        }
        close(conn);
        kill(pid, SIGUSR1);
        sleep(1);
    }
    else if(pid == 0)   //parent process
    {
        memset(sendbuf, 0, sizeof(sendbuf));
        while(fgets(sendbuf,sizeof(sendbuf), stdin) != NULL )
        {
            printf("sendbuf is:%s", sendbuf);
            write(conn, sendbuf, strlen(sendbuf));
            memset(sendbuf, 0, sizeof(sendbuf));
        }
    }
    close(sockfd);

    return 0;
}
  • 如果服务器断开客户端怎么退出呢
#include <stdio.h>
#include <string.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

void handle (int num)
{
    printf("recv num:%d\n", num);
    exit(0);
}

int main()
{
    signal(SIGUSR1, handle);
    /*
    man socket
    int socket(int domain, int type, int protocol);
    */
    int sockfd = 0;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd == -1)
    {
        perror("fun socket\n");
        exit(0);
    }
    /*
    man 7 ip
    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
    };

    struct sockaddr
    {
        sa_family_t sa_family;
        char        sa_data[14];
    };
    */
    struct sockaddr_in srvaddr;
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(8002);
    srvaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    if(connect(sockfd, (struct sockaddr *)(&srvaddr), sizeof(srvaddr)) < 0)
    { 
        perror("fun socket\n");
        exit(0);
    }

    char revbuf[1024];
    char sendbuf[1024];
    int pid = 0;
    pid = fork();
    if(pid>0)
    {
        memset(sendbuf, 0, sizeof(sendbuf));
        while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
        {
            printf("sendbuf is :%s", sendbuf);
            write(sockfd, sendbuf, strlen(sendbuf)); //server send message back
            memset(sendbuf, 0, sizeof(sendbuf));
        }
    }
    else if(pid==0)
    {
        memset(revbuf, 0, sizeof(revbuf));
        while(1)
        {
            int ret = 0;
            ret = read(sockfd, revbuf, sizeof(revbuf));
            if(ret<0)
            {
                printf("read err\n");
                break;
            }
            if(ret==0)
            {
                printf("server closed\n");
                break;
            }
            fputs(revbuf, stdout);
            memset(revbuf, 0, sizeof(revbuf));
        }
        close(sockfd);
        kill(getppid(), SIGUSR1);
        exit(0);
    }
    return 0;

}

tcpip协议流与粘包

  • 比如说发送两个消息:M1,M2
    • 一个是流模式即无边界,这就可能导致服务器B可能同时收到M1、M2;也有可能在收到M1的同时,收到M2的一部分,下一次收到M2.2,也有可能收到M1.1,在下一次收到M2和M1.2。这就导致了读出现了很大的bug。前面read没有出问题只是在局域网内比较快,没有出现问题,但一旦放到公网,这里的问题就很大了
    • 解决办法:人为的定义流的边界
      • 目前主流的方法有两种:
      • 1:加 \r 或 \n
      • 2.加自定义报文:在数据的前面加报头,长度为4个字节
  • 产生粘包的原因
    • 1 SO_SNDBUF 套接字本身有缓冲区(发送缓冲区、接收缓冲区)
    • 2 tcp传送的端mss 大小限制
    • 3 链路层也有MTU大小限制,如果包大于>MTU(MTU是指一种通信协议的某一层上面所能通过的最大数据包大小(以字节为单位)。)要在IP层进行分片,导致消息分割。
    • 4 tcp的流量控制和拥塞控制,也可能导致粘包。
    • 5 tcp延迟发送机制 等等
  • 结论:tcp/ip协议,在传输层没有处理粘包问题。
  • 解决办法:
    • 1 定长包;
    • 2 包尾加 \r \n (ftp)
      • \n作为边界协议:
        • (ssize_t recv(int sockfd, void *buf, size_t len, int flags);)
        • 后面解释这个函数。
    • 3 包头加上包体长度(加4个字节 )
    • 4 更加复杂的应用层协议
  • readn, writen,readline函数

  • 为什么要封装一个readn 函数和 writen 函数,现有的read 函数和 write 含有有什么缺陷?

  • 这个是因为在调用read(或 write)函数的时候,读(写)一次的返回值可能不是我们想到读的字节数(即read函数中的 count 参数)就前面讲道的,这经常在读取管道,或者网络数去时出现。
  • readn函数
  • readn保证在没有遇到EOF的情况下,一定可以读取n个字节。它的返回值有三种:
          a) >0,表示成功读取的字节数,如果小于n,说明中间遇到了EOF;
          b)==0 表示一开始读取就遇到EOF;
          c) -1 表示错误(这里的errno绝对不是EINTR)。
    readn函数功能:读满n个字节才返回
    int readn(int fd, void *vptr, size_t n)
    {
      size_t          nleft = n;          //readn函数还需要读的字节数
      ssize_t        nread = 0;          //read函数读到的字节数
      unsigned char  *ptr = (char *)vptr; //指向缓冲区的指针
      while (nleft > 0)
      {
          nread = read(fd, ptr, nleft);
          if (-1 == nread)
          {
              if (EINTR == errno)
                  nread = 0;
              else
                  return -1;
          }
          else if (0 == nread)
          {
              break;
          }
          nleft -= nread;
          ptr += nread;
      }
      return n - nleft;
    }
    
  • writen函数
  • writen函数保证一定写满n个字节,返回值:
    a)n 表示写入成功n个字节
    b)-1 写入失败(这里也没有EINTR错误)
    writen函数功能:写满n个字节才返回
    writen代码实现:
    int writen(int fd, const void *vptr, size_t n)
    {
      size_t          nleft = n;  //writen函数还需要写的字节数
      ssize_t        nwrite = 0; //write函数本次向fd写的字节数
      const char*    ptr = vptr; //指向缓冲区的指针
      while (nleft > 0)
      {
          if ((nwrite = write(fd, ptr, nleft)) <= 0)
          {
              if (nwrite < 0 && EINTR == errno)
                  nwrite = 0;
              else
                  return -1;
          }
          nleft -= nwrite;
          ptr += nwrite;
      }
      return n;
    }
    

*加报头

/*********************************************************************************
 *      Copyright:  (C) 2018 anzhihong<[email protected]>
 *                  All rights reserved.
 *
 *       Filename:  socket_server.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(2018年08月04日)
 *         Author:  anzhihong <[email protected]>
 *      ChangeLog:  1, Release initial version on "2018年08月04日 09时00分22秒"
 *                 
 ********************************************************************************/
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

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


struct packet 
{
    int len;
    char buf[1024];
};

ssize_t readn(int fd, void *buf, size_t count)
{
    size_t          nleft = count; //the number of bytes that need to read
    ssize_t         nread;         //the number of bytes that read
    char            *bufp = (char*)buf;  

    while(nleft>0)
    {
        if((nread = read(fd, bufp, nleft)) < 0) //Rutrn the bytes that read when read successfully
        {
            if(errno == EINTR)   //If it is interrupted by a signal, it is not an error. Read again
                continue;
            return -1;
        }
        else if(nread == 0)         //The data read is empty and returns 0, indicating that the connection is disconnected
            return (count - nleft);

        bufp += nread;//读完nread字节数据之后,指针往下偏移这个量,下次的&bufd就是从这里开始
        nleft -= nread; //这里的设定是为了多次把nleft读取完,只要nleft还有即>0,就重复这个循环 
    }

    return count;   //全部执行正确返回读取的count,一开始是4,之后是len

}

ssize_t writen(int fd, void *buf, size_t count)
{
    size_t          nleft = count;
    ssize_t         nwritten;
    char            *bufp = (char*)buf;

    while(nleft>0)
    {
        if((nwritten = write(fd, bufp, nleft)) < 0)
        {
            if(errno == EINTR)
                continue;
            return -1;
        }
        else if(nwritten == 0)
            continue;

        bufp += nwritten;
        nleft -= nwritten;
    }

    return count;
}

void do_service(int conn)
{
    struct packet recvbuf;
    int n;
    while(1)
    {
        memset(&recvbuf, 0, sizeof(recvbuf));
        int ret = 0; 
        ret = readn(conn, &recvbuf.len, 4);
        if(ret == -1)
            printf("read erro\n");
        else if(ret < 4)
        {
            printf("client is closed\n");
            break;
        }

        n = ntohl(recvbuf.len);   //将网络字节序转换为机器字节序  这里 recvbuf.len 的长度等于n = strlen(sendbuf.buf);  sendbuf.len = htonl(n) 的长度

        ret = readn(conn, recvbuf.buf, n);
        if(ret == -1)
            printf("read erro\n");
        else if(ret < n)
        {
            printf("client is closed\n");
            break;
        }
        fputs(recvbuf.buf, stdout);

        writen(conn, &recvbuf, 4+n);
        memset(&recvbuf, 0, sizeof(recvbuf));

    }
}

int main(void)
{
    int sockfd;
    if((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
        printf("creat socket failure\n");

    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(8001);
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    //inet_aton("127.0.0.1", &servaddr.sin_addr);
    int optval = 1;
    if(setsockopt(sockfd, SOL_SOCKET,  SO_REUSEADDR, &optval,sizeof(optval)) < 0 )
        printf("setsockopt erro\n");

    if(bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
        printf("bind erro\n");

    if(listen(sockfd, SOMAXCONN) < 0)
        printf("listen erro\n");

    struct sockaddr_in peeraddr;  //tcpip address structure
    socklen_t peerlen = sizeof(peerlen);

    unsigned int conn = 0;
    pid_t pid;

    while(1)
    {
        if((conn = accept(sockfd, (struct sockaddr *)&peeraddr, &peerlen)) < 0)
            printf("accept erro\n");

        printf("peeraddr:%s\n peerport:%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));

        pid = fork();
        if(pid == 0)
        {
            close(sockfd);
            do_service(conn);
            exit(EXIT_SUCCESS);
        }
        else if(pid > 0)
        {
            close(conn);
        }
        else
        {
            printf("creat fork failure");
            exit(0);
        }

    }
    return 0;
}
/*********************************************************************************
 *      Copyright:  (C) 2018 anzhihong<[email protected]>
 *                  All rights reserved.
 *
 *       Filename:  socket_client.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(2018年08月04日)
 *         Author:  anzhihong <[email protected]>
 *      ChangeLog:  1, Release initial version on "2018年08月04日 10时03分33秒"
 *                 
 ********************************************************************************/

#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

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

#define ERR_EXIT(m) \
    do \
{\
        perror(m);\
        exit(EXIT_FAILURE);\
}while(0)

struct packet
{
    int len;
    char buf[1024];
};

ssize_t readn(int fd, void *buf, size_t count)
{
    size_t          nleft = count;
    ssize_t         nread;
    char            *bufp = (char*)buf;

    while(nleft>0)
    {
        if((nread = read(fd, bufp, nleft)) < 0)
        {

            if(errno == EINTR)
                continue;
            return -1;
        }
        else if(nread == 0)
            return (count - nleft);
        bufp += nread;
        nleft -= nread;
    }

    return count;
}

ssize_t writen(int fd, void *buf, size_t count)
{
    size_t          nleft = count;
    ssize_t         nwritten;
    char            *bufp = (char*)buf;

    while(nleft>0)
    {
        if((nwritten = write(fd, bufp, nleft)) < 0)
        {
            if(errno == EINTR)
                continue;

            return -1;
        }
        else if(nwritten == 0)
            continue;

        bufp += nwritten;
        nleft -= nwritten;
    }

    return count;
}

int main(void)
{
    int sockfd;
    if((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
        ERR_EXIT("creat socket failure");

    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(8001);
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    unsigned int conn = 0;

    if(connect(sockfd, (struct sockaddr*)(&servaddr), sizeof(servaddr)) < 0)
        ERR_EXIT("accept erro");

    struct packet sendbuf;
    struct packet recvbuf;
    memset(&sendbuf, 0, sizeof(sendbuf));
    memset(&recvbuf, 0, sizeof(recvbuf));

    int n = 0;
    while(fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin) != NULL)
    {
        printf("sendbuf is :%s\n", sendbuf.buf);
        n = strlen(sendbuf.buf);
        sendbuf.len = htonl(n);
        writen(sockfd, &sendbuf, 4+n);

        int ret = 0;
        ret = readn(sockfd, &recvbuf.len, 4);
        if(ret == -1)
            ERR_EXIT("read errno");
        else if(ret < 4)
        {
            printf("client is closed\n");
            break;
        }

        n = ntohl(recvbuf.len);
        ret = readn(sockfd, recvbuf.buf, n);
        if(ret == -1)
            ERR_EXIT("read errno");
        else if(ret < n)
        {
            printf("client is closed");
            break;
        }

        fputs(recvbuf.buf, stdout);
        memset(&sendbuf, 0, sizeof(sendbuf));
        memset(&recvbuf, 0, sizeof(recvbuf));
    }
    close(sockfd);

    return 0;
}
  • readline函数
  • readline函数:ssize_t readline(int fd, void *usrbuf, size_t maxlen),它的返回值:
        a)错误返回-1,不包括EINTR;
        b)取过程中碰到\n;
        c)没有碰到\n,而是读满maxlen-1个字节。
readline函数功能:读到'\n'或者读满缓冲区才返回
readline函数实现:
static ssize_t readch(int fd, char *ptr)
{
    static int          count = 0;
    static char*        read_ptr = 0;
    static char        read_buf[1024*4] = {0};
    if (count <= 0)
    {
    again:
        count = read(fd, read_buf, sizeof(read_buf));
        if (-1 == count)
            if (EINTR == errno)
                goto again;
            else
                return -1;
        else if (0 == count)
            return 0;
        read_ptr = read_buf;
    }
    count--;
    *ptr = *read_ptr++;
    return 1;
}
ssize_t readline(int fd, void *vptr, size_t maxlen)
{
    ssize_t        i = 0;
    ssize_t        ret = 0;
    char            ch = '\0';
    char*          ptr = NULL;
    ptr = (char *)vptr;
    for (i = 1; i < maxlen; ++i)
    {
        ret = readch(fd, &ch);
        if (1 == ret)
        {
            *ptr++ = ch;
            if ('\n' == ch)
                break;
        }
        else if (0 == ret)
        {
            *ptr = 0;
            return i-1;
        }
        else
            return -1;
    }
    *ptr = 0;
    return i;
}
  • recv函数
    • 函数原型:
    • int recv( In SOCKET s, Out char *buf, In int len, In int flags);
    • int flag 的参数功能:
    • MSG_DONTROUTE 绕过路由表查找。
      MSG_DONTWAIT 仅本操作非阻塞。
      MSG_OOB 发送或接收带外数据。 //常用
      MSG_PEEK 窥看外来消息。 // 常用(数据包的提前预读)
      MSG_WAITALL 等待所有数据。
      返回值:
      若无错误发生,recv()返回读入的字节数。如果连接已中止,返回0。如果发生错误,返回-1,应用程序可通过perror()获取相应错误信息

  • 加\n
/*********************************************************************************
 *      Copyright:  (C) 2018 anzhihong<[email protected]>
 *                  All rights reserved.
 *
 *       Filename:  socket_server.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(2018年08月05日)
 *         Author:  anzhihong <[email protected]>
 *      ChangeLog:  1, Release initial version on "2018年08月05日 08时23分10秒"
 *                 
 ********************************************************************************/

#include <unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

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

ssize_t readn(int fd,void *buf,size_t count)
{
    size_t nleft=count;//还需要读的字节数
    ssize_t nread;//已经接收字节数
    char *bufp=(char *)buf;

     while(nleft>0)
     {
        if((nread=read(fd,bufp,nleft))<0)
        {
            if(errno==EINTR)//read是可中断的,所以当被外来信号中断打断时,不算错误,任然继续执行
                continue;
            return -1;
        }
        else if(nread==0)//对等方关闭
            return count-nleft;//读到EOF,对方关闭
        bufp  += nread;
        nleft -= nread;
     }
    return count;
}

ssize_t writen(int fd,void *buf,size_t count)
{
    size_t nleft=count;//剩余发送字节数
    ssize_t nwritten;//已经发送字节数
    char *bufp=(char *)buf;

     while(nleft>0)//一般而言,write缓冲区大于发送数据缓冲区,不阻塞
     {
        if((nwritten=write(fd,bufp,nleft))<0)
        {
            if(errno==EINTR)//被中断
                continue;
            return -1;
        }
        else if(nwritten==0)//对等方关闭
            continue;//读到EOF,对方关闭
        bufp+=nwritten;
        nleft-=nwritten;
     }
     return count;
}

ssize_t recv_peek(int sockfd,void *buf,size_t len)
{
    while(1)
    {
        int ret=recv(sockfd,buf,len,MSG_PEEK); //提前偷窥缓冲区的数据,并不读取。
        if(ret==-1&&errno==EINTR)//操作被信号中断,recv认为链接正常,继续执行。
            continue;
        return ret;
    }
}

ssize_t readline(int sockfd,void *buf,size_t maxline)
{
    int ret = 0;
    int nread;
    char *bufp = buf;
    int nleft = maxline; //读取遇到\n返回,不会超过maxline

    while(1)
    {
        ret = recv_peek(sockfd, bufp, nleft);
        if(ret < 0)
            return ret;
        nread = ret;

        int i;
        for(i=0; i<nread; i++)
        {
            if(bufp[i] == '\n')
            {
                ret = readn(sockfd, bufp, i+1);
                if(ret != i+1)
                    exit(EXIT_FAILURE); //偷窥失败
                return ret;
            }
        }
        if(nread > nleft)    //偷窥到的数据不能大于maxline
            exit(EXIT_FAILURE);
        nleft -= nread; //剩余字节数
        ret = readn(sockfd, bufp, nread); //将已经读到数据nread从缓冲区移除
        if(ret != nread)
            exit(EXIT_FAILURE);
        bufp += nread;   //继续偷窥
    }
    return 1;
}

void do_service(int conn)
{
    char recvbuf[1024];

    while(1)
    {
        memset(recvbuf, 0, sizeof(recvbuf)); //初始化recvbuf
        int ret=readline(conn,recvbuf,1024);//一行一行接收数据。
        if(ret == -1)
            printf("readline");
        if(ret == 0)
        {
            printf("client close");
            break;
        }

        fputs(recvbuf, stdout);
        writen(conn, recvbuf, strlen(recvbuf));
    }
}

int main(void)
{
    int listenfd;
    /* if((listenfd=socket(AF_INET,SOCK_STEAM,IPPOTO_TCP))<0) */
    if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0)
        printf("socket err\n");

     //IPV4地址结构
     struct sockaddr_in servaddr;
     memset(&servaddr,0,sizeof(servaddr));
     servaddr.sin_family=AF_INET;//地址家族
     servaddr.sin_port=htons(8001);//端口,主机转网络
     /*servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");
     inet_aton("127.0.0.1",&servaddr.sin_addr);*/
     servaddr.sin_addr.s_addr=htonl(INADDR_ANY);

    //地址复用
    int on=1;
    if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
        printf("setsockopt err\n");

    //绑定端口号,ip地址
    if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
        printf("bind err\n");

    //监听
    if(listen(listenfd,SOMAXCONN)<0)
        printf("bind err\n");

    struct sockaddr_in peeraddr;
    socklen_t peerlen =sizeof(peeraddr);//typedef int socklen_t
    int conn;//已连接套接字
     pid_t pid;
     while(1)
     {
        if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
            printf("conn err\n");

        printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));

        pid = fork();
        if(pid==-1)
            printf("fork err\n");
        if(pid==0)
        {
            close(listenfd);
            do_service(conn);
            exit(EXIT_SUCCESS);//将子进程退出,要不它还会fork()
        }

        else
            close(conn);
     }
     return 0;

}
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

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


ssize_t readn(int fd,void *buf,size_t count)
{
//ssize_t=int,size_t=unsigned int
//接收count个字节数
        size_t nleft=count;//剩余字节数
        ssize_t nread;//已经接收字节数
        char *bufp=(char *)buf;
        while(nleft>0)
        {
                if((nread=read(fd,bufp,nleft))<0)
                {
                        if(errno==EINTR)//被中断
                                continue;
                        return -1;
                }
                else if(nread==0)//对等方关闭
                        return count-nleft;//读到EOF,对方关闭
                bufp+=nread;
                nleft-=nread;
        }
        return count;
}

ssize_t writen(int fd,void *buf,size_t count)
{
        size_t nleft=count;//剩余发送字节数
        ssize_t nwritten;//已经发送字节数
        char *bufp=(char *)buf;
        while(nleft>0)//一般而言,write缓冲区大于发送数据缓冲区,不阻塞
        {
                if((nwritten=write(fd,bufp,nleft))<0)
                {
                        if(errno==EINTR)//被中断
                                continue;
                        return -1;
                }
                else if(nwritten==0)//对等方关闭
                        continue;//读到EOF,对方关闭
                bufp+=nwritten;
                nleft-=nwritten;
        }
        return count;
}
ssize_t recv_peek(int sockfd,void *buf,size_t len)
{
        while(1){
                int ret=recv(sockfd,buf,len,MSG_PEEK);
                if(ret==-1&&errno==EINTR)//操作被信号中断,recv认为链接正常,继续
                        continue;
                return ret;//返回读取的字节数
        }

}
ssize_t readline(int sockfd,void *buf,size_t maxline)
{
        int ret;
        int nread;
        char *bufp=buf;
        int nleft=maxline;//读取遇到\n返回,不会超过maxline
        while(1){
                ret=recv_peek(sockfd,bufp,nleft);
                if(ret<=0)
                        return ret;
                nread=ret;
                int i;//接下来判断接收的缓冲区是否有\n
                for(i=0;i<nread;i++){
                        if(bufp[i]=='\n'){
                                ret=readn(sockfd,bufp,i+1);
                                if(ret!=i+1)
                                        exit(EXIT_FAILURE);//偷窥方法
                                return ret;
                        }
                }
                if(nread>nleft){
                //偷窥到的数据不能大于maxline
                        exit(EXIT_FAILURE);
                }
                nleft-=nread;//剩余字节数
                ret=readn(sockfd,bufp,nread);//将nread数据从缓冲区移除
                if(ret!=nread)
                        exit(EXIT_FAILURE);
                bufp+=nread;//继续偷窥
        }
        return 1;                       
}

//发送定长包
int main()
{
    int sock;
    /*if((listenfd=socket(AF_INET,SOCK_STEAM,IPPOTO_TCP))<0) */
    if((sock=socket(AF_INET,SOCK_STREAM,0))<0)
        printf("socket err\n");

    //IPV4地址结构
    struct sockaddr_in servaddr;
    memset(&servaddr,0,sizeof(servaddr));
    servaddr.sin_family=AF_INET;//地址家族
    servaddr.sin_port=htons(8001);//端口,主机转网络
    servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");
    /*inet_aton("127.0.0.1",&servaddr.sin_addr);*/


    if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
        printf("connect err\n");
    //getsockname

    char sendbuf[1024]={0};
    char recvbuf[1024]={0};
    while(fgets(sendbuf,sizeof(sendbuf),stdin) !=NULL){//从文件读取一行,送到缓冲区
        writen(sock,&sendbuf,strlen(sendbuf));//将接收到的数据发送出去
        int ret=readline(sock,recvbuf,sizeof(recvbuf));//函数从打开的文件,设备中读取数据
                if(ret==-1)
                {
                      printf("readline err");
                }
                else if(ret==0)
                {
                      printf("client_close\n");
                      break;
                }

        fputs(recvbuf,stdout);//发送数据到文件
        memset(sendbuf,0,sizeof(sendbuf));
        memset(recvbuf,0,sizeof(recvbuf));
    }
    close(sock);
    return 0;

}

猜你喜欢

转载自blog.csdn.net/buhuiguowang/article/details/81590597