[英]C socket programming - Read and Write of TCP Server and Client out of sync
[英]TCP client / server : when stop read socket
我在創建像客戶端/服務器(tcp)這樣的小型 ftp 時面臨多個問題
客戶端有提示。
如何停止接收問題。 通過我的套接字從我的客戶端向服務器發送數據,反之亦然。
例如,從服務器向客戶端發送一些字符串。 客戶端如何知道何時停止讀取套接字並離開recv()
循環以打印回提示
這就是為什么我創建了transfert functions
來知道何時通過預發送停止(如果它是最后一次發送)( CONT - DONE
)並且它工作得很好。 (代碼如下)
然后我需要在服務器上執行像ls
這樣的命令並將結果發送到客戶端,所以我想了 2 個選項。
char*
dup execv()
並使用我的轉移函數。execv()
直接在我的套接字中輸出。 第二個選項更簡潔,但它讓我面臨關於如何停止recv()
循環的第一個問題。
獎金問題:如何將execv()
到字符串。 由於fd
參數,我想使用mmap
,但我仍然需要提前知道大小。
# define BUFFSIZE 512
# define DONE "DONE"
# define CONT "CONT"
int send_to_socket(int sock, char *msg)
{
size_t len;
int ret[2];
char buff[BUFFSIZE+1];
len = strlen(msg);
bzero(buff, BUFFSIZE+1);
strncpy(buff, msg, (len <= BUFFSIZE) ? len : BUFFSIZE);
/*strncpy(buff, msg, BUFFSIZE);*/
ret[0] = send(sock, (len <= BUFFSIZE) ? DONE : CONT, 4, 0);
ret[1] = send(sock, buff, BUFFSIZE, 0);
if (ret[0] <= 0 || ret[1] <= 0)
{
perror("send_to_socket");
return (-1);
}
// recursive call
if (len > BUFFSIZE)
return (send_to_socket(sock, msg + BUFFSIZE));
return (1);
}
char *recv_from_socket(int cs)
{
char state[5];
char buff[BUFFSIZE+1];
char *msg;
int ret[2];
msg = NULL;
while (42)
{
bzero(state, 5);
bzero(buff, BUFFSIZE+1);
ret[0] = recv(cs, state, 4, 0);
ret[1] = recv(cs, buff, BUFFSIZE, 0);
if (ret[0] <= 0 || ret[1] <= 0)
{
perror("recv_from_socket");
return (NULL);
}
// strfljoin(); concat the strings and free the left parameter
msg = (msg) ? strfljoin(msg, buff) : strdup(buff);
if (strnequ(state, DONE, 4))
break ;
}
return (msg);
}
您已經正確地判斷,要通過面向流的套接字進行無差別流以外的任何通信,您需要在通信雙方之間應用某種應用層協議。 這就是你的send_to_socket()
和recv_from_socket()
函數正在做的事情,盡管它們有缺陷。 *
假設您確實需要使用應用層協議,那么讓子進程直接寫入套接字根本不是一種選擇,除非您的特定 ALP 可以將整個程序輸出封裝為單個塊,你正在使用不能做。
話雖如此,您至少還有一個您沒有考慮過的其他選擇:讓父級在子級生成時將子級的輸出從套接字發送出去,而不是收集所有輸出並在之后才發送。 這將涉及在子級和父級之間建立管道,可能還有一個單獨的send_to_socket()
版本, send_to_socket()
從 FD 而不是從字符串讀取要發送的數據。 您可能會一次累積一個中等大小的緩沖區。 這種方法將是我的建議。
獎金問題:如何將 execv() 復制到字符串。 由於 fd 參數,我想使用 mmap,但我仍然需要提前知道大小。
mmap()
接受一個文件描述符參數,指定要映射的文件,但這並不意味着它只適用於任何文件描述符。 它只能保證與指定常規文件和共享內存對象的 FD 一起使用,並且您不能指望它適用於指定瞬態數據管道的 FD。 要在內存中捕獲子進程的輸出,您需要像我在第三個選項中描述的那樣進行操作,但將讀取的數據存儲在動態分配(並根據需要重新分配)的緩沖區中,而不是將其發送到客戶端作為它被讀取。 這可能既昂貴又混亂。
*您的功能缺陷:
send()
和recv()
函數來准確地傳輸請求的字節數,或者失敗。 事實上, send()
和recv()
可以執行部分傳輸。 為避免丟失數據和/或不同步,您必須將這些函數的返回值與您嘗試傳輸的字節數進行比較以檢測部分傳輸,並且在發生部分傳輸的情況下,您必須發出另一個調用發送數據的余額。 由於同樣的事情可能會再次發生,您通常需要將整個事情放在一個循環中,不斷調用send()
或recv()
直到所有數據都被發送或真正的失敗發生。
遞歸是發送函數的一個糟糕的實現選擇。 如果您有大量數據要發送,那么您可能會耗盡堆棧,而且每個遞歸函數調用比僅循環返回的開銷要大得多。
以固定長度的塊發送數據是不必要的,它需要在發送之前將數據復制到單獨的緩沖區的開銷。
考慮發送消息長度而不是“CONT”或“DONE”,后跟那么多字節(但見上文)。 您還可以將標志位合並到消息長度中以傳達附加信息——例如,表示當前塊是否是最后一個的位。
send()
和recv()
調用可能會因與連接及其持續可行性無關的原因而失敗。 例如,它們可以被信號中斷。 由於您無法在發送方和接收方之間的通信中斷時重新同步通信,因此您應該確保在出現錯誤時終止通信,盡管這實際上不必由您的發送和接收函數本身處理.while (42) 有什么意義
它與 while(1) 沒有什么不同,如果你只是希望它永遠循環
如果我理解正確,您想使用 execv 來執行 'ls' 並捕獲輸出,以便您可以發送它。
看看'popen'函數,它將創建一個管道文件描述符(並返回它),然后在輸出連接到管道的情況下執行命令。
然后你可以使用常規的 read() 系統調用來讀取文件描述符的輸出。
像這樣的東西
#include <stdio.h>
FILE *f
char buf[BUF_MAX]
f = popen("ls", "r")
if (!f)
// send error message or whatever
return
while (fgets(buf, BUF_MAX, f))
// send buf with your other socket function
pclose(f);
如果您不想使用 libc stdio,只是低級系統例程,那么要做的就是檢查 fork() 、 pipe() 和 dup() 系統調用,
首先使用 pipe() (為每一端提供兩個文件描述符)
然后 fork()
然后關閉未使用的文件描述符(),
然后使用 dup() 或 dup2() 僅將新進程中的 fd 1 更改為之前管道調用的輸出 fd 編號。
然后最后你 execv() 運行“ls”命令,它的輸出將進入管道
在原始進程中,您從之前 pipe() 調用返回的第一個 fd 中 read()
由於您希望客戶端知道服務器何時完成,您可以執行以下操作:
const int MSG_DATA = 1;
const int MSG_DONE = 2;
strict message {
int messagetype;
int len;
}
char buf[BUF_SIZE]
message msg;
// Put data into buf
// set msg.len
msg.messagetype= MSG_DATA;
send(msg,size of(msg))
send (buf,msg.len)
然后當你完成。
msg.messagetype=MSG_DONE
msg.len = 0
send(msg,sizeof(msg))
在客戶端:
Message msg;
Char buf[]
while()
...
recv(&msg)
...
if(msg.messagetype == MSG_DATA)
recv(buf)
elif msg.message type == MSG_DONE)
DoSomething()
但是,請記住,無論如何,您的客戶端應該始終接收數據包。
如果不是為了學校練習,你不能做你想做的事,最好使用現有的庫,例如消息隊列庫,也許是 zeromq,而不是用困難的方式來做。
還有一個簡單的 curl C 庫,你可以用它來發送 HTTP,它已經整理了所有這些東西,也可以做不確定長度的內容。
另外(我假設您使用的是 TCP 套接字,而不是 UDP),連接的任何一端都不能發送 N 個字節,並期望其他站點上的 recv 將獲得 N 個字節。 recv 只會在其 TCP 緩沖區中獲取操作系統的網絡堆棧當前已接收和緩沖的內容。 並且接收者也可以在一次接收中獲得多個已發送的塊。 網絡上的數據包大小可能在 1400-1500 字節左右,您的服務器可能會發送一條數 kB 的消息,該消息將被拆分為多個數據包,並可能在第一個數據包之后由接收器處理,然后其余部分進入,或者您的服務器可能會發送幾條帶有 DONE 或 CONT 標頭的小消息,而接收者可能會在一個 recv() 中獲得所有這些信息。 即使您很幸運並且 DONE 或 CONT 實際上位於 TCP 緩沖區的開頭,您的代碼也會讀取 OS TCP 緩沖區中的所有內容,並且只會檢查前 4 個字節以查看第一個的 DONE 或 CONT消息,並將所有其余部分視為數據。 所以你真的需要發送一個長度字段。 您可以完全廢棄 DONE 或 CONT 並發送長度,然后發送 0 長度來表示 DONE。 然后您的客戶端在接收到時,可以 recv() 將它可以獲取的所有內容放入您的緩沖區,然后使用長度字段依次處理包含在該緩沖區中的每條消息。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.