Reputation: 6615
The situation: I am making a client-server chat.
Initially it works fine, then I introduced a log-in and register system.
The Problem:
I have introduced two features in register:
If user able to register successfully, then it is promoted to log-in.
Problem in this: In server code, After successfully registering the new user I am calling login_check() function. This function first block to receive username from the client. But here recv get some garbage data which I haven't send from the client.
To get rid of this, I put one extra recv there which collects this garbage data and ignore it. Now My code for this part works fine but Why I need to put an extra recv there?
Case: during registering process, user enter a username which already exists. If this happens I am sending a message(kinda signal) to the client that username already exists. Receiving this the client asks user for again registering or to quit. Then from the client I am sending user's choice back to server. 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?
The Source Code: I am providing here the whole source code of server and client. But relevant function to see for my problem are for server: thread_function, rgstr, login_check and for client: registerYourself and 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;
}
Upvotes: 1
Views: 6984
Reputation: 177406
TCP is a "streaming" protocol and has no concept of message boundaries, so when you have code like this:
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
}
You are trying to send 50 bytes twice, with no indication where the username ends and the password begins and causing problems with recv
later.
Read the documentation of send
carefully. send
returns the number of bytes successfully sent, which can be < 50. In simple testing this may work, but during high network bandwidth send
can and will send less than requested. It is your responsibility to check the return value and send the rest of the data if it wasn't transmitted successfully.
Another problem is you are sending the entire username
and password
buffer, so if the user types "Mark" and "123" as the username and password, you'll still send 100 total bytes.
On the server side your buffers aren't the same size:
struct clients{
char name[50];
char password[20];
}client;
So when the data is read:
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 bytes, then 20 bytes are attempted to be read, leaving 30 bytes unread...the source of your extra data when calling recv
.
Note that recv
also has no concept of message boundaries and can return less than the amount of data requested, and it can contain the last part of one send
and the first part of another. It is your responsibility to define a protocol that buffers data until you have a complete message.
Since you seem to be always sending nul-terminated strings, one algorithm would be:
recv
request, say 4K bytes, else goto #5.Also make sure to send only the string and the nul-termination byte, not the entire buffer, and check the return value of send. If you send 100 bytes and send
returns 50, call send
again with a pointer to the remaining 50 bytes...repeat until all the data is sent.
Upvotes: 6