recv函数不会阻塞和recv一些垃圾值

时间:2012-04-15 11:40:10

标签: c sockets recv

情况:我正在进行客户端 - 服务器聊天 最初它工作正常,然后我介绍了一个登录和注册系统 问题: 我在寄存器中介绍了两个功能:

  1. 如果用户能够成功注册,则会将其提升为登录 此问题:在服务器代码中,成功注册新用户后,我调用了login_check()函数。此函数首先阻止从客户端接收用户名。 But here recv get some garbage data which I haven't send from the client.为了解决这个问题,我在那里放了一个额外的recv来收集这些垃圾数据并忽略它。现在我的这部分代码工作正常,但 Why I need to put an extra recv there?

  2. 案例:在注册过程中,用户输入已存在的用户名。如果发生这种情况,我正在向客户端发送一条消息(有点信号)用户名已经存在。收到此消息后,客户端会要求用户再次注册或退出。然后从客户端我将用户的选择发送回服务器。 But there in server my recv got some garbage data not the choice I am sending and also it doesn't block(i.e server recv reading garbage data before user enter the choice at client). I have also tried to put some extra recv functions to get rid of garbage data but it doesn't work. So, why recv is not blocking here?

  3. 源代码:我在这里提供服务器和客户端的完整源代码。但是我的问题的相关功能是针对服务器:thread_function,rgstr,login_check和客户端:registerYourself和login

    SERVER CODE

    #include <pthread.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    #include <error.h>
    #include<fcntl.h>
    
    #define PORT "1618" //port we are listening on is a divine port
    
    struct clients{
    char name[50];
    char password[20];
    }client;
    
    
    //get sockaddr , IPv4 or IPv6:
    void *get_in_addr(struct sockaddr *sa)
    {
        if (sa->sa_family == AF_INET){
            return &(((struct sockaddr_in*)sa)->sin_addr);
        }
        return &(((struct sockaddr_in6*)sa)->sin6_addr);
    }
    
    //need to add one more string argument to show which part is calling this func
    void recv_err(int nbytes)
    {
        //got an error or connection is closed by client
        if(nbytes == 0) 
        {
            //connection closed
            printf("selectserver: socket hung up\n");
        } 
        else 
        {
            perror("recv");
        }
    }
    
    int login_check(int fd)
    {
        char data[50],choice[2],*name, *pass;
        int nbytes,ret_status,limit = 50;
        ///////////////////////////////////////   Here Problem        /////////////////////////////////////
        recv(fd,client.name,sizeof(client.name),0);//extra recv as mentioned in 1st doubt
        if(( nbytes = recv(fd,client.name,sizeof(client.name),0)) <= 0) //receiving username
        {
            recv_err(nbytes);
            return 0;
        }
        if(( nbytes = recv(fd,client.password,sizeof(client.password),0)) <= 0) //receiving password
        {
            recv_err(nbytes);
            return 0;
        }
        //opening file containing login info
        FILE * record = fopen("records.txt","r+");
        if(record == NULL)
        {
            //need to write errno to log
            return -1;
        }
        while(fgets(data,100,record)!=NULL)
        {//stored in file in one login info per lineas : username,password
            name = strtok(data,",");
            pass = strtok(NULL,",");
            pass = strtok(pass,"\n");
            if((strcmp(client.name,data)==0) && (strcmp(client.password,pass)==0))
            {
                if(send(fd,"1",2,0) == -1)//signaling client that everythin is fine
                {
                    recv_err(-1);
                    return 0;
                }
                return 1;
            }
        }
                if(send(fd,"0",2,0) == -1)//if username already exists
                {
                    recv_err(-1);
                    return 0;
                }
                //waiting for user chooice to enter pass again or not
                if((nbytes = recv(fd,choice,sizeof(choice),0)) <= 0)
                {
                    recv_err(-1);
                    return 0;
                }
                if(!strcmp(choice,"1"))
                {
                    ret_status = login_check(fd);
    
    
                    if(ret_status == 1)
                        return 1;
                    else if(ret_status == 0)
                        return 0;
                    else if(ret_status == -1)
                        return -1;      
                }
                else if(!strcmp(choice,"0"))
                {
                    return 0;       
                }
    
        return 0;   
    
    }
    
    int rgstr(int fd)
    {
        int nbytes,record_n,ret_status,limit = 50,passed =1;
        char data[50],choice[2], *name, *toBeWrit;
    
        if((nbytes = recv(fd,client.name,sizeof(client.name),0)) <= 0)//receiving username
        {
            recv_err(nbytes);
            return 0;
        }
        if((nbytes = recv(fd,client.password,sizeof(client.password),0)) <= 0) // receiving password
        {
            recv_err(nbytes);
            return 0;
        }
            //structing data to write in the file
        toBeWrit = (char *)malloc(strlen(client.name)+strlen(client.password)+3);
        strcpy(toBeWrit,client.name);
        strcat(toBeWrit,",");
        strcat(toBeWrit,client.password);
        strcat(toBeWrit,"\n");
    
        FILE * record = fopen("records.txt","r+");
        if(record == NULL)
        {
            //need to write errno to log
            return -1;
        }
        //checking if username already exists
        while(fgets(data,100,record)!=NULL)
        {
            name = strtok(data,",");
            if(strcmp(client.name,data)==0)
            {
                passed = 0;
                break;
            }
        }   
                //if everything work fine
        if(passed == 1)
        {
            if(send(fd,"1",2,0) == -1)//singaling client
            {
                recv_err(-1);
                return 0;
            }
            fclose(record);
    
            if((record_n = open("records.txt",O_WRONLY|O_APPEND)) == -1)
            {
                recv_err(-1);
                return 0;
            }
            if((nbytes =write(record_n,toBeWrit,strlen(toBeWrit))) <= 0)
            {
                recv_err(nbytes);
                return 0;
            }
    
            return 1;
        }
        else//if username already exists
        {
            if(send(fd,"0",2,0) == -1) //singaling client to again ask to user whether to rgstr agian or not
            {
                recv_err(-1);
                return 0;
            }
    /////////////////////////////////////            Here the problem              ///////////////////////////////
    
            //waiting for user chooice to enter pass again or not
            if((nbytes = recv(fd,choice,sizeof(choice),0)) <= 0) // this recv getting garbage data before user select its choice
            {
                recv_err(nbytes);
                return 0;
            }printf("3 %s %d\n",choice,nbytes);
    
            printf("cc\n");
            if(!strcmp(choice,"1"))
            {
                ret_status = rgstr(fd);
    
                if(ret_status == 1)
                    return 1;
                else if(ret_status == 0)
                    return 0;
                else if(ret_status == -1)
                    return -1;      
            }
            else if(!strcmp(choice,"0"))
            {
                return 0;       
            }
        }
        return 0;
    }
    
    void* thread_func(void* arg)// function that handles each connection
    {
        int fd=(int)arg, login_status=0, rgstr_status=0, record_n;
        char choice[2];
    
        //opening record.txt- creating file
        record_n = open("records.txt",O_CREAT,0644);    
        close(record_n);
    
        printf("Set to receive the choice:\n");
        recv(fd,choice,sizeof(choice),0);
        printf("Received choice %s\n",choice);      
        if(!strcmp(choice,"1"))
        {
            rgstr_status = rgstr(fd);
            if(rgstr_status == 1)
            {
                printf("New User Registered\n");
                strcpy(choice,"2");
            }
            else if(rgstr_status == 0)
            {
                printf("rUsr close the connection\n");
                close(fd);
                return ((void*)0);
            }
            else if(rgstr_status == -1)
            {
                printf("There is some problem..\n");
                close(fd);
                return ((void*)0);
            }
        }
        if(!strcmp(choice,"2"))
        {
            login_status = login_check(fd);
    
            if(login_status == 1)
            {
                printf("HI %s\n",client.name);
                while(1);
            }
            else if(login_status == 0)
            {
                printf("lUsr close the connection\n");
                close(fd);
                return ((void*)0);
            }
            else if(login_status == -1)
            {
                printf("There is some problem..\n");
                close(fd);
                return ((void*)0);
            }
        }
    
        printf("Done with user\n");
        return ((void*)0);
    }
    
    
    
    int main(void)
    {
    //  fd_set updated_fds; //main file descriptor list which is updated continously on any change
    //  fd_set read_fds; //temp file descriptor list for select() which always asiigned to updated_fds before call to select
    //  int fdmax; //The maximum filedescriptor for select
    
        int listener_fd; //listening the socket descriptor which is listening for new calls
        int new_fd;    //newly accept()ed file descriptor , for each connection
    
        struct sockaddr_storage clientaddr; // client address is stored in this with a typecasting dependent on family of connection (IPv4 or IPv6)
        socklen_t addrlen;  //length of clientaddr for inet_ntoi
    
        char buf[256];  //buffer for client - reading buffer
        int nbytes;//return value of recv function
    
        char remoteIP[INET6_ADDRSTRLEN]; //store the ip of the remote client. Its size is defined as INET6_ADDRSTRLEN because now it can store both IP - IPv4 and IPv6
    
        int yes=1; // integer passed to setsockopt
        int i, j, rv;
    
        struct addrinfo hints, *link_struc, *struc_iter;// to use with getaddrinfo
    
        int err; // pthread_create error
        pthread_t ntid;
        void *a;
    
    //  FD_ZERO(&updated_fds); //clear the updated_fds and temp sets
    //  FD_ZERO(&read_fds);
    
        //get us a socket and bind it
        memset(&hints, 0, sizeof hints);
    
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_flags = AI_PASSIVE;
    
        if((rv = getaddrinfo(NULL, PORT, &hints, &link_struc)) != 0) {
            fprintf(stderr, "selectserver : %s\n",gai_strerror(rv));
            exit(1);
        }
    
        for(struc_iter = link_struc;struc_iter != NULL ; struc_iter = struc_iter->ai_next) {
            listener_fd = socket(struc_iter->ai_family, struc_iter->ai_socktype, struc_iter->ai_protocol);
            if(listener_fd < 0){
                continue;
            }
    
            //lose the pesky "address already in use " error message
            setsockopt(listener_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
    
            if(bind(listener_fd, struc_iter->ai_addr, struc_iter->ai_addrlen) < 0) {
                close(listener_fd);
                continue;
            }
    
            break;
        }
    
        //if we got here, it means we didnot get bound
        if(struc_iter == NULL){
            fprintf(stderr, "selectserver: failed to bind\n");
            exit(2);
        }
    
        freeaddrinfo(link_struc); // all  done with this
    
        //listen
        if(listen(listener_fd, 10) == -1){ //make that socket active to listen with max connection queue - 10
            perror("listen");
            exit(3);
        }
    
        //add the listener_fd to the updated_fds set
    //  FD_SET(listener_fd, &updated_fds);
    
        //keep track of the bigge file descriptor
    //  fdmax = listener_fd; // so far it is this one
    
        //main loop
        for(;;){
            addrlen = sizeof clientaddr;
            new_fd = accept(listener_fd, (struct sockaddr *)&clientaddr, &addrlen);
    
            if(new_fd == -1){
                perror("accept");
                continue;
            }
    
            inet_ntop(clientaddr.ss_family, get_in_addr((struct sockaddr *)&clientaddr), remoteIP, INET6_ADDRSTRLEN);
            printf("server : got connection from %s\n", remoteIP);
    
            a=(void *)new_fd;
    
            err = pthread_create(&ntid, NULL, thread_func,a );
            if(err != 0)
            {
                printf("can't create thread: %s\n", strerror(err));
            }
    
            err = pthread_detach(ntid);
            if(err != 0)
            {
                printf("can't detaching thread: %s\n", strerror(err));
            }
        }//END for(;;)-- an you thought it would never end!
    
        return 0;
    }  
    

    Client Code

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <netdb.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    
    #define PORT "1618" //the port client will be connecting to
    #define MAXDATASIZE 100 // max number of bytes we can get at once
    
    static const int BUFFER_SIZE = 16*1024;
    //get sockaddr ,IPv4 or IPv6:
    
    
    
    
    void *get_in_addr(struct sockaddr *sa)
    {
        if(sa->sa_family ==AF_INET) {
            return &(((struct sockaddr_in*)sa)->sin_addr);
        }
    
        return &(((struct sockaddr_in6*)sa)->sin6_addr);
    }
    void registerYourself(int sockfd)
    {
        char username[50], password[50];
        printf("You have chosen to register yourself\n");
        printf("Please enter a username and password\n");
        printf("Username:");
        scanf("%s",username);
        //printf("You entered %s\n",username);
        send(sockfd,username,sizeof(username),0); // sending username
        printf("\nPassword:");
        scanf("%s",password);
        //printf("-- %s\n",password);
        send(sockfd,password,sizeof(password),0);       //sending password
    }
    
    void login(int sockfd)
    {
        char username[50],password[50];
        printf("You have chosen to login yourself\n");
        printf("Please enter a username and password\n");
        printf("Username:");
        scanf("%s",username);
    //  printf("-- %s\n",username);
        send(sockfd,username,sizeof(username),0);//send username
        printf("\nPassword:");
        scanf("%s",password);
        //printf("-- %s\n",password);
        send(sockfd,password,sizeof(password),0);//send password
    }
    
    
    int main(int argc, char *argv[])
    {
        char fname[50], s[INET6_ADDRSTRLEN], buf[MAXDATASIZE], username[50], password[50], registerStatus[2], login_status[2];
        int sockfd, numbytes,fp,i,rv, choice;
    
        struct addrinfo hints, *servinfo, *p;
    
        if(argc != 2) 
            {
                fprintf(stderr,"usage: client hostname\n");
                exit(1);
            }
    
            memset(&hints, 0, sizeof hints);
            hints.ai_family = AF_UNSPEC;
            hints.ai_socktype = SOCK_STREAM;
    
            if((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0) 
            {
                fprintf(stderr,"getaddrinfo: %s\n",gai_strerror(rv));
                return 1;
            }
    
            //lopp through all the results and connect to the first we can
            for(p = servinfo; p != NULL; p = p->ai_next) 
            {
                if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1){
                    perror("client: socket");
                    continue;
                }
    
                if(connect(sockfd, p->ai_addr, p->ai_addrlen) == -1)
                {
                    close(sockfd);
                    perror("client: connect");
                    continue;
                }
                break;
            }
    
            if(p ==NULL) 
            {
                fprintf(stderr,"client: failed to connect\n");
                return 2;
            }
    
            inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr), s, sizeof s);
            printf("client : connecting to %s\n", s);
    
            freeaddrinfo(servinfo); // all done with this structure
    
            printf("\n----------------Menu-------------------------\n1.Register\n2.Log in\n");
            scanf("%d",&i);
    
            switch(i)
            {
                case 1: 
                    send(sockfd,"1", 2, 0);//sending server that user want to register
                    registerYourself(sockfd);
    
                    recv(sockfd,registerStatus,sizeof(registerStatus),0);//receiving from server that register is successful or not
                    if(strcmp(registerStatus,"1")==0) //If register successful then promoting for log-in
                    {
                        login(sockfd);
                        break;
                    }
                    else if(strcmp(registerStatus,"0")==0)// Asking user what to do
                    {
                        printf("Username already taken...Please try again with other username...\nPress:\t 1 to try again\n\t2 to quit");
                        scanf("%d",&choice);
                        switch(choice)
                        {
                            case 1:
                                send(sockfd,"1", 2, 0);
                                login(sockfd);
                                break;
                            case 2:
                                send(sockfd,"0",2,0);
                                return;
                        }           
                    }
    
                case 2:
                    send(sockfd,"2", 2, 0); //sending server that user want to login
                    login(sockfd);
                    recv(sockfd,login_status,sizeof(login_status),0); // //receiving from server that login is successful or not
                    if(strcmp(login_status,"1")==0)
                    {
                        printf("login successfully\n");
                        while(1);// doing other job here
                    }
                    else if(strcmp(login_status,"0")==0)
                    {
                        printf("Wrong Username And/Or Password\nPress:\n__1)__To Enter Again  |  __2)__To Quit");
                        scanf("%d",&choice);
                        switch(choice)
                        {
                            case 1:
                                send(sockfd,"1", 2, 0);// sending that user want to login again
                                login(sockfd);
                                break;
                            case 2:
                                send(sockfd,"0",2,0);// sending that user want to stop
                                return;
                        }
                    }
                    break;//case 2 ends
            }//switch ends
    
            while(1); //doing other job
            close(sockfd);
    
            return 0;
    }
    

1 个答案:

答案 0 :(得分:6)

TCP是一种“流”协议,没有消息边界的概念,所以当你有这样的代码时:

void login(int sockfd)
{
    char username[50],password[50];
    ...
    scanf("%s",username);
    send(sockfd,username,sizeof(username),0);//send username
    ...
    scanf("%s",password);
    send(sockfd,password,sizeof(password),0);//send password
}

您尝试两次发送50个字节,但没有指示用户名结束的位置以及密码开始并导致recv稍后出现问题。

仔细阅读send的文档。 send返回成功发送的字节数,可以是&lt; 50.在简单测试中,这可能有效,但在高网络带宽期间,send可以并且发送的数量少于请求。如果未成功传输,您有责任检查返回值并发送其余数据。

另一个问题是你发送了整个usernamepassword缓冲区,所以如果用户输入“Mark”和“123”作为用户名和密码,你仍然会发送100个字节

在服务器端,缓冲区的大小不同:

struct clients{
char name[50];
char password[20];
}client;

所以当读取数据时:

recv(fd,client.name,sizeof(client.name),0);//extra recv as mentioned in 1st doubt
...
if(( nbytes = recv(fd,client.password,sizeof(client.password),0)) <= 0) //receiving password

50个字节,然后尝试读取20个字节,留下30个字节未读...在调用recv时,您的额外数据来源。

请注意,recv也没有消息边界的概念,并且可以返回少于请求的数据量,并且它可以包含一个send的最后一部分和另一个的第一部分。您有责任定义一个缓冲数据的协议,直到您有完整的消息。

由于您似乎总是发送以空字符结尾的字符串,因此一种算法将是:

  1. 维护recv缓冲区和当前长度计数。
  2. 当需要数据时,检查recv buffer是否为空终止字节。
  3. 如果没有空字节,请发出一个大的recv请求,比如4K字节,否则转到#5。
  4. 将收到的数据添加到recv缓冲区并返回#3。
  5. 从缓冲区中删除数据到nul,留下任何剩余的缓冲区以满足下一个请求。
  6. 还要确保只发送字符串和nul-termination字节,而不是整个缓冲区,并检查send的返回值。如果您发送100个字节并且send返回50,请再次使用指向剩余50个字节的指针调用send ...重复直到所有数据都被发送。