Internet Application 6-7:你们一家人长得可真像

TASK: Write the programs for data sending based on UDP

  • You can design the running command format, e.g.,./<exefile> <server> <data>
  • The server may be specified in domain name or IP address
  • Multiple wordscan be delivered each time
  • Each time the server receives data, the source (client’s IP address and port number) and the data should be displayed on the screen

代码如下(请勿抄袭):

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
//EchoClient.cpp
#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() */
#define ECHOMAX 255 /* Longest string to echo */
int main(int argc, char *argv[])
{
int sock;
struct sockaddr_in echoServAddr;
unsigned short echoServPort = 6666;
char *servIP;
char *echoString[20];
char echoBuffer[20][ECHOMAX+1];
int echoStringLen[20];
int respStringLen[20];
int i=0;

if ( argc < 3 ){
printf("Usage: %s <Server IP> <Echo Word> <Echo Word> ...\n",argv[0]);
exit(1);
}
servIP = argv[1]; /* First arg: server IP address (dotted quad) */
for(;i<20;i++){
if(argv[i+2] != NULL && (strlen(argv[i+2])<=ECHOMAX)){
echoStringLen[i]=strlen(argv[i+2]);
echoString[i] = argv[i+2];
}else if(argv[i+2] != NULL && (echoStringLen[i]=strlen(argv[i+2])>ECHOMAX)){
printf("Echo word too long.\n");
break;
}
else break;
}

if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 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("%s: Sending data to \"%s\"\n",argv[0],argv[1]);
for(int j=0;j<i;j++){
if ((sendto(sock, echoString[j], echoStringLen[j], 0,(struct sockaddr *) &echoServAddr, sizeof(echoServAddr)))!= echoStringLen[j])
{
printf("sendto() sent a different number of bytes than expected.\n");
break;
}else continue;
}

close(sock);
exit(0);
}
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
//EchoServer.cpp
#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>

#define ECHOMAX 255 /* Longest string to echo */

int main(int argc, char *argv[])
{
int sock;
struct sockaddr_in echoServAddr;
struct sockaddr_in echoClntAddr;
unsigned int cliAddrLen;
char echoBuffer[20][ECHOMAX];
unsigned short echoServPort = 6666;
int recvMsgSize[20];

printf("Port Number: %d\n",echoServAddr.sin_port);
if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
printf("socket() failed.\n");
memset(&echoServAddr, 0, sizeof(echoServAddr));
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");

printf("Server Address: %s\n",inet_ntoa(echoServAddr.sin_addr));
printf("%s: waiting for data on port UDP %d \n",argv[0],echoServPort);

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

for(int i=0;i<20;i++){
if ((recvMsgSize[i] = recvfrom(sock, echoBuffer[i], ECHOMAX,0,(struct sockaddr *) &echoClntAddr, &cliAddrLen)) < 0)
printf("recvfrom() failed.\n");
printf("%s: from %s: UDP%d: %s\n",argv[0],inet_ntoa(echoClntAddr.sin_addr),echoClntAddr.sin_port,echoBuffer[i]);
}
}
}

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

1
2
[email protected]:~/lab6/test2$ ./EchoClient 10.128.*.192 hello? who are you?
./EchoClient: Sending data to "10.128.*.192"
1
2
3
4
5
6
7
8
[email protected]:~/lab6/test2$ ./EchoServer
Port Number: 2052
Server Address: 0.0.0.0
./EchoServer: waiting for data on port UDP 6666
./EchoServer: from 10.128.*.192: UDP24719: hello?
./EchoServer: from 10.128.*.192: UDP24719: who
./EchoServer: from 10.128.*.192: UDP24719: are
./EchoServer: from 10.128.*.192: UDP24719: you?

in_addr、inet_addr()和inet_aton()长得可真像

in_addr

1
2
3
4
5
6
7
8
9
//32-bit IPv4 addresses are stored in an IP Address structure.
//defined in <netinet/in.h>
typedef uint32_t in_addr_t;
struct in_addr {
in_addr_t s_addr;
};

//defined in<stdint.h>
typedef unsigned int uint32_t;

in_addr是一个表示IP地址的结构体类型,而这个结构体中只包含一个元素:一个类型为unsigned int/uint32_t/in_addr_t的s_addr(真正的32位IP地址本尊)。

问:为什么IP地址的表示非要用in_addr这种结构体类型,且结构体里还只有一个元素?
答:历史原因。早期版本(4.2BSD)把in_addr结构定义为多种结构的union,允许访问一个32位IPv4地址中的所有4个字节,或者访问它的两个16位值,因为那时候采用分类地址,便于获取地址中的适当字节。后来我们有CIDR了,不需要分类了,就废除了该union,仅让它包含一个in_addr_t类型的字段。

问:以前学C语言的时候unsigned int不应该是16位吗???怎么成32位了?
答:查询整形数据常见存储空间和值的范围得知,unsigned int有两种情况(2-byte和4-byte),我们在这里使用的是uint32_t,是4-byte即32位。附两位网友的发言:

C语言标准并未规定这些基本数据类型的长度,其具体长度与编译器以及所在的平台(86,ARM等等)有关。

一般数据类型有的占有的字节的数跟编译器有关,并不和你的电脑室32位呀,64位呀有关,千万别以为你的电脑是64位的就应该比32位的数据类型占用高一些,那大家都用64位机写的东西是不是在32位上同一个编译器都溢出了?那样恐怕不好吧,另外,64位的系统也可以装32位的编译器,例如,64位系统可以装xp,32位机器上可以有16/32位的编译器(XP上有tc是16位的,其他常见的是32位的),即使是32位的编译器也可以弄出64位的integer来(int64)。 所以请记住,数据类型所占直接主要由编译器决定(占多少位由编译器在编译期间说了算), 数据类型所占的字节数完全是和你用什么编译器有关的,编译器不一样,所分配给数据类型的字节数也会有所不同。

inet_addr()

1
2
3
4
//inet_addr()函数原型:
//defined in <arpa/inet.h>
in_addr_t inet_addr(const char *strprt);
//返回值说明:若字符串有效则返回为32位二进制网络字节序的IPv4地址,否则为INADDR_NONE

inet_addr()是一个函数,用来将strptr所指的字符串转换成32位二进制网络字节序的IPv4地址。该函数存在一个问题,所有2^32个可能的二进制值都是有效的IP地址(0.0.0.0~255.255.255.255),但是出错时函数返回的INADDR_NONE(通常是一个32位均为1的值)。这意味着点分十进制串255.255.255.255(broadcast address)不能由该函数处理。

并且一些手册说该函数出错时返回-1而不是INADDR_NONE。这种情况下对该函数的返回值(一个无符号的值)和一个负常值(-1)进行比较时可能会发生问题,具体取决于C编译器。

书上说如今inet_addr已经被废弃,逐渐改用inet_aton()函数。

inet_aton()

1
2
3
4
//inet_aton()函数原型
//defined in <arpa/inet.h>
int inet_aton(const char *strptr, struct in_addr *addrptr);
//返回值说明:若字符串有效则为1,否则为0

inet_aton()是一个函数,干着和inet_addr类似的事情:将strptr所指的字符串转换成32位二进制网络字节序的IPv4地址,并通过*addrptr来存储。

inet_aton()有一个没有写入正式文档的特征:如果*addrptr为空,那么该函数仍然对输入的字符串执行有效性检查,但是不存储任何结果。【这仿佛一句废话】

套接字地址结构

大多数套接字函数都需要一个指向套接字地址结构的指针作为参数。每个协议族都定义它自己的套接字地址结构。这些结构的名字以sockaddr_开头,并对应每个协议族的唯一后缀结尾。

IPv4套接字地址结构

1
2
3
4
5
6
7
//sockaddr_in, Internet-specific socket address(bits/socket.h)
struct sockaddr_in {
unsigned short sin_family; /* address family (always AF_INET),2 byte */
unsigned short sin_port; /* port num in network byte order,2 byte */
struct in_addr sin_addr; /* IP addr in network byte order, 4 byte */
unsigned char sin_zero[8]; /* pad to sizeof(struct sockaddr), 8 byte */
};

通用套接字地址结构

1
2
3
4
5
6
struct sockaddr {
unsigned short sa_family; /* protocol family */
char sa_data[14]; /* protocol-specific address, up to 14 bytes. */
};
//Pointer to generic socket address is used for address
//arguments to connect(), bind() and accept()

问:为什么我们要用到通用套接字地址结构?
答:使用套接字函数时,要把套接字地址结构的指针作为参数传递给套接字函数,然而这样的话不同类型的套接字就需要以不同类型套接字结构指针作为参数的函数,为了方便我们直接定义一个通用的,调用时参数直接转型就好了。

其他相关原型:

1
2
3
4
//memset()函数原型
//defined in <string.h>
void *memset(void *dest, int c, size_t len);
//memset()把目标字节串指定数目的字节置为值c。

今日思考:
1.TCP:如果Server接收了几个请求,并在同一个端口上为它们一人建立了一个Socket方便通信,这些Socket要怎么区分?

  • socket不需要得到区分,connection能区分就行了。
    2.FTP:如果Server接收了几个请求,并随机开新端口为它们一人建立了一个Socket方便通信,容易有什么后果?
    3.UDP:只把Server的Socket和sockaddr_in通过bind()绑在一块了,Client的socket该咋办?光溜溜只有一个socket descriptor要怎么查看它挂在哪个端口上?
  • 确实没办法?

Scan the QR code to add me on Wechat