Internet Application 9-10:我们中间隔着缓冲区

TASK: Write two programs (server and client) so that the server can transfer a local file (indicate by client) to client using TCP.

  • You can design the running command format, e.g., ./<exefile> <server> <filename>
  • The server may be specified in domain name or IP address
  • The transferred file should be indicated by client’s input
  • Client should rename and save the transferred content as a new file
  • Each time the server sends data, following information has to be printed:
    the destination (client’s IP address and port number)
    Amount of data that has been sent by “Byte”

代码如下(请勿抄袭):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//SERVER
#include <stdio.h> /* for printf() and fprintf() */
#include <sys/socket.h> /* for socket(), bind(), sendto()and recvfrom() */
#include <arpa/inet.h> /* for sockaddr_in and inet_ntoa() */
#include <stdlib.h> /* for atoi() and exit() */
#include <string.h> /* for memset() */
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
int sock, serviceSock, QueueLen = 5,fd, fileSize = 0, sendLen,recvLen;
struct sockaddr_in echoServAddr;
struct sockaddr_in echoClntAddr;
unsigned int cliAddrLen;
char fileName[100], buffer[100];
unsigned short echoServPort = 6666;

if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
printf("socket() failed.\n");
memset(&echoServAddr, 0, sizeof(echoServAddr));
memset(&fileName, 0, sizeof(fileName));
echoServAddr.sin_family = AF_INET;
echoServAddr.sin_addr.s_addr = htonl(INADDR_ANY);
echoServAddr.sin_port =htons(echoServPort);

if ((bind(sock, (struct sockaddr *) &echoServAddr, sizeof(echoServAddr))) < 0)
printf("bind() failed.\n");

listen(sock, QueueLen);

for (;;){
cliAddrLen = sizeof(echoClntAddr);
/* Block until receive message from a client */

serviceSock = accept(sock,(struct sockaddr *) &echoClntAddr, &cliAddrLen);

printf("***********************************\n");
printf("Accept client %s on TCP port %d\n",inet_ntoa(echoClntAddr.sin_addr),echoServPort);

if((recvLen = recv(serviceSock,fileName,100,0)) > 0){
//fileName[strlen(fileName)] = '\0'; wrong code
fileName[recvLen] = '\0'; //correct code
printf("This client request for file name: %s\n",fileName);
fd = open(fileName, O_RDONLY);
lseek(fd,0,SEEK_SET);

while((sendLen = read(fd, buffer ,10)) > 0){
send(serviceSock, buffer, sendLen, 0);
//printf("sendLen - %d\n",sendLen);
fileSize += sendLen;
}
//printf("sendLen - %d\n",sendLen);
printf("End of the file \n");
printf("%d BYTES data have been sent. \n", fileSize);
close(fd);
}
close(serviceSock);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//CLIENT
#include <stdio.h> /* for printf() and fprintf() */
#include <sys/socket.h> /* for socket(), sendto() andrecvfrom() */
#include <arpa/inet.h> /* for sockaddr_in and inet_addr() */
#include <stdlib.h> /* for atoi() and exit() */
#include <string.h> /* for memset() */
#include <unistd.h> /* for close() */
#include <fcntl.h>

int main(int argc, char *argv[])
{
int sock,fd,fileSize = 0,recvLen;
struct sockaddr_in echoServAddr;
unsigned short echoServPort = 6666;
char *servIP, *fileName;
char *buffer[100];


if ( argc != 3 ){
printf("Usage: %s <Server IP> <File Name> ...\n",argv[0]);
exit(1);
}
servIP = argv[1]; /* First arg: server IP address (dotted quad) */

if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
printf("socket() failed.\n");

memset(&echoServAddr, 0, sizeof(echoServAddr));
echoServAddr.sin_family = AF_INET;
echoServAddr.sin_addr.s_addr = inet_addr(servIP);
echoServAddr.sin_port = htons(echoServPort);

printf("Connecting to server: %s\n", argv[1]);
if (connect(sock,(struct sockaddr *) &echoServAddr, sizeof(echoServAddr)) < 0){
printf("Can Not Connect To %s!\n", argv[1]);
exit(1);
}

if (send(sock, argv[2], strlen(argv[2]), 0)!= strlen(argv[2])){
printf("send() sent a different number of bytes than expected.\n");
exit(1);
}else
printf("Request for file \"%s\" has been sent.\n",argv[2]);

fileName = strcat(argv[2],".bak");
fd = open(fileName, O_RDWR|O_CREAT,0644);
printf("Connecting to the server...\n");

memset(buffer,0,sizeof(buffer));
while((recvLen = recv(sock, buffer, 10, 0)) > 0){
write(fd, buffer, recvLen);
//printf("recvLen - %d\n",recvLen);
fileSize += recvLen;
}

close(fd);
close(sock);
printf("file received.\n %d bytes received, and stored in %s \n",fileSize,fileName);

exit(0);
}

运行结果(怕暴露把IP地址隐匿了一部分):

需要提前生成测试用的两个文件:test.txt, testc.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SERVER TEST 
[email protected]:~/tcp$ ./s
***********************************
Accept client 10.109.242.* on TCP port 6666
This client request for file name: test.txt
End of the file
48 BYTES data have been sent.
***********************************
Accept client 10.109.242.* on TCP port 6666
This client request for file name: testc.cpp
End of the file
132 BYTES data have been sent.
***********************************
Accept client 10.109.242.* on TCP port 6666
This client request for file name: c
End of the file
7917 BYTES data have been sent.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CLIENT TEST 
[email protected]:~/tcp$ ./c 10.109.242.* test.txt
Connecting to server: 10.109.242.*
Request for file "test.txt" has been sent.
Connecting to the server...
file received.
48 bytes received, and stored in test.txt.bak
[email protected]:~/tcp$ ./c 10.109.242.* testc.cpp
Connecting to server: 10.109.242.*
Request for file "testc.cpp" has been sent.
Connecting to the server...
file received.
84 bytes received, and stored in testc.cpp.bak
[email protected]:~/tcp$ ./c 10.109.242.* c
Connecting to server: 10.109.242.211
Request for file "c" has been sent.
Connecting to the server...
file received.
7785 bytes received, and stored in c.bak
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
RESULT 
[email protected]:~/tcp$ ll
total 60
drwxrwxr-x 2 student student 4096 May 5 22:54 ./
drwxr-xr-x 13 student student 4096 May 5 22:54 ../
-rwxrwxr-x 1 student student 7785 May 5 22:35 c*
-rw-r--r-- 1 student student 1881 May 5 22:34 c.cpp
-rwxrwxr-x 1 student student 7895 May 5 22:35 s*
-rw-r--r-- 1 student student 1958 May 5 22:32 s.cpp
-rw-r--r-- 1 student student 12288 May 5 19:41 .s.cpp.swp
-rw-r--r-- 1 student student 48 May 5 21:19 test.txt
-rw-r--r-- 1 student student 48 May 5 22:54 test.txt.bak
-rw-r--r-- 1 student student 84 May 5 22:28 testc.cpp
-rw-r--r-- 1 student student 84 May 5 22:59 testc.cpp.bak
[email protected]:~/tcp$ od -c test.txt
0000000 t h i s i s a t e s t f
0000020 i l e . \n t h i s i s a t
0000040 e s t f i l e . \n o v e r . \n
0000060
[email protected]:~/tcp$ od -c test.txt.bak
0000000 t h i s i s a t e s t f
0000020 i l e . \n t h i s i s a t
0000040 e s t f i l e . \n o v e r . \n
0000060
[email protected]:~/tcp$ od -c testc.cpp
0000000 # i n c l u d e < s t d i o .
0000020 h > \n \n i n t m a i n ( ) \n
0000040 { \n p r i n t f ( " H e
0000060 l l o , W o r l d ! \ n " )
0000100 ; \n \n r e t u r n 0
0000120 ; \n } \n
0000124
[email protected]:~/tcp$ od -c testc.cpp.bak
0000000 # i n c l u d e < s t d i o .
0000020 h > \n \n i n t m a i n ( ) \n
0000040 { \n p r i n t f ( " H e
0000060 l l o , W o r l d ! \ n " )
0000100 ; \n \n r e t u r n 0
0000120 ; \n } \n
0000124

QUESTIONS

Q: 为什么我把上一个UDP的实验改成TCP以后,一次传输multiple words会被TCP一次性接收到一个buffer里?
A:

对于UDP,一个recvfrom()对应一个sendto(),sendto()执行了4次,那么recvfrom()也会相应的执行4次;

UDP的sendto对应着recvfrom,一发一收.如果sendto的数据大于MTU,则会在IP层分片发送,到达目标后由IP层重组,再从recvfrom一次性返回.如果使用IP层分片重组则存在乱序,丢包,重包的问题.调用一次sendto,只要数据长度小于MTU都会以一个独立的UDP包发送.recvfrom的接收大小必须大于或等于sendto时的是数据大小.更正确的说法应该是UDP的包单位是以IP层的包为单位的. —— 参见文末“推荐阅读”

对于TCP,每一个socket在被创建之后,系统都会给它分配两个缓冲区,即输入缓冲区和输出缓冲区。

数据的发送和接收是独立的,并不是发送方执行一次send,接收方就执行一次recv。recv函数不管发送几次,都会从输入缓冲区尽可能多的获取数据。如果发送方发送了多次信息,接收方没来得及进行recv,则数据堆积在输入缓冲区中,取数据的时候会都取出来。换句话说,recv并不能判断数据包的结束位置。 —— 参见文末“推荐阅读”


Scan the QR code to add me on Wechat