[英]recv function doesn't block and recv some garbage value
情況:我正在與客戶/服務器聊天。
最初它運行良好,然后我介紹了一個登錄和注冊系統。
問題:我在寄存器中引入了兩個功能:
如果用戶能夠成功注冊,那么它將升級為登錄。
問題所在 :在服務器代碼中,成功注冊新用戶后,我正在調用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?
案例:在注冊過程中,用戶輸入了已經存在的用戶名。 如果發生這種情況,我正在向客戶端發送一條消息(有點信號),表明用戶名已經存在。 收到此消息后,客戶端會要求用戶再次注冊或退出。 然后從客戶端將用戶的選擇發送回服務器。 But there in server my recv got some garbage data not the choice I am sending and also it doesn't block(ie 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?
源代碼:我在這里提供服務器和客戶端的整個源代碼。 但是要查看我的問題的相關功能是針對服務器的: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;
}
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
文檔。 send
返回成功發送的字節數,該字節數可以小於50。在簡單的測試中,此方法可能有效,但在網絡帶寬較高的情況下, send
可以並且將發送的請求數量少於請求的數量。 您有責任檢查返回值並發送其余數據(如果未成功傳輸)。
另一個問題是您要發送整個username
和password
緩沖區,因此,如果用戶鍵入“ 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
的最后一部分和另一個send
的第一部分。 定義一個緩沖數據的協議是您的責任,直到您收到完整的消息為止。
由於您似乎總是發送以nul結尾的字符串,因此一種算法是:
recv
請求,例如4K字節,否則轉到#5。 還要確保僅發送字符串和nul終止字節,而不發送整個緩沖區,並檢查send的返回值。 如果發送100字節並send
返回50,請再次調用send
並指向剩余的50字節...重復直到所有數據發送完畢。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.