服务器程序卡在 read() 函数上,但 write() 函数在客户端程序中运行良好
Server program stuck on read() function but write() function works well in the client program
我正在开发一个简单的聊天室程序,使用套接字编程方法 C。我已经实现了客户端可以连接到服务器并向服务器发送消息,服务器应该向其他人分发消息。
但是当我尝试测试我的程序并尝试让两个客户端进行通信时,发生了一些奇怪的事情。如果我按照以下步骤操作,就会发生奇怪的事情。
- 运行 服务器程序正在等待连接。
- 打开一个客户端程序并连接到服务器,然后发送两条消息,都成功。
- 再打开一个客户端,同样操作,第一个客户端可以完美接收到第二个客户端发送的消息
- 但是当我切换到第一台服务器并尝试编写新消息并发送到要分发的服务器时,它失败了。并且第二个客户端和服务器都无法接收到此消息。
- 经过一些调试,服务器程序似乎卡在了 read() 函数上。
顺便说一下,我在我的 Uubuntu18.04 虚拟机上在不同的终端.
上完成了所有这些工作
更多积分:
- 我已经尝试调试并打印了所有我能得到的值,客户端程序的write()函数似乎可以正常工作,但是服务器程序在第二个函数read( ) 在接收线程中。
- 我认为这可能是关于 read() 函数的阻塞状态的问题,但我是网络编程的新手,不太明白。
这是我的问题截图(聊天室:左边是服务器,右边是两个客户端):
这是我的client.c和server.c代码,我已经指出了问题发生的位置。
CLIENT.C
/* tcp-client.c */
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <strings.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
//send the message to the server any time terminal get input
void* Send(void* Socked)
{
char sender[80];
//save the socked into a int pointer
int *SockedCopy = Socked;
while(fgets(sender, sizeof(sender), stdin)){
//whenever enter a string, send it
int messageSize = strlen(sender) + 1;
write(*SockedCopy, &messageSize, sizeof(int));
int i = write(*SockedCopy, sender, messageSize); //both work when sending message
printf("write over! the write() returns %d\n", i);
//check whether this is a quit message
if(strcmp(sender, "q!") == 0)
exit(1);
}
}
//receive message from server
void* Receive(void* Socked)
{
int *SockedCopy = Socked;
char Receiver[80];
while(1){
//read message continuosly
int reveiverEnd = 0;
reveiverEnd = read (*SockedCopy, Receiver, 1000);
Receiver[reveiverEnd] = '[=10=]';
fputs(Receiver, stdout);
Receiver[0] = '[=10=]';
}
}
int main ()
{
int sockfd, n;
pthread_t threadSend;
pthread_t threadReceive;
struct sockaddr_in serv, cli;
char rec[1000];
char send[80];
//input UserName
printf("Input Username:" );
fgets(send, sizeof(send), stdin);
send[strlen(send) - 1] = '[=10=]';
int MessageSize = strlen(send);
//create socked
sockfd = socket (PF_INET, SOCK_STREAM, 0);
//create server information
bzero (&serv, sizeof (serv));
serv.sin_family = PF_INET;
serv.sin_port = htons (8888);
serv.sin_addr.s_addr = inet_addr ("127.0.0.1" /*local machine*/);
//connect to the server
connect (sockfd, (struct sockaddr *) &serv, sizeof (struct sockaddr));
//send the user name to the server
write(sockfd, &MessageSize, sizeof(int));
write (sockfd, send, sizeof(send));
//get successfully connecting message
n = read (sockfd, rec, 1000);//n marks real length
rec[n] = '[=10=]';
fputs(rec, stdout);
send[0] = '[=10=]';
//open send thread
pthread_create(&threadSend, 0, Send, &sockfd);
//open receiving message thread
pthread_create(&threadReceive, 0, Receive, &sockfd);
//close socked and close two threads
for(int i = 0; i < 100; ++i)
sleep(100000);
pthread_exit(&threadSend);
pthread_exit(&threadReceive);
close(sockfd);
return 0;
}
SERVER.C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <unistd.h>
#include <unistd.h>
pthread_t thread;
pthread_t threadClient[100];
int ServerSock;
//Every client's information
typedef struct
{
int sock;
char UserName[16];
struct sockaddr address;
int addr_len;
} connection_t;
static connection_t conn[100];
//this function distributes the messsage/status of single client to the other
//Info is the message needed to be distributed
int SendInfo(void* Info)
{
char *info = Info;
for(int i = 0; i < 100; ++i)
//send to the client that exists and doesn't quit room
if(conn[i].addr_len != -1 && conn[i].addr_len != 0){
if(send (conn[i].sock, info , strlen(info) + 1, 0) == -1)
printf("error occured, send to %s fail", conn[i].UserName);
printf("send %s to %s successfully!\n", info, conn[i].UserName);
}
return 0;
}
//This function deals with single client, aim to receive message from this client
//and then send them to another using SendIinfo
void* Receive(void* clientnumber)
{
int* Clientnumber = clientnumber;
while(1)
{
//read the message from the client
char *Buffer;
int messageLen = 0;
read(conn[*Clientnumber].sock, &messageLen, sizeof(int));
printf("receive from %d\n", messageLen);
if(messageLen > 0)
{
Buffer = (char *)malloc((messageLen+1)*sizeof(char));
read(conn[*Clientnumber].sock, Buffer, messageLen); // the program stucks here and don't know why
if(Buffer[0] != ':') continue;
Buffer[messageLen] = '[=11=]';
//whether the client want to quit
if( Buffer[1] == 'q' && Buffer[2] == '!' )
{
//constitute quit message and delete this client
char quit[] = " quit the chat room";
char quitMessage[20];
quitMessage[0] = '[=11=]';
strcat(conn[*Clientnumber].UserName, quit);
SendInfo(quitMessage);
conn[*Clientnumber].addr_len = -1;
pthread_exit(&threadClient[*Clientnumber]);
}
else{
//constitute the message
char begin[] = " says";
char messageDistribute[200];
messageDistribute[0] = '[=11=]';
strcat(messageDistribute, conn[*Clientnumber].UserName);
strcat(messageDistribute, begin);
strcat(messageDistribute, Buffer);
SendInfo(messageDistribute);
}
free(Buffer);
}
else
continue;
}
}
//aim to accept whenever there is a client trying to connect
void * process(void * ptr)
{
pthread_t clientThread[100];
char * buffer;
int len;
//the number of the client connecting now
int clientNumber = 0;
long addr = 0;
while(1){
//waiting to be connected
if(clientNumber < 100)
{
conn[clientNumber].sock = accept(ServerSock, &conn[clientNumber].address, &conn[clientNumber].addr_len);
}
else
break;
//the length of the message
read(conn[clientNumber].sock, &len, sizeof(int));
if (len > 0)
{
//multiple information of a client
addr = (long)((struct sockaddr_in *)&conn[clientNumber].address)->sin_addr.s_addr;
buffer = (char *)malloc((len+1)*sizeof(char));
buffer[len] = 0;
read(conn[clientNumber].sock, buffer, len);
//send success message to the client
send (conn[clientNumber].sock, "You have entered the chatroom, Start CHATTING Now!\n", 51, 0);
//save client's nick name
strcpy(conn[clientNumber].UserName, buffer);
printf("User <%s> has entered the Chatroom!\n", conn[clientNumber].UserName);
printf("There are %d people in Chatroom now!\n",clientNumber+1);
free(buffer);
//create a thread dealing the messages from a single client
int number = clientNumber;
pthread_create(&threadClient[clientNumber], 0, Receive, &number);
}
clientNumber += 1;
}
pthread_exit(0);
}
int main(int argc, char ** argv){
struct sockaddr_in address;
int port = 8888;
connection_t * connection;
//create socked
ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//bind the socked to a port
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
if (bind(ServerSock, (struct sockaddr *)&address, sizeof(struct sockaddr_in)) < 0)
{
fprintf(stderr, "error: cannot bind socket to port %d\n", port);
return -4;
}
//listen for connections
listen(ServerSock, 100);
printf("the server is ready and listening\n");
//creating a thread dealing with connections
pthread_create(&thread, 0, process, (void *)connection);
//keep this program working
for(int i = 0; i < 100; ++i)
sleep(10000);
//close socked and thread
pthread_detach(thread);
close(ServerSock);
return 0;
}
这是一个学术作业。
您的问题根本不是网络问题,而是您将 clientnumber
参数传递给 Receive
线程的方式出错:
if (len > 0)
{
[...]
int number = clientNumber;
pthread_create(&threadClient[clientNumber], 0, Receive, &number);
} // number gets destroyed here
请注意,在上面的代码中,number
是一个局部变量,这意味着它会在范围退出时立即被销毁(即一旦执行到 }
行,上面).
但是,您正在将指向 number
(即 &number
)的指针传递给您的接收线程,并且您的接收线程会尝试使用该指针来查找有关客户端的数据。这意味着 Receive
线程正在取消引用悬空指针,这会调用未定义的行为。 (在这种情况下,我看到的是程序运行了一小段时间,然后停止运行,因为 Clientnumber
指向的内存位置已被其他数据覆盖,因此下一次Receive
线程试图取消引用指针,它最终会查看错误的数据,因此在错误的套接字 fd 上调用 read()
...但原则上任何事情都可能发生,因为一旦你调用未定义的行为,所有赌注均无效)
无论如何,解决问题的一种简单方法是直接传递指向 connection_t
对象的指针,而不是传递索引,即:
pthread_create(&threadClient[clientNumber], 0, Receive, &conn[clientNumber] );
..... 然后你可以直接在你的 Receive
线程中使用那个指针:
void* Receive(void* arg)
{
connection_t * conn = (connection_t *) arg;
[...]
int r = read(conn->sock, &messageLen, sizeof(int));
这样做不会调用未定义的行为,因为connection_t conn[100];
数组被声明为static/global,因此保证在进程退出之前不会销毁其中的对象.
如果您仍然需要从 Receive()
中访问 clientNumber
索引值,您可以将该值添加为 connection_t
结构定义中的另一个字段。
我正在开发一个简单的聊天室程序,使用套接字编程方法 C。我已经实现了客户端可以连接到服务器并向服务器发送消息,服务器应该向其他人分发消息。
但是当我尝试测试我的程序并尝试让两个客户端进行通信时,发生了一些奇怪的事情。如果我按照以下步骤操作,就会发生奇怪的事情。
- 运行 服务器程序正在等待连接。
- 打开一个客户端程序并连接到服务器,然后发送两条消息,都成功。
- 再打开一个客户端,同样操作,第一个客户端可以完美接收到第二个客户端发送的消息
- 但是当我切换到第一台服务器并尝试编写新消息并发送到要分发的服务器时,它失败了。并且第二个客户端和服务器都无法接收到此消息。
- 经过一些调试,服务器程序似乎卡在了 read() 函数上。 顺便说一下,我在我的 Uubuntu18.04 虚拟机上在不同的终端. 上完成了所有这些工作
更多积分:
- 我已经尝试调试并打印了所有我能得到的值,客户端程序的write()函数似乎可以正常工作,但是服务器程序在第二个函数read( ) 在接收线程中。
- 我认为这可能是关于 read() 函数的阻塞状态的问题,但我是网络编程的新手,不太明白。
这是我的问题截图(聊天室:左边是服务器,右边是两个客户端):
这是我的client.c和server.c代码,我已经指出了问题发生的位置。
CLIENT.C
/* tcp-client.c */
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <strings.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
//send the message to the server any time terminal get input
void* Send(void* Socked)
{
char sender[80];
//save the socked into a int pointer
int *SockedCopy = Socked;
while(fgets(sender, sizeof(sender), stdin)){
//whenever enter a string, send it
int messageSize = strlen(sender) + 1;
write(*SockedCopy, &messageSize, sizeof(int));
int i = write(*SockedCopy, sender, messageSize); //both work when sending message
printf("write over! the write() returns %d\n", i);
//check whether this is a quit message
if(strcmp(sender, "q!") == 0)
exit(1);
}
}
//receive message from server
void* Receive(void* Socked)
{
int *SockedCopy = Socked;
char Receiver[80];
while(1){
//read message continuosly
int reveiverEnd = 0;
reveiverEnd = read (*SockedCopy, Receiver, 1000);
Receiver[reveiverEnd] = '[=10=]';
fputs(Receiver, stdout);
Receiver[0] = '[=10=]';
}
}
int main ()
{
int sockfd, n;
pthread_t threadSend;
pthread_t threadReceive;
struct sockaddr_in serv, cli;
char rec[1000];
char send[80];
//input UserName
printf("Input Username:" );
fgets(send, sizeof(send), stdin);
send[strlen(send) - 1] = '[=10=]';
int MessageSize = strlen(send);
//create socked
sockfd = socket (PF_INET, SOCK_STREAM, 0);
//create server information
bzero (&serv, sizeof (serv));
serv.sin_family = PF_INET;
serv.sin_port = htons (8888);
serv.sin_addr.s_addr = inet_addr ("127.0.0.1" /*local machine*/);
//connect to the server
connect (sockfd, (struct sockaddr *) &serv, sizeof (struct sockaddr));
//send the user name to the server
write(sockfd, &MessageSize, sizeof(int));
write (sockfd, send, sizeof(send));
//get successfully connecting message
n = read (sockfd, rec, 1000);//n marks real length
rec[n] = '[=10=]';
fputs(rec, stdout);
send[0] = '[=10=]';
//open send thread
pthread_create(&threadSend, 0, Send, &sockfd);
//open receiving message thread
pthread_create(&threadReceive, 0, Receive, &sockfd);
//close socked and close two threads
for(int i = 0; i < 100; ++i)
sleep(100000);
pthread_exit(&threadSend);
pthread_exit(&threadReceive);
close(sockfd);
return 0;
}
SERVER.C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <unistd.h>
#include <unistd.h>
pthread_t thread;
pthread_t threadClient[100];
int ServerSock;
//Every client's information
typedef struct
{
int sock;
char UserName[16];
struct sockaddr address;
int addr_len;
} connection_t;
static connection_t conn[100];
//this function distributes the messsage/status of single client to the other
//Info is the message needed to be distributed
int SendInfo(void* Info)
{
char *info = Info;
for(int i = 0; i < 100; ++i)
//send to the client that exists and doesn't quit room
if(conn[i].addr_len != -1 && conn[i].addr_len != 0){
if(send (conn[i].sock, info , strlen(info) + 1, 0) == -1)
printf("error occured, send to %s fail", conn[i].UserName);
printf("send %s to %s successfully!\n", info, conn[i].UserName);
}
return 0;
}
//This function deals with single client, aim to receive message from this client
//and then send them to another using SendIinfo
void* Receive(void* clientnumber)
{
int* Clientnumber = clientnumber;
while(1)
{
//read the message from the client
char *Buffer;
int messageLen = 0;
read(conn[*Clientnumber].sock, &messageLen, sizeof(int));
printf("receive from %d\n", messageLen);
if(messageLen > 0)
{
Buffer = (char *)malloc((messageLen+1)*sizeof(char));
read(conn[*Clientnumber].sock, Buffer, messageLen); // the program stucks here and don't know why
if(Buffer[0] != ':') continue;
Buffer[messageLen] = '[=11=]';
//whether the client want to quit
if( Buffer[1] == 'q' && Buffer[2] == '!' )
{
//constitute quit message and delete this client
char quit[] = " quit the chat room";
char quitMessage[20];
quitMessage[0] = '[=11=]';
strcat(conn[*Clientnumber].UserName, quit);
SendInfo(quitMessage);
conn[*Clientnumber].addr_len = -1;
pthread_exit(&threadClient[*Clientnumber]);
}
else{
//constitute the message
char begin[] = " says";
char messageDistribute[200];
messageDistribute[0] = '[=11=]';
strcat(messageDistribute, conn[*Clientnumber].UserName);
strcat(messageDistribute, begin);
strcat(messageDistribute, Buffer);
SendInfo(messageDistribute);
}
free(Buffer);
}
else
continue;
}
}
//aim to accept whenever there is a client trying to connect
void * process(void * ptr)
{
pthread_t clientThread[100];
char * buffer;
int len;
//the number of the client connecting now
int clientNumber = 0;
long addr = 0;
while(1){
//waiting to be connected
if(clientNumber < 100)
{
conn[clientNumber].sock = accept(ServerSock, &conn[clientNumber].address, &conn[clientNumber].addr_len);
}
else
break;
//the length of the message
read(conn[clientNumber].sock, &len, sizeof(int));
if (len > 0)
{
//multiple information of a client
addr = (long)((struct sockaddr_in *)&conn[clientNumber].address)->sin_addr.s_addr;
buffer = (char *)malloc((len+1)*sizeof(char));
buffer[len] = 0;
read(conn[clientNumber].sock, buffer, len);
//send success message to the client
send (conn[clientNumber].sock, "You have entered the chatroom, Start CHATTING Now!\n", 51, 0);
//save client's nick name
strcpy(conn[clientNumber].UserName, buffer);
printf("User <%s> has entered the Chatroom!\n", conn[clientNumber].UserName);
printf("There are %d people in Chatroom now!\n",clientNumber+1);
free(buffer);
//create a thread dealing the messages from a single client
int number = clientNumber;
pthread_create(&threadClient[clientNumber], 0, Receive, &number);
}
clientNumber += 1;
}
pthread_exit(0);
}
int main(int argc, char ** argv){
struct sockaddr_in address;
int port = 8888;
connection_t * connection;
//create socked
ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//bind the socked to a port
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
if (bind(ServerSock, (struct sockaddr *)&address, sizeof(struct sockaddr_in)) < 0)
{
fprintf(stderr, "error: cannot bind socket to port %d\n", port);
return -4;
}
//listen for connections
listen(ServerSock, 100);
printf("the server is ready and listening\n");
//creating a thread dealing with connections
pthread_create(&thread, 0, process, (void *)connection);
//keep this program working
for(int i = 0; i < 100; ++i)
sleep(10000);
//close socked and thread
pthread_detach(thread);
close(ServerSock);
return 0;
}
这是一个学术作业。
您的问题根本不是网络问题,而是您将 clientnumber
参数传递给 Receive
线程的方式出错:
if (len > 0)
{
[...]
int number = clientNumber;
pthread_create(&threadClient[clientNumber], 0, Receive, &number);
} // number gets destroyed here
请注意,在上面的代码中,number
是一个局部变量,这意味着它会在范围退出时立即被销毁(即一旦执行到 }
行,上面).
但是,您正在将指向 number
(即 &number
)的指针传递给您的接收线程,并且您的接收线程会尝试使用该指针来查找有关客户端的数据。这意味着 Receive
线程正在取消引用悬空指针,这会调用未定义的行为。 (在这种情况下,我看到的是程序运行了一小段时间,然后停止运行,因为 Clientnumber
指向的内存位置已被其他数据覆盖,因此下一次Receive
线程试图取消引用指针,它最终会查看错误的数据,因此在错误的套接字 fd 上调用 read()
...但原则上任何事情都可能发生,因为一旦你调用未定义的行为,所有赌注均无效)
无论如何,解决问题的一种简单方法是直接传递指向 connection_t
对象的指针,而不是传递索引,即:
pthread_create(&threadClient[clientNumber], 0, Receive, &conn[clientNumber] );
..... 然后你可以直接在你的 Receive
线程中使用那个指针:
void* Receive(void* arg)
{
connection_t * conn = (connection_t *) arg;
[...]
int r = read(conn->sock, &messageLen, sizeof(int));
这样做不会调用未定义的行为,因为connection_t conn[100];
数组被声明为static/global,因此保证在进程退出之前不会销毁其中的对象.
如果您仍然需要从 Receive()
中访问 clientNumber
索引值,您可以将该值添加为 connection_t
结构定义中的另一个字段。