C中的基本套接字编程

所以我遇到了一个奇怪的问题,我在我的程序中遇到的基本上是C中的客户端/服务器套接字编程示例。我已经花时间远离问题,但我似乎找不到导致问题的原因。

如果需要,我可以发布更多细节,但以下是不起作用的。

while(1) { char word[256]; gets(word); printf("%s",word); if(strcmp(word, "TRANSLATE") == 0 || strcmp(word, "GET") == 0 ||strcmp(word, "STORE") == 0 || strcmp(word, "EXIT") == 0) { if(strcmp(word, "TRANSLATE") == 0) { if(send(sock , word , strlen(word) , 0)<0) { printf("Send Error\n"); return 1; } bzero(server_reply,2000); if(recv(sock , server_reply , 2000 , 0)<0) { puts("Recieving Error\n"); } //Print out 200ok message printf("%s\n", server_reply); //Get the first word gets(word2); while(strcmp(word2,"." )!= 0) { if(send(sock , word2 , strlen(word2) , 0)<0) { printf("Send Error\n"); return 1; } gets(word2); } if(recv(sock , server_reply , 2000 , 0)<0) { puts("Recieving Error\n"); } printf("%s\n", server_reply); //STILL NEED TO WORK ON PARSING OUT SPACES BECAUSE RESULT IS IN A STRING, JUST WITH SPACES. //ALSO NEED TO DO SERVER SIDE CAPITALIZATION } if(strcmp(word, "GET") == 0) { printf("Getting"); /* if(send(sock , word , strlen(word) , 0)<0) { printf("Send Error\n"); return 1; } bzero(server_reply,2000); if(recv(sock , server_reply , 2000 , 0)<0) { puts("Recieving Error\n"); } //Print out 200ok message printf("%s\n", server_reply); /* if(recv(sock , server_reply , 2000 , 0)<0) { puts("Recieving Error\n"); } //Print out the KANSAS message printf("%s\n", server_reply); */ } } 

一旦我在“GET”选择中取消注释,代码就会中断,并且它甚至不会在if语句之前命中printf()语句。如果我将其保留,它会完美地击中所有正确的语句。 我错过了什么吗? 此外,我认为它可能是服务器通信的东西,所以这是服务器代码。

  #include #include //strlen #include #include //inet_addr #include //write int main(int argc , char *argv[]) { int socket_desc , client_sock , c , read_size; struct sockaddr_in server , client; char client_message[2000]; //Create socket socket_desc = socket(AF_INET , SOCK_STREAM , 0); if (socket_desc == -1) { printf("Could not create socket"); } puts("Socket created"); //Prepare the sockaddr_in structure server.sin_family = AF_INET; server.sin_addr.s_addr = INADDR_ANY; server.sin_port = htons( 5149 ); //Bind if( bind(socket_desc,(struct sockaddr *)&server , sizeof(server)) < 0) { //print the error message printf("bind failed. Error"); return 1; } puts("bind done"); //Listen listen(socket_desc , 3); //Accept and incoming connection puts("Waiting for incoming connections..."); c = sizeof(struct sockaddr_in); //accept connection from an incoming client client_sock = accept(socket_desc, (struct sockaddr *)&client, (socklen_t*)&c); if (client_sock  0 ) { if(strcmp(client_message,"TRANSLATE")==0) { *(client_message + read_size) = '\0'; char word[256]="200 OK TRANSLATE"; write(client_sock, word, strlen(word)); /* while( (read_size = recv(client_sock , client_message , 2000 , 0)) > 0 ) { *(client_message + read_size) = '\0'; puts(client_message); write(client_sock,client_message,strlen(client_message)); if(strcmp(client_message,".")==0) { printf("breaking"); break; } printf("IN while loop"); } printf("Out of while loop"); */ } if(strcmp(client_message,"GET")==0) { *(client_message + read_size) = '\0'; char word[256]="200 OK GET"; write(client_sock, word , strlen(word)); char word2[256]="I don't think we're in Kansas anymore."; write(client_sock, word2 , strlen(word2)); } if(strcmp(client_message,"STORE")==0) { char word[256]="200 OK STORE"; write(client_sock, word , strlen(word)); } } if(read_size == 0) { puts("Client disconnected"); fflush(stdout); } else if(read_size == -1) { printf("recv failed"); } return 0; } 

你应该在每个printf格式控制字符串的末尾添加一个\n和/或调用fflush(3)

程序中的主要问题是错误地认为给定套接字(7)的 send (或write )对应于另一方的单个recv (或read )。

tcp(7)实现只是一个字节流 ; 没有隐含的消息边界。 因此,您的应用程序应缓冲并明确地将该流拆分为有意义的消息

可能发生,它确实发生,一方能够一次sendwrite 4000字节,然后再send 1000字节,但另一方将首先recv (或read )例如39字节,然后1字节,然后1960字节然后3000字节。 然后你就会明白“消息”并不完整,只有一个字节流。

另一个困难是这种行为是不可再现的。 下次运行服务器和客户端程序时,它们的行为会有所不同!

实际上,定义消息边界的位置很有帮助。 您可以在HTTP中执行 ,即在每条消息的开头放置一些内容长度(在HTTP中, Content-Length:回复标题中的属性)。 您还可以确定消息是单个长行,其中一个换行符终止它(然后,您可能需要一个约定来转义内部换行符),例如看看JSON 。 当然,你应该缓冲双方。

您可能希望使用诸如poll(2)之类的多路复用调用来测试套接字(或管道)的可读性或可写性(可能在您的事件循环中 )

我强烈建议阅读高级Linux编程和一些Linux套接字教程 。

顺便说一句,总是测试你的系统调用失败。 参见intro(2)并使用perror(3)或errno(3) (通常使用strerror(3) )。

当然编译所有警告和调试信息( gcc -Wall -g )并使用调试器( gdb )(也许还有strace(1) )。 您可能希望在不同的终端中使用两个 gdb会话(一个用于服务器,一个用于客户端)进行调试。

顺便说一句,不要使用过时的弃用的 bzero函数,而是使用标准的memset(3) 。