我正在尝试在C中实现TCP客户端,需要按如下方式工作:
例如,客户端应该能够打开与任意HTTP服务器的连接,发送“HEAD”消息并打印从HTTP服务器到达的响应。
(我的目标是为我日常工作中使用的特定软件环境创建一个通用的“TCP客户端插件”,但缺乏网络功能。我非常了解环境的SDK,但我不知道我真的有很多套接字编程经验。)
目前,我有2个独立的线程用于发送和接收数据。接收器线程的工作流程(一旦服务器地址和端口由用户设置就自动启动)是下面的一个(这里我只提到套接字调用的主要顺序):
globalSocket = socket(); // Create socket and store it globally
bind(); // Bind the local port
connect(); // Connect to remote host & port
listen(); // Listen to the socket
while (isAlive) {
select(... &readfds ...); // Check for ready reader descriptors
accept(); // Accept the incoming connection
recv(); // Receive data from server
}
close(); // End the connection
发送方线程非常简单,它使用接收方线程创建的globalSocket
来执行send()
命令。
现在,这是我的问题:我可以毫无问题地打开与远程服务器的连接。我也可以毫无问题地发送任意数据(我确认我发送的数据实际上没有问题地到达服务器端)。但是,我无法从服务器获取任何数据。经过一些测试后,似乎select
永远不会返回正值。
我在我的代码中尝试了很多修改(例如将参数更改为select
,省略listen
等),我在这一天至少阅读了Beej的指南10次并尝试了每一个广告 - 我可以想象的特定变化,但行为仍然是相同的。因此,在我开始询问具体代码摘录的具体问题之前,我想知道我对这个问题的处理方法是否正确,或者我是否在这里遇到了一些严重的概念问题。
非常感谢您的回答,
ADAM
P.S。我无法在评论中发布此代码部分,因为它太长了;这是管理select
- accept
- recv
周期的代码段:
while ( thread->isActive ) {
// Accept connection
timeVal.tv_sec = TIMEOUT_SEC;
timeVal.tv_usec = TIMEOUT_USEC;
FD_ZERO( & fileDescriptor );
FD_SET( socketDescriptor, & fileDescriptor ); // socketDescriptor is the global socket
result = select ( FD_SETSIZE, & fileDescriptor, NULL, NULL, & timeVal );
if ( result > 0 ) {
post ( "select" ); // This line is actually never reached
connectionDescriptor = accept ( socketDescriptor, ( struct sockaddr * ) & clientAddress, & clientLength ); // connectionDescriptor is the local socket created by accept
if ( connectionDescriptor < 0 ) { // Some error happened
outlet_int ( thread->parent->statusOutlet, errno );
} else {
// Receive data
data.clear ( ); // 'data' is an std::vector of chars that stores the incoming data and makes it accessible for the rest of the environment
size = thread->parent->bufferSize;
buffer = new unsigned char [ size ]; // this buffer is used for receiving the data from 'recv'
receivedBytes = 1;
while ( receivedBytes > 0 ) {
receivedBytes = recv ( connectionDescriptor, ( char * ) buffer, size, 0 );
if ( receivedBytes < 0 ) { // Socket error
outlet_int ( thread->parent->statusOutlet, errno );
}
data.insert ( data.end ( ), buffer, buffer + receivedBytes );
}
delete [ ] buffer;
#ifdef WIN_VERSION
closesocket ( connectionDescriptor );
#else
close ( connectionDescriptor );
#endif
// Output received data
... blah ... blah ... blah
}
}
}
答案 0 :(得分:2)
其他人已对connect(2)
和listen(2)
的不兼容性发表了评论。你也不需要bind(2)
。
使用select(2)
时最常见的错误是在每次迭代时不重新初始化文件描述符集。 select(2)
的第二个到第二个参数是输入输出,因此您每次都必须重做它们。
在您发布代码之后,让我补充一点,您完全没有从select(2)
给您的I / O解复用中受益。通过新的连接尝试从select(2)
唤醒和客户端在您输入阻止accept(2)
之前删除该连接之间也存在众所周知的竞争。
如果您不热衷于使用非阻止路线(使用select(2)
,poll(2)
,epoll(7)
,kqueue(2)
等的首选方式),也可以摆脱select(2)
并在循环中接受和处理客户端连接。
答案 1 :(得分:2)
首先,您是否实际从远程服务器接收数据包?我问,因为我在分配期间遇到了类似的问题,当我运行tcpdump时,它也没有显示任何收到的数据包。该问题原来是一个防火墙,它显然允许传出流量但阻止所有传入的数据包......
答案 2 :(得分:0)
所以,这里有几个独立的问题,它们在几个答案和评论之间分开,所以我将对它们进行总结。
发件人帖子没问题。但是,连接器/侦听器线程应如下所示:
socket();
connect();
while (isAlive) {
if (select(... &readfds ...) > 0) {
recv();
}
}
close();
关于最初发布的代码摘录,用于处理while
的{{1}}周期是一个概念错误,应该删除,因为它会阻塞线程 - 这会使recv
1}}语句不必要(因为在这种情况下,select
没有实际效果)。
还应该指出,如果select
返回0,这意味着套接字被远程对等体关闭,因此在这种情况下,recv
循环应该被中止,即使{{1}父线程未将其设置为false。但是,循环内部不需要额外的while (isAlive)
语句(这也是原始代码中的概念错误)。