最近,我一直致力于使用线程从服务器发送和接收消息的一些客户端代码。以下代码在运行时表现异常。在输入要发送到服务器的消息时,代码完成任务,尽管有“套接字已在使用中”错误,服务器获取它。但是我尝试发送到服务器的每个后续消息都没有立即收到,但是当客户端程序终止时,它似乎都立即收到了。
(另外,我确定错误是客户端的,如果对输出函数进行注释,则不会出现奇怪的行为。)
如何修复此错误?
客户端
#include <stdio.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <string>
#include <iostream>
#include <errno.h>
#include <pthread.h>
void* input(void* ptr)
{
int on = 1;
bool *input_done = ((struct thread_args*)ptr)->process_done;
struct addrinfo *res = ((struct thread_args*)ptr)->result;
char msg[256];
int sock = socket(res->ai_family,res->ai_socktype,res->ai_protocol);
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(char *)&on,sizeof(on));
bind(sock,res->ai_addr,res->ai_addrlen);
connect(sock,res->ai_addr,res->ai_addrlen);
cin.getline(msg,256);
if (msg[0] == '/') {exit(1);}
send(sock,msg,sizeof msg,0);
cout << "You:" << msg << endl;
*input_done = 1;
close(sock);
pthread_exit(NULL);
}
void* output(void* ptr)
{
int on = 1;
bool *output_done = ((struct thread_args*)ptr)->process_done;
struct addrinfo *res = ((struct thread_args*)ptr)->result;
char msg[256];
int sock = socket(res->ai_family,res->ai_socktype,res->ai_protocol);
bind(sock,res->ai_addr,res->ai_addrlen);
connect(sock,res->ai_addr,res->ai_addrlen);
recv(sock,msg,sizeof msg,0);
cout << "Recieved:" << msg;
*output_done = 1;
close(sock);
pthread_exit(NULL);
}
void io_client()
{
//thread function variables
pthread_t t1,t2;
bool input_done = 1, output_done = 1;
//socket setup variables
struct addrinfo hints, *res;
memset(&hints,0,sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
getaddrinfo("localhost","8080",&hints,&res);
//setting up structures to pass data to threaded functions
struct thread_args i_args, o_args;
i_args.result = res; i_args.process_done = &input_done;
o_args.result = res; o_args.process_done = &output_done;
while(1)
{
if (output_done)
{
pthread_create(&t2,NULL,output,&o_args);
output_done = 0;
}
if (input_done)
{
pthread_create(&t1,NULL,input,&i_args);
input_done = 0;
}
}
}
int main()
{
io_client();
}
服务器
void server()
{
struct addrinfo hints, *res;
int sock=-1, newsock=-1;
int length, on=1;
char **address_list; int entries = 0;
//fd_set read_fd;
//struct timeval timeout;
char buffer[100];
memset(&hints,0,sizeof hints);
res = NULL;
memset(&res,0,sizeof res);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
getaddrinfo("localhost","8080",&hints,&res);
sock = socket(res->ai_family,res->ai_socktype,res->ai_protocol);
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(char *)&on,sizeof(on));
bind(sock,res->ai_addr,res->ai_addrlen);
listen(sock,10);
while(1)
{
struct sockaddr_storage addr;
char ipstr[INET6_ADDRSTRLEN];
socklen_t len;
len = sizeof addr;
newsock = accept(sock,NULL,NULL);
getpeername(newsock,(struct sockaddr*)&addr,&len);
struct sockaddr_in *s = (struct sockaddr_in*)&addr;
inet_ntop(AF_INET,&s->sin_addr,ipstr,sizeof ipstr);
length = 100;
setsockopt(newsock,SOL_SOCKET,SO_RCVLOWAT, (char*)&length,sizeof length);
recv(newsock,buffer,sizeof buffer,0);
cout << buffer << endl;
}
if (newsock != -1)
{
close(newsock);
}
if (sock != -1)
{
close(sock);
}
}
int main()
{
server();
}
答案 0 :(得分:3)
看起来您正在尝试将客户端bind()连接到与服务器相同的端口。这不是必要的。更糟糕的是,您正在尝试绑定到服务器的IP地址 - 这也是一个更大的问题。通常,对于要调用connect()函数的客户端套接字,您应该将套接字绑定到端口0和IP 0,从而让操作系统为您选择一个随机可用的端口并启用使用正确的本地IP地址和用于连接的适配器。调用connect后,您可以调用getsockname()来发现操作系统为您选择的端口。
如果您让操作系统为您选择客户端端口,则不需要SO_REUSESADDR调用。虽然,您的服务器代码可以调用它,以便在关闭之后需要重新启动,但仍有待关闭的连接。
另外。您没有检查任何套接字调用的返回值。这可能就是为什么你会得到一些神秘的结果。对bind()的调用更有可能失败,因为你指定了服务器IP,但是connect()正在成功,因为它会自动绑定套接字(如果它还没有)。
这是您的input()函数的清理版本。转换output()函数是一个留给读者的练习。如果你按照我的榜样,你将处于良好的状态。
void* input(void* ptr)
{
int on = 1;
bool *input_done = ((struct thread_args*)ptr)->process_done;
int ret;
int success = true;
struct sockaddr_in addrLocal = {};
struct addrinfo *res = ((struct thread_args*)ptr)->result;
char msg[256];
int sock = socket(AF_INET, SOCK_STREAM, 0);
success = (sock != -1);
if (success)
{
addrLocal.sin_family = AF_INET;
addrLocal.sin_port = INADDR_ANY; // INADDR_ANY == 0 --> pick a random port for me
addrLocal.sin_addr.s_addr = INADDR_ANY; // INADDR_ANY == 0 --> use all appropriate network
ret = bind(sock,(sockaddr*)&addrLocal,sizeof(addrLocal));
if (ret == -1) perror("bind: ");
success = (ret != -1);
}
if (success)
{
ret = connect(sock,res->ai_addr,res->ai_addrlen);
if (ret == -1) perror("connect: ");
success = (ret != -1);
}
if (success)
{
cin.getline(msg,256);
if (msg[0] == '/') {exit(1);}
ret = send(sock,msg,sizeof msg,0);
if (ret == -1) perror("send: ");
success = (ret != -1);
}
if (success)
{
cout << "You:" << msg << endl;
*input_done = 1;
}
if (sock != -1)
{
close(sock);
sock = -1;
}
return NULL;
}
答案 1 :(得分:0)
我想你提供的“SO_REUSEADDR”套接字选项就是问题。
你是否一次又一次地调用该函数而不关闭客户端套接字?在那种情况下,它将无法正常工作。
此套接字选项的目的是“当同一地址的已打开套接字处于TIME_WAIT状态时重用该地址,否则您将收到上述错误”。
如果您的客户端每次都打开一个新连接,那么我必须说您必须更有效地构建代码并处理套接字关闭方案。