zoukankan      html  css  js  c++  java
  • linux网络编程-socket(2)

    当客户端调用close函数的时候,服务器的read函数读到的数据是0读到文件结束通知,表示对端关闭了tcp连接

    我们现实实现下面的功能:

     1、tcp客户端从标准的输入流中得到输入数据发送到服务器,服务器收到数据之后,不做任何改变,将书法返回给客户端,客户端收到服务器的数据之后,在标准输出流中输出

     

    上面代码中PF_INET和AF_INET都是一样的都是代码tcp的协议族

    tcp协议对应的流式套接字,所以写成sock_STREAM

    第三个参数可以写成IPPPOTO_TCP或者0都是可以的

    第四个结构体中服务器绑定的地址可以使用inet_addr函数将本机的点分十进制的ip地址转换成32位的网络地址

    也可以使用htonl函数将本机的任何地址转换成32位的函数,其中INADDR_ANY,表示本机的任何地址都可以

    对于端口也必须是网络字节序,所以需要使用htons将本机端口转换成16位的无符号网络字节端口

    Read函数

        Ssize_t read(int fd,void *buf,size_t nbyte)

        Read函数是负责从fd中读取内容,当读取成功时,read返回实际读取到的字节数,如果返回值是0,表示已经读取到文件的结束了,小于0表示是读取错误。

        如果错误是EINTR表示在写的时候出现了中断错误,如果是EPIPE表示网络连接出现了问题。

    Write函数

        Ssize_t write(int fd,const void *buf,size_t nbytes);

        Write函数将buf中的nbytes字节内容写入到文件描述符中,成功返回写的字节数,失败返回-1.并设置errno变量。在网络程序中,当我们向套接字文件描述舒服写数据时有两种可能:

    我们来看下服务器的代码:





    #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> /* *定义一个宏,输出错误信息并且退出 */ #define ERR_EXIT(m) do { perror(m); exit(EXIT_FAILURE); }while(0) int main(int argc, char *argv[]) { int serv_sock; struct sockaddr_in serv_addr; serv_sock = socket(AF_INET, SOCK_STREAM, 0); if (serv_sock == -1) { ERR_EXIT("socket创建失败"); } memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); serv_addr.sin_port = htons(9999); if((connect(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)))<0){ ERR_EXIT("客户端connect失败"); } char revbuf[1024]; char sendbuf[1024]; memset(revbuf,0,sizeof(revbuf)); memset(sendbuf,0,sizeof(revbuf)); while((fgets(sendbuf,sizeof(sendbuf),stdin)!= NULL)){ write(serv_sock,sendbuf,strlen(sendbuf)); read(serv_sock,revbuf,sizeof(revbuf)); fputs(revbuf,stdout); //读到多少数据就给客户端返回多少字节的数据 memset(sendbuf,0,sizeof(revbuf)); memset(revbuf,0,sizeof(revbuf)); } close(serv_sock); return 0; }

     服务器代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    
    /*
    *定义一个宏,输出错误信息并且退出 
    */
    #define ERR_EXIT(m) 
    do 
    {
    perror(m);
    exit(EXIT_FAILURE);
    }while(0)
    
    int main(int argc, char *argv[])
    {
        int serv_sock;
        int clnt_sock;
        
        struct sockaddr_in serv_addr;
        struct sockaddr_in clnt_addr;
        socklen_t clnt_addr_size;
        
         
        
        serv_sock = socket(AF_INET, SOCK_STREAM, 0);
        if (serv_sock == -1)
        {
        ERR_EXIT("socket创建失败");
        }
        
        
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        serv_addr.sin_port = htons(9999);
        
        if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1){
        ERR_EXIT("bind失败");
        }
        //SOMAXCON系统默认的最大的客户端的连接数据 , (listen(serv_sock, 5)表示最大允许5个客户端的连接 
        if (listen(serv_sock, SOMAXCONN) == -1){
        ERR_EXIT("listen失败");    
        }
        clnt_addr_size = sizeof(clnt_addr);
        clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
        if (clnt_sock == -1){
        ERR_EXIT("accept失败");    
        }
        
        char revbuf[1024];
        while(1){
        memset(revbuf,0,sizeof(revbuf));
        int len = read(clnt_sock,revbuf,sizeof(revbuf)); //len读到数据的字节长度 
        if(len == 0){ //说明客户端终止了数据的发送 
            break;
        }
        fputs(revbuf,stdout);
        //读到多少数据就给客户端返回多少字节的数据 
        write(clnt_sock,revbuf,len);
        }
        close(clnt_sock);
        close(serv_sock);
    
    return 0;
    }
    
    void error_handling(char *message)
    {
    fputs(message, stderr);
    fputc('
    ', stderr);
    exit(1);
    }

    在ubuntu系统上执行编译:

    gcc client.c -o client

    执行客户端程序就是./client

    编译服务器程序

    gcc server.c -o server

    执行服务器程序

    ./server

     上面的代码只能对应一个客户端连接一个服务器,如果一个服务器要支持多个客户端的请求,请看socke(37)章节的代码

    我们来看服务器的函数:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <pthread.h> 
    
    /*
    *定义一个宏,输出错误信息并且退出 
    */
    #define ERR_EXIT(m) 
    do 
    {
    perror(m);
    exit(EXIT_FAILURE);
    }while(0)
    
    void*thread_exc(void* arg){
        pthread_detach(pthread_self()); //将线程设置成分离状态,避免僵尸线程 
       int     clnt_sock = (int)arg;
       char revbuf[1024];
        while(1){
        memset(revbuf,0,sizeof(revbuf));
        int len = read(clnt_sock,revbuf,sizeof(revbuf)); //len读到数据的字节长度 
        if(len == 0){ //说明客户端终止了数据的发送 
            break;
        }
        fputs(revbuf,stdout);
        //读到多少数据就给客户端返回多少字节的数据 
        write(clnt_sock,revbuf,len);
        }
        close(clnt_sock); //记得关闭线程 
    }
    
    int main(int argc, char *argv[])
    {
        int serv_sock;
        int clnt_sock;
        
        struct sockaddr_in serv_addr;
        struct sockaddr_in clnt_addr;
        socklen_t clnt_addr_size;
        
         
        
        serv_sock = socket(AF_INET, SOCK_STREAM, 0);
        if (serv_sock == -1)
        {
        ERR_EXIT("socket创建失败");
        }
        
        
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        serv_addr.sin_port = htons(9999);
        
        if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1){
        ERR_EXIT("bind失败");
        }
        //SOMAXCON系统默认的最大的客户端的连接数据 , (listen(serv_sock, 5)表示最大允许5个客户端的连接 
        if (listen(serv_sock, SOMAXCONN) == -1){
        ERR_EXIT("listen失败");    
        }
        
        while(1){ //在while循环中一直等待客户端的监听
         
          clnt_addr_size = sizeof(clnt_addr);
           clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
           if (clnt_sock == -1){
              ERR_EXIT("accept失败");    
           }
           //每一个客户端的请求都开启一个线程进行处理
              pthread_t thread_id ;
              int ret;
          //将clnt_sock通过第三个参数传递到线程函数中 
        if((ret = pthread_create(&thread_id,NULL,thread_exc,(void*)clnt_sock)) != 0){
            ERR_EXIT("线程创建失败");
        }
            
        }
        
        
        close(serv_sock);
    
    return 0;
    }
    
    void error_handling(char *message)
    {
    fputs(message, stderr);
    fputc('
    ', stderr);
    exit(1);
    }

    我们来看客户端的函数:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    
    /*
    *定义一个宏,输出错误信息并且退出 
    */
    #define ERR_EXIT(m) 
         do 
         {
            perror(m);
            exit(EXIT_FAILURE);
         }while(0)
    
    int main(int argc, char *argv[])
    {
        int serv_sock;
        struct sockaddr_in serv_addr;
    
    
    
        serv_sock = socket(AF_INET, SOCK_STREAM, 0);
        if (serv_sock == -1)
            {
                ERR_EXIT("socket创建失败");
            }
    
    
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        serv_addr.sin_port = htons(9999);
    
    
    
        if((connect(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)))<0){
            ERR_EXIT("客户端connect失败");
        }
        
           char revbuf[1024];
            char sendbuf[1024];
          memset(revbuf,0,sizeof(revbuf));
          memset(sendbuf,0,sizeof(revbuf));
         while((fgets(sendbuf,sizeof(sendbuf),stdin)!= NULL)){
    
            write(serv_sock,sendbuf,strlen(sendbuf));
            read(serv_sock,revbuf,sizeof(revbuf));
             fputs(revbuf,stdout);
             //读到多少数据就给客户端返回多少字节的数据 
             memset(sendbuf,0,sizeof(revbuf));
             memset(revbuf,0,sizeof(revbuf));
         }
        close(serv_sock);
    
        return 0;
    }

    对应服务器的函数:有一点很关键在创建线程的的执行函数的入口处调用 pthread_detach(pthread_self()); //将线程设置成分离状态,避免僵尸线程 

    第二在客户端关闭连接,服务器read字节数为0的时候,记得关闭客户端的连接

    close(clnt_sock); //记得关闭线程
    }

    我们来看程序运行的效果

    服务器端收到了客户端1和客户端2的数据

     客户端1:

    客户端2:

    这里千万不能将线程的地址传递进行会存在多线程隐患的问题,千万不能写成(void*)&clnt_sock

    void*thread_exc(void* arg){
    pthread_detach(pthread_self()); //将线程设置成分离状态,避免僵尸线程
    int clnt_sock = *(*int)arg;

    这样会存在多线程隐患的问题,当第一个线程正在执行thread_exc执行int clnt_sock = *(*int)arg或者自己线程的sockid的时候,此时第二个线程创建了成功改变了sockid的值,第一个线程通过*(*int)arg获得的sockid就是刚刚创建的第二个线程的。所以这里存在线程安全问题,所以不能使用指针传递,所以必须使用值传递

    但是上面的代码还存在一个小bug

    就是将int类型强制转换成了void*类型void*)clnt_sock)存在问题,例如在64位的系统上指针void*是8个字节,int是4个字节,存在转换的问题,可以使用下面的方式进行解决

    程序的代码修改如下所示:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <pthread.h> 
    
    /*
    *定义一个宏,输出错误信息并且退出 
    */
    #define ERR_EXIT(m) 
    do 
    {
    perror(m);
    exit(EXIT_FAILURE);
    }while(0)
    
    void*thread_exc(void* arg){
        pthread_detach(pthread_self()); //将线程设置成分离状态,避免僵尸线程 
       int     clnt_sock = *((int*)arg);
       //记得关闭指针
       free(arg); 
       char revbuf[1024];
        while(1){
        memset(revbuf,0,sizeof(revbuf));
        int len = read(clnt_sock,revbuf,sizeof(revbuf)); //len读到数据的字节长度 
        if(len == 0){ //说明客户端终止了数据的发送 
            break;
        }
        fputs(revbuf,stdout);
        //读到多少数据就给客户端返回多少字节的数据 
        write(clnt_sock,revbuf,len);
        }
        close(clnt_sock); //记得关闭线程 
    }
    
    int main(int argc, char *argv[])
    {
        int serv_sock;
        int clnt_sock;
        
        struct sockaddr_in serv_addr;
        struct sockaddr_in clnt_addr;
        socklen_t clnt_addr_size;
        
         
        
        serv_sock = socket(AF_INET, SOCK_STREAM, 0);
        if (serv_sock == -1)
        {
        ERR_EXIT("socket创建失败");
        }
        
        
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        serv_addr.sin_port = htons(9999);
        
        if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1){
        ERR_EXIT("bind失败");
        }
        //SOMAXCON系统默认的最大的客户端的连接数据 , (listen(serv_sock, 5)表示最大允许5个客户端的连接 
        if (listen(serv_sock, SOMAXCONN) == -1){
        ERR_EXIT("listen失败");    
        }
        
        while(1){ //在while循环中一直等待客户端的监听
         
          clnt_addr_size = sizeof(clnt_addr);
           clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
           if (clnt_sock == -1){
              ERR_EXIT("accept失败");    
           }
           //每一个客户端的请求都开启一个线程进行处理
              pthread_t thread_id ;
              int ret;
          //将clnt_sock通过第三个参数传递到线程函数中 
          int * p = (int*)malloc(sizeof(int));
          *p = clnt_sock;
        if((ret = pthread_create(&thread_id,NULL,thread_exc, p ))!= 0){
            ERR_EXIT("线程创建失败");
        }
            
        }
        
        
        close(serv_sock);
    
    return 0;
    }
    
    void error_handling(char *message)
    {
    fputs(message, stderr);
    fputc('
    ', stderr);
    exit(1);
    }
  • 相关阅读:
    安装node-gyp
    node版本切换
    electron-vue运行只出现项目目录不出现效果
    高级运维工程师的必备技术
    linux 下的shutdown指令
    数据库实体联系模型与关系模型
    数据库表设计1
    实体-关系模型
    Excel中怎么快速选中区域
    EXCEL中给包含某个字段的单元格所在行标注颜色
  • 原文地址:https://www.cnblogs.com/kebibuluan/p/7086423.html
Copyright © 2011-2022 走看看