[英]TCP/IP Stream dumping text in arbitrary clumps (C Socket Programming)
我正在編寫服務器端和客戶端的聊天應用程序。 為長代碼道歉。 大概是我對 TCP/IP 的整體誤解了,但我不知道從哪里開始。 問題是,當多個客戶端連接到服務器時,如果向服務器發送了許多消息,則不會立即將消息傳遞給客戶端。 我可以收集到許多消息被附加到服務器端套接字連接上的緩沖區,並且在任意數量的消息之后,該緩沖區被轉儲到其中一個客戶端。 此轉儲在客戶端按 Enter 后立即發生,因此我認為多線程中也存在問題。
我的想法:
(另外,我在使用 GDB 進行調試時遇到問題,附加 function,因此我無法調試我的程序:/我正在運行 Ubuntu 20.04,我也將不勝感激)
客戶:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[]) {
int cli_sock, port;
struct sockaddr_in serv_addr;
char buffer[1024];
port = atoi(argv[1]);
if((cli_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket failed.\n");
exit(1);
}
printf("Client socket initialised.\n");
memset(&serv_addr, '\0', sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if(connect(cli_sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
perror("Error in connection.\n");
exit(1);
}
while(1) {
printf("Client: ");
fgets(buffer, 1024, stdin);
send(cli_sock, buffer, strlen(buffer), 0);
if(strcmp(buffer, "/leave") == 0) {
close(cli_sock);
printf("Disconnected from server.\n");
exit(1);
}
if(recv(cli_sock, buffer, 1024, 0) < 0) {
printf("Error in receiving data.\n");
}
else {
if(sizeof(*buffer) == 1) { // prints data for all but sender.
printf("%s", buffer);
}
}
}
return 0;
}
服務器:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int fileLineCount(FILE *);
void deleteSockFromFile(FILE *, int);
int main(int argc, char *argv[]) {
int* senderID = calloc(1, sizeof(int));
int serv_sock, port;
struct sockaddr_in serv_addr;
int new_sock;
struct sockaddr_in new_addr;
socklen_t addrlen;
char buffer[1024];
int roomSize;
port = atoi(argv[1]);
roomSize = atoi(argv[2]);
if((serv_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Server socket failed.\n");
exit(1);
}
memset(&serv_addr, '\0', sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if(bind(serv_sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
printf("Bind unsuccessful.\n");
exit(1);
}
if(listen(serv_sock, roomSize) != 0) {
printf("Listening...\n");
}
else {
perror("Listening failed.\n");
exit(1);
}
memset(buffer, '\0', sizeof(buffer));
FILE *clientsFile = NULL;
if(!(clientsFile = fopen("clients.txt", "w+"))) { // file for tracking active client connections (socket fds)
perror("Failed to open file.\n");
exit(1);
}
while(1) {
new_sock = accept(serv_sock, (struct sockaddr *) &new_addr, &addrlen);
if(new_sock < 0) { //handling failed socket creation.
perror("Socket failed.\n");
exit(1);
}
printf("Connection accepted from %s:%d. Sock fd = %d\n", inet_ntoa(new_addr.sin_addr), ntohs(new_addr.sin_port), new_sock);
printf("lineCount: %d\n", fileLineCount(clientsFile));
fseek(clientsFile, 0, SEEK_END);
fprintf(clientsFile, "%d\n", new_sock);
printf("lineCount: %d\n", fileLineCount(clientsFile));
if(fork() == 0) {
close(serv_sock);
while(1) {
memset(buffer, '\0', sizeof(buffer));
recv(new_sock, buffer, 1024, 0);
if(strcmp(buffer, "/leave") == 0) {
printf("Disconnected from %s:%d.\n", inet_ntoa(new_addr.sin_addr), ntohs(new_addr.sin_port));
deleteSockFromFile(clientsFile, new_sock);
break;
}
else {
printf("Client: %s", buffer);
int temp_sock;
size_t length = 10;
char *line = calloc(length, sizeof(char));
rewind(clientsFile);
while(getline(&line, &length, clientsFile) >= 0) { // reads file with client sock fd
line[strcspn(line, "\n")] = 0;
temp_sock = atoi(line); // set temp_sock to a client sock fd
if(temp_sock != new_sock) { // compares thread value of new_sock with all client socks, so the sender isn't sent the message, but everyone else is.
send(temp_sock, buffer, strlen(buffer), 0);
printf("SHOULD SEND TO %d\n", temp_sock); //debugging line #1/2
}
else {
send(temp_sock, senderID, sizeof(senderID), 0);
printf("Should not send, cause sender.\n"); //debugging line #2/2
}
}
}
}
break;
}
}
fclose(clientsFile);
remove("clients.txt");
close(new_sock);
}
int fileLineCount(FILE *ptr) {
int ch, lineCount = 0;
fseek(ptr, 0, SEEK_SET);
do {
ch = fgetc(ptr);
if(ch == '\n')
lineCount++;
}
while (ch != EOF);
return lineCount;
}
void deleteSockFromFile(FILE *ptr, int sock2delete) {
size_t length = 10;
char *line = calloc(length, sizeof(char));
char *tempLine = calloc(length, sizeof(char));
FILE *tempPtr = NULL;
if((tempPtr = fopen("temp.txt", "w")) == 0) {
perror("Unable to open file.\n");
exit(1);
}
rewind(ptr); // must reset file cursor before switching from writing to reading mode (w+)
while(getline(&line, &length, ptr) >= 0) {
strcpy(tempLine, line);
tempLine[strcspn(tempLine, "\n")] = 0; //remove newline char
if(atoi(tempLine) != sock2delete) {
fputs(line, tempPtr);
}
}
remove("clients.txt");
rename("temp.txt", "clients.txt");
fclose(tempPtr);
free(line);
free(tempLine);
}
這里是測試 output 的上下文,測試沒有 go 很好,但你可以看到只有一小塊消息通過(看@左上角終端,“c2”是從客戶端 2 收到的)
(也許這個問題應該重命名為 GDB attach 會導致筆記本電腦在使用 fork() 在多線程程序上使用 sudo 運行時凍結,感謝您的想法!)
問題是,當多個客戶端連接到服務器時,如果向服務器發送了許多消息,則不會立即將消息傳遞給客戶端。
您的客戶端在 fgets() 上阻塞、等待來自 stdin 的輸入和在 recv() 上阻塞、等待來自服務器的輸入之間交替進行,因此它們只有在兩個方向之間存在嚴格交替時才能工作。 在 TCP 套接字上對recv()
的單個調用可以為您提供由對send()
的多次調用寫入的數據,因為 TCP 確實是一個字節 stream,因此您可以一次區分它們. 但是fgets()
一次只給你一行,如果客戶端正在等待從服務器接收一些東西,它就不能同時從標准輸入讀取更多的輸入。
也許有一種替代方法可以在服務器端跟蹤客戶端連接而不是使用文件。
在文件中存儲文件描述符編號似乎根本沒有任何意義。 fd 數只是小整數,對於不同的進程,它們的含義是不同的。 它們不是全球唯一標識符!
您正在為每個傳入連接創建一個新進程。 每個子進程都將獲得當時服務器已打開的文件描述符的副本。 因此,第一個孩子將獲得第一個客戶端連接的 fd,第二個孩子將獲得前兩個客戶端連接的 fd,依此類推。但是沒有一個孩子將獲得稍后出現的客戶端的 fd,所以你應該每次嘗試從文件中寫入 fd 時都會出現許多錯誤。
只有當客戶端不需要直接交互時,才能為每個連接分叉一個孩子。 此外,如果您的子進程突破內部while(1)
,那么您的子進程似乎會退出整個main
function ,並且第一個這樣做的人將刪除clients.txt
文件。 更重要的是,您似乎沒有注意recv()
調用的返回值,所以如果遠程關閉連接,您將永遠在recv()
上旋轉,它返回0
標記“結束文件”。
相反,您需要將所有客戶端 fd 保存在單個服務器進程中,然后該進程可以讀取和寫入所有這些文件。 將它們的數字存儲在數組等中。 在這里,您與客戶端處於相同的情況,您需要能夠同時監視多個 fd 的輸入,而不是一次阻塞一個。 為此,您需要使用select()
(或poll()
或epoll()
)。 客戶端也是如此。 在 GNU libc 文檔中有一個使用select()
的示例: https://www.gnu.org/software/libc/manual/html_node/Server-Example.html
TCP/IP是純數據stream而不是包傳輸(message-by-message),所以我不應該這樣對待。
確實是的。 如果遙控器發送兩行,每行一個write()
/ send()
調用,打個hello!\n
how are you?\n
,另一端原則上可以分三個部分接收: hell
, o!\nhow
和are you?\n
. 沒有任何保證,但尤其是在負載輕到無負載且消息短的本地網絡上,這樣的分裂是不太可能的。 但是操作系統會給你完整的hello?\nhow are you?\n
在一個 go 中,如果在進程開始閱讀之前全部收到的話。 應用程序的工作是找出何時收到完整的行,以及之后是否有任何內容。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.